IDFTP DirExists and MakeDir

Bill picture Bill · Jan 15, 2015 · Viewed 10.8k times · Source

I am using Indy IDFTP to make a directory. I need to find a reliable way to determine if a directory exists and if it does not exist, to call MakeDir. I have tried the following code but an exception did not occur when List was called so MakeDir was not executed even though the directory did not exist at the time. How can I determine if a directory exists and create the directory if it does not exist?

{ Check to see if the '/public_html/XXX/' + iDomain + 'Thumbnails' directory exists }
    try
      IdFTP1.List(nil, '/public_html/XXX/' + iDomain + 'Thumbnails', False);
    except
      on e: EIdReplyRFCError do
      begin
        { '/public_html/XXX/' + iDomain + 'Thumbnails' directory does not exist }
        StatusBar1.SimpleText := 'Making thumbnail directory...';
        StatusBar1.Update;
        iFolder := '/public_html/XXX/' + iDomain;
        { Change directory to /public_html/XXX/iDomain }
        IdFTP1.ChangeDir(iFolder);
        iFolder := 'Thumbnails';
        { Create FTP Directory for Thumbnails }
        IdFTP1.MakeDir(iFolder);
      end;
    end;

During my testing the directory did not exist but at runtime e was nil? Is my approach to this correct?

Answer

Remy Lebeau picture Remy Lebeau · Jan 15, 2015

If TIdFTP.List() is not raising an exception, the FTP server is most likely returning a 450 reply code, which simply means "Requested file action not taken". TIdFTP.InternalGet() (which is used by TIdFTP.List(), TIdFTP.ExtListDir(), and TIdFTP.Get()) does not treat 450 as an error condition, as some servers (like Ericsson Switch FTP) send 450 when listing the contents of an empty but otherwise existent directory, so there is no listing data to send. Some servers do send 450 when the requested directory does not exist, though. TIdFTP.List() does not try to differentiate. However, if TIdFTP.List() does not raise an exception, you can look at the TIdFTP.LastCmdResult property to differentiate manually if needed.

Also, you can't just rely on the fact that an exception is raised to mean the folder does not exist. Any number of possible errors could occur. You have to actually look at the error and act accordingly, eg:

var
  Exists: Boolean;

try
  IdFTP1.List(nil, '/public_html/XXX/' + iDomain + 'Thumbnails', False);
  Exists := True;
  if IdFTP1.LastCmdResult.NumericCode = 450 then
  begin
    if (IdFTP1.LastCmdResult.Text.Text has a message like 'No such file or directory' or similar) then begin
      Exists := False;
    end;
    // look for other possible text messages...
  end;
except
  on e: EIdReplyRFCError do
  begin
    if (e.ErrorCode <> 550) or (e.Message does not have a message like 'Directory not found' or similar) then begin
      raise;
    end;
    Exists := false;
  end;
end;

if not Exists then
begin
  { '/public_html/XXX/' + iDomain + 'Thumbnails' directory does not exist }
  StatusBar1.SimpleText := 'Making thumbnail directory...';
  StatusBar1.Update;
  iFolder := '/public_html/XXX/' + iDomain;
  { Change directory to /public_html/XXX/iDomain }
  IdFTP1.ChangeDir(iFolder);
  iFolder := 'Thumbnails';
  { Create FTP Directory for Thumbnails }
  IdFTP1.MakeDir(iFolder);
end;

A better way would be to either:

  1. ChangeDir() to the target directory directly and see if it fails. If so, start walking backwards through the path, calling ChangeDir() for each parent directory until one finally succeeds, then walk back up the path using MakeDir() and ChangeDir() to create the missing sub-directories as needed.

  2. Start with the first directory in the path and ChangeDir() to it, then List() it to see if the next sub-folder is present, MakeDir() it if needed, then ChangeDir() to it, and repeat as needed until you reach the target directory.

Welcome to FTP. It is not a very efficient protocol for directory management.