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.