FindFirst, FindNext, and FindClose are evil
In the process of writing some FTP client server stuff, I have come accross
many limitations in Borland's directory routines (FindFirst, FindNext, and
FindClose). This is particularly the case with the FTP server we have. There are
- The TSearchRec still uses a standard signed Integer to hold the size. This
means that a filesize will not be correct if something is larger than 2GB's.
- The Last Modified Time is given in Local Time ZONE instead of standard GMT
(or Universal Time). While this is helpful for a user, GMT is the standard for
many Internet protocols such as FSP, SSH File Transfer (SFTP), and FTP. In
FTP, the only place where a local time-zone is used is in the MDTM set file
date command (for compatibility) and for the standard LIST output (the Unix
and DOS formats).
- In DotNET, Borland for the sake of consistency made the routines use
P/Invoke Win32 API calls meaning that anything use those routines couldn't be
used on Mono .NET Framework in Linux and P/Invoke is problematic in some
- In Win32, you can not use those routines with multibyte character
filenames even though those are valid in NTFS. You have to use the WideString
Win32 API calls. This is important because some languages such as
Japanese, Chinies, Korean, and Arabic do not use a Latin alphabet.
- There are quite a number of attributes that Borland’s routines do not
support. Those are compressed, encrypted, offline, reparse point, sparse
file, temporary, device, and not content indexed.
- In Kylix, you only need permission bits but user and group
- Some server protocols not only support a last modified time, a but a
creation time, and a last access time. The Indy FTP server can support
any of these as an option by setting the MLSDFacts property and our FTP client
also supports these in the TidMLSTFTPListItem object located in the
I do have some code a from a FTP server example that I haven't finished that
may help. It doesn't support Multi-byte characters but it is a nice
attempt to work what I described in Win32:
function TForm1.FileTimeToTDateTime(const AFileTime: TFileTime): TDateTime;
var LDosTime : LongInt;
Result := 0;
if Windows.FileTimeToDosDateTime(AFileTime, LongRec(LDosTime).Hi,
Result := SysUtils.FileDateToDateTime(LDosTime);
procedure TForm1.IdFTPServer1ListDirectory(ASender: TIdFTPServerContext;
const APath: string; ADirectoryListing: TIdFTPListOutput; const ACmd,
SR : TSearchRec;
SRI : Integer;
LTmpPath : String;
SRI := FindFirst(LTmpPath, faAnyFile , SR);// - faHidden - faSysFile, SR);
While SRI = 0 do
LFTPItem := ADirectoryListing.Add;
LFTPItem.FileName := SR.Name;
//This is necessary because the Borland RTL FindData Size is an Integer and can't handle
//anything greater than 2GB.
LFTPItem.Size := Int64(SR.FindData.nFileSizeHigh shl 32) + SR.FindData.nFileSizeLow;
//We don't use the DosDate from Borland's FindFirst, FindNext for two reasons:
//1: We should be dealing with GMT time in all cases. For Unix and WinNT style lists,
// it will be converted to LocalTime.
//2: The Win32_FIND_DATA record has more information than simply the last modified date
// and the MLSD/MLST command permits us to return all of this in a standardized way.
// Indy can support a "Create", "Modified" and "windows.lastaccess" fact.
// For Linux, the POSIX filesystem, you would want to support only the Modified fact
// for file dates.
LFTPItem.ModifiedDateGMT := FileTimeToTDateTime( SR.FindData.ftLastWriteTime);
LFTPItem.CreationDateGMT := FileTimeToTDateTime( SR.FindData.ftCreationTime);
LFTPItem.LastAccessDateGMT := FileTimeToTDateTime( SR.FindData.ftLastAccessTime);
LFTPItem.WinAttribs := SR.FindData.dwFileAttributes;
SRI := FindNext(SR);
Thank goodness that Delphi can access the API functions