How to use FindFirst() to enumerate subdirectories? - delphi

I have just installed Delphi Community Edition and thought as a newbie I was doing well until I hit the FindFirst() procedure.
What I have is a directory list:
c:\BFUtils\01Utils
\02Utils
\03Utils
\04Utils
Then, to try out FindFirst() to get the subdirectories of BFUtils, I wrote the following code, which I feel is needed to simply count the number of subdirectories:
sStartPath := 'c:\BFUtils';
Result := 0;
res := FindFirst(sStartPath, faDirectory, SearchRec);
if res = 0 then begin
try
while res = 0 do begin
if SearchRec.FindData.dwFileAttributes and faDirectory <> 0 then begin
Name := SearchRec.FindData.cFileName;
if (Name <> '.') and (Name <> '..') then begin
inc(Result);
end;
end;
res := FindNext(SearchRec);
end;
finally
FindClose(SearchRec);
end;
end;
This, as it is, compiles and runs, but it only finds the BFUtils directory.
Searching the Internet, I feel that the file mask (sStartPath) is not correct. So I have tried:
sStartPath + '\'
sStartPath + '\*'
sStartPath + '\*.'
sStartPath + '\*.*
All these options will compile and link without errors, but when the program runs it displays an error:
"'.'" is an Invalid Item.
What else can I try here?

In modnern Delphi versions you don't necessarily need to work with FindFirst, FindNext and FindClose directly anymore!
Add the unit System.IOUtils to a uses section in your unit.
There is, among some others, the record TDirectory:
Contains a large number of static utility methods used in directory manipulations.
TDirectory is a record containing only static methods used to perform
various operations on directories. You should not declare variables of
type TDirectory, since TDirectory has no instance methods or fields.
Possible operations that can be completed using TDirectory include:
Creating, renaming, and deleting directories.
Traversing directories (also recursively).
Manipulating directory attributes and timestamps.
For finding all subdirectories you can then use one of the overloaded GetDirectories methods:
Use GetDirectories to obtain a list of subdirectories in a given
directory. The return value of GetDirectories is a dynamic array of
strings in which each element stores the name of a subdirectory.
There are three forms of the GetDirectories method:
The first form only accepts the path of the directory for which subdirectories are enumerated.
The second form includes a search pattern used when matching subdirectory names.
The third form includes an option specifying whether a recursive mode will be used while enumerating.
So for counting all subdirectories you can simply do the following:
function SubDirCount(const Path: string): integer;
begin
result := Length(TDirectory.GetDirectories(Path));
end;
// somewhere else
ShowMessage(SubDirCount('C:\BFUtils').ToString);
And for your original code use this as a FileMask: sStartPath + '\.*'

You are asking FindFirst() to locate your c:\BFUtils folder itself. To enumerate the contents of that folder, you need to use a wildcard mask instead:
function CountSubDirs(const Path: string): Integer;
var
sMask: string;
SearchRec: TSearchRec;
res: Integer;
begin
Result := 0;
sMask := IncludeTrailingPathDelimiter(Path) + '*'; // or '*.*'
res := FindFirst(sMask, faDirectory, SearchRec);
if res <> 0 then
begin
if res <> ERROR_FILE_NOT_FOUND then
RaiseLastOSError(res);
end else
begin
try
repeat
if (SearchRec.FindData.dwFileAttributes and faDirectory) = faDirectory then
begin
if (StrComp(SearchRec.FindData.cFileName, '.') <> 0) and
(StrComp(SearchRec.FindData.cFileName, '..') <> 0) then
begin
Inc(Result);
end;
end;
res := FindNext(SearchRec);
until res <> 0;
if res <> ERROR_NO_MORE_FILES then
RaiseLastOSError(res);
finally
FindClose(SearchRec);
end;
end;
end;
var NumSubDirs: Integer;
NumSubDirs := CountSubDirs('c:\BFUtils');
That has always worked fine for me. If it is not working for you, then something else is wrong with code you have not shown yet, as there is nothing in the code actually shown that can cause the "'.'" is an Invalid Item error message you claim.

Related

IdFTP performance issue when Parsing all the files in a directory and subdirectories

I need to parse every single file in a directory including the files in sub directories and sub sub directories and ...
I have already successfully done this using the code below :
class function TATDFTPUtility.findAllDirectoryFiles(var ftpClient: TIdFTP; directory: String; deepness: Integer = 0): TidFTPListItems;
var
I: Integer;
localDirectoryListing: TIdFTPListItems;
baseDirectory: string;
begin
Result := TIdFTPListItems.Create;
*// this function uses ftpClient.ChangeDirUp until it reaches the '' directory*
changeUpToDirectory(ftpClient, '');
try
ftpClient.ChangeDir(directory);
ftpClient.List;
Result.Assign(ftpClient.DirectoryListing);
localDirectoryListing := Result;
baseDirectory := ftpClient.RetrieveCurrentDir;
for I := 0 to localDirectoryListing.Count - 1 do
begin
if (localDirectoryListing.Items[i].ItemType = ditDirectory) then
begin
result := addTwoFTPListItems(result, findAllDirectoryFiles(ftpClient, baseDirectory + '/' + localDirectoryListing.Items[i].FileName));
end;
end;
except
end;
end;
class function TATDFTPUtility.addTwoFTPListItems(listA: TIdFTPListItems; listB: TIdFTPListItems): TidFTPListItems;
var
i: integer;
begin
Result := listA;
for I := 0 to listB.Count - 1 do
begin
with Result.Add do
begin
Data := listB.Items[i].data;
Size := listB.Items[i].Size;
ModifiedDate := listB.Items[i].ModifiedDate;
LocalFileName := listB.Items[i].LocalFileName;
FileName := listB.Items[i].FileName;
ItemType := listB.Items[i].ItemType;
SizeAvail := listB.Items[i].SizeAvail;
ModifiedAvail := listB.Items[i].ModifiedAvail;
PermissionDisplay := listB.Items[i].PermissionDisplay;
end;
end;
end;
Now the problem is that this takes about 15-20 minutes !!!
Is there a more efficient way ?
Here is a few fact about this particular case :
1- After i ran the program it found about 12000 files with almost 100-200 directories but the highest deepness was about 7
2- I only need to parse and i do not need to download or upload anything
3- The reason i have used an exception is because inside the FTP there are a few folder which i do not have access and this causes an access violation Error in IdFTP and i used try...except to ignore any directory which can not be accessed.
You are calling ChangeDirUp() (potentially many times?) and then calling ChangeDir() afterwards. If directory is an absolute path, you can just call ChangeDir() one time to jump directly to the target folder and avoid ChangeDirUp() altogether. The recursive loop inside of findAllDirectoryFiles() is using absolute paths from RetrieveCurrentDir(), so the repeated calls to ChangeDirUp() and ChangeDir() are wasted overhead. You can greatly reduce overhead by not navigating up and down the folder tree unnecessarily.
findAllDirectoryFiles() is returning a newly allocated TIdFTPListItems that the caller must free. That in itself is generally a bad design choice, but especially in this case because the recursive loop is not freeing those secondary TIdFTPListItems objects at all, so they are being leaked.
When adding files to the output TIdFTPListItems, you are only adding their filenames and not their paths. What good is recursively searching for files if the caller does not know where each file was found? Or do you only care about the filenames and not the paths?
You are ignoring the deepness parameter completely.
With that said, try something more like this instead:
class procedure TATDFTPUtility.findAllDirectoryFiles(ftpClient: TIdFTP; const directory: String;var files: TIdFTPListItems; deepness: Integer = -1);
var
I: Integer;
baseDirectory: string;
subDirectories: TStringList;
item: TIdFTPListItem;
localDirectoryListing: TIdFTPListItems;
begin
try
if directory <> '' then
ftpClient.ChangeDir(directory);
ftpClient.List;
except
Exit;
end;
baseDirectory := ftpClient.RetrieveCurrentDir;
localDirectoryListing := ftpClient.DirectoryListing;
subDirectories := nil;
try
for I := 0 to localDirectoryListing.Count - 1 do
begin
case localDirectoryListing[i].ItemType of
ditFile: begin
item := files.Add;
item.Assign(localDirectoryListing[i]);
// if you need the full path of each file...
item.FileName := baseDirectory + '/' + item.FileName;
end;
ditDirectory: begin
item := localDirectoryListing[i];
if ((item.FileName <> '.') and (item.FileName <> '..')) and
((deepness = -1) or (deepness > 0)) then
begin
if subDirectories = nil then
subDirectories := TStringList.Create;
subDirectories.Add(baseDirectory + '/' + item.FileName);
end;
end;
end;
end;
if subDirectories <> nil then
begin
if (deepness > 0) then Dec(deepness);
for I := 0 to subDirectories.Count - 1 do begin
findAllDirectoryFiles(ftpClient, subDirectories[I], files, deepness);
end;
end;
finally
subDirectories.Free;
end;
end;
When calling findAllDirectoryFiles() for the first time, you can set directory to either:
a blank string to start searching in the current directory.
a subfolder that is relative to the current directory.
an absolute folder that is relative to the server's root.
And set deepness to either
-1 for endless recursion
>= 0 to specify how deep to recurse.
files := TIdFTPListItems.Create;
try
TATDFTPUtility.findAllDirectoryFiles(ftpClient, 'desired directory', files, desired deepness);
// use files as needed...
finally
files.Free;
end;

Delphi - Get last created folder name from a given path

Is there a function to get the last created folder from a given path?
I want to see the last created folder in order to check if my camera has taken photos today.
Another approach I was thinking was to get the system date and then start searching for a folder that contained the current date.However if the camera date is wrong then this approach wont work!
Thanks. Any other ideas?
ex:
if lastcreatedfolder(dir_path):='05012016' then
showmessage('TODAY A FOLDER WAS CREATED')
else
showmessage('NO FOLDER WAS CREATED TODAY!');
Delphi 2010 also has the IOUtils.pas unit.
Using this unit, the last created folder may be found as follows:
uses
IOUtils, Types, DateUtils;
function FindLastCreatedDirectory(const APath: string): string;
var
LastCreateTime : TDateTime;
PathsInQuestion: TStringDynArray;
n : Integer;
begin
LastCreateTime := MinDateTime;
Result := '';
PathsInQuestion := TDirectory.GetDirectories(APath);
for n := Low(PathsInQuestion) to High(PathsInQuestion) do
begin
if CompareDateTime(TDirectory.GetCreationTime(PathsInQuestion[n]), LastCreateTime) = GreaterThanValue then
begin
LastCreateTime := TDirectory.GetCreationTime(PathsInQuestion[n]);
Result := PathsInQuestion[n];
end;
end;
end;
The last created directory in a given path can be found using the System.SysUtils.FindFirst function.
The TimeStamp field of the TSearchRec record can be checked using the function's var F parameter in order to evaluate the timestamp of a file system element.
uses
System.SysUtils,
Winapi.Windows;
function getLastCreatedDirectory(const APath: string): string;
var
res: TSearchRec;
lastCreatedFileTime: TFileTime;
begin
Result := '';
FillChar(lastCreatedFileTime, SizeOf(TFileTime), 0);
if FindFirst(APath, faDirectory, res) = 0 then begin
try
repeat
if (res.Attr and faDirectory) = 0 then
Continue;
if (res.Name = '.') or (res.Name = '..') then
Continue;
{if res.TimeStamp > lastCreatedTime then begin
lastCreatedTime := res.TimeStamp;
Result := ExtractFilePath(APath) + res.Name;
end;}
if CompareFileTime(res.FindData.ftCreationTime, lastCreatedFileTime) = 1 then begin
lastCreatedFileTime := res.FindData.ftCreationTime;
Result := ExtractFilePath(APath) + res.Name;
end;
until FindNext(res) <> 0;
finally
System.SysUtils.FindClose(res);
end;
end;
end;
begin
WriteLn(getLastCreatedDirectory('C:\Program Files (x86)\*'));
ReadLn;
end.
EDIT 2
Since res.TimeStamp seems to give the last modified date and the TSearchRec.Time field has been deprecated, the folder's creation time can be obtained evaluating the res.FindData.ftCreationTime field of the TSearchRec record.
if folder created by yourself you can save that folder name on an array even if name is random character, but if folder created by program or etc,
you can go to folder
cd "path"
exp: cd /home
and use 'bellow' command that sort by date created files and folders,
so if there is meny files use this:
ls -lt | less
also 'head' command can be usefull for you i think

Determining which file in a folder is the latest by "modified date"?

I need to scan a specific folder for the latest file (basically check the modified date to see which is the newest), But keep in mind that the files have random names. Here's what I got so far:
procedure TForm1.Button1Click(Sender: TObject);
begin
ftp.Host := 'domain';
ftp.Username := 'username';
ftp.password := 'password';
ftp.Connect;
ftp.Put('random-filename.ext'); //This is where it should grab only the latest file
ftp.Quit;
ftp.Disconnect;
end;
Is this possible?
Thank you!
Assuming that OP wants to scan specific local folder and find the most recent modified file, here's a very simple function to do just that:
function GetLastModifiedFileName(AFolder: String; APattern: String = '*.*'): String;
var
sr: TSearchRec;
aTime: Integer;
begin
Result := '';
aTime := 0;
if FindFirst(IncludeTrailingPathDelimiter(AFolder) + APattern, faAnyFile, sr) = 0 then
begin
repeat
if sr.Time > aTime then
begin
aTime := sr.Time;
Result := sr.Name;
end;
until FindNext(sr) <> 0;
FindClose(sr);
end;
end;
AFolder should be an absolute or relative path to a folder you want to scan, APattern is optional and should contain a standard DOS pattern that specifies which files should be checked. If nothing is specified for 2nd parameter, *.* (all files) is assumed. Result will be the file name that has the most recent modified date.
Because i think you are trying to put the latest file from your local machine to an ftp server, you can use the shell Api function: ShGetFileInfo
Get all files from your source folder first and then get the FileInfo for every file - Keep the filename with the latest modified date in a temp. var.
See this site for example: http://www.scip.be/index.php?Page=ArticlesDelphi06&Lang=EN

Why does FindFirst return file names that don't match the mask?

I pass the parameter value '*1.dat' to FindFirst, still the first file that the FindFirst() routine return is 46checks5.dat, very consistently.
Is this a known problem?
vpath:=trim(vpath);
result:=true;
try
res:=findfirst(vpath+'\'+vmask,faarchive,search); //vmask = *1.dat
try
while res=0 do
begin
vlist.add(search.name); //searchname returned is 46checks5.dat!!!
res:=findnext(search);
end;
finally
findclose(search);
end;
except
result:=false;
end;
The reason is that the file has a "long" name, i.e. with more than 8 characters. For such files Windows also creates "short" names, that usually are created in the form longna~1.dat and this short name is found via *1.dat wildcard.
You can easily reproduce the same behaviour in command prompt in an empty directory:
C:\TEMP>echo. > 46checks5.dat
C:\TEMP>dir /x *1.dat
Volume in drive C has no label.
Volume Serial Number is 5C09-D9DE
Directory of C:\TEMP
2011.04.15 21:37 3 46CHEC~1.DAT 46checks5.dat
1 File(s) 3 bytes
The documentation for FindFirstFile(), which is the underlying API for FindFirst states:
The search includes the long and short
file names.
To workaround this issue, then, rather than using Delphi's wrapper to FindFirstFile(), call the Win32 API FindFirstFileEx(). Pass FindExInfoBasic to the fInfoLevelId parameter.
You have something else wrong.
I created a folder C:\Temp\Test, and put three files in it:
TestFile1.txt
TestFile2.txt
TestFile3.txt
I then dropped a TMemo on a new blank form in a new project, and added this code to the 'FormCreate' event:
procedure TForm1.FormCreate(Sender: TObject);
var
sPath: string;
sFile: string;
SR: TSearchRec;
begin
sPath := 'C:\Temp\Test';
sFile := '*1.txt';
Memo1.Lines.Clear;
if FindFirst(sPath + '\' + sFile, faArchive, SR) = 0 then
begin
try
repeat
Memo1.Lines.Add(SR.Name);
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
end;
When the form was shown, the TMemo showed exactly one file, TestFile1.txt, just as I would expect.

Copying lots of files in Delphi

In my application I need to copy over 1000 small files
Here is the code I am using but it is VERY SLOW
Is there a better way of doing this ?
procedure Tdatafeeds.RestotreTodaysFiles;
var
SearchRec: TSearchRec;
FromFn, ToFn: string;
Begin
if DirectoryExists(BackupPath1) then
begin
try
if FindFirst(BackupPath1 + '\*.*', (faAnyFile AND NOT(faDirectory)), SearchRec) = 0 then
begin
repeat
FromFn := BackupPath1 + '\' + SearchRec.name;
ToFn := DatafeedsPath1 + '\' + SearchRec.name;
CopyFile(Pchar(FromFn), Pchar(ToFn), false);
until FindNext(SearchRec) <> 0;
end;
finally
FindClose(SearchRec);
end;
end;
End;
Definitely go with SHFileOperation() as suggested above, CopyFile is way too slow for that many files. It looks like you are basically restoring an entire folder so the search function may be unnecessary and slow things down further. Something like this may be of help:
uses ShellApi;
function CopyDir(const fromDir, toDir: string): Boolean;
var
fos: TSHFileOpStruct;
begin
ZeroMemory(#fos, SizeOf(fos));
with fos do
begin
wFunc := FO_COPY;
fFlags := FOF_FILESONLY;
pFrom := PChar(fromDir + #0);
pTo := PChar(toDir)
end;
Result := (0 = ShFileOperation(fos));
end;
This function will raise a prompt to overwrite existing files though (maybe it can be tweaked to skip that) but the user can select "All" so it's a one-click procedure, much faster, has a progress bar and can be canceled if desired.
You can use the SHFileOperation() API call and use a wildcard in the file name of the struct. That way one call would be used to copy all of the files in one go. There's even the possibility to show the progress (via a callback function) and allow the user to cancel the operation.
I can't test your code right now, but check out this corrected version
// (!) faAnyFile-faDirectory <--- this is wrong
// we don't subtract flag values because the value will be meaningless
if FindFirst(BackupPath1 + '\*.*', faAnyFile, SearchRec) = 0 then
begin
repeat
if not (SearchRec.Attr and faDirectory)
And SearchRec.Name <> "."
And SearchRec.Name <> ".." Then
Begin
FromFn := BackupPath1 + '\' + SearchRec.name;
ToFn := DatafeedsPath1 + '\' + SearchRec.name;
CopyFile(Pchar(FromFn), Pchar(ToFn), false);
End;
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
Perhaps you might experiment with reading a bunch of files into memory and then writing them all to disk at once (like XCOPY). That might be nicer on the filesystem.

Resources