Search code examples
delphiftptreeviewindy

How do I populate a Treeview via FTP


Scenario

I'm trying to duplicate the standard way to fill a Treeview with directories/folders from a folder structure, starting at the root, but using IdFTP to get the structure from a remote server instead of my local hard drive. I'd like the result to look similar to clients like Filezilla.

I used this reasonably standard code from the Swiss Delphi Centre (which works to display my hard drive's structure) and then modified it to use IdFTP.ChangeDir(Directory) and IdFTP.List instead of FindFirst() and FindNext().

Problem

I seem to have got myself in a muddle as it is not correctly 'unwinding' the recursion so that once it traverses down the /cpanel/cashes/config directories on the remote server it doesn't return and traverse all the other directories hanging off the root but exits the procedure without displaying anything else. Also it doesn't seem to show all the top level folders but this could be simply due to the order that IdFTP.List returns them in

Can anyone tell me what I have done wrong here?

If you can also tell me how I should get the root (/) shown as well that would be very helpful

(I've commented out displaying non directories as I only want folders at this stage)

What I expected to see Copied from Filezilla

enter image description here

What I did see Using a Ttreeview in Delphi

enter image description here

My Code

procedure TForm2.Button1Click(Sender: TObject);
var StartingDir : string;
begin
TreeView1.Items.BeginUpdate;
try
    StartingDir :=    '/';
    Screen.Cursor := crHourGlass;
    TreeView1.Items.Clear;
    FTPconnect;  //procedure to connect to remote server
    GetDirectories(TreeView1, StartingDir, nil, True);
    FTPDisconnect; //procedure to disconnect from remote server
finally
    TreeView1.Items.EndUpdate;
    Screen.Cursor := crDefault;
end;
end;

procedure TForm2.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean);
var
  ItemTemp: TTreeNode;
  DirItemType : TIdDirItemType  ;
  Filename , NewStartingDirectory: string;
  i : Integer;
begin
  Tree.Items.BeginUpdate;
  IdFTP.ChangeDir(Directory);
  IdFTP.List;      //get directory of remote folder
  i:=0;
  repeat
     DirItemType := IdFTP.DirectoryListing[I].ItemType;
     Filename := IdFTP.DirectoryListing[I].FileName;
     If (DirItemType = ditDirectory) and (Filename <> '.') and (Filename <> '..')then
        begin
        if DirItemType = ditDirectory then
              Item := Tree.Items.AddChild(Item, Filename);
        ItemTemp := Item.Parent;
        if Directory = '/' then
            NewStartingDirectory := Directory  + Filename
        else
            NewStartingDirectory := Directory + '/' +Filename;
        GetDirectories(Tree, NewStartingDirectory, Item, IncludeFiles);
        Item := ItemTemp;
        end
     else 
        if IncludeFiles then
           begin  //this bit commented out as we only want to see directories
//         if (Filename <> '.') and (Filename <> '..') then
//         Tree.Items.AddChild(Item, Filename);
           end;
     inc(i);
  until i = IdFTP.DirectoryListing.Count;
  Tree.Items.EndUpdate;
end;

Swiss Delhpi Centre's code (for comparison)

procedure TForm1.Button1Click(Sender: TObject);
var
  Node: TTreeNode;
  Path: string;
  Dir: string;
begin
  Dir := 'c:\temp';
  Screen.Cursor := crHourGlass;
  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;
    GetDirectories(TreeView1, Dir, nil, True);
  finally
    Screen.Cursor := crDefault;
    TreeView1.Items.EndUpdate;
  end;
end;

procedure TForm1.GetDirectories(Tree: TTreeView; Directory: string; Item: TTreeNode; IncludeFiles: Boolean);
var
  SearchRec: TSearchRec;
  ItemTemp: TTreeNode;
begin
  Tree.Items.BeginUpdate;
  if Directory[Length(Directory)] <> '\' then Directory := Directory + '\';
  if FindFirst(Directory + '*.*', faDirectory, SearchRec) = 0 then
  begin
    repeat
      if (SearchRec.Attr and faDirectory = faDirectory) and (SearchRec.Name[1] <> '.') then
      begin
        if (SearchRec.Attr and faDirectory > 0) then
          Item := Tree.Items.AddChild(Item, SearchRec.Name);
        ItemTemp := Item.Parent;
        GetDirectories(Tree, Directory + SearchRec.Name, Item, IncludeFiles);
        Item := ItemTemp;
      end
      else if IncludeFiles then
        if SearchRec.Name[1] <> '.' then
          Tree.Items.AddChild(Item, SearchRec.Name);
    until FindNext(SearchRec) <> 0;
    FindClose(SearchRec);
  end;
  Tree.Items.EndUpdate;
end;

I've looked on SO here - too complicated and wrong language and here - similar to the Swiss Delphi Centre and here - wrong language and not sure what its doing.

if it's better to use a TlistView, can you please show me the equivalent code to use that instead?


Solution

  • Untested:

    • I made the TIdFTP variable a parameter, since TTreeView was also one and it should be done consistently, not archaic.
    • Using for loops instead of repeat until.
    • Eliminating IncludeFiles when it wasn't used anyway.
    • Eliminating weird logic to always get the new TreeNode's parent.
    • Not locking the TreeView anymore - do this once before calling this method and unlock it after calling - otherwise you do that dozens of times in vain.
    • Basic logic is as I wrote in the comments:
      1. Store all folder strings into your own list and avoid recursion at this point.
      2. Fix the path to be concatenated once, not with every iteration of a loop.
      3. Go through that list to do the recursion - at this point the state of FTP is irrelevant and you won't mess up listings at different levels.
      4. Of course, release the created instance of the StringList.
    procedure TForm2.GetFolders
    ( Ftp: TIdFTP  // The source, from which we read the content
    ; Tree: TTreeView  // The destination, which we want to fill
    ; ParentNode: TTreeNode  // Node under which all new child nodes should be created
    ; Path: String  // Starting directory
    );
    var
        NewNode: TTreeNode;  // New child in the tree
        Filename: String;  // Check against unwanted folder entries
        i: Integer;  // Looping over both lists
        sl: TStringList;  // Collect folders only
    begin
        FTP.ChangeDir( Path );
        FTP.List;  // Entire remote listing
    
        sl:= TStringList.Create;  // Collect all entries we're interested in
        try
            for i:= 0 to FTP.DirectoryListing.Count- 1 do begin  // For each entry
                Filename:= FTP.DirectoryListing[i].FileName;
                if  (FTP.DirectoryListing[i].ItemType= ditDirectory)  // Only folders
                and (Filename<> '.')
                and (Filename<> '..') then begin
                    sl.Add( Filename );  // Only the name, not the full path
                end;
            end;
    
            // Do this only once
            if Path<> '/' then Path:= '/'+ Path+ '/';
    
            for i:= 0 to sl.Count- 1 do begin  // All collected folders
                NewNode:= Tree.Items.AddChild( ParentNode, sl[i] );  // Populate tree
                GetFolders( Ftp, Tree, NewNode, Path+ sl[i] );  // Recursion of folder name + current path
            end;
        finally
            sl.Free;
        end;
    end;
    

    Untested, but should compile.