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
Related
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.
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;
Im working in a Tdbadvgrid from TMS. I am adding colums dynamically based on a sql query length. Everything works great, with the exception of the column width saving.
Adding the columns:
//Captions
for i := 0 to oRow.Count - 1 do
begin
grdFieldData.Columns.Insert(1);
grdFieldData.Cells[i + 1, 0] := TabelList.Captions[i].Caption;
end;
//Data
for r := 0 to TabelList.Count - 1 do //rows
begin
for c := 0 to oRow.Count - 1 do //cols
begin
grdFieldData.Cells[c+1, r+1] := TabelList.Rows[r].Fields[c].Value;
end;
if r <> TabelList.Count - 1 then
grdFieldData.RowCount := grdFieldData.RowCount + 1;
end;
Now the save function is built into the TMS TDBAdvGrid, and looks like this:
I have tried messing with all the options with no luck.
The table has 1 fixed row (for captions) and one empty row. The row is only there because the number of fixed rows must be smaller then the number of rows.
When saving the data to either .ini file or to registry, it saves and loads the first column, but the dynamicly added ones get written in as default value (64) but never saved/loaded when i drag them to adjust size. The .ini file table looks like this:
[Recept]
Col0=20
Col1=97
Col2=64
Col3=64
Col4=64
Col5=64
Col6=64
Col7=64
Col8=64
Col9=64
Col10=64
Col11=64
Col12=64
Col13=64
Col14=64
When loaded in it looks like this:
Does anyone know what I can do to make the columns save properly so the widths will be saved?
So, after a few hours of sitting with it, I decided that there was no reason to struggle so much with something built in when I could just build something simple myself.
In this instance, it simply saves to a chosen ini file at a chosen directory. The filename and directory are currently hardcoded to each inhreited class. A feature to change them could easily be implemented. Perhaps even a directory selecter to a button or something like it.
The code for loading (called on FormShow):
procedure TfrmReceptEditor.LoadColWidths;
var
Ini: TIniFile;
i: Integer;
path: String;
filename: String;
begin
inherited;
path := 'C:\';
filename := 'grid.ini';
Ini := TIniFile.Create(path + filename);
try
for i := 0 to grdFieldData.ColCount - 1 do
begin
grdFieldData.Columns.Items[i].Width := Ini.ReadInteger('Recept','col'+IntToStr(i),75);
end;
grdFieldData.FixedColWidth := 20;
finally
Ini.Free;
end
end;
And the code for saving the data (on FormClose):
procedure TfrmReceptEditor.SaveColWidths;
var
Ini: TIniFile;
i: Integer;
path: String;
filename: String;
begin
inherited;
path := 'C:\';
filename := 'grid.ini';
Ini := TIniFile.Create(path + filename);
try
for i := 0 to grdFieldData.ColCount - 1 do
begin
Ini.WriteInteger('Recept', 'col'+IntToStr(i), grdFieldData.Columns.Items[i].Width);
end;
finally
Ini.Free;
end;
end;
Some might want to build in features to check if the Ini files exists (aka if the entered path is correct). But it will work smoothly even if the file doesnt exist, it will simply create it.
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
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.