using code from a tutorial and making various modifications i have got working code for a recursive procedure which searches for a file with a file name which has been entered by the user at a given path and through sub folders when the parameters are passed from another procedure at a button click.
it is as follows :
procedure TfrmProject.btnOpenDocumentClick(Sender: TObject);
begin
FileSearch('C:\Users\Guest\Documents', edtDocument.Text+'.docx');
end;
procedure TfrmProject.FileSearch(const Pathname, FileName : string);
var Word : Variant;
Rec : TSearchRec;
Path : string;
begin
Path := IncludeTrailingBackslash(Pathname);
if FindFirst(Path + FileName, faAnyFile - faDirectory, Rec) = 0
then repeat Word:=CreateOLEObject('Word.Application');
Word.Visible:=True;
Word.Documents.Open(Path + FileName);
until FindNext(Rec) <> 0;
FindClose(Rec);
if FindFirst(Path + '*.*', faDirectory, Rec) = 0 then
try
repeat
if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name<>'.') and (Rec.Name<>'..') then
FileSearch(Path + Rec.Name, FileName);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
end; //procedure FileSearch
After trying to learn what is happening of i have gained a good understanding up until the point of the first FindClose(Rec), however this section of code i'm still unsure of :
if FindFirst(Path + '*.*', faDirectory, Rec) = 0 then
try
repeat
if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name<>'.') and (Rec.Name<>'..') then
FileSearch(Path + Rec.Name, FileName);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
end;
my guess is that the first line is checking whether there are any subfolders found in the given path, but i'm not too sure on the rest and how it works if that is even correct.
help would be appreciated.
Related
A parent directory D:\AAA has 2 child empty Directory D:\AAA\BB1 and D:\AAA\BB2
my requirement is how to remove empty Directory recursively.
Here are two function found on internet as below :
//remove empty Directory recursively
function RemoveEmptyDirectory(path: string) : Boolean;
var
MySearch: TSearchRec;
Ended: Boolean;
begin
if FindFirst(path + '\*.*', faDirectory, MySearch) = 0 then
begin
repeat
if ((MySearch.Attr and faDirectory) = faDirectory) and
(MySearch.Name[1] <> '.') then
begin
if DirectoryIsEmpty(path + '\' + MySearch.Name) then
TDirectory.Delete(path + '\' + MySearch.Name)
else
begin
RemoveEmptyDirectory(path + '\' + MySearch.Name);
if DirectoryIsEmpty(path + '\' + MySearch.Name) then
RemoveEmptyDirectory(path + '\' + MySearch.Name);
end;
end;
until FindNext(MySearch) <> 0;
FindClose(MySearch);
end;
end;
// check directory is empty or not
function DirectoryIsEmpty(Directory: string): Boolean;
var
SR: TSearchRec;
i: Integer;
begin
Result := False;
FindFirst(IncludeTrailingPathDelimiter(Directory) + '*', faAnyFile, SR);
for i := 1 to 2 do
if (SR.Name = '.') or (SR.Name = '..') then
Result := FindNext(SR) <> 0;
FindClose(SR);
end;
My problem is here : at first run function RemoveEmptyDirectory will found D:\AAA is not empty, then will run send round (recursively way),
After remove 2 child directory D:\AAA\BB1 and D:\AAA\BB2, the parent will become an empty Directory,
Back to first round place the function DirectoryIsEmpty report the parent is not an empty directory!!!!
Why !!!!
Is windows system still not change the directory state ???
So, is there any good suggestion that could meet my requirement.
You never check D:\AAA itself.
Just make checking and deletion in the end:
function RemoveEmptyDirectory(path: string) : Boolean;
var
MySearch: TSearchRec;
Ended: Boolean;
begin
if FindFirst(path + '\*.*', faDirectory, MySearch) = 0 then
begin
repeat
if ((MySearch.Attr and faDirectory) = faDirectory) and
(MySearch.Name[1] <> '.') then
begin
if DirectoryIsEmpty(path + '\' + MySearch.Name) then
TDirectory.Delete(path + '\' + MySearch.Name)
else
begin
RemoveEmptyDirectory(path + '\' + MySearch.Name);
if DirectoryIsEmpty(path + '\' + MySearch.Name) then
RemoveEmptyDirectory(path + '\' + MySearch.Name);
end;
end;
until FindNext(MySearch) <> 0;
FindClose(MySearch);
end;
if DirectoryIsEmpty(path) then
TDirectory.Delete(path);
end;
You can use TDirectory as
TDirectory.Delete('D:\AAA', True);
If you need to check if the directories are empty or not, you can use TDirectory.GetDirectories() as
Var
S: string;
begin
for S in TDirectory.GetDirectories('D:\AAA', '*', TSearchOption.soAllDirectories) do
begin
if TDirectory.IsEmpty(S) then
TDirectory.Delete(S);
end;
If TDirectory.IsEmpty('D:\AAA') then
TDirectory.Delete('D:\AAA');
I think this is simple and straightforward and should do fine if top performance is not crucial:
procedure RemoveEmptyDirs;
var
i,Removed:integer;
Arr:TStringDynArray;
const
TargedDir = 'C:\BunchOfDirs\';
begin
Arr := TDirectory.GetDirectories(TargedDir,'*',TSearchOption.soAllDirectories);
Repeat
Removed := 0;
For i := High(Arr) downto Low(Arr) do begin
If TDirectory.IsEmpty(Arr[i]) then begin
TDirectory.Delete(Arr[i]);
System.Delete(Arr,i,1);
Inc(Removed);
end;
end;
Until Removed = 0;
end;
I am using the following code to get a list of files and folders. I cannot seem to get the list to include hidden files and folders.
procedure GetAllSubFolders(sPath: String; Listbox: TListbox);
var
Path: String;
Rec: TSearchRec;
begin
try
Path := IncludeTrailingBackslash(sPath);
if FindFirst(Path + '*.*', faDirectory, Rec) = 0 then
try
repeat
if (Rec.Name <> '.') and (Rec.Name <> '..') then
begin
if (ExtractFileExt(Path + Rec.Name) <> '') And (Directoryexists(Path + Rec.Name + '\') = False) then
Begin
Listbox.Items.Add(Path+Rec.Name);
End;
GetAllSubFolders(Path + Rec.Name, Listbox);
end;
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
except
on e: Exception do
Showmessage('Err : TForm1.GetAllSubFolders - ' + e.Message);
end;
end;
Here's a quote from Delphi help:
The Attr parameter specifies the special files to include in addition to all normal files. Choose from these file attribute constants when specifying the Attr parameter.
You should use faDirectory or faHidden or other flags instead of just faDirectory and read help on FindFirst!
To change the attribute of a file is easy with FileSetAttr.
I want to change the attributes of all files located on any partition ("D:" for example).
For the search function I tried:
procedure FileSearch(const PathName, FileName : string) ;
var
Rec : TSearchRec;
Path : string;
begin
Path := IncludeTrailingPathDelimiter(PathName) ;
if FindFirst (Path + FileName, faAnyFile - faDirectory, Rec) = 0 then
try
repeat
ListBox1.Items.Add(Path + Rec.Name) ;
until FindNext(Rec) <> 0;
finally
FindClose(Rec) ;
end;
But how can I use this to traverse the entire drive?
You will indeed need to iterate across the entire drive setting attributes file by file. You will need to modify the code to recurse into sub-directories. And obviously you will actually need to call the function that sets attributes.
The basic approach looks like this:
type
TFileAction = reference to procedure(const FileName: string);
procedure WalkDirectory(const Name: string; const Action: TFileAction);
var
F: TSearchRec;
begin
if FindFirst(Name + '\*', faAnyFile, F) = 0 then begin
try
repeat
if (F.Attr and faDirectory <> 0) then begin
if (F.Name <> '.') and (F.Name <> '..') then begin
WalkDirectory(Name + '\' + F.Name, Action);
end;
end else begin
Action(Name + '\' + F.Name);
end;
until FindNext(F) <> 0;
finally
FindClose(F);
end;
end;
end;
I've written this in a generic way to allow you to use the same walking code with different actions. If you were to use this code you'd need to wrap up the attribute setting code into a procedure which you pass as Action. If you don't need the generality, then remove all mention of TFileAction and replace the call to Action with your attribute setting code. Like this:
procedure WalkDirectory(const Name: string);
var
F: TSearchRec;
begin
if FindFirst(Name + '\*', faAnyFile, F) = 0 then begin
try
repeat
if (F.Attr and faDirectory <> 0) then begin
if (F.Name <> '.') and (F.Name <> '..') then begin
WalkDirectory(Name + '\' + F.Name);
end;
end else begin
DoSetAttributes(Name + '\' + F.Name);
end;
until FindNext(F) <> 0;
finally
FindClose(F);
end;
end;
end;
Expect this to take quite a while when you try to run it on an entire volume. You'll want to do your testing on a directory containing only a few files and a couple of sub-directory levels.
Also, be prepared for your code that modifies attributes to fail for some files. You cannot expect to perform volume wide operations without sometimes encountering failures due to, for instance, security. Make your code robust to such scenarios.
I am trying to find all files that have the extenstion .cbr or .cbz
If i set my mask to *.cb?
it finds *.cbproj files. How can i set the mask to only find .cbr and .cbz files?
here is code i am using.
I have two edit boxes EDIT1 is the location to search, EDIT2 is where i put my mask. A listbox to show what it found and a Search button.
edit1 := c:\
edit2 := mask (*.cb?)
space
procedure TFAutoSearch.FileSearch(const PathName, FileName : string; const InDir : boolean);
var Rec : TSearchRec;
Path : string;
begin
Path := IncludeTrailingBackslash(PathName);
if FindFirst(Path + FileName, faAnyFile - faDirectory, Rec) = 0 then
try
repeat
ListBox1.Items.Add(Path + Rec.Name);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
If not InDir then Exit;
if FindFirst(Path + '*.*', faDirectory, Rec) = 0 then
try
repeat
if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name<>'.') and (Rec.Name<>'..') then
FileSearch(Path + Rec.Name, FileName, True);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
end; //procedure FileSearch
procedure TFAutoSearch.Button1Click(Sender: TObject);
begin
FileSearch(Edit1.Text, Edit2.Text, CheckBox1.State in [cbChecked]);
end;
end.
The easiest way is to use ExtractFileExt against the current filename and check to see if it matches either of your desired extensions.
Here's a fully-rewritten version of your FileSearch routine which does exactly what you're trying to do (according to your question, anyway):
procedure TFAutoSearch.FileSearch(const ARoot: String);
var
LExt, LRoot: String;
LRec: TSearchRec;
begin
LRoot := IncludeTrailingPathDelimiter(ARoot);
if FindFirst(LRoot + '*.*', faAnyFile, LRec) = 0 then
begin
try
repeat
if (LRec.Attr and faDirectory <> 0) and (LRec.Name <> '.') and (LRec.Name <> '..') then
FileSearch(LRoot + LRec.Name)
else
begin
LExt := UpperCase(ExtractFileExt(LRoot + LRec.Name));
if (LExt = '.CBR') or (LExt = '.CBZ') then
ListBox1.Items.Add(LRoot + LRec.Name);
end;
until (FindNext(LRec) <> 0);
finally
FindClose(LRec);
end;
end;
end;
While the other answer suggesting the use of multiple extensions as a mask *.cbr;*.cbz should (in principal anyway) work, I've noted through bitter experience that the FindFirst and FindNext methods in Delphi tend not to accept multiple extensions in a mask!
The code I've provided should work just fine for your needs, so enjoy!
UPDATED: To allow the use of multiple extensions in a Mask dynamically at runtime (as indicated by the OP's first comment to this answer).
What we're going to do is take a String from your TEdit control (this String is one or more File Extensions as you would expect), "Explode" the String into an Array, and match each file against each Extension in the Array.
Sounds more complicated than it is:
type
TStringArray = Array of String; // String Dynamic Array type...
// Now let's provide a "Mask Container" inside the containing class...
TFAutoSearch = class(TForm)
// Normal stuff in here
private
FMask: TStringArray; // Our "Mask Container"
end;
This code will populate FMask with each individual mask extension separated by a ; such as .CBR;.CBZ.
Note this method will not accept Wildcard characters or any other Regex magic, but you can modify it as you require!
procedure TFAutoSearch.ExplodeMask(const AValue: String);
var
LTempVal: String;
I, LPos: Integer;
begin
LTempVal := AValue;
I := 0;
while Length(LTempVal) > 0 do
begin
Inc(I);
SetLength(FMask, I);
LPos := Pos(';', LTempVal);
if (LPos > 0) then
begin
FMask[I - 1] := UpperCase(Copy(LTempVal, 0, LPos - 1));
LTempVal := Copy(LTempVal, LPos + 1, Length(LTempVal));
end
else
begin
FMask[I - 1] := UpperCase(LTempVal);
LTempVal := EmptyStr;
end;
end;
end;
We now need a function to determine if the nominated file matches any of the defined Extensions:
function TFAutoSearch.MatchMask(const AFileName: String): Boolean;
var
I: Integer;
LExt: String;
begin
Result := False;
LExt := UpperCase(ExtractFileExt(LExt));
for I := Low(FMask) to High(FMask) do
if (LExt = FMask[I]) then
begin
Result := True;
Break;
end;
end;
Now here's the modified FileSearch procedure:
procedure TFAutoSearch.FileSearch(const ARoot: String);
var
LRoot: String;
LRec: TSearchRec;
begin
LRoot := IncludeTrailingPathDelimiter(ARoot);
if FindFirst(LRoot + '*.*', faAnyFile, LRec) = 0 then
begin
try
repeat
if (LRec.Attr and faDirectory <> 0) and (LRec.Name <> '.') and (LRec.Name <> '..') then
FileSearch(LRoot + LRec.Name)
else
begin
if (MatchMask(LRoot + LRec.Name)) then
ListBox1.Items.Add(LRoot + LRec.Name);
end;
until (FindNext(LRec) <> 0);
finally
FindClose(LRec);
end;
end;
end;
Finally, here's how you initiate your search:
procedure TFAutoSearch.btnSearchClick(Sender: TObject);
begin
ExplodeMask(edMask.Text);
FileSearch(edPath.Text);
end;
Where edMask is defined in your question as Edit2 and edPath is defined in your question as Edit1. Just remember that this method doesn't support the use of Wildcard or other Special Chars, so edMask.Text should be something like .CBR;.CBZ
If you use the Regex library for Delphi, you could easily modify this method to support all of the Expression Cases you could ever imagine!
Dorin's suggestion to replace your mask with *.cbr;*.cbz should work. That is, it won't match cbproj anymore. It would, however, still match cbzy or any other extension that starts with cbr or cbz. The reason for this is that FindFirst/FindNext match both the long form and the legacy short forms (8.3) of file names. So the short forms will always have truncated extensions where cbproj is shortened to cbp, and therefore matches cb?.
This is supposed to be avoidable by using FindFirstEx instead, but this requires a small rewrite of your search function and actually didn't work for me. So instead I just double checked all matches with the MatchesMask function.
I have got a procedure which searches for a file entered by the user in a path and subpaths, i have a good understanding of the most of it except for this line:
if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name<>'.') and (Rec.Name<>'..')
The whole procedure is as follows, help would be appreciated as im not sure exactly of the purpose of this line of code, is it checking something in the subpath?.
procedure TfrmProject.btnOpenDocumentClick(Sender: TObject);
begin
FileSearch('C:\Users\Guest\Documents', edtDocument.Text+'.docx');
end;
procedure TfrmProject.FileSearch(const Pathname, FileName : string);
var Word : Variant;
Rec : TSearchRec;
Path : string;
begin
Path := IncludeTrailingBackslash(Pathname);
if FindFirst(Path + FileName, faAnyFile - faDirectory, Rec) = 0
then repeat Word:=CreateOLEObject('Word.Application');
Word.Visible:=True;
Word.Documents.Open(Path + FileName);
until FindNext(Rec) <> 0;
FindClose(Rec);
if FindFirst(Path + '*.*', faDirectory, Rec) = 0 then
try
repeat
if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name<>'.') and (Rec.Name<>'..') then
FileSearch(Path + Rec.Name, FileName);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
end; //procedure FileSearch
1) The faDirectory attibute indicates whether the entry is a directory.
(Rec.Attr and faDirectory) <> 0 //check if the current TSearchRec element is a directory
2) Each directory has two Dot Directory Names, which must be avoided in the recursive scan.
(Rec.Name<>'.') and (Rec.Name<>'..') //check the name of the entry to avoid scan when is `.` or `..`
In other words that line means: only scan if the current entry is a directory and is not a Dot Directory.