xpher
asked on
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
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
ASKER
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.Creat e(CreateSu spended: boolean);
begin
inherited Create(CreateSuspended);
FFtp := IdFtp1;
FreeOnTerminate := true;
end;
procedure TFtpAfterLoginThread.Execu te();
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?
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.Creat
begin
inherited Create(CreateSuspended);
FFtp := IdFtp1;
FreeOnTerminate := true;
end;
procedure TFtpAfterLoginThread.Execu
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?
ASKER
Sorry forgot to add
procedure TForm1.IdFTP1AfterClientLo gin(Sender : TObject);
var
fta: TFtpAfterLogInThread;
begin
fta := TFtpAfterLoginThread.Creat e(false);
end;
procedure TForm1.IdFTP1AfterClientLo
var
fta: TFtpAfterLogInThread;
begin
fta := TFtpAfterLoginThread.Creat
end;
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
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
ASKER
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.
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.
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)
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)
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.
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.
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(S ender: 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;
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(S
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;
ASKER
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) ;-)
I don't remember if I put Insert BeginUpdate and EndUpdate staements around the
tree loading code but that may help the performance.
tree loading code but that may help the performance.
ASKER
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, IdExplicitTLSClientServerB ase, 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(Sen der: 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(TempI tem,'');
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(CreateSu spended: 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.Creat e(CreateSu spended: boolean);
begin
inherited Create(CreateSuspended);
FFtp := Form1.IdFtp1;
FreeOnTerminate := true;
end;
procedure TFtpAfterLoginThread.Execu te();
begin
FTPDirToTreeView(FFtp, Form1.Treeview1, '/', nil, False);
Form1.Caption := 'Connected to: '+ FFtp.Host;
Terminate();
end;
procedure TForm1.SpeedButton1Click(S ender: TObject);
var
ftc: TFtpThread;
begin
ftc := TFtpThread.Create(false);
end;
procedure TForm1.IdFTP1AfterClientLo gin(Sender : TObject);
var
fta: TFtpAfterLogInThread;
begin
fta := TFtpAfterLoginThread.Creat e(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(Send er: TObject);
begin
if TreeView1.SelectionCount > 0 then
TreeView1.Selected.Expande d := True;
end;
procedure TForm1.TreeView1Collapsed( Sender: TObject; Node: TTreeNode);
begin //avoid adding same nodes again
Node.DeleteChildren;
TreeView1.Items.AddChild(N ode,'');
end;
end.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Buttons, ComCtrls, IdBaseComponent, IdComponent,
IdTCPConnection, IdTCPClient, IdExplicitTLSClientServerB
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(Sen
procedure IdFTP1Disconnected(Sender:
procedure TreeView1Expanding(Sender:
var AllowExpansion: Boolean);
procedure TreeView1Click(Sender: TObject);
procedure TreeView1Collapsed(Sender:
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
ATree.Items.AddChild(TempI
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(CreateSu
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.Creat
begin
inherited Create(CreateSuspended);
FFtp := Form1.IdFtp1;
FreeOnTerminate := true;
end;
procedure TFtpAfterLoginThread.Execu
begin
FTPDirToTreeView(FFtp, Form1.Treeview1, '/', nil, False);
Form1.Caption := 'Connected to: '+ FFtp.Host;
Terminate();
end;
procedure TForm1.SpeedButton1Click(S
var
ftc: TFtpThread;
begin
ftc := TFtpThread.Create(false);
end;
procedure TForm1.IdFTP1AfterClientLo
var
fta: TFtpAfterLogInThread;
begin
fta := TFtpAfterLoginThread.Creat
end;
procedure TForm1.IdFTP1Disconnected(
begin
Form1.Caption := 'Not connected';
end;
function NodePath(Node: TTreeNode): string;
begin
Result:='';
while Node<>nil do
begin
Result:=Concat(Node.Text,'
Node:=Node.Parent
end
end;
procedure TForm1.TreeView1Expanding(
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(Send
begin
if TreeView1.SelectionCount > 0 then
TreeView1.Selected.Expande
end;
procedure TForm1.TreeView1Collapsed(
begin //avoid adding same nodes again
Node.DeleteChildren;
TreeView1.Items.AddChild(N
end;
end.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
meikl ;-)