Jump to content
robertjohns

File Search

Recommended Posts

2 hours ago, programmerdelphi2k said:

procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string; const AListBox: TListBox);
var
  LSearchRecord: TSearchRec;
  LIsDirectory : boolean;
  LContinue    : boolean;
begin
  for var LPattern in APatternToSearch do
    begin
      LContinue := FindFirst(IncludeTrailingPathDelimiter(AFolderRoot) + LPattern, faAnyFile, LSearchRecord) = 0;
      //
      if LContinue then
        try
          while LContinue do
            begin
              LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
              //
              if not((LSearchRecord.Name = '.') or (LSearchRecord.Name = '..')) then
                begin
                  if LIsDirectory then
                    AListBox.Items.Add('Dir: ' + LSearchRecord.Name)
                  else
                    AListBox.Items.Add('File: ' + LSearchRecord.Name);
                end;
              //
              LContinue := FindNext(LSearchRecord) = 0;
            end;
        finally
          FindClose(LSearchRecord);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  MyFindFilePattern('d:\MyZip', ['*.zip', '*.txt'], ListBox1);
end;

 

In Delphi Tokyo for var LPattern in APatternToSearch do does not work and what is LPattern ?  , Undeclared identifier: 'LPattern'

Share this post


Link to post
2 minutes ago, robertjohns said:

Delphi Tokyo for var LPattern in APatternToSearch do does not work and what is LPattern 

you can use default-way:

Quote

var i:integer;

begin

  for i := 0 to ( length(APatternToSearch) -1) do

....  APatternToSearch[ i ] ....

 

Share this post


Link to post
2 hours ago, programmerdelphi2k said:

procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string; const AListBox: TListBox);
var
  LSearchRecord: TSearchRec;
  LIsDirectory : boolean;
  LContinue    : boolean;
begin
  for var LPattern in APatternToSearch do
    begin
      LContinue := FindFirst(IncludeTrailingPathDelimiter(AFolderRoot) + LPattern, faAnyFile, LSearchRecord) = 0;
      //
      if LContinue then
        try
          while LContinue do
            begin
              LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
              //
              if not((LSearchRecord.Name = '.') or (LSearchRecord.Name = '..')) then
                begin
                  if LIsDirectory then
                    AListBox.Items.Add('Dir: ' + LSearchRecord.Name)
                  else
                    AListBox.Items.Add('File: ' + LSearchRecord.Name);
                end;
              //
              LContinue := FindNext(LSearchRecord) = 0;
            end;
        finally
          FindClose(LSearchRecord);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  MyFindFilePattern('d:\MyZip', ['*.zip', '*.txt'], ListBox1);
end;

 

oh! I tested this code .. this does the same task what the codes does I posted in my first post.

Actually I am trying to get all the files from Drive C:\ from its all directories and sub-directories even from special and hidden folders and this function fails for it

Share this post


Link to post

@robertjohns

 

first, try read all files/dirs would can be "very slowly" and "consume so much memory", etc... for that exists functions and library for this tasks. In RAD Studio you can try use "System.IOUtils.pas" and the "TDirectory" class for example.

 

now, said this, you can try this way: but remember in "c:\" you can have zillions of files/dirs!!!! do the little test with a folder contained a little files/subdirs!!

 

// when using "Recursive function, like this try dont use visual components like ListBox or any other"

type
  TMyArrToStoreTheResulted = array of string;

  // procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string;   const AListBox: TListBox);
procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string; var AArrToStoreTheResulted: TMyArrToStoreTheResulted);
var
  LSearchRecord: TSearchRec;
  LIsDirectory : boolean;
  LContinue    : boolean;
begin
  // for var LPattern in APatternToSearch do
  // ....."LPattern" will receive each element in "APatternToSearch" array!!!
  //
  for var i: integer := 0 to high(APatternToSearch) do
    begin
      AFolderRoot := IncludeTrailingPathDelimiter(AFolderRoot);
      //
      LContinue := FindFirst(AFolderRoot + APatternToSearch[i], faAnyFile, LSearchRecord) = 0;
      //
      if LContinue then
        try
          while LContinue do
            begin
              LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
              //
              if not((LSearchRecord.Name = '.') or (LSearchRecord.Name = '..')) then
                begin
                  if LIsDirectory then
                    begin
                      // AListBox.Items.Add('....... subDir (just for text on screen, delete this line): ' + AFolderRoot + LSearchRecord.Name);
                      //
                      MyFindFilePattern(AFolderRoot + LSearchRecord.Name, APatternToSearch, AArrToStoreTheResulted)
                    end
                  else
                    // AListBox.Items.Add('File: ' + AFolderRoot + LSearchRecord.Name);
                    AArrToStoreTheResulted := AArrToStoreTheResulted + [AFolderRoot + LSearchRecord.Name];
                end;
              //
              LContinue := FindNext(LSearchRecord) = 0;
            end;
        finally
          FindClose(LSearchRecord);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyResult: TMyArrToStoreTheResulted; // of course, more elements, more memory it's necessary! then, dont abuse!
begin
  // MyFindFilePattern('d:\MyZip', ['*.*', '*.txt', '*.zip'], ListBox1);
  //
  MyFindFilePattern('d:\MyZip', ['*.*', '*.txt', '*.zip'], LMyResult);
  //
  ListBox1.Items.AddStrings(LMyResult);
end;

 

Project1_5QJyp60k0W.gif

Edited by programmerdelphi2k

Share this post


Link to post
43 minutes ago, programmerdelphi2k said:

@robertjohns

 

first, try read all files/dirs would can be "very slowly" and "consume so much memory", etc... for that exists functions and library for this tasks. In RAD Studio you can try use "System.IOUtils.pas" and the "TDirectory" class for example.

 

now, said this, you can try this way: but remember in "c:\" you can have zillions of files/dirs!!!! do the little test with a folder contained a little files/subdirs!!

 


// when using "Recursive function, like this try dont use visual components like ListBox or any other"

type
  TMyArrToStoreTheResulted = array of string;

  // procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string;   const AListBox: TListBox);
procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: array of string; var AArrToStoreTheResulted: TMyArrToStoreTheResulted);
var
  LSearchRecord: TSearchRec;
  LIsDirectory : boolean;
  LContinue    : boolean;
begin
  // for var LPattern in APatternToSearch do
  // ....."LPattern" will receive each element in "APatternToSearch" array!!!
  //
  for var i: integer := 0 to high(APatternToSearch) do
    begin
      AFolderRoot := IncludeTrailingPathDelimiter(AFolderRoot);
      //
      LContinue := FindFirst(AFolderRoot + APatternToSearch[i], faAnyFile, LSearchRecord) = 0;
      //
      if LContinue then
        try
          while LContinue do
            begin
              LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
              //
              if not((LSearchRecord.Name = '.') or (LSearchRecord.Name = '..')) then
                begin
                  if LIsDirectory then
                    begin
                      // AListBox.Items.Add('....... subDir (just for text on screen, delete this line): ' + AFolderRoot + LSearchRecord.Name);
                      //
                      MyFindFilePattern(AFolderRoot + LSearchRecord.Name, APatternToSearch, AArrToStoreTheResulted)
                    end
                  else
                    // AListBox.Items.Add('File: ' + AFolderRoot + LSearchRecord.Name);
                    AArrToStoreTheResulted := AArrToStoreTheResulted + [AFolderRoot + LSearchRecord.Name];
                end;
              //
              LContinue := FindNext(LSearchRecord) = 0;
            end;
        finally
          FindClose(LSearchRecord);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyResult: TMyArrToStoreTheResulted; // of course, more elements, more memory it's necessary! then, dont abuse!
begin
  // MyFindFilePattern('d:\MyZip', ['*.*', '*.txt', '*.zip'], ListBox1);
  //
  MyFindFilePattern('d:\MyZip', ['*.*', '*.txt', '*.zip'], LMyResult);
  //
  ListBox1.Items.AddStrings(LMyResult);
end;

 

Project1_5QJyp60k0W.gif

This line does not work in Tokyo

 

MyFindFilePattern('d:\MyZip', ['*.*', '*.txt', '*.zip'], LMyResult);
ListBox1 .Items.AddStrings(LMyResult);

 

There is no overloaded version of 'AddStrings' that can be called with these arguments

 

 

Share this post


Link to post

@robertjohns

 

First, YOU DONT need quote the old post all time, ok! just "quote" the line in question ... not all!!!

Second, I think that "ITEMS" should have it, because is a TStrings used in many classes, like: TListBox, TMemo, TStringList, etc..

 

any way, try this

  //ListBox1.Items.AddStrings(LMyResult);
  //
  for var i: integer := 0 to high(LMyResult) do    //  high(xxxx)  =  ( length(xxxx) -1 )
    ListBox1.Items.Add(LMyResult[i]);
  // or
  for var ElementX in LMyResult do
    ListBox1.Items.Add(ElementX);

 

Edited by programmerdelphi2k

Share this post


Link to post
52 minutes ago, robertjohns said:

This line does not work in Tokyo

 

MyFindFilePattern('d:\MyZip', ['*.*', '*.txt', '*.zip'], LMyResult);
ListBox1 .Items.AddStrings(LMyResult);

 

There is no overloaded version of 'AddStrings' that can be called with these arguments

AddStrings() has an overload that takes a TArray<string> instead of a dynamic "array of string" (even in 10.2 Tokyo), eg:

type
  //TMyArrToStoreTheResulted = array of string;
  TMyArrToStoreTheResulted = TArray<string>;
Edited by Remy Lebeau

Share this post


Link to post
23 minutes ago, programmerdelphi2k said:

@robertjohns

 

First, YOU DONT need quote the old post all time, ok! just "quote" the line in question ... not all!!!

Second, I think that "ITEMS" should have it, because is a TString used in many classes, like: TListBox, TMemo, TStringList, etc..

 

any way, try this


  //ListBox1.Items.AddStrings(LMyResult);
  //
  for var i: integer := 0 to high(LMyResult) do
    ListBox1.Items.Add(LMyResult[i]);
  // or
  for var ElementX in LMyResult do
    ListBox1.Items.Add(ElementX);

 

Thanks it keeps repeating the search in folders again and again and never ends I tried C:\ '*.*'

Share this post


Link to post
9 minutes ago, robertjohns said:

and never ends I tried C:\ '*.

I said that "dont abuse"... c:\ have a zillions objects... using my code it works for sure!

  • using "c:\" , [ '*.*',  '*.txt' ] = duplicating many results because a ".TXT" is contained in a "*.*"
Edited by programmerdelphi2k

Share this post


Link to post
3 minutes ago, programmerdelphi2k said:

I said that "dont abuse"... c:\ have a zillions objects... using my code it works for sure!

  • using "c:\" , [ '*.*',  '*.txt' ] = duplicating many results because a ".TXT" is contained in a "*.*"

But I am using MyFindFilePattern('C:', ['*.*'], LMyResult);

It still keep repeating search files in the directories and sub-directors again and again and never ends

 

like

 

dir A

dir B

dir C

sub dir a

sub dir b

sub dir c

....

dir A

dir B

dir C

sub dir a

sub dir b

sub dir c

.... and never ends keeps going on again and again

 

Share this post


Link to post
5 minutes ago, robertjohns said:

keep repeating search files in the directories and sub-directors again and again and never ends

for sure!  if A contains B, and B contains C... what happens here?  read A... find B... read B.... find C... read C... and go back to B... and go on.... until the end and go back to A... etc...

Edited by programmerdelphi2k

Share this post


Link to post
On 5/26/2023 at 12:18 AM, programmerdelphi2k said:

for sure!  if A contains B, and B contains C... what happens here?  read A... find B... read B.... find C... read C... and go back to B... and go on.... until the end and go back to A... etc...

Is there any way to search C:\ Driver with all sub-directories but skip Windows directory ?

Share this post


Link to post

Here is my way on Windows to search for files or folders by using fastest method.
There is still space for optimizations.

Written and tested with FreePascal.

unit uMain;

{$mode objfpc}{$H+}

interface

uses
  Windows, Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ExtCtrls;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    btnStart: TButton;
    cbSubFolder: TCheckBox;
    edtBase: TEdit;
    edtFolder: TEdit;
    edtMask: TEdit;
    lblResult: TLabel;
    lbFiles: TListBox;
    pnlOptions: TPanel;
    pnlResult: TPanel;
    procedure btnStartClick(Sender: TObject);
  private
  public

  end;

var
  frmMain: TfrmMain;

implementation

{$R *.lfm}

type
  // My variant of an "StringList"
  TStrArr = array of WideString;

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: TStrArr; const AString: WideString); 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 BasePath: WideString; const IncludeSubFolders: Boolean = False): TStrArr;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFINDEX_INFO_LEVELS;
  FindExSearchOps : TFINDEX_SEARCH_OPS;
  AdditionalFlags : DWORD;
  tmp             : TStrArr;
  i, ii           : Integer;
begin
  SetLength(Result{%H-}, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(BasePath) + '*.*')
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    repeat
      if ((Win32FindData.cFileName <> '.') and (Win32FindData.cFileName <> '..')
          and (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
        AddStrArr(Result{%H-}, IncludeTrailingBackslash(BasePath) + Win32FindData.cFileName);
    until not Windows.FindNextFileW(FindExHandle, Win32FindData);
  Windows.FindClose(FindExHandle);
  if IncludeSubFolders then
    for i := Low(Result) to High(Result) do
      begin
        tmp := FindExFolders(Result[i], IncludeSubFolders);
        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 BasePath: WideString; const FileMask: WideString = '*.*'; const IncludeSubFolders: Boolean = False): TStrArr;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFINDEX_INFO_LEVELS;
  FindExSearchOps : TFINDEX_SEARCH_OPS;
  AdditionalFlags : DWORD;
  tmp, Folders    : TStrArr;
  i, ii           : Integer;
begin
  SetLength(Result{%H-}, 0);
  SetLength(Folders{%H-}, 0);
  SetLength(tmp{%H-}, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(BasePath) + FileMask)
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    repeat
      if ((Win32FindData.cFileName <> '.') and (Win32FindData.cFileName <> '..')) then
        begin
          if (0 = (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)) then
            AddStrArr(Result, IncludeTrailingBackslash(BasePath) + Win32FindData.cFileName);
          if (IncludeSubFolders and
             (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
            AddStrArr(Folders, IncludeTrailingBackslash(BasePath) + Win32FindData.cFileName);
        end;
    until not Windows.FindNextFileW(FindExHandle, Win32FindData);
  Windows.FindClose(FindExHandle);
  if IncludeSubFolders then
    for i := Low(Folders) to High(Folders) do
      begin
        tmp := FindExFiles(Folders[i], FileMask, IncludeSubFolders);
        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"
// BaseFolder = what foldername is a must for results? eg "Documents", can be left empty for all
// 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 BasePath: WideString; const BaseFolder: WideString = ''; const FileMask: WideString = '*.*'; const IncludeSubFolders: Boolean = False): TStrArr;
var
 tmp, Folders, Files: TStrArr;
 i, ii: Integer;
begin
  SetLength(tmp{%H-}, 0);
  SetLength(Folders{%H-}, 0);
  SetLength(Files{%H-}, 0);
  // collect folder(s) to work on
  if IncludeSubFolders then
    tmp := FindExFolders(BasePath, IncludeSubFolders)
    else
    AddStrArr(tmp, BasePath);
  // sieve out folders that match criteria
  if (BaseFolder <> '') then
    begin
      for i := Low(tmp) to High(tmp) do
        if (0 <> Pos(UpperCase(BaseFolder), UpperCase(tmp[i]))) then
          AddStrArr(Folders, tmp[i]);
    end
    else
      Folders := tmp;
  SetLength(tmp, 0);
  // get files that matching the criteria
  for i := Low(Folders) to High(Folders) do
    begin
      tmp := FindExFiles(Folders[i], FileMask); // do not enable the IncludeSubFolders switch here (!)
      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;

{ TfrmMain }

procedure TfrmMain.btnStartClick(Sender: TObject);
var
  i: Integer;
  List: TStrArr;
begin
  lblResult.Caption := 'Search in progress, program may freeze, please wait...';
  lblResult.Refresh;
  List := FindEx(WideString(edtBase.Text), WideString(edtFolder.Text), WideString(edtMask.Text), cbSubFolder.Checked);
  lbFiles.Items.BeginUpdate;
  lbFiles.Clear;
  for i := Low(List) to High(List) do
    lbFiles.Items.Add(AnsiString(List[i]));
  lbFiles.Items.EndUpdate;
  lblResult.Caption := 'Found ' + IntToStr(lbFiles.Count) + ' file(s).';
  SetLength(List, 0);
end;

end.

It should be easy to adapt any different kind of specifications, like excluding a defined folder.

Enjoy!

Share this post


Link to post
3 hours ago, KodeZwerg said:

Here is my way on Windows to search for files or folders by using fastest method.
There is still space for optimizations.

Written and tested with FreePascal.


unit uMain;

{$mode objfpc}{$H+}

interface

uses
  Windows, Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ExtCtrls;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    btnStart: TButton;
    cbSubFolder: TCheckBox;
    edtBase: TEdit;
    edtFolder: TEdit;
    edtMask: TEdit;
    lblResult: TLabel;
    lbFiles: TListBox;
    pnlOptions: TPanel;
    pnlResult: TPanel;
    procedure btnStartClick(Sender: TObject);
  private
  public

  end;

var
  frmMain: TfrmMain;

implementation

{$R *.lfm}

type
  // My variant of an "StringList"
  TStrArr = array of WideString;

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: TStrArr; const AString: WideString); 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 BasePath: WideString; const IncludeSubFolders: Boolean = False): TStrArr;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFINDEX_INFO_LEVELS;
  FindExSearchOps : TFINDEX_SEARCH_OPS;
  AdditionalFlags : DWORD;
  tmp             : TStrArr;
  i, ii           : Integer;
begin
  SetLength(Result{%H-}, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(BasePath) + '*.*')
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    repeat
      if ((Win32FindData.cFileName <> '.') and (Win32FindData.cFileName <> '..')
          and (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
        AddStrArr(Result{%H-}, IncludeTrailingBackslash(BasePath) + Win32FindData.cFileName);
    until not Windows.FindNextFileW(FindExHandle, Win32FindData);
  Windows.FindClose(FindExHandle);
  if IncludeSubFolders then
    for i := Low(Result) to High(Result) do
      begin
        tmp := FindExFolders(Result[i], IncludeSubFolders);
        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 BasePath: WideString; const FileMask: WideString = '*.*'; const IncludeSubFolders: Boolean = False): TStrArr;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFINDEX_INFO_LEVELS;
  FindExSearchOps : TFINDEX_SEARCH_OPS;
  AdditionalFlags : DWORD;
  tmp, Folders    : TStrArr;
  i, ii           : Integer;
begin
  SetLength(Result{%H-}, 0);
  SetLength(Folders{%H-}, 0);
  SetLength(tmp{%H-}, 0);
  FindExInfoLevels := FindExInfoBasic;
  FindExSearchOps  := FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Windows.FindFirstFileExW(
                        PWideChar(IncludeTrailingBackslash(BasePath) + FileMask)
                        ,FindExInfoLevels, @Win32FindData, FindExSearchOps, nil
                        ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    repeat
      if ((Win32FindData.cFileName <> '.') and (Win32FindData.cFileName <> '..')) then
        begin
          if (0 = (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)) then
            AddStrArr(Result, IncludeTrailingBackslash(BasePath) + Win32FindData.cFileName);
          if (IncludeSubFolders and
             (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then
            AddStrArr(Folders, IncludeTrailingBackslash(BasePath) + Win32FindData.cFileName);
        end;
    until not Windows.FindNextFileW(FindExHandle, Win32FindData);
  Windows.FindClose(FindExHandle);
  if IncludeSubFolders then
    for i := Low(Folders) to High(Folders) do
      begin
        tmp := FindExFiles(Folders[i], FileMask, IncludeSubFolders);
        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"
// BaseFolder = what foldername is a must for results? eg "Documents", can be left empty for all
// 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 BasePath: WideString; const BaseFolder: WideString = ''; const FileMask: WideString = '*.*'; const IncludeSubFolders: Boolean = False): TStrArr;
var
 tmp, Folders, Files: TStrArr;
 i, ii: Integer;
begin
  SetLength(tmp{%H-}, 0);
  SetLength(Folders{%H-}, 0);
  SetLength(Files{%H-}, 0);
  // collect folder(s) to work on
  if IncludeSubFolders then
    tmp := FindExFolders(BasePath, IncludeSubFolders)
    else
    AddStrArr(tmp, BasePath);
  // sieve out folders that match criteria
  if (BaseFolder <> '') then
    begin
      for i := Low(tmp) to High(tmp) do
        if (0 <> Pos(UpperCase(BaseFolder), UpperCase(tmp[i]))) then
          AddStrArr(Folders, tmp[i]);
    end
    else
      Folders := tmp;
  SetLength(tmp, 0);
  // get files that matching the criteria
  for i := Low(Folders) to High(Folders) do
    begin
      tmp := FindExFiles(Folders[i], FileMask); // do not enable the IncludeSubFolders switch here (!)
      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;

{ TfrmMain }

procedure TfrmMain.btnStartClick(Sender: TObject);
var
  i: Integer;
  List: TStrArr;
begin
  lblResult.Caption := 'Search in progress, program may freeze, please wait...';
  lblResult.Refresh;
  List := FindEx(WideString(edtBase.Text), WideString(edtFolder.Text), WideString(edtMask.Text), cbSubFolder.Checked);
  lbFiles.Items.BeginUpdate;
  lbFiles.Clear;
  for i := Low(List) to High(List) do
    lbFiles.Items.Add(AnsiString(List[i]));
  lbFiles.Items.EndUpdate;
  lblResult.Caption := 'Found ' + IntToStr(lbFiles.Count) + ' file(s).';
  SetLength(List, 0);
end;

end.

It should be easy to adapt any different kind of specifications, like excluding a defined folder.

Enjoy!

 

Undeclared identifier in Delphi 10.02

 

  FindExInfoLevels: TFINDEX_INFO_LEVELS;
  FindExSearchOps : TFINDEX_SEARCH_OPS;

 

Undeclared identifier: 'TFINDEX_INFO_LEVELS'

Undeclared identifier: 'TFINDEX_SEARCH_OPS'

 

 

 

Share this post


Link to post
3 hours ago, robertjohns said:

 

type
  _FINDEX_INFO_LEVELS = (FindExInfoStandard, FindExInfoBasic, FindExInfoMaxInfoLevel);
  TFINDEX_INFO_LEVELS = _FINDEX_INFO_LEVELS;
  _FINDEX_SEARCH_OPS  = (FindExSearchNameMatch, FindExSearchLimitToDirectories,
                         FindExSearchLimitToDevices, FindExSearchMaxSearchOp);
  TFINDEX_SEARCH_OPS  = _FINDEX_SEARCH_OPS;

 

Share this post


Link to post
1 hour ago, KodeZwerg said:

type
  _FINDEX_INFO_LEVELS = (FindExInfoStandard, FindExInfoBasic, FindExInfoMaxInfoLevel);
  TFINDEX_INFO_LEVELS = _FINDEX_INFO_LEVELS;
  _FINDEX_SEARCH_OPS  = (FindExSearchNameMatch, FindExSearchLimitToDirectories,
                         FindExSearchLimitToDevices, FindExSearchMaxSearchOp);
  TFINDEX_SEARCH_OPS  = _FINDEX_SEARCH_OPS;

 

Thanks but actually not working in Delphi

 

E2010 Incompatible types: 'Winapi.Windows._FINDEX_INFO_LEVELS' and 'Unit1._FINDEX_INFO_LEVELS'
E2010 Incompatible types: 'Winapi.Windows._FINDEX_SEARCH_OPS' and 'Unit1._FINDEX_SEARCH_OPS'

 

Share this post


Link to post
// when using "Recursive function, like this try dont use visual components like ListBox or any other"

type
  TMyArrToStoreTheResulted = array of string;
  TMyPatternToSearch       = array of string;
  TMyFoldersToSkip         = array of string;

function MyFoldersToSkip(AFoldersToSkip: TMyFoldersToSkip; ACurrentFolderOnSearch: string): boolean;
begin
  result := false;
  //
  if ACurrentFolderOnSearch.IsEmpty then { or  (length(AFoldersToSkip) = 0)  if not verifyed before on MyFindFilePattern() }
    exit;
  //
  for var F in AFoldersToSkip do
    if (F = ACurrentFolderOnSearch) then
      exit(true);
end;

procedure MyFindFilePattern(AFolderRoot: string; APatternToSearch: TMyPatternToSearch; AFoldersToSkip: TMyFoldersToSkip; var AArrToStoreTheResulted: TMyArrToStoreTheResulted);
var
  LSearchRecord    : TSearchRec;
  LIsDirectory     : boolean;
  LContinue        : boolean;
  LLookFolderToSkip: boolean;
begin
  LLookFolderToSkip := (length(AFoldersToSkip) > 0); // to avoid verify all time...
  //
  for var i: integer := 0 to high(APatternToSearch) do
    begin
      AFolderRoot := IncludeTrailingPathDelimiter(AFolderRoot);
      //
      LContinue := FindFirst(AFolderRoot + APatternToSearch[i], faAnyFile, LSearchRecord) = 0;
      //
      if LContinue then
        try
          while LContinue do
            begin
              LIsDirectory := ((LSearchRecord.Attr and faDirectory) = faDirectory);
              //
              if not((LSearchRecord.Name = '.') or (LSearchRecord.Name = '..')) then
                begin
                  if LIsDirectory then
                    begin
                      if not(LLookFolderToSkip and MyFoldersToSkip(AFoldersToSkip, LSearchRecord.Name)) then
                        MyFindFilePattern(AFolderRoot + LSearchRecord.Name, APatternToSearch, AFoldersToSkip, AArrToStoreTheResulted);
                    end
                  else
                    AArrToStoreTheResulted := AArrToStoreTheResulted + [AFolderRoot + LSearchRecord.Name];
                end;
              //
              LContinue := FindNext(LSearchRecord) = 0;
            end;
        finally
          FindClose(LSearchRecord);
        end;
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LMyResult : TMyArrToStoreTheResulted; // of course, more elements, more memory it's necessary! then, dont abuse!
  LTickCount: UInt64;
begin
  LTickCount := GetTickCount64;
  //
  // MyFindFilePattern('c:\', ['*.*'], [], LMyResult);
  MyFindFilePattern('D:\Downloads', ['*.*'], ['DontLookThisFolder'], LMyResult);
  //
  LTickCount := GetTickCount64 - LTickCount;
  //
  ListBox1.Items.AddStrings(LMyResult);
  //
  ListBox1.Items.Add('Items: ' + ListBox1.Items.Count.ToString + ', Time total: ' + LTickCount.ToString);
  //
  { for var i: integer := 0 to high(LMyResult) do
    ListBox1.Items.Add(LMyResult[i]);
    // or
    for var ElementX in LMyResult do
    ListBox1.Items.Add(ElementX); }
end;

 

Edited by programmerdelphi2k

Share this post


Link to post
1 hour ago, programmerdelphi2k said:

then JUMP it"

I think the word you're looking for is "skip".

But yes that's exactly how to implement this.

Share this post


Link to post

@programmerdelphi2k

 

Actually I am after the Delphi Search File function which will search all the directories and sub-directories in Drive C:\  excluding Windows Folder  [need to skip Windows folder from search]

 

Like the normal cmd command does

 

dir /S /B /A:-D *.exe | findstr /V /I /C:"\\Windows\\"

 

But None of the Delphi function does the same , when Search starts it search in Windows folder too

Share this post


Link to post
4 hours ago, robertjohns said:

Like the normal cmd command does

 

dir /S /B /A:-D *.exe | findstr /V /I /C:"\\Windows\\"

 

But None of the Delphi function does the same , when Search starts it search in Windows folder too

This command also searches all subdirectories and only later applies a filter removing all entries that start with c:\Windows\.

 

The function @programmerdelphi2k gave you is actually more efficient because it doesn't descend into the windows directory at all.

  • Like 1

Share this post


Link to post
21 hours ago, robertjohns said:

Thanks but actually not working in Delphi

 


E2010 Incompatible types: 'Winapi.Windows._FINDEX_INFO_LEVELS' and 'Unit1._FINDEX_INFO_LEVELS'
E2010 Incompatible types: 'Winapi.Windows._FINDEX_SEARCH_OPS' and 'Unit1._FINDEX_SEARCH_OPS'

 

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

Share this post


Link to post
5 hours ago, robertjohns said:

But None of the Delphi function does the same , when Search starts it search in Windows folder too

I think you may be wrong, because as @dummzeuch said, my proposal above is precisely "ignore access to the indicated directory, in this case "Windows". This way, when receiving the name of the directory called "Windows", the function simply move to the next one, so it won't even be accessed!


But as I said at the beginning, there are many libraries and functions ready to be used in Delphi! One of them is in the "System.IOUtils.pas" unit, that is, TDirectory, among others!

 

image.thumb.png.29fe375de50b8900a91e20e37d560133.png

Edited by programmerdelphi2k

Share this post


Link to post

Here a more simple way using "TDirectory.GETFILES(...)", but it was slow than my function above  ( +20 seconds )

procedure TForm1.Button2Click(Sender: TObject);
var
  LRootPath     : string;
  LPatternSrc   : string;
  LExcludeFolder: string;
  LFoundFiles   : TArray<string>;
  LTickCounter  : UInt64;
begin
  {
    NOTE:
    The "GetFiles" function will only return a value if there is at least one file within the search directory!
    Otherwise, nothing will be returned.

    So, there must be at least one file inside a directory for the search to show any result.
    This is a consequence of the "GetFiles" function of the "TDirectory" record.
  }
  LRootPath      := 'C:\';      // where start?
  LPatternSrc    := '*.*';      // what see?
  LExcludeFolder := '\Windows'; // any "object" like this, will be "excluded from search"
  //
  LTickCounter := GetTickCount64;
  //
  try
    // GetFiles raise a exception if any error on syntax!
    LFoundFiles := TDirectory.GetFiles(LRootPath, LPatternSrc, TSearchOption.soAllDirectories, { }
      function(const Path: string; const SearchRec: TSearchRec): boolean                       { }
      begin                                                                                    { }
        result := not Path.Contains(LExcludeFolder); // unfortunatelly, ALL object will be compared! = more time consumed
      end);
    //
    LTickCounter := GetTickCount64 - LTickCounter;
    //
    ListBox1.Items.Clear;
    ListBox1.Items.AddStrings(LFoundFiles);
    //
    ShowMessage(Format('Time: %dms', [LTickCounter]));
  except
    on E: Exception do
      ShowMessage(E.Message);
  end;
end;

image.thumb.png.6cdd6f7b4e9ad55a8de3f6734f375e3b.png

Edited by programmerdelphi2k

Share this post


Link to post
41 minutes ago, programmerdelphi2k said:

image.thumb.png.6cdd6f7b4e9ad55a8de3f6734f375e3b.png

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.

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

×