ICS FTP - Function to check if folder exists on the ftp server - delphi

I'm trying to create a function to check if a folder exists using Overbyte ICS FTP component.Using the DIR command from the icsftp does not display anything in my memo log.
I'm interested in parsing the result of the dir command into a stringlist in order to search for a specific folder.
For the moment I use an indy function like this. How can I make the same thing with ICS?
function exista_textul_in_stringlist(const stringul_pe_care_il_caut:string; stringlistul_in_care_efectuez_cautarea:Tstringlist):boolean;
begin
if stringlistul_in_care_efectuez_cautarea.IndexOf(stringul_pe_care_il_caut) = -1 then
begin
result:=false;
//showmessage('Textul "'+text+'" nu exista!' );
end
else
begin
result:=true;
//showmessage('Textul "'+text+'" exista la pozitia '+ inttostr(ListBox.Items.IndexOf(text)));
end;
end;
function folder_exists_in_ftp(folder_name_to_search_for,ftp_hostname,ftp_port,ftp_username,ftp_password,ftp_root_folder:string;memo_loguri:Tmemo):boolean;
Var
DirList : TStringList;
ftp:Tidftp;
antifreeze:TidAntifreeze;
var i,k:integer;
begin
dateseparator:='-';
Result := False;
DirList := TStringList.Create;
ftp:=tidftp.Create;
antifreeze:=TidAntifreeze.Create;
try
antifreeze.Active:=true;
ftp.Host:=ftp_hostname;
ftp.Port:=strtoint(ftp_port);
ftp.username:=ftp_username;
ftp.password:=ftp_password;
ftp.Passive:=true;
ftp.Connect;
ftp.ChangeDir(ftp_root_folder);
ftp.List(DirList, folder_name_to_search_for, True);
if DirList.Count > 0 then begin
k := DirList.Count;
DirList.Clear; // DIRLIST will hold folders only
for i := 0 to k - 1 do begin
if (ftp.DirectoryListing.Items[i].FileName <> '.') and (ftp.DirectoryListing.Items[i].FileName <> '..') then begin
if ftp.DirectoryListing.Items[i].ItemType = ditDirectory then begin
DirList.Add(ftp.DirectoryListing.Items[i].FileName);
end;
end;
end;
end;
if exista_textul_in_stringlist(folder_name_to_search_for,DIRLIST) then
begin
Result := True;
memo_loguri.Lines.Add(datetimetostr(now)+' - caut folderul "'+folder_name_to_search_for+'" in directorul ftp "'+ftp_root_folder+'" => EXISTS!');
end
ELSE
begin
result:=false;
memo_loguri.Lines.Add(datetimetostr(now)+' - caut folderul "'+folder_name_to_search_for+'" in directorul ftp "'+ftp_root_folder+'" => NOT exists!');
end;
finally
ftp.Free;
antifreeze.Free;
DirList.Free;
end;
end;

I assume you are using the latest released version of OverbyteIcs (ICS-V8.16 (Apr, 2015)).
If you just need to check if a remote directory exists its a good recommendation mentioned in the other answer to avoid a list (it could be a time consuming operation if a lot of files and folders are returned).
I suggest you just try to be "optimistic" and change to the remote dir you wish to investigate using FTP.Cwd. If this call return true the folder of course exists, and if you plan to continue with the same client you have to change back to the original dir. On the other hand, if the call fails, the directory does not exist if the ftp server reponds with code 550.
I have included a simple sample doing the above (however, it does not provide the "change-back-to-original-dir-on-success" feature):
uses
...
OverbyteIcsFtpCli;
function FtpRemoteDirExists(
HostName: String;
UserName: String;
Password: String;
HostDirToCheck : String ) : Boolean;
const
cFtpCode_FileOrDirNotExists = 550;
var
FTP: TFtpClient;
begin
FTP := TFtpClient.Create(nil);
try
FTP.HostName := HostName;
FTP.Passive := True;
FTP.Binary := True;
FTP.Username := UserName;
FTP.Password := Password;
FTP.Port := '21';
if not FTP.Open then
raise Exception.Create('Failed to connect: ' + FTP.ErrorMessage);
if (not FTP.User) or (not FTP.Pass) then
raise Exception.Create('Failed to login: ' + FTP.ErrorMessage);
FTP.HostDirName := HostDirToCheck;
if FTP.Cwd then
Result := True
else
begin
if FTP.StatusCode = cFtpCode_FileOrDirNotExists then
Result := False
else
raise Exception.Create('Failed to change dir: ' + FTP.ErrorMessage);
end;
finally
FTP.Free;
end;
end;

You better use a command like SIZE (TFtpClient.Size) or MLST (TFtpClient.Mlst) to check for file existence.
Using LIST is quite an overkill.

Related

Why could CreateFile fail on a network drive

I have one installation that behaves VERY oddly... Every time we try to copy something on a network drive we check accessibility with code like this:
procedure TForm1.TestAccess;
var fn : string;
hdl : THandle;
res : boolean;
dir : string;
flags : Cardinal;
begin
dir := edDir.Text;
flags := FILE_FLAG_DELETE_ON_CLOSE or FILE_FLAG_NO_BUFFERING or FILE_ATTRIBUTE_HIDDEN;
fn := FindUnusedFileName( IncludeTrailingPathDelimiter( dir ) + IntToStr( Random(10000) ) + '.tst' );
memLog.Lines.Add('Try to create file: ' + fn);
hdl := CreateFile( PChar(fN), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_DELETE, nil, CREATE_NEW,
flags, 0 );
res := hdl <> INVALID_HANDLE_VALUE;
if not res then
begin
memLog.Lines.Add('Error: ' + SysErrorMessage(GetLastError));
end
else
memLog.Lines.Add('Success');
if res then
CloseHandle(hdl);
end;
where memLog is a TMemo and edDir is simply an edit field.
Now here is the strange part... I get an access denied on that system meaning uploading will fail most of the time (most of the time is the strange part here).
Another thing is that in a first attempt I used the JvDirectoryEdit control. In that case the result is twofold... If I enter the directory (UNC Path) there without a backslash I get the access denied too. If I enter a final backslash and the combo box window pops up showing the content (aka directories) in that folder it finally works!!!
So... First: has anyone a clue what the problem might be and do I something wrong here?
I have used this procedure in the past to check if a directory is read-only, maybe it can be useful:
FUNCTION DirRO(NomeCartella : String) : Boolean;
VAR VarFile : TextFile;
NomCart : String;
BEGIN
Result := False;
If NomeCartella[Length(NomeCartella)]='\'
Then NomCart := NomeCartella
Else NomCart := NomeCartella+'\';
Try AssignFile(VarFile,NomCart+'^ghi.kol');
{$I-}
Rewrite(VarFile);
{$I-}
If IOResult<>0 Then
Begin
Result := True;
Exit;
End;
CloseFile(VarFile);
Erase(VarFile);
Except Result := True;
End;
END;

Delphi and Indy TIdFTP: Copy all files from one folder on the server to another

I'm using TIdFTP (Indy 10.6) for a client application and I need to be able to copy all files from one folder on the server to another. Can this be done?
I know how to rename or move a file, we can use TIdFTP.Rename(Src, Dst).
How about the copy? Would I need to use Get() and Put() with a new path / name, knowing that the number of files in the server can exceed 500,000 files.
In our company, we have some files whose size exceeds 1.5 GB. By using my code, it consumes a lot of memory and the file is not copied from one directory to another: in less code, the source directory is named "Fichiers" and the destination directory is named "Sauvegardes".
Here is my code:
var
S , directory : String;
I: Integer;
FichierFTP : TMemoryStream;
begin
IdFTP1.Passive := True;
idftp1.ChangeDir('/Fichiers/');
IdFTP1.List();
if IdFTP1.DirectoryListing.Count > 0 then begin
IdFTP1.List();
for I := 0 to IdFTP1.DirectoryListing.Count-1 do begin
with IdFTP1.DirectoryListing.Items[I] do begin
if ItemType = ditFile then begin
FichierFTP := TMemoryStream.Create;
S := FileName;
idftp1.Get( FileName , FichierFTP , false );
Application.ProcessMessages
idftp1.ChangeDir('/Sauvegardes/' );
idftp1.Put(FichierFTP , S );
Application.ProcessMessages;
FichierFTP.Free;
end;
end;
end;
IdFTP1.Disconnect;
end;
Does anyone have any experience with this? How can I change my code to resolve this problem?
There are no provisions in the FTP protocol, and thus no methods in TIdFTP, to copy/move multiple files at a time. Only to copy/move individual files one at a time.
Moving a file from one FTP folder to another is easy, that can be done with the TIdFTP.Rename() method. However, copying a file typically requires issuing separate commands to download the file locally first and then re-upload it to the new path.
Some FTP servers support custom commands for copying files, so that you do not need to download/upload them locally. For example, ProFTPD's mod_copy module implements SITE CPFR/CPTO commands for this purpose. If your FTP server supports such commands, you can use the TIdFTP.Site() method, eg:
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
try
IdFTP1.Site('CPFR ' + Item.FileName);
IdFTP1.Site('CPTO /Sauvegardes/' + Item.FileName);
except
// fallback to another transfer option, see further below...
end;
end;
If that does not work, another possibility to avoid having to copy each file locally is to use a site-to-site transfer between 2 separate TIdFTP connections to the same FTP server. If the server allows this, you can use the TIdFTP.SiteToSiteUpload() and TIdFTP.SiteToSiteDownload() methods to make the server transfer files to itself, eg:
IdFTP2.Connect;
...
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
try
IdFTP1.SiteToSiteUpload(IdFTP2, Item.FileName, '/Sauvegardes/' + Item.FileName);
except
try
IdFTP2.SiteToSiteDownload(IdFTP1, Item.FileName, '/Sauvegardes/' + Item.FileName);
except
// fallback to another transfer option, see further below...
end;
end;
end;
...
IdFTP2.Disconnect;
But, if using such commands is simply not an option, then you will have to resort to downloading each file locally and then re-uploading it. When copying a large file in this manner, you should use TFileStream (or similar) instead of TMemoryStream. Do not store large files in memory. Not only do you risk a memory error if the memory manager can't allocate enough memory to hold the entire file, but once that memory has been allocated and freed, the memory manager will hold on to it for later reuse, it does not get returned back to the OS. This is why you end up with such high memory usage when you transfer large files, even after all transfers are finished.
If you really want to use a TMemoryStream, use it for smaller files only. You can check each file's size on the server (either via TIdFTPListItem.Size if available, otherwise via TIdFTP.Size()) before downloading the file, and then choose an appropriate TStream-derived class to use for that transfer, eg:
const
MaxMemoryFileSize: Int64 = ...; // for you to choose...
var
...
FichierFTP : TStream;
LocalFileName: string;
RemoteFileSize: Int64;
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
LocalFileName := '';
if Item.SizeAvail then
RemoteFileSize := Item.Size
else
RemoteFileSize := IdFTP1.Size(Item.FileName);
if (RemoteFileSize >= 0) and (RemoteFileSize <= MaxMemoryFileSize) then
begin
FichierFTP := TMemoryStream.Create;
end else
begin
LocalFileName := MakeTempFilename;
FichierFTP := TFileStream.Create(LocalFileName, fmCreate);
end;
try
IdFTP1.Get(Item.FileName, FichierFTP, false);
IdFTP1.Put(FichierFTP, '/Sauvegardes/' + Item.FileName, False, 0);
finally
FichierFTP.Free;
if LocalFileName <> '' then
DeleteFile(LocalFileName);
end;
end;
There are other optimizations you can make to this, for instance creating a single TMemoryStream with a pre-sized Capacity and then reuse it for multiple transfers that will not exceed that Capacity.
So, putting this all together, you could end up with something like the following:
var
I: Integer;
Item: TIdFTPListItem;
SourceFile, DestFile: string;
IdFTP2: TIdFTP;
CanAttemptRemoteCopy: Boolean;
CanAttemptSiteToSite: Boolean;
function CopyFileRemotely: Boolean;
begin
Result := False;
if CanAttemptRemoteCopy then
begin
try
IdFTP1.Site('CPFR ' + SourceFile);
IdFTP1.Site('CPTO ' + DestFile);
except
CanAttemptRemoteCopy := False;
Exit;
end;
Result := True;
end;
end;
function CopyFileSiteToSite: Boolean;
begin
Result := False;
if CanAttemptSiteToSite then
begin
try
if IdFTP2 = nil then
begin
IdFTP2 := TIdFTP.Create(nil);
IdFTP.Host := IdFTP1.Host;
IdFTP.Port := IdFTP1.Port;
IdFTP.UserName := IdFTP1.UserName;
IdFTP.Password := IdFTP1.Password;
// copy other properties as needed...
IdFTP2.Connect;
end;
try
IdFTP1.SiteToSiteUpload(IdFTP2, SourceFile, DestFile);
except
IdFTP2.SiteToSiteDownload(IdFTP1, SourceFile, DestFile);
end;
except
CanAttemptSiteToSite := False;
Exit;
end;
Result := True;
end;
end;
function CopyFileManually: Boolean;
const
MaxMemoryFileSize: Int64 = ...;
var
FichierFTP: TStream;
LocalFileName: String;
RemoteFileSize: Int64;
begin
Result := False;
try
if Item.SizeAvail then
RemoteFileSize := Item.Size
else
RemoteFileSize := IdFTP1.Size(SourceFile);
if (RemoteFileSize >= 0) and (RemoteFileSize <= MaxMemoryFileSize) then
begin
LocalFileName := '';
FichierFTP := TMemoryStream.Create;
end else
begin
LocalFileName := MakeTempFilename;
FichierFTP := TFileStream.Create(LocalFileName, fmCreate);
end;
try
IdFTP1.Get(SourceFile, FichierFTP, false);
IdFTP1.Put(FichierFTP, DestFile, False, 0);
finally
FichierFTP.Free;
if LocalFileName <> '' then
DeleteFile(LocalFileName);
end;
except
Exit;
end;
Result := True;
end;
begin
CanAttemptRemoteCopy := True;
CanAttemptSiteToSite := True;
IdFTP2 := nil;
try
IdFTP1.Passive := True;
IdFTP1.ChangeDir('/Fichiers/');
IdFTP1.List;
for I := 0 to IdFTP1.DirectoryListing.Count-1 do
begin
Item := IdFTP1.DirectoryListing[I];
if Item.ItemType = ditFile then
begin
SourceFile := Item.FileName;
DestFile := '/Sauvegardes/' + Item.FileName;
if CopyFileRemotely then
Continue;
if CopyFileSiteToSite then
Continue;
if CopyFileManually then
Continue;
// failed to copy file! Do something...
end;
end;
finally
IdFTP2.Free;
end;
IdFTP1.Disconnect;
end;

Delphi export HKEY_CURRENT_USER key not working - empty result file

I am trying to export a registry key using either TRegistry.SaveKey or RegSaveKey functions with no luck. All I get is an empty file 0 bytes. I have seen examples online none seems to be working on Windows10.
reg := TRegistry.Create;
reg.RootKey := HKEY_CURRENT_USER;
reg.Access := KEY_ALL_ACCESS;
if reg.OpenKey('\Software\MyCompanyName\MyApplication\', True) then
begin
reg.WriteInteger('background', Self.Color);
reg.SaveKey('HKEY_CURRENT_USER\Software\MyCompanyName\MyApplication', 'test.txt'); //not working
RegSaveKey(reg.CurrentKey, 'test.reg', nil); //creates empty file
end;
reg.CloseKey;
reg.Free;
Also if I extract existing key from RegEdit and then try to load it in the application using TRegistry.LoadKey or RegLoadKey nothing is happening
I do have admin right on the machine I run this.
Anyone familiar with the issue?
From the documentation of RegSaveKey:
The calling process must have the SE_BACKUP_NAME privilege enabled.
My guess is that RegSaveKey returning a value other than ERROR_SUCCESS. which your code does not check.
See also:
RegSaveKey returns ERROR_PRIVILEGE_NOT_HELD
Another thing to check for is that the destination file does not exists before you try to save, or else the function will fail (this is also mentioned in the documentation), and obviously that you have write permissions to the file location.
Here is a working example.
Be aware that you must run the program as administrator.
program SO59753973;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Registry,
Windows,
System.SysUtils;
function NTSetPrivilege(sPrivilege: string; bEnabled: Boolean): Boolean;
var
hToken: THandle;
TokenPriv: TOKEN_PRIVILEGES;
PrevTokenPriv: TOKEN_PRIVILEGES;
ReturnLength: Cardinal;
errval:Cardinal;
begin
Result := True;
errval:=0;
// Only for Windows NT/2000/XP and later.
if not (Win32Platform = VER_PLATFORM_WIN32_NT) then Exit;
Result := False;
// obtain the processes token
if OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then
try
// Get the locally unique identifier (LUID) .
if LookupPrivilegeValue(nil, PChar(sPrivilege),TokenPriv.Privileges[0].Luid) then
begin
TokenPriv.PrivilegeCount := 1; // one privilege to set
case bEnabled of
True: TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
False: TokenPriv.Privileges[0].Attributes := 0;
end;
ReturnLength := 0; // replaces a var parameter
PrevTokenPriv := TokenPriv;
// enable or disable the privilege
if AdjustTokenPrivileges(hToken, False, TokenPriv, SizeOf(PrevTokenPriv),PrevTokenPriv, ReturnLength) then
Result := True
else
begin
errval:= GetLastError;
Result := errval = 0;
end;
end;
finally
CloseHandle(hToken);
end;
// test the return value of AdjustTokenPrivileges.
//Result := GetLastError = ERROR_SUCCESS;
if not Result then
raise Exception.Create(SysErrorMessage(errval));
end;
var
Reg: TRegistry;
sKeyFileName: String;
begin
try
if not NTSetPrivilege('SeBackupPrivilege',true) then
Exit;
sKeyFileName := 'C:\temp\tempReg.reg';
if FileExists(sKeyFileName) then
DeleteFile(sKeyFileName);
Reg := TRegistry.Create(KEY_ALL_ACCESS);
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.SaveKey('\Software\Microsoft', sKeyFileName)
then
Writeln('Registry has been saved!')
else
Writeln('Failed to save registry, received error: ' + IntToStr(Reg.LastError) + '!');
finally
Reg.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
For restoring the registry, you must enable the SE_RESTORE_NAME in addition to the SE_BACKUP_NAME Privilege.
Code has been taken (and adapted) from this old forum post

Why is my code causing a I/O 104 error?

This program raises an I/O 104 error on EoF when first entering the while loop.
The purpose of the program is to look up if a username is already taken. The existing usernames are stored in a text file.
procedure TForm1.btnRegisterClick(Sender: TObject);
begin
sCUser := edtUserName.Text;
AssignFile(tNames, 'Names.txt');
begin
try
Reset(tNames);
except
ShowMessage('File not found');
Exit;
end;
end;
rewrite(tNames);
while not EoF(tNames) do // I get a I/O 104 Error here `
begin
Readln(tNames, sLine);
iPosComme := Pos(',', sLine);
sUser := Copy(sLine, 1, iPosComme - 1);
Delete(sLine, 1, iPosComme - 1);
if sCUser = sUser then begin
ShowMessage('Username taken');
end
else
begin
rewrite(tNames);
Writeln(tNames, sCUser + ',' + '0');
CloseFile(tNames);
end;
end;
end;
Remove the call to Rewrite()before Eof(). Even if you were not getting an IO error, your code would still fail because Rewrite() closes the file you opened with Reset() and then it creates a new bank file, so Eof() would always be True.
Update: error 104 is file not open for input, which means Reset() is not opening the file but is not raising an exception (which sounds like an RTL bug if Eof() is raising an exception, indicating that {I+} is active).
In any case, using AssignFile() and related routines is the old way to do file I/O. You should use newer techniques, like FileOpen() with FileRead(), TFileStream with TStreamReader, TStringList, etc...
Update: your loop logic is wrong. You are comparing only the first line. If it does not match the user, you are wiping out the file, writing the user to a new file, closing the file, and then continuing the loop. EoF() will then fail at that point. You need to rewrite your loop to the following:
procedure TForm1.btnRegisterClick(Sender: TObject
var
SCUser, sUser: String;
tNames: TextFile;
iPosComme: Integer;
Found: Boolean;
begin
sCUser := edtUserName.Text;
AssignFile(tNames,'Names.txt');
try
Reset(tNames);
except
ShowMessage('File not found');
Exit;
end;
try
Found := False;
while not EoF(tNames) do
begin
Readln(tNames,sLine);
iPosComme := Pos(',', sLine);
sUser := Copy(sLine ,1,iPosComme -1);
if sCUser = sUser then
begin
ShowMessage('Username taken') ;
Found := True;
Break;
end;
end;
if not Found then
Writeln(tNames,sCUser + ',0');
finally
CloseFile(tNames);
end;
end;
For the sake of completeness, this Version works for me, but it is hard to guess what the code is intended to do. Especially the while loop seems a bit displaced, since the file will contain exactly one line after the rewrite-case has ben hit once.
program wtf;
{$APPTYPE CONSOLE}
{$I+}
uses
SysUtils;
procedure Sample( sCUser : string);
var sUser, sLine : string;
iPosComme : Integer;
tnames : textfile;
begin
AssignFile(tNames,'Names.txt');
try
Reset(tNames);
except
Writeln('File not found');
Exit;
end;
while not EoF(tNames) do
begin
Readln(tNames,sLine);
iPosComme := Pos(',', sLine);
sUser := Copy(sLine ,1,iPosComme -1);
Delete( sLine,1, iPosComme -1);
if sCuser = sUser then begin
Writeln('Username taken') ;
end
else begin
Rewrite(tNames);
Writeln(tNames,sCUser + ',' + '0');
CloseFile(tNames);
Break; // file has been overwritten and closed
end;
end;
end;
begin
try
Sample('foobar');
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
end.
I wrote a version of this method that uses the newer TStreamReader and TStreamWriter classes.
This won't work with Delphi 7 of course, it's just to show how this could be done in newer versions of Delphi.
The code was heavily inspired by Remys answer.
procedure TForm1.btnRegisterClick(Sender: TObject);
var
Stream: TStream;
Reader: TStreamReader;
Writer: TStreamWriter;
Columns: TStringList;
UserName: string;
Found: Boolean;
FileName: string;
Encoding: TEncoding;
begin
FileName := ExpandFileName('Names.txt'); // An absolute path would be even better
UserName := edtUsername.Text;
Found := False;
Encoding := TEncoding.Default; // or another encoding, e.g. TEncoding.Unicode for Unicode
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Reader := TStreamReader.Create(Stream, Encoding);
try
Columns := TStringList.Create;
try
Columns.Delimiter := ',';
Columns.StrictDelimiter := True; // or False, depending on the file format
while not Reader.EndOfStream do
begin
Columns.DelimitedText := Reader.ReadLine;
if Columns.Count > 0 then
begin
if AnsiSameStr(Columns[0], UserName) then // or AnsiSameText if UserName is not case-sensitive
begin
ShowMessage('Username taken') ;
Found := True;
Break;
end;
end;
end;
finally
Columns.Free;
end;
finally
Reader.Free;
end;
finally
Stream.Free;
end;
if not Found then
begin
Writer := TStreamWriter.Create(FileName, True, Encoding);
try
// Warning: This will cause problems when the file does not end with a new line
Writer.WriteLine(UserName + ',0');
finally
Writer.Free;
end;
end;
end;
If performance and memory usage are not a concern:
procedure TForm1.btnRegisterClick(Sender: TObject);
var
Rows: TStringList;
Columns: TStringList;
UserName: string;
Found: Boolean;
FileName: string;
Encoding: TEncoding;
Row: string;
begin
FileName := ExpandFileName('Names.txt'); // An absolute path would be even better
UserName := edtUsername.Text;
Found := False;
Encoding := TEncoding.Default; // or another encoding, e.g. TEncoding.Unicode for Unicode
Rows := TStringList.Create;
try
Rows.LoadFromFile(FileName, Encoding);
Columns := TStringList.Create;
try
Columns.Delimiter := ',';
Columns.StrictDelimiter := True; // or False, depending on the file format
for Row in Rows do
begin
Columns.DelimitedText := Row;
if Columns.Count > 0 then
begin
if AnsiSameStr(Columns[0], UserName) then // or AnsiSameText if UserName is not case-sensitive
begin
ShowMessage('Username taken') ;
Found := True;
Break;
end;
end;
end;
finally
Columns.Free;
end;
if not Found then
begin
Rows.Add(UserName + ',0');
Rows.SaveToFile(FileName, Encoding);
end;
finally
Rows.Free;
end;
end;
This solution can be adapted to Delphi 7 by removing the Encoding variable.
If it's part of a bigger database it should be stored in a real database management system rather than a text file.

Delete Directory with non empty subdirectory and files

How to delete one directory having some files and some non empty sub directory.
I have tried SHFileOperation Function. It has some compatibility issue in Windows 7.
Then I have tried IFileOperation Interface. But it is not compatible in Windows XP.
Then I have tried the following codes as suggested by David Heffernan :
procedure TMainForm.BitBtn01Click(Sender: TObject);
var
FileAndDirectoryExist: TSearchRec;
ResourceSavingPath : string;
begin
ResourceSavingPath := (GetWinDir) + 'Web\Wallpaper\';
if FindFirst(ResourceSavingPath + '\*', faAnyFile, FileAndDirectoryExist) = 0 then
try
repeat
if (FileAndDirectoryExist.Name <> '.') and (FileAndDirectoryExist.Name <> '..') then
if (FileAndDirectoryExist.Attr and faDirectory) <> 0 then
//it's a directory, empty it
ClearFolder(ResourceSavingPath +'\' + FileAndDirectoryExist.Name, mask, recursive)
else
//it's a file, delete it
DeleteFile(ResourceSavingPath + '\' + FileAndDirectoryExist.Name);
until FindNext(FileAndDirectoryExist) <> 0;
//now that this directory is empty, we can delete it
RemoveDir(ResourceSavingPath);
finally
FindClose(FileAndDirectoryExist);
end;
end;
But it does not get compiled mentioning error as Undeclared Identifier at ClearFolder, mask and recursive. My requirement is to that "If any sub folder exist under WALLPAPER folder it will be deleted". The same sub folder may contain any number of non empty sub folder or files.
Well, for starters, SHFileOperation has no compatibility issues on Windows 7 or Windows 8. Yes, you are now recommended to use IFileOperation instead. But if you want to support older operating systems like XP, then you can and should just call SHFileOperation. It works and will continue to work. It's pefectly fine to use it on Windows 7 and Windows 8 and I'll eat my hat if it's ever removed from Windows. Microsoft go to extraordinary lengths to maintain backwards compatibility. So, SHFileOperation is your best option in my view.
Your FindFirst based approach fails because you need to put it in a separate function in order to allow recursion. And the code I posted in that other answer is incomplete. Here is a complete version:
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;
This deletes a directory and its contents. You'd want to walk the top level directory and then call this function for each subdirectory that you found.
Finally I have implemented the following Code:
uses
ShellAPI;
...
...
function GetWinDir: string;
var
WindowsDirectory: array[0..MAX_PATH] of Char;
begin
GetWindowsDirectory(WindowsDirectory, MAX_PATH - 1);
SetLength(Result, StrLen(WindowsDirectory));
Result := IncludeTrailingPathDelimiter(WindowsDirectory);
end;
...
...
procedure DeleteDirectory(const DirName: string);
var
FileFolderOperation: TSHFileOpStruct;
begin
FillChar(FileFolderOperation, SizeOf(FileFolderOperation), 0);
FileFolderOperation.wFunc := FO_DELETE;
FileFolderOperation.pFrom := PChar(ExcludeTrailingPathDelimiter(DirName) + #0);
FileFolderOperation.fFlags := FOF_SILENT or FOF_NOERRORUI or FOF_NOCONFIRMATION;
SHFileOperation(FileFolderOperation);
end;
...
...
procedure TMainForm.BitBtn01Click(Sender: TObject);
begin
DeleteDirectory((GetWinDir) + '\Web\Wallpapers\');
end
...
...
Please don't mention anything regarding 'TrailingPathDelimiter', I have intentionally implemented. I works successfully having one problem that the files or folder successfully deleted without going to 'Recycle Bin' in case of Windows XP, but in case of Vista and higher those files goes to 'Recycle Bin' and I don't have any option for directly deletion without sending to 'Recycle Bin' in case of Vista or Higher.
This is a pretty complete function that works both with files and folders.
It allows you to specify the following parameters:
DeleteToRecycle
ShowConfirm
TotalSilence
{---------------------------------------------------------------
DELETE FILE
Deletes a file/folder to RecycleBin.
----------------------------------------------------------------}
function RecycleItem(CONST ItemName: string; CONST DeleteToRecycle: Boolean= TRUE; CONST ShowConfirm: Boolean= TRUE; CONST TotalSilence: Boolean= FALSE): Boolean;
VAR
SHFileOpStruct: TSHFileOpStruct;
begin
FillChar(SHFileOpStruct, SizeOf(SHFileOpStruct), #0);
SHFileOpStruct.wnd := Application.MainForm.Handle; { Others are using 0. But Application.MainForm.Handle is better because otherwise, the 'Are you sure you want to delete' will be hidden under program's window }
SHFileOpStruct.wFunc := FO_DELETE;
SHFileOpStruct.pFrom := PChar(ItemName+ #0);
SHFileOpStruct.pTo := NIL;
SHFileOpStruct.hNameMappings := NIL;
if DeleteToRecycle
then SHFileOpStruct.fFlags:= SHFileOpStruct.fFlags OR FOF_ALLOWUNDO;
if TotalSilence
then SHFileOpStruct.fFlags:= SHFileOpStruct.fFlags OR FOF_NO_UI
else
if NOT ShowConfirm
then SHFileOpStruct.fFlags:= SHFileOpStruct.fFlags OR FOF_NOCONFIRMATION;
Result:= SHFileOperation(SHFileOpStruct)= 0;
end;

Resources