2

Language: Delphi 10.1 Berlin

Problem:
There is a directory with measurement files (*.csv) and other files.
Every few hours a new measurement file will be created.
I need a possibility to delete all .csv files in that folder that are older than a specific number of days. All other file types should not be touched.

Question:
Is there any built-in function in Delphi to do that job? If not, what is an efficient way to solve this problem?

Michael Hutter
  • 1,064
  • 13
  • 33

2 Answers2

8

I didn't find a Delphi built-in function for that specific problem.
This function worked for me:

function TUtilities.DeleteFilesOlderThanXDays(
    Path: string;
    DaysOld: integer = 0; // 0 => Delete every file, ignoring the file age
    FileMask: string = '*.*'): integer;
var
  iFindResult : integer;
  SearchRecord : tSearchRec;
  iFilesDeleted: integer;
begin
  iFilesDeleted := 0;
  iFindResult := FindFirst(TPath.Combine(Path, FileMask), faAnyFile, SearchRecord);
  if iFindResult = 0 then begin
    while iFindResult = 0 do begin
      if ((SearchRecord.Attr and faDirectory) = 0) then begin
        if (FileDateToDateTime(SearchRecord.Time) < Now - DaysOld) or (DaysOld = 0) then begin
          DeleteFile(TPath.Combine(Path, SearchRecord.Name));
          iFilesDeleted := iFilesDeleted + 1;
        end;
      end;
      iFindResult := FindNext(SearchRecord);
    end;
    FindClose(SearchRecord);
  end;
  Result := iFilesDeleted;
end;
Michael Hutter
  • 1,064
  • 13
  • 33
  • This is the "old school" way. I assume a solution using System.IOUtils and TDirectory would work too. – Rudy Velthuis Jan 23 '19 at 08:40
  • At least this approach avoids all the heap allocation of IOUtils, a historically bug ridden unit fwiw – David Heffernan Jan 23 '19 at 16:01
  • I'd say that the `FindClose` is nominally misplaced - it should follow the `if iFindResult = 0` statement (else `FindClose` will not be executed if `FindFirst` returns 0) and that the `if iFindResult = 0` is redundant since the `while` will take care of the case when `FindFirst` returns 0. – Magoo Jan 23 '19 at 18:25
  • @Magoo The findclose function closes a successful FindFirst (and FindNext) file search. It frees up the resources used by the search in SearchResults. A findclose call is not necessary if FindFirst finds nothing, but must be called if it does, even if a subsequent FindNext call fails. – Michael Hutter Jan 24 '19 at 05:29
  • @Magoo https://stackoverflow.com/questions/32357314/what-can-my-32-bit-app-be-doing-that-consumes-gigabytes-of-physical-ram Especially the comment under the answer matches here. – Michael Hutter Jan 24 '19 at 05:47
  • @MichaelHutter Certainly. That's why I said 'nominally' - on the basis that doing and doing an action should be balanced, not conditional (seen too many problems caused by imbalance.) states specifically `FindFirst allocates resources (memory) that must be released by calling FindClose` but the code-example violates that direction. Examining the code shows that `findClose` is executed by `FindFirst` automatically for a non-zero return. – Magoo Jan 24 '19 at 07:25
3
procedure DeleteFilesOlderThan(
  const Days: Integer;
  const Path: string;
  const SearchPattern: string = '*.*');
var
  FileName: string;
  OlderThan: TDateTime;
begin
  Assert(Days >= 0);
  OlderThan := Now() - Days;
  for FileName in TDirectory.GetFiles(Path, SearchPattern) do
    if TFile.GetCreationTime(FileName) < OlderThan then
      TFile.Delete(FileName);
end;
Andreas
  • 506
  • 4
  • 6
  • Why is it bad to perform this in a single routine? Your approach involves two separate enumerations which seems like a bad idea to me. – David Heffernan Jan 23 '19 at 14:29
  • I read the OP as both requirements have to be met (match a mask AND be older than some days), so you can't split the search in two routines, you need to check both conditions on the same routine. – Marc Guillot Jan 23 '19 at 15:03
  • Yes, both requirements have to be met, which is done actually. Default value for DaysOld and the related comment in the OP's answer make it seem like there is additional intention for the routine - deleting files by mask. Basically deleting files with something like DeleteFilesOlderThanXDays('C:\') makes the code unclear. Someone else reading the code might be uncertain about the meaning: is it intended to delete all files (what's with the routine name then?) or you just forgot to specify the parameter? It might require extra typing, but explicit code is better than implicit. – Andreas Jan 24 '19 at 16:09