【问题标题】:Why is FileAge returning unexpected values?为什么 FileAge 返回意外的值?
【发布时间】:2021-10-12 12:53:20
【问题描述】:

所以我有一个“下载”文件夹,我将下载的所有内容放在我的日常工作中。你知道我们总是自动化一切,所以我正在尝试构建一个简单的应用程序来每天运行以删除超过 30 天的文件,因为我必须不时手动执行此操作以避免文件夹变得太大。

这是我的代码:

function TForm1.deleteOldDownloads: boolean;
var
  f: string;
  i, d: Integer;
var
  sl: tstringlist;
begin
try
  FileListBox1.Directory := '\\psf\home\downloads';
  FileListBox1.refresh;
  sl := tstringlist.create;
  for i := 0 to FileListBox1.items.count - 1 do
    begin
    f := FileListBox1.Directory + '\' + FileListBox1.items[i];
    if fileexists(f) then
      d := daysbetween(FileAge(f), now)
    else
      d := 0;
    if d > 30 then // problem is here, d is always a big number, not the actually age of file
      sl.Add(f);
    end;
  if sl.count > 0 then
    begin
    for i := 0 to sl.count do
      begin
      f := sl[i];
      deletefile(f);
      end;
    end;
  sl.Free;
except
  on e: Exception do
    begin     
    end;
end;

问题是“d”变量返回非常大的数字,例如 1397401677,即使文件只有 1 天。

这里唯一的细节是我在 Parallels 虚拟机中运行 Windows,并且“\psf\home\downloads”文件夹在 Mac 上,但我可以使用 Windows 资源管理器正常访问这个文件夹,所以对于 Delphi 来说就像一个普通的本地文件夹。

我错过了什么?

【问题讨论】:

  • 我不明白为什么 FileAge 重载返回的值可以与 Now 进行比较。据我所知,FileAge 重载返回的不是日期时间。我希望文档会告诉您 FileAge 返回的内容。是的,刚刚查了,都在。你已经花时间写了这个问题,但是你通过阅读文档比你花时间写这篇文章更快地得到了解决方案。在这个网站上呆了这么多年后,我仍然对人们没有意识到文档存在并且可以阅读感到惊讶。
  • @DavidHeffernan 非常正确。在这种情况下,您甚至无法猜测 FileAge 返回什么。我的意思是,它可能是以十分之一毫秒为单位的年龄,在这种情况下,您的值大约是一天!
  • 另外:(1) 您必须使用try..finally 块保护您的资源。 (2)sl[sl.Count] 错误。 (3)if sl.count > 0 then不是必须的; for i := 0 to -1 是完全有效的代码,不会产生任何迭代。 (4) 不要默默地忽略异常。
  • 不要默默地忽略异常。
  • 或至少将 try/except 移动到“deletefile”...

标签: delphi delphi-10.4-sydney


【解决方案1】:

你读过documentation 的FileAge 吗?在编程学校的第一天,你被教导“当你开始使用一个新的函数或 API 时,你首先要阅读它的文档。”在这种情况下,函数的文档说

FileAge 的 [one-argument] 重载版本已弃用。

所以你正在使用一个你不应该使用的功能。

不过,这个功能应该还是可以工作的。

但是您期望它返回什么?好吧,显然文档说它应该返回的东西:

第一个重载返回一个整数,表示文件的操作系统时间戳。稍后可以使用FileDateToDateTime 函数将结果转换为TDateTime。

但是当您在DaysBetween 中使用它时,您会认为它已经是TDateTime!

为什么 FileAge 返回意外的值?

不是。它可能返回的正是它的文档说它应该返回的东西。

【讨论】:

  • 更有理由使用 2 参数 FileAge(),因为它的输出值是 TDateTime 而不是 Integer。
【解决方案2】:

您使用的是旧版本的 FileAge(),它以 DOS 数字格式返回时间戳,但您将其视为 TDateTime,但事实并非如此。正如FileAge documentation 所说:

第一个重载返回一个整数,表示文件的操作系统时间戳。 稍后可以使用FileDateToDateTime() 函数将结果转换为TDateTime。

所以,按照文档说的去做,例如:

var
  age: Integer;

age := FileAge(f);
if age <> -1 then
  d := DaysBetween(FileDateToDateTime(age), Now)

否则,请使用较新版本的FileAge() 以输出TDateTime 开头,例如:

var
  dt: TDateTime;

if FileAge(f, dt) then
  d := DaysBetween(dt, Now)

【讨论】:

    【解决方案3】:

    这不是您问题的直接答案,但我不能将其作为评论发布。 所以,问题是,你永远不应该直接删除用户文件。如果你犯了错误怎么办?如果您的程序的用户犯了错误怎么办? 始终将文件删除到回收站:

    {--------------------------------------------------------------------------------------------------
       DELETE FILE
       Deletes a file/folder to RecycleBin.
       Old name: Trashafile
       Note related to UNC: The function won't move a file to the RecycleBin if the file is UNC. MAYBE it was moved to the remote's computer RecycleBin
    --------------------------------------------------------------------------------------------------}
    function RecycleItem(CONST ItemName: string; CONST DeleteToRecycle: Boolean= TRUE; CONST ShowConfirm: Boolean= TRUE; CONST TotalSilence: Boolean= FALSE): Boolean;
    VAR
       SHFileOpStruct: TSHFileOpStruct;
    begin
     FillChar(SHFileOpStruct, SizeOf(SHFileOpStruct), #0);
     SHFileOpStruct.wnd              := Application.MainForm.Handle;                                   { Others are using 0. But Application.MainForm.Handle is better because otherwise, the 'Are you sure you want to delete' will be hidden under program's window }
     SHFileOpStruct.wFunc            := FO_DELETE;
     SHFileOpStruct.pFrom            := PChar(ItemName+ #0);                                           { ATENTION!   This last #0 is MANDATORY. See this for details: http://stackoverflow.com/questions/6332259/i-cannot-delete-files-to-recycle-bin  -   Although this member is declared as a single null-terminated string, it is actually a buffer that can hold multiple null-delimited file names. Each file name is terminated by a single NULL character. The last file name is terminated with a double NULL character ("\0\0") to indicate the end of the buffer }
     SHFileOpStruct.pTo              := NIL;
     SHFileOpStruct.hNameMappings    := NIL;
    
     if DeleteToRecycle
     then SHFileOpStruct.fFlags:= SHFileOpStruct.fFlags OR FOF_ALLOWUNDO;
    
     if TotalSilence
     then SHFileOpStruct.fFlags:= SHFileOpStruct.fFlags OR FOF_NO_UI
     else
       if NOT ShowConfirm
       then SHFileOpStruct.fFlags:= SHFileOpStruct.fFlags OR FOF_NOCONFIRMATION;
    
     Result:= SHFileOperation(SHFileOpStruct)= 0;
    
     //DEBUG ONLY if Result<> 0 then Mesaj('last error: ' + IntToStr(Result)+ CRLF+ 'last error message: '+ SysErrorMessage(Result));
     //if fos.fAnyOperationsAborted = True then Result:= -1;
    end;
    

    此外,您不需要那个过时的控件来获取文件夹中的文件。你可以使用这个:

    { FIND FILES }
    function ListFilesOf(CONST aFolder, FileType: string; CONST ReturnFullPath, DigSubdirectories: Boolean): TStringList;
    { If DigSubdirectories is false, it will return only the top level files,
      else it will return also the files in subdirectories of subdirectories.
      If FullPath is true the returned files will have full path.
      FileType can be something like '*.*' or '*.exe;*.bin'
      Will show also the Hidden/System files.
      Source Marco Cantu Delphi 2010 HandBook
    
       // Works with UNC paths}
    VAR
      i: Integer;
      s: string;
      SubFolders, filesList: TStringDynArray;
      MaskArray: TStringDynArray;
      Predicate: TDirectory.TFilterPredicate;
    
       procedure ListFiles(CONST aFolder: string);
       VAR strFile: string;
       begin
        Predicate:=
              function(const Path: string; const SearchRec: TSearchRec): Boolean
              VAR Mask: string;
              begin
                for Mask in MaskArray DO
                  if System.Masks.MatchesMask(SearchRec.Name, Mask)
                  then EXIT(TRUE);
                EXIT(FALSE);
              end;
    
        // Long paths will raise an EPathTooLongexception exception, so we simply don't process those folders
        if Length(aFolder) > MAXPATH
        then exit;
    
        filesList:= TDirectory.GetFiles (aFolder, Predicate);
        for strFile in filesList DO
         if strFile<> ''                                                                                 { Bug somewhere here: it returns two empty entries ('') here. Maybe the root folder?  }
         then Result.Add(strFile);
       end;
    
    begin
     { I need this in order to prevent the EPathTooLongexception (reported by some users) }
     if aFolder.Length >= MAXPATH then
      begin
       MesajError('Path is longer than '+ IntToStr(MAXPATH)+ ' characters!');
       EXIT(NIL);
      end;
    
     if NOT System.IOUtils.TDirectory.Exists (aFolder)
     then RAISE exception.Create('Folder does not exist! '+ CRLF+ aFolder);
    
     Result:= TStringList.Create;
    
     { Split FileType in subcomponents }
     MaskArray:= System.StrUtils.SplitString(FileType, ';');
    
     { Search the parent folder }
     ListFiles(aFolder);
    
     { Search in all subfolders }
     if DigSubdirectories then
      begin
       SubFolders:= TDirectory.GetDirectories(aFolder, TSearchOption.soAllDirectories, NIL);
       for s in SubFolders DO
        begin
         if ccIO.DirectoryExists(s)                                                                     { This solves the problem caused by broken 'Symbolic Link' folders }
         then ListFiles(s);
        end;
      end;
    
     { Remove full path }
     if NOT ReturnFullPath then
      for i:= 0 to Result.Count-1 DO
       Result[i]:= TPath.GetFileName(Result[i]);
    end;
    

    以上代码来自:https://github.com/GodModeUser/Delphi-LightSaber

    【讨论】:

    • 好主意,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-28
    相关资源
    最近更新 更多