Autor Beitrag
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Fr 25.11.11 00:05 
Hallo,

nach einer Frage im Forum habe ich einmal eine allgemeingültige Funktion zur Suche nach Dateien mit bestimmten Bedingungen erstellt. Diese ist explizit nicht für hohe Geschwindigkeit, sondern für viele Möglichkeiten gedacht. Dennoch ist die Geschwindigkeit denke ich hoch genug für die meisten Fälle.

Was ist möglich?
  • Suche nach einer oder mehreren Masken
  • Ausschließen nach einer oder mehreren Masken
  • Einschränken nach Minimaldatum und/oder Maximaldatum
  • Suche nach Dateiattributen
  • Suche in Unterordnern oder nur im angegebenen Ordner
  • Callback für jeden durchsuchten Ordner als Statusanzeige
  • Dateiliste mit:
    • kompletten Pfaden
    • oder relativen Pfaden
    • oder nur mit den Dateinamen
Das ganze sieht dann so aus:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
{
  GetAllFilesEx by Sebastian Jänicke (jaenicke on delphi-forum.de) - 2011
  inspired by Popov: http://www.delphi-library.de/viewtopic.php?t=21275
  use as you want, but please don't remove this comment
}

type
  TFileSearchOptions = set of (fsoRecursive, fsoRelativePaths, fsoOnlyFilenames);
  TSearchFilesCallback = reference to procedure(const ACurrentPath: string);

function PathCombine(lpszDest: PWideChar; const lpszDir, lpszFile: PWideChar): PWideChar; stdcall;
  external 'shlwapi.dll' name 'PathCombineW';

procedure GetAllFilesEx(AFileList: TStrings; const AParentPath, ASearchPath, AMasks: stringconst AExcludes: string = '';
  ASearchOptions: TFileSearchOptions = [fsoRecursive]; AAttributes: Integer = faAnyFile; ACallback: TSearchFilesCallback = nil;
  AMinAge: TDateTime = 0; AMaxAge: TDateTime = 2 * MAXWORD);
var
  CompletePath, ParentPath, SearchPath, CurrentMask, CurrentFilename: string;
  Search: TSearchRec;
  MaskList, ExcludeList, ExcludedFiles: TStringList;
begin
  // combine paths to complete path
  if ASearchPath <> '' then
    SearchPath := IncludeTrailingPathDelimiter(ASearchPath)
  else
    SearchPath := '';
  if AParentPath <> '' then
    ParentPath := IncludeTrailingPathDelimiter(AParentPath)
  else
    ParentPath := '';
  SetLength(CompletePath, MAX_PATH);
  PathCombine(PChar(CompletePath), PChar(ParentPath), PChar(SearchPath));
  SetLength(CompletePath, StrLen(PChar(CompletePath)));

  // callback
  if Assigned(ACallback) then
    ACallback(CompletePath);

  // fill file list
  ExcludedFiles := TStringList.Create;
  try
    // find excluded files
    ExcludeList := TStringList.Create;
    try
      ExcludeList.StrictDelimiter := True;
      ExcludeList.CommaText := AExcludes;
      for CurrentMask in ExcludeList do
        if FindFirst(CompletePath + CurrentMask, AAttributes, Search) = 0 then
        begin
          repeat
            ExcludedFiles.Add(AnsiUpperCase(CompletePath + Search.Name));
          until FindNext(Search) <> 0;
          FindClose(Search);
        end;
    finally
      ExcludeList.Free;
    end;

    // find all matching files
    MaskList := TStringList.Create;
    try
      MaskList.StrictDelimiter := True;
      MaskList.CommaText := AMasks;
      for CurrentMask in MaskList do
        if FindFirst(CompletePath + CurrentMask, AAttributes, Search) = 0 then
        begin
          repeat
            CurrentFilename := CompletePath + Search.Name;
            if (ExcludedFiles.IndexOf(AnsiUpperCase(CurrentFilename)) < 0)
              and (Search.TimeStamp >= AMinAge) and (Search.TimeStamp <= AMaxAge) then
            begin
              if fsoRelativePaths in ASearchOptions then
                AFileList.Add(ExtractRelativePath(ParentPath, CurrentFilename))
              else if fsoOnlyFilenames in ASearchOptions then
                AFileList.Add(Search.Name)
              else
                AFileList.Add(CurrentFilename);
            end;
          until FindNext(Search) <> 0;
          FindClose(Search);
        end;
    finally
      MaskList.Free;
    end;
  finally
    ExcludedFiles.Free;
  end;

  // recurse into subfolders
  if fsoRecursive in ASearchOptions then
  begin
    if FindFirst(CompletePath + '*.*', faDirectory, Search) = 0 then
    begin
      repeat
        if ((Search.Attr and faDirectory) = faDirectory) and (Search.Name[1] <> '.'then
          GetAllFilesEx(AFileList, AParentPath, SearchPath + Search.Name, AMasks, AExcludes, ASearchOptions, AAttributes,
            ACallback, AMinAge, AMaxAge);
      until FindNext(Search) <> 0;
      FindClose(Search);
    end;
  end;
end;
Die Trennung in AParentPath und ASearchPath dient dazu, dass die Dateinamen relativ zu dem Hauptordner in AParentPath zurückgegeben werden können. Im zweiten Beispiel wird zwar unterhalb von C:\Program Files (x86)\Embarcadero\RAD Studio gesucht, aber "RAD Studio" ist in den Dateipfaden enthalten.

Ein kurzer Beispielaufruf, nur mit mehreren Masken:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
var
  FileList: TStringList;
begin
  FileList := TStringList.Create;
  try
    GetAllFilesEx(FileList, 'C:\Program Files (x86)\Embarcadero''''*.exe,*.txt');
    ShowMessage(FileList.Text);
  finally
    FileList.Free;
  end;
end;

Und ein ausführlicher Beispielaufruf sieht dann so aus, wobei der aktuelle Ordner als Status in die Caption des Formulars geschrieben wird:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
var
  FileList: TStringList;
begin
  FileList := TStringList.Create;
  try
    GetAllFilesEx(FileList, 'C:\Program Files (x86)\Embarcadero''RAD Studio''*.exe,*.txt''bds.exe,g*',
      [fsoRecursive, fsoRelativePaths], faAnyFile,
      procedure(const ACurrentPath: string)
      begin
        Caption := ACurrentPath;
      end);
    ShowMessage(FileList.Text);
  finally
    FileList.Free;
  end;
end;
Hier werden alle .exe oder .txt Dateien gesucht, wobei bds.exe und alle mit g beginnenden Dateien ausgeschlossen werden.

Die Funktion ist in der jetzigen Form ausschließlich mit Delphi XE und höher verwendbar. Für Delphi 2010 oder früher sind Anpassungen erforderlich (bei Delphi 2010 gibt es noch kein TimeStamp in TSearchRec und vor Delphi 2006 gibt es noch keine for..in Schleife). Ich denke aber der Sinn sollte klar werden, bei Bedarf könnt ihr euch das ja anpassen.

Weitere bereits vorhandene Varianten für eine Dateisuche von anderen Membern:
www.delphi-library.d...en+suchen_21275.html
www.delphi-library.d...issen+suchen_94.html
www.delphi-library.d...tsanzeige_47880.html
www.delphi-library.d...tVariante_15203.html
www.delphi-library.d...ien+suchen_1107.html

Viel Spaß damit ;-)
Schönen Gruß,
Sebastian