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.
Related
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!
How can I put the files from a specific folder and subfolders to read only in delphi?
I know that I can put the folder with FileSetAttr to read only but is there a way to put the files from the folder and subfolders ?
Thanks
You need to iterate over all the files in a directory, and recursively over all the sub-directories. You can use this function to do that:
type
TFileEnumerationCallback = procedure(const Name: string);
procedure EnumerateFiles(const Name: string;
const Callback: TFileEnumerationCallback);
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
EnumerateFiles(Name + '\' + F.Name, Callback);
end;
end else begin
Callback(Name + '\' + F.Name);
end;
until FindNext(F) <> 0;
finally
FindClose(F);
end;
end;
end;
This is a general purpose routine. You can supply a callback procedure that will be called with the name of each file. Inside that callback procedure do what ever you want.
Your callback procedure would look like this:
procedure MakeReadOnly(const Name: string);
begin
FileSetAttr(Name, FileGetAttr(Name) or faReadOnly);
end;
And you'd put it together like this:
EnumerateFiles('C:\MyDir', MakeReadOnly);
This question already has an answer here:
Delphi function, Not allowing files and folders from main directory during compression
(1 answer)
Closed 9 years ago.
I surrender, I spend my time almost 12hours to get what I want, but I can't.
This code search all folders and filenames, but I want to exclude some folders including the sub directory of folders I want to exclude from searching.
I wish there's someone can help.
procedure TForm1.CombineDir(InDir : string; OutStream : TStream);
var AE : TArchiveEntry;
dFound:boolean;
procedure RecurseDirectory(ADir : string);
var sr : TSearchRec;
TmpStream : TStream;
begin
if FindFirst(ADir + '*', faAnyFile, sr) = 0 then begin
repeat
if (sr.Attr and (faDirectory or faVolumeID)) = 0 then begin
//ShowMessage('Filename is :>'+ ADir + sr.Name);
if (NotThisPath.IndexOf(ADir + sr.Name)>=0) or dFound then begin
ShowMessage('DO NOT INCLUDE THIS FILENAME :>'+ ADir + sr.Name);
end else begin
ShowMessage('>>> INCLUDE THIS FILENAME :>'+ ADir + sr.Name);
// We have a file (as opposed to a directory or anything
// else). Write the file entry header.
AE.EntryType := aeFile;
AE.FileNameLen := Length(sr.Name);
AE.FileLength := sr.Size;
OutStream.Write(AE, SizeOf(AE));
OutStream.Write(sr.Name[1], Length(sr.Name));
// Write the file itself
TmpStream := TFileStream.Create(ADir + sr.Name, fmOpenRead or fmShareDenyWrite);
OutStream.CopyFrom(TmpStream, TmpStream.Size);
TmpStream.Free;
end;
end;
if (sr.Attr and faDirectory) > 0 then begin
if (sr.Name <> '.') and (sr.Name <> '..') then begin
//ShowMessage('DIR is:>'+ ADir + sr.Name);
//if (Pos(ADir, NotThisPath.Text)>0) then
if (NotThisPath.IndexOf(ADir + sr.Name)>=0) then begin
ShowMessage('DO NOT INCLUDE THIS DIR:>'+ ADir + sr.Name);
dFound:=True;
end else begin
ShowMessage('>>> INCLUDE THIS DIR:>'+ ADir + sr.Name);
// Write the directory entry
AE.EntryType := aeDirectory;
AE.DirNameLen := Length(sr.Name);
OutStream.Write(AE, SizeOf(AE));
OutStream.Write(sr.Name[1], Length(sr.Name));
end;
// Recurse into this directory
RecurseDirectory(IncludeTrailingPathDelimiter(ADir + sr.Name));
end;
end;
until FindNext(sr) <> 0;
FindClose(sr);
end;
// Show that we are done with this directory
AE.EntryType := aeEOD;
OutStream.Write(AE, SizeOf(AE));
end;
begin
RecurseDirectory(IncludeTrailingPathDelimiter(InDir));
end;
NotThisPath is a TStringList;
I think your fundamental problem is that you have mixed together file enumeration, file name filtering, and your GUI into one unholy blob of goo. You simply should not see FindFirst being called from a method of a form. Code that calls FindFirst belongs in helper classes or functions.
I'm not going to attempt to answer your question directly, not least because you did not actually ask a question. What I'm going to attempt is to show you how to separate the concerns of enumerating files and filtering for names.
First of all, I'm going to implement this function:
procedure EnumerateFiles(Dir: string;
const EnumerateFileName: TEnumerateFileNameMethod);
This function is passed a directory in the Dir parameter and it proceeds to enumerate all files within that directory, its sub-directories, and so on recursively. Each file that is found is passed to the callback method EnumerateFileName. This is defined like so:
type
TEnumerateFileNameMethod = procedure(const FileName: string) of object;
The implementation is very simple indeed. It's just the standard FindFirst based repeat loop. The function rejects the special directories . and ... It will recurse into any directories that it encounters.
procedure EnumerateFiles(Dir: string;
const EnumerateFileName: TEnumerateFileNameMethod);
var
SR: TSearchRec;
begin
Dir := IncludeTrailingPathDelimiter(Dir);
if FindFirst(Dir + '*', faAnyFile, SR) = 0 then
try
repeat
if (SR.Name = '.') or (SR.Name = '..') then
continue;
if (SR.Attr and faDirectory) <> 0 then
EnumerateFiles(Dir + SR.Name, EnumerateFileName)
else
EnumerateFileName(Dir + SR.Name);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
Now, this should be simple enough to follow I hope. The next issue is filtering. You can implement that in the callback method that you provide. Here's a complete demo that illustrates filtering that picks out Delphi source files with the .pas extension.
program EnumerateFilesDemo;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TEnumerateFileNameMethod = procedure(const FileName: string) of object;
procedure EnumerateFiles(Dir: string;
const EnumerateFileName: TEnumerateFileNameMethod);
var
SR: TSearchRec;
begin
Dir := IncludeTrailingPathDelimiter(Dir);
if FindFirst(Dir + '*', faAnyFile, SR) = 0 then
try
repeat
if (SR.Name = '.') or (SR.Name = '..') then
continue;
if (SR.Attr and faDirectory) <> 0 then
EnumerateFiles(Dir + SR.Name, EnumerateFileName)
else
EnumerateFileName(Dir + SR.Name);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
type
TDummyClass = class
class procedure EnumerateFileName(const FileName: string);
end;
class procedure TDummyClass.EnumerateFileName(const FileName: string);
begin
if SameText(ExtractFileExt(FileName), '.pas') then
Writeln(FileName);
end;
procedure Main;
begin
EnumerateFiles('C:\Users\heff\Development', TDummyClass.EnumerateFileName);
end;
begin
try
Main;
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Now, I know that's not the type of filtering that you want to do, but the point is that we now have generality. You can replace the call to SameText with whatever filtering you want. And once you have picked out the files that you want to deal with, you can do what you like with them.
I used a class method for convenience. I did not want my demo to be laden down with the boiler-plate of instantiating an object. But for your needs you would want to create a class to handle the enumeration callback. That class would encapsulate the file archiving operation that you are performing. That class would own an instance of the output stream. And the callback method would be an instance method that would write to the archive.
Now, I've not implemented a complete solution to your problem, but I hope I've done something better. Namely to show you how to factor code to make solving your problem simple.
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 am trying to delete a folder and all of its sub-folders recursively but it is not working at all, so can someone please check the code and tell me what I am doing wrong here?
I am running this code through D7 under Windows XP
if FindFirst (FolderPath + '\*', faAnyFile, f) = 0 then
try
repeat
if (f.Attr and faDirectory) <> 0 then
begin
if (f.Name <> '.') and (f.Name <> '..') then
begin
RemoveDir(FolderPath +'\'+ f.Name);
end
else
begin
//Call function recursively...
ClearFolder(FolderPath +'\'+ f.Name, mask, recursive);
end;
end;
until (FindNext (f) <> 0);
finally
SysUtils.FindClose (f)
end;
end;
Rather than do all this hard work yourself, I'd just use SHFileOperation:
uses
ShellAPI;
procedure DeleteDirectory(const DirName: string);
var
FileOp: TSHFileOpStruct;
begin
FillChar(FileOp, SizeOf(FileOp), 0);
FileOp.wFunc := FO_DELETE;
FileOp.pFrom := PChar(DirName+#0);//double zero-terminated
FileOp.fFlags := FOF_SILENT or FOF_NOERRORUI or FOF_NOCONFIRMATION;
SHFileOperation(FileOp);
end;
For what it is worth, the problem with your code is that it doesn't ever call DeleteFile. And so the directories are never getting emptied, the calls to RemoveDir fail and so on. The lack of error checking in your code doesn't really help, but adding code to delete files would get that code in half-decent shape. You also need to take care with the recursion. You must make sure that all the children are deleted first, and then the parent container. That takes a certain degree of skill to get right. The basic approach is like this:
procedure DeleteDirectory(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
DeleteDirectory(Name + '\' + F.Name);
end;
end else begin
DeleteFile(Name + '\' + F.Name);
end;
until FindNext(F) <> 0;
finally
FindClose(F);
end;
RemoveDir(Name);
end;
end;
I've omitted error checking for the sake of clarity, but you should check the return values of DeleteFile and RemoveDir.
procedure DeleteDir(const DirName: string);
var
Path: string;
F: TSearchRec;
begin
Path:= DirName + '\*.*';
if FindFirst(Path, faAnyFile, F) = 0 then begin
try
repeat
if (F.Attr and faDirectory <> 0) then begin
if (F.Name <> '.') and (F.Name <> '..') then begin
DeleteDir(DirName + '\' + F.Name);
end;
end
else
DeleteFile(DirName + '\' + F.Name);
until FindNext(F) <> 0;
finally
FindClose(F);
end;
end;
RemoveDir(DirName);
end;
Starting with Delphi 2010 there is a TDirectory record in System.IOUtils unit with some methods, including
TDirectory.Delete('path_to_dir', True);