TreeView speed with IdFtp

Hi

I'm experimenting with the code from http://www.delphipages.com/threads/thread.cfm?ID=103981&G=102295

For creating directory list in a TreeView from IdFtp. It works OK apart from a couple of things but the main one is that it is very slow to display the directory structure. Is there any way to speed this up. (The directories load quite fast if just loaded into a ListView). I know there are components like VirtualTrees but I am looking for code assistance and not links to third party components as it's part of the way I like to learn.

Many thanks in anticipation.

Chris
LVL 1
xpherAsked:
Who is Participating?
 
PAQ_ManCommented:
Closed, 250 points refunded.
PAQ_Man
Community Support Moderator
0
 
kretzschmarCommented:
maybe you could show, how you do it now?

meikl ;-)
0
 
xpherAuthor Commented:
I gave the link to the code in original post as I was not sure about rights of reproducing here etc.

Then using that code I have created a thread for AfterLogin

type
  TFtpAfterLoginThread = class(TThread)
  private
    FFtp: TIdFtp;
  protected
    constructor Create(CreateSuspended: boolean);
    procedure Execute(); override;
  end;

......
......

constructor TFtpAfterLoginThread.Create(CreateSuspended: boolean);
begin
  inherited Create(CreateSuspended);
  FFtp := IdFtp1;
  FreeOnTerminate := true;
end;

procedure TFtpAfterLoginThread.Execute();
begin
  Form1.Caption := 'Connected to: '+ FFtp.Host;
  FTPDirToTreeView(FFtp, Treeview1, '/', nil, False);
  Terminate();
end;


I am not wanting to list the files only directories.

Hope makes sense?
0
Cloud Class® Course: Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

 
xpherAuthor Commented:
Sorry forgot to add

procedure TForm1.IdFTP1AfterClientLogin(Sender: TObject);
var
  fta: TFtpAfterLogInThread;
begin
  fta := TFtpAfterLoginThread.Create(false);
end;
0
 
LMuadDIbCommented:
I dont know how much you could speed up the listing in the treeview, esp when the items could be a few items to thousands. Plus the speed on the transfer would have a big impact on it too.

I would download the dir tree into a xml file/memory, and then update the treeview every so often in another thread. Keeping track of were the last item was added etc, so I wouldnt have to update the whole list in the treeview.
Saving the results and searching of the tree would be easier thru the xml file then the treeview
0
 
xpherAuthor Commented:
I'm working on the idea of just displaying first level, and assume there are sub-directories so adding a dummy child to get the + button, then display next level onexpanding at the same time deleting the dummy. I've just about got it to work and it seems to be effective apart from the odd exception showing message 'invalid number of arguments' but my code is a little messed at the moment so going to start with clean slate again which may end the problem.

Thanks for your comment but I think it would still take time to create whole directory structure as was my first problem.

I am going to leave this open until I iron out my exception, if I do then I will stick with my solution. I might delete this question so best not leave any comments until I've had another go. Many thanks.
0
 
TheRealLokiSenior DeveloperCommented:
how are you parsing the listing into files and directories?
Is it your custom code? could that be causing the slowdown?
Obviously an FTP site with a big file/dir listing is going to take a while...
to check if it is just the site that is slow, you could try a 3rd party ftp client, or simply type this in a dos box :-
ftp someplace.com
username
password
LS

see how long that takes, if that's the slowdown, then there's not much you can do
If it's fast though, then I'd check your file listing parsing code.
(p.s. using that site's code, I can see our servers dir listing in the tree at a good speed)
0
 
LMuadDIbCommented:
He wants a recursive directory listing Loki

and the only way I know off at retrieving a recursive dir list fast is if you have execute rights on the ftp server. Then you can execute a dos cmd via a ftp to retrieve the list. Very fast, but rarely will a user get execute rights on the server. There are a few ftp servers that have special cmds to do a recursive listing, but IIS and Apache do not support it, which is about 99% of all the ftp servers out there.

only option left is to do it OnDemand, a single dir at a time whenever a user wants to enter a certain dir.
0
 
TheRealLokiSenior DeveloperCommented:
yep, i understand that. I've tried on a couple of servers. 1 that brings up a recursive list, and 1 that just has the current 'dir' at a time. The bigger (and deeper) the list, the worse the performance.
The code from the page he is using will make a node with the caption "DirName/", so you can easily tell it is a drectory because of the /.
His suggestion of doing the current dir, and then only showing (retrieving) the subdirectory contents on, lets say, a double click of a "DirName/" folder isn't a bad idea.

// to do so, simply comment out the recursion bit below
     for i := 0 to LS.Count - 1 do
     begin
       FTPDirToTreeView(AFTP, ATree, ADirectory +
       LS.Strings[i] + '/', TTreeNode(LS.Objects[i]), AIncludeFiles);
     end;

and on the "double click" event, build the next directory something like this :-

procedure TForm1.TreeView1DblClick(Sender: TObject);
    var
        s, dirname: string;
        currentnode, node: TTreeNode;
    begin
        if TreeView1.Selected = nil then exit;
        currentnode := TreeView1.Selected;
        node := currentnode;
        s := node.Text;
        if s[length(s)] = '/' then
        begin // build dir name
            while node <> nil do
            begin
                dirname := node.Text + dirname;
                node := node.parent;
            end;
            dirname := '/' + dirname;
            FTPDirToTreeView(idFTP1, Treeview1, Dirname, currentnode, False); //True);
        end;
    end;

0
 
xpherAuthor Commented:
As I said earlier I've allready done it by loading a level at a time, it is as fast as the connection. Please read my earlier comment. I don't have time today but I'll tidy my code up and post it here. I'm impressed with it (but I'm easily pleased) ;-)
0
 
Eddie ShipmanAll-around developerCommented:
I don't remember if I put Insert BeginUpdate and EndUpdate staements around the
tree loading code but that may help the performance.
0
 
xpherAuthor Commented:
This is what I have done and it works well for me. I'm showing it here first before I ask for question to be deleted. It is not final but it's basically it.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons, ComCtrls, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdExplicitTLSClientServerBase, IdFTP, IdFTPList,
  IdAllFTPListParsers, IdReplyRFC, IdFtpCommon, ImgList;

type
  TFtpThread = class(TThread)
  private
    FFtp: TIdFtp;
  protected
    constructor Create(CreateSuspended: boolean);
    procedure Execute(); override;
  end;

type
  TFtpAfterLoginThread = class(TThread)
  private
    FFtp: TIdFtp;
  protected
    constructor Create(CreateSuspended: boolean);
    procedure Execute(); override;
  end;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    SpeedButton1: TSpeedButton;
    IdFTP1: TIdFTP;
    ImageList1: TImageList;
    procedure SpeedButton1Click(Sender: TObject);
    procedure IdFTP1AfterClientLogin(Sender: TObject);
    procedure IdFTP1Disconnected(Sender: TObject);
    procedure TreeView1Expanding(Sender: TObject; Node: TTreeNode;
      var AllowExpansion: Boolean);
    procedure TreeView1Click(Sender: TObject);
    procedure TreeView1Collapsed(Sender: TObject; Node: TTreeNode);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure FTPDirToTreeView(AFTP: TIdFTP; ATree: TTreeView;
                          const ADirectory: String; AItem: TTreeNode;
                          AIncludeFiles:Boolean);
var
 TempItem: TTreeNode;
 I: Integer;
 DirList: TIdFTPListItems;
 DirItem: TIdFTPListItem;
 LS: TStringList;
begin
 LS := TStringList.Create;
 try
   LS.Sorted := True;
   ATree.Items.BeginUpdate;
   try
     if (ADirectory <> '') then
       AFTP.ChangeDir(ADirectory);
     AFTP.TransferType := ftASCII;
     AFTP.List(nil);

     DirList := AFTP.DirectoryListing;
     for i := 0 to DirList.Count - 1 do
     begin
     try
       DirItem := DirList.Items[i];
       if (DirItem.ItemType = ditDirectory) then
       begin
         TempItem := ATree.Items.AddChild(AItem, Trim(DirItem.FileName));
         ATree.Items.AddChild(TempItem,'');
         TempItem.StateIndex := 0;
            {Set the image used when the node is not selected}
         TempItem.ImageIndex := 0;
            {Image used when the node is selected}
         TempItem.SelectedIndex := 1;
       end;
     except
     end;
     end;
   finally
      ATree.AlphaSort(true);
      ATree.Items.EndUpdate;
   end;
 finally
   LS.Free;  
 end;
end;

constructor TFtpThread.Create(CreateSuspended: boolean);
begin
  inherited Create(CreateSuspended);
  FFtp := Form1.IdFtp1;
  FreeOnTerminate := true;
end;

procedure TFtpThread.Execute();
begin
  if not FFtp.Connected then
  FFtp.Connect
  else
  FFtp.Disconnect;
  Terminate();
end;

constructor TFtpAfterLoginThread.Create(CreateSuspended: boolean);
begin
  inherited Create(CreateSuspended);
  FFtp := Form1.IdFtp1;
  FreeOnTerminate := true;
end;

procedure TFtpAfterLoginThread.Execute();
begin
  FTPDirToTreeView(FFtp, Form1.Treeview1, '/', nil, False);
  Form1.Caption := 'Connected to: '+ FFtp.Host;
  Terminate();
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  ftc: TFtpThread;
begin
  ftc := TFtpThread.Create(false);
end;

procedure TForm1.IdFTP1AfterClientLogin(Sender: TObject);
var
  fta: TFtpAfterLogInThread;
begin
  fta := TFtpAfterLoginThread.Create(false);
end;

procedure TForm1.IdFTP1Disconnected(Sender: TObject);
begin
  Form1.Caption := 'Not connected';
end;

function NodePath(Node: TTreeNode): string;
begin
  Result:='';
  while Node<>nil do
    begin
      Result:=Concat(Node.Text,'/',Result);
      Node:=Node.Parent
    end
end;

procedure TForm1.TreeView1Expanding(Sender: TObject; Node: TTreeNode;
  var AllowExpansion: Boolean);
var
  s: string;
begin
  s := '/'+NodePath(node);
  if node.Item[0].Text = '' then node.Item[0].Delete;
  FTPDirToTreeView(iDfTP1, TreeView1, S, node, False);
end;

procedure TForm1.TreeView1Click(Sender: TObject);
begin
  if TreeView1.SelectionCount > 0 then
  TreeView1.Selected.Expanded := True;
end;

procedure TForm1.TreeView1Collapsed(Sender: TObject; Node: TTreeNode);
begin    //avoid adding same nodes again
  Node.DeleteChildren;
  TreeView1.Items.AddChild(Node,'');
end;

end.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.