Jump to content
robertjohns

File Search

Recommended Posts

14 minutes ago, KodeZwerg said:

Please compare with my approach, I posted earlier a full demo app that works straight out of the box, would like to see time results and if my way would also benefit of a full exclusion from giving names.

Thanks but it still search the windows directory even after Exclusion

 

 

Screenshot_3.png

Share this post


Link to post
31 minutes ago, robertjohns said:

Thanks but it still search the windows directory even after Exclusios

You should specify the complete path to exclude:

C:\Windows

Not just

Windows

 

Share this post


Link to post
44 minutes ago, dummzeuch said:

You should specify the complete path to exclude:

C:\Windows

Not just

Windows

 

Same result not excludes

Share this post


Link to post
function FindEx(const ABasePath: string; const AFoldersMustExist: string = ''; AExludedFolders: string = ''; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;
var
 tmp, Folders, Files: TFindArray;
 splitIncluded, splitExcluded: TFindArray;
 i, ii: Integer;
begin
  SetLength(tmp, 0);
  SetLength(Folders, 0);
  SetLength(Files, 0);
  SetLength(splitIncluded, 0);
  SetLength(splitExcluded, 0);
  if (Length(AFoldersMustExist) > 0) then
    begin
      if (0 <> Pos(';', AFoldersMustExist)) then
        splitIncluded := AFoldersMustExist.Split([';'])
      else
        AddStrArr(splitIncluded, AFoldersMustExist);
    end;
  if (Length(AExludedFolders) > 0) then
    begin
      if (0 <> Pos(';', AExludedFolders)) then
        splitExcluded := AExludedFolders.Split([';'])
      else
        AddStrArr(splitExcluded, AExludedFolders);
    end;
  // collect folder(s) to work on
  if AIncludeSubFolders then
    tmp := FindExFolders(ABasePath, AIncludeSubFolders)
    else
    AddStrArr(tmp, ABasePath);
  // sieve out folders that match criteria
  if (Length(splitIncluded) > 0) then
    begin
      for i := Low(tmp) to High(tmp) do
        for ii := Low(splitIncluded) to High(splitIncluded) do
          if (0 <> Pos(UpperCase(splitIncluded[ii]), UpperCase(tmp[i]))) then
            AddStrArr(Folders, tmp[i]);
    end
    else
      Folders := tmp;
  // remove unwanted folders from search
  if (Length(splitExcluded) > 0) then
    for i := High(Folders) downto Low(Folders) do
      for ii := Low(splitExcluded) to High(splitExcluded) do
        if (0 <> Pos(PathDelim + UpperCase(splitExcluded[ii]), UpperCase(Folders[i]))) then
          Delete(Folders, i, 1);
  SetLength(tmp, 0);
  // get files that matching the criteria
  for i := Low(Folders) to High(Folders) do
    begin
      if (Length(AFileMask) > 0) then
        tmp := FindExFiles(Folders[i], AFileMask) // do not enable the IncludeSubFolders switch here (!)
      else
        tmp := FindExFiles(Folders[i]);
      for ii := Low(tmp) to High(tmp) do
        AddStrArr(Files, tmp[ii]);
    end;
  Result := Files;
  SetLength(tmp, 0);
  SetLength(Folders, 0);
  SetLength(Files, 0);
end;

I apology, please update that method like shown above and now it really should work, I've not tested it much with extra large filesystems, on a small one it works like it should.
You can enter "Windows" or "windows" or just "win", everything that has this in name will be excluded from the deeper file search mechanism.
When I got more free time I will continue making it better.

Share this post


Link to post
24 minutes ago, KodeZwerg said:

When I got more free time I will continue making it better.

After +10 minutes waiting, I had that stop it...!!!  -- verify the "Range check error"

 

I think that your approach of "excluding" the undesirable directories, should be done during the search of the files, and not in a previous or later step, because, in this way, you are doing a search in 2 or more steps, that is, first you are searching everything, then you will delete everything that is not desirable...

 

So, you should do this exclusion of undesirables during the main search, avoiding a second or third action!
I think your logic here is wrong!


Since the owner of the post needs to find c:\*.*, your search is being carried out in all directories, and, only at the end, you are excluding "C:\Windows" from the directory, which further increases the task!

Here on my PC, your search started at 3:26 pm, and I "STOP IT" 3:37 pm, and none resulted on listbox yet, using your latest "FindEx" update, CPU using 15~20% all the time!

 

... below, before your last FindEx update...

 

image.thumb.png.e69f8e652255c57eebc083b9e07d1f3e.png

Edited by programmerdelphi2k

Share this post


Link to post

Please not use a full path at excluding, for that my code ain't designed yet.

Share this post


Link to post
27 minutes ago, KodeZwerg said:

AddStrArr

my tip: Avoid the excessive usage of "SetLenght()", try define a conditicional for it, like:  each 100...n items, use SetLenght()! that way, if exists 1000 object, then you'll have 10 SetLength()

  • Like 1

Share this post


Link to post
1 minute ago, KodeZwerg said:

for that my code ain't designed yet.

You have to consider this, because the search must know the full path, to avoid excluding the search in a different subdirectory, for example:

  • c:\Windows and c:\Other\Windows <--- are different things!
  • Like 1

Share this post


Link to post

MEA CULPA: in my code propose I forgot verify about c:\Windows and c:\Other\Windows <--- are different things!  --> sorry!  😝 

if not(LLookFolderToSkip and MyFoldersToSkip(AFoldersToSkip, LSearchRecord.Name)) then  <--- it should be: AFolderRoot + LSearchRecord.Name

now, you need this: (or some way to do it automatically...

  • MyFindFilePattern('D:\zzTest', ['*.*'],   ['D:\zzTest\Windows'],   LMyResult);
Edited by programmerdelphi2k

Share this post


Link to post
unit uFindEx;

interface

uses
  Winapi.Windows,
  System.SysUtils;

type
  // My variant of an "StringList"
  TFindArray = TArray<string>; // array of WideString;

function FindEx(const ABasePath: string; const AFoldersMustExist: string = ''; AExludedFolders: string = ''; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;

implementation


const
  // missing FindEx flags
  FIND_FIRST_EX_CASE_SENSITIVE       = $00000001;
  FIND_FIRST_EX_LARGE_FETCH          = $00000002;
  FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY = $00000004;

// Small helper to add strings in my "StringList"
procedure AddStrArr(var AArr: TFindArray; const AString: string); inline;
var
  i: Integer;
begin
  i := Length(AArr);
  SetLength(AArr, Succ(i));
  AArr[i] := AString;
end;

// This method will crawl thru a folder and collect their names
// IncludeSubFolders switch will get every folder, False by default
// Based upon very fast FindEx Api (Windows)
// The result will contain full path
function FindExFolders(const ABasePath: string; const AInclusionList, AExclusionList: TFindArray; const AIncludeSubFolders: Boolean = False): TFindArray;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFindexInfoLevels;
  FindExSearchOps : TFindexSearchOps;
  AdditionalFlags : DWORD;
  tmp             : TFindArray;
  i, ii           : Integer;
  s:string;
begin
  SetLength(Result, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Winapi.Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(ABasePath) + '*.*')
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    begin
      repeat
        if ((Win32FindData.cFileName <> string('.')) and (Win32FindData.cFileName <> string('..'))
            and (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
              begin
                if (Length(AInclusionList) > 0) then
                  begin
                    for i := Low(AInclusionList) to High(AInclusionList) do
                      if (0 <> Pos(UpperCase(AInclusionList[i]), UpperCase(IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName))) then
                        AddStrArr(Result, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
                  end
                  else
                    AddStrArr(Result, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
                if (Length(AExclusionList) > 0) then
                  begin
                    for i := High(Result) downto Low(Result) do
                      for ii := Low(AExclusionList) to High(AExclusionList) do
                        if (0 <> Pos(UpperCase(AExclusionList[ii]), UpperCase(Result[i]))) then
                          Delete(Result, i, 1);
                  end;
              end;
      until not Winapi.Windows.FindNextFileW(FindExHandle, Win32FindData);
      Winapi.Windows.FindClose(FindExHandle);
    end;
  if AIncludeSubFolders then
    for i := Low(Result) to High(Result) do
      begin
        tmp := FindExFolders(Result[i], AInclusionList, AExclusionList, AIncludeSubFolders);
        for ii := Low(tmp) to High(tmp) do
          AddStrArr(Result, tmp[ii]);
      end;
  SetLength(tmp, 0);
end;

// This method will crawl thru a folder and collect their filenames
// IncludeSubFolders switch will get every filename, False by default
// Based upon very fast FindEx Api (Windows)
// The result will contain full path + filename
function FindExFiles(const ABasePath: string; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFindexInfoLevels;
  FindExSearchOps : TFindexSearchOps;
  AdditionalFlags : DWORD;
  tmp, Folders    : TFindArray;
  i, ii           : Integer;
begin
  SetLength(Result, 0);
  SetLength(Folders, 0);
  SetLength(tmp, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Winapi.Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(ABasePath) + AFileMask)
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    begin
      repeat
        if ((Win32FindData.cFileName <> string('.')) and (Win32FindData.cFileName <> string('..'))) then
          begin
            if (0 = (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)) then
              AddStrArr(Result, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
            if (AIncludeSubFolders and
               (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
              AddStrArr(Folders, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
          end;
      until not Winapi.Windows.FindNextFileW(FindExHandle, Win32FindData);
      Winapi.Windows.FindClose(FindExHandle);
    end;
  if AIncludeSubFolders then
    for i := Low(Folders) to High(Folders) do
      begin
        tmp := FindExFiles(Folders[i], AFileMask, AIncludeSubFolders);
        for ii := Low(tmp) to High(tmp) do
          AddStrArr(Result, tmp[ii]);
      end;
  SetLength(Folders, 0);
  SetLength(tmp, 0);
end;

// My variant of how a file search method can be done for windows systems
// BasePath = where do we start at? eg "C:\Users"
// FoldersMustExist = what foldername is a must for results? eg "Documents", can be left empty for all (seperate with ";" if more than 1)
// ExludedFolders = in what foldername you do not want to search? eg "Documents", can be left empty for all (seperate with ";" if more than 1)
// FileMask = what files you hunt for? eg "*.pas"
// IncludeSubFolders = yes or no, you choose. False by default
// based upon my "FindExFolders" and "FindExFiles" methods
function FindEx(const ABasePath: string; const AFoldersMustExist: string = ''; AExludedFolders: string = ''; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;
var
 tmp, Folders, Files: TFindArray;
 splitIncluded, splitExcluded: TFindArray;
 i, ii: Integer;
begin
  SetLength(tmp, 0);
  SetLength(Folders, 0);
  SetLength(Files, 0);
  SetLength(splitIncluded, 0);
  SetLength(splitExcluded, 0);
  if (Length(AFoldersMustExist) > 0) then
    begin
      if (0 <> Pos(';', AFoldersMustExist)) then
        splitIncluded := AFoldersMustExist.Split([';'])
      else
        AddStrArr(splitIncluded, AFoldersMustExist);
    end;
  if (Length(AExludedFolders) > 0) then
    begin
      if (0 <> Pos(';', AExludedFolders)) then
        splitExcluded := AExludedFolders.Split([';'])
      else
        AddStrArr(splitExcluded, AExludedFolders);
    end;
  // collect folder(s) to work on
  if AIncludeSubFolders then
    Folders := FindExFolders(ABasePath, splitIncluded, splitExcluded, AIncludeSubFolders)
    else
    AddStrArr(Folders, ABasePath);
  // get files that matching the criteria
  for i := Low(Folders) to High(Folders) do
    begin
      if (Length(AFileMask) > 0) then
        tmp := FindExFiles(Folders[i], AFileMask) // do not enable the IncludeSubFolders switch here (!)
      else
        tmp := FindExFiles(Folders[i]);
      for ii := Low(tmp) to High(tmp) do
        AddStrArr(Files, tmp[ii]);
    end;
  Result := Files;
  SetLength(tmp, 0);
  SetLength(Folders, 0);
  SetLength(Files, 0);
end;

end.

Can you retry please @programmerdelphi2k @robertjohns, just replace the full units code with above.
Thanks for tipp about SetLength() @programmerdelphi2k, will be added later, in above i put now the exclusions right into the searching job, should be in theory faster now 😛

Ps: Now it is working with a full given pathname for exclusion, eg: "c:\windows"

Edited by KodeZwerg
  • Like 1

Share this post


Link to post
10 minutes ago, KodeZwerg said:

should be in theory faster now

Now was quick  :classic_biggrin: 

  • NOTE:  I didn't check if the 133,676 files were from c:\Windows or not... 😜

 

image.thumb.png.0adfce14cf93d986c7a45fcafd41719d.png

Share this post


Link to post

"Folder must exist" is currently not working, I will update but not today 😛

Share this post


Link to post
unit uFindEx;

interface

uses
  Winapi.Windows,
  System.SysUtils;

type
  // My variant of an "StringList"
  TFindArray = TArray<string>; // array of WideString;

function FindEx(const ABasePath: string; const AFoldersMustExist: string = ''; AExludedFolders: string = ''; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;

implementation

const
  // missing FindEx flags
  FIND_FIRST_EX_CASE_SENSITIVE       = $00000001;
  FIND_FIRST_EX_LARGE_FETCH          = $00000002;
  FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY = $00000004;

// Small helper to add strings in my "StringList"
procedure AddFindArray(var AFindArray: TFindArray; const AString: string); inline;
var
  i: Integer;
begin
  i := Length(AFindArray);
  SetLength(AFindArray, Succ(i));
  AFindArray[i] := AString;
end;

// This method will crawl thru a folder and collect their names
// The ExclusionList should contain full path names that be total excluded from search, by default everything is included
// IncludeSubFolders switch will get every folder, False by default
// Based upon very fast FindEx Api (Windows)
// The result will contain full path
function FindExFolders(const ABasePath: string = ''; const AExclusionList: TFindArray = []; const AIncludeSubFolders: Boolean = False): TFindArray;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFindexInfoLevels;
  FindExSearchOps : TFindexSearchOps;
  AdditionalFlags : DWORD;
  tmp             : TFindArray;
  i, ii           : Integer;
  s:string;
begin
  SetLength(Result, 0);
  if ((ABasePath = '') or (not DirectoryExists(ABasePath))) then
    Exit;
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Winapi.Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(ABasePath) + '*.*')
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    begin
      repeat
        if ((Win32FindData.cFileName <> string('.')) and (Win32FindData.cFileName <> string('..'))
            and (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
          begin
            if (Length(AExclusionList) > 0) then
              begin
                for i := Low(AExclusionList) to High(AExclusionList) do
                  if (0 = Pos(UpperCase(AExclusionList[i]), UpperCase(IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName))) then
                    AddFindArray(Result, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
              end
              else
                AddFindArray(Result, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
          end;
      until (not Winapi.Windows.FindNextFileW(FindExHandle, Win32FindData));
      Winapi.Windows.FindClose(FindExHandle);
    end;
  if AIncludeSubFolders then
    for i := Low(Result) to High(Result) do
      begin
        tmp := FindExFolders(Result[i], AExclusionList, AIncludeSubFolders);
        for ii := Low(tmp) to High(tmp) do
          AddFindArray(Result, tmp[ii]);
      end;
  SetLength(tmp, 0);
end;

// This method will crawl thru a folder and collect their filenames
// IncludeSubFolders switch will get every filename, False by default
// Based upon very fast FindEx Api (Windows)
// The result will contain full path + filename
function FindExFiles(const ABasePath: string = ''; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFindexInfoLevels;
  FindExSearchOps : TFindexSearchOps;
  AdditionalFlags : DWORD;
  tmp, Folders    : TFindArray;
  i, ii           : Integer;
begin
  SetLength(Result, 0);
  if ((ABasePath = '') or (not DirectoryExists(ABasePath))) then
    Exit;
  SetLength(Folders, 0);
  SetLength(tmp, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Winapi.Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(ABasePath) + AFileMask)
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    begin
      repeat
        if ((Win32FindData.cFileName <> string('.')) and (Win32FindData.cFileName <> string('..'))) then
          begin
            if (0 = (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)) then
              AddFindArray(Result, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
            if (AIncludeSubFolders and
               (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
              AddFindArray(Folders, IncludeTrailingBackslash(ABasePath) + Win32FindData.cFileName);
          end;
      until (not Winapi.Windows.FindNextFileW(FindExHandle, Win32FindData));
      Winapi.Windows.FindClose(FindExHandle);
    end;
  if AIncludeSubFolders then
    for i := Low(Folders) to High(Folders) do
      begin
        tmp := FindExFiles(Folders[i], AFileMask, AIncludeSubFolders);
        for ii := Low(tmp) to High(tmp) do
          AddFindArray(Result, tmp[ii]);
      end;
  SetLength(Folders, 0);
  SetLength(tmp, 0);
end;

// My variant of how a file search method can be done for windows systems
// BasePath = where do we start at? eg "C:\Users"
// FoldersMustExist = what foldername is a must for results? eg "Documents", can be left empty for all (seperate with ";" if more than 1)
// ExludedFolders = in what foldername you do not want to search? eg "Documents", can be left empty for all (seperate with ";" if more than 1)
// FileMask = what files you hunt for? eg "*.pas"
// IncludeSubFolders = yes or no, you choose. False by default
// based upon my "FindExFolders" and "FindExFiles" methods
function FindEx(const ABasePath: string; const AFoldersMustExist: string = ''; AExludedFolders: string = ''; const AFileMask: string = '*.*'; const AIncludeSubFolders: Boolean = False): TFindArray;
var
 tmp, Folders, Files: TFindArray;
 splitIncluded, splitExcluded: TFindArray;
 i, ii: Integer;
begin
  SetLength(Result, 0);
  SetLength(tmp, 0);
  SetLength(Folders, 0);
  SetLength(Files, 0);
  SetLength(splitIncluded, 0);
  SetLength(splitExcluded, 0);

  // prepare splittings
  if (Length(AFoldersMustExist) > 0) then
    begin
      if (0 <> Pos(';', AFoldersMustExist)) then
        splitIncluded := AFoldersMustExist.Split([';'])
      else
        AddFindArray(splitIncluded, AFoldersMustExist);
    end;
  if (Length(AExludedFolders) > 0) then
    begin
      if (0 <> Pos(';', AExludedFolders)) then
        splitExcluded := AExludedFolders.Split([';'])
      else
        AddFindArray(splitExcluded, AExludedFolders);
    end;

  // collect folder(s) to work on
  if AIncludeSubFolders then
    tmp := FindExFolders(ABasePath, splitExcluded, AIncludeSubFolders)
    else
    AddFindArray(tmp, ABasePath);

  // sieve out folders that match criteria
  if (Length(splitIncluded) > 0) then
    begin
      for i := Low(tmp) to High(tmp) do
        for ii := Low(splitIncluded) to High(splitIncluded) do
          if (0 <> Pos(UpperCase(splitIncluded[ii]), UpperCase(tmp[i]))) then
            AddFindArray(Folders, tmp[i]);
    end
    else
      Folders := tmp;

  // get files that matching the criteria
  for i := Low(Folders) to High(Folders) do
    begin
      if (Length(AFileMask) > 0) then
        tmp := FindExFiles(Folders[i], AFileMask) // do not enable the IncludeSubFolders switch here (!)
      else
        tmp := FindExFiles(Folders[i]);
      for ii := Low(tmp) to High(tmp) do
        AddFindArray(Files, tmp[ii]);
    end;

  Result := Files;
  SetLength(tmp, 0);
  SetLength(Folders, 0);
  SetLength(Files, 0);
  SetLength(splitIncluded, 0);
  SetLength(splitExcluded, 0);
end;

end.

Fixed the "Folder must included" thingy, now it works pretty well for me.

Share this post


Link to post
On 5/29/2023 at 6:20 PM, robertjohns said:

Same result not excludes

Does it now works for you?

On 5/30/2023 at 9:42 AM, Fr0sT.Brutal said:

Why setting length to 0 at the start and end? Dynamic arrays are managed internally

Feel free to remove 🤭

Share this post


Link to post

@KodeZwerg   @robertjohns

 

a little less code... if we remove the frills, we have even a little less.... if we remove everything, we have even less  image.png.dcd078ff002e77013585d33c6ccaa8b4.png  

  • in my tests all is working...
type
  TFindFileDirectory    = (ffdDirectory, ffdFile);
  TFindFilesDirectories = set of TFindFileDirectory;
  TArrObjectsFound      = TArray<string>;

const
  // missing FindEx flags
  FIND_FIRST_EX_CASE_SENSITIVE       = $00000001;
  FIND_FIRST_EX_LARGE_FETCH          = $00000002;
  FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY = $00000004;

var
  LFindFileDirectory: TFindFilesDirectories;
  LDirRootToSearch  : string = '';

function FindExcludingFolders(ARootDir: string; AListFolders: TArrObjectsFound): boolean;
begin
  result := false;
  //
  if (ARootDir.IsEmpty) or (length(AListFolders) = 0) then
    exit;
  //
  for var F in AListFolders do
    if (LowerCase(F) = LowerCase(ARootDir)) then
      exit(true);
end;

procedure FindDirsRecursive(                     { }
  ARootDir: string;                              { }
  var ADirsFound, AFilesFound: TArrObjectsFound; { }
  AFindObjects: TFindFilesDirectories;           { }
  ADirsExclude: TArrObjectsFound);
var
  LFilename        : PWideChar;
  LFIndexInfoLevels: TFindexInfoLevels;
  LFindFileData    : TWin32FindData; // Pointer;
  LFindexSearchOps : TFindexSearchOps;
  // LSearchFilter    : Pointer; = always null, for while in mswindows!
  LAdditionalFlags: cardinal;
  LFindResult     : NativeUInt;
  //
  // LGetLastError    : cardinal;
  LIsPointDirectory : boolean;
  LIsDirectory      : boolean;
  LFileFound        : string;
  LRootDirNormalized: string;
  LExcludeFolder    : boolean;
begin
  ARootDir       := IncludeTrailingPathDelimiter(ARootDir); // for new IDEs
  LExcludeFolder := (length(ADirsExclude) > 0);
  //
  LFilename         := PWideChar(ARootDir + '*.*');
  LFIndexInfoLevels := _FINDEX_INFO_LEVELS.FindExInfoStandard;
  LFindexSearchOps  := _FINDEX_SEARCH_OPS.FindExSearchLimitToDirectories;
  LAdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  //
  LFindResult := Winapi.Windows.FindFirstFileEx( { }
    LFilename,                                   { }
    LFIndexInfoLevels,                           { }
    @LFindFileData,                              { }
    LFindexSearchOps,                            { }
    nil { it should be NULL } ,                  { }
    LAdditionalFlags);
  //
  try
    // LGetLastError := GetLastError;
    //
    if (LFindResult <> INVALID_HANDLE_VALUE) then
      begin
        repeat
          LFileFound := LFindFileData.cFileName;
          //
          LIsPointDirectory  := ((LFileFound = '.') or (LFileFound = '..'));
          LIsDirectory       := ((LFindFileData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0);
          LRootDirNormalized := ARootDir + LFileFound;
          //
          // either file or directory, all in a few lines of code!
          if not(LIsPointDirectory) then
            begin
              if LIsDirectory and (ffdDirectory in AFindObjects) then
                begin
                  if not(LExcludeFolder and FindExcludingFolders(LRootDirNormalized, ADirsExclude)) then
                    begin
                      ADirsFound := ADirsFound + [LRootDirNormalized];
                      //
                      FindDirsRecursive(LRootDirNormalized, ADirsFound, AFilesFound, AFindObjects, ADirsExclude);
                    end;
                end
              else begin
                  if (ffdFile in AFindObjects) then
                    AFilesFound := AFilesFound + [LRootDirNormalized];
                end;
            end;
          //
        until not Winapi.Windows.FindNextFile(LFindResult, LFindFileData);
      end;
  finally
    if (LFindResult <> INVALID_HANDLE_VALUE) then
      Winapi.Windows.FindClose(LFindResult);
  end;
end;

 

for test:

var
  LArrFoundDirectories: TArrObjectsFound;
  LArrFoundFiles      : TArrObjectsFound;
begin
    LFindFileDirectory := LFindFileDirectory + [TFindFileDirectory.ffdDirectory, TFindFileDirectory.ffdFile];
    //
	FindDirsRecursive(Edit1.Text,                                    { }
    	LArrFoundDirectories,                                          { }
    	LArrFoundFiles,                                                { }
    	LFindFileDirectory,                                            { }
    	['d:\_WinBackups', 'd:\AndroidStudioSDK', 'd:\DelphiEditions', { }
    	'd:\RADStudioTests', 'd:\RADRX112Samples', 'd:\SDKsRAD']);

 

 

Project1_KTopvFOzLv.gif

 

Edited by programmerdelphi2k

Share this post


Link to post
3 hours ago, KodeZwerg said:

To me your way is useless, you lost what I was needing, finding specific files in specific folders.

you're wrong, my code find any file where you desire!

Share this post


Link to post

Let me tell it that way @programmerdelphi2k, with my posted source I was able to enter "documents" as an included folder-name and "*.pdf" as a file-mask (also "c:\windows" to exclude that folder) and as a result i get all "*.pdf" files that are inside of any "documents" folder. exemplary "c:\users\documents\manual.pdf" or "c:\users\public\documents\another.pdf" or "c:\users\username\documents\etc.pdf" ...

How-to with your way?

Share this post


Link to post
10 hours ago, KodeZwerg said:

How-to with your way?

it's quite simple:

  1. FindFirstFileEx accept: * and ? mask chars like D.O.S.
  2. or
  3. you can verify the files names before add in "AFilesFound"

I forgot add this, but it's quite simple add it. 😋

Share this post


Link to post
On 5/29/2023 at 6:20 PM, KodeZwerg said:

Yes, it was very complicated to fix.
Uploaded a fully working Demo application based on my above mentioned code, added "Exclude" capabilities as you wanted.

FindEx.zip

Bild_2023-05-29_144931591.png

Sorry but completely not working on Windows 7 32/64 bit

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×