Delphi 和 Indy TIdFTP:将所有文件从服务器上的一个文件夹复制到另一个文件夹
Delphi and Indy TIdFTP: Copy all files from one folder on the server to another
我正在使用 TIdFTP
(Indy 10.6) 作为客户端应用程序,我需要能够将所有文件从服务器上的一个文件夹复制到另一个文件夹。这能做到吗?
我知道如何重命名或移动文件,我们可以使用 TIdFTP.Rename(Src, Dst)
。
副本呢?我是否需要将 Get()
和 Put()
与新路径/名称一起使用,因为服务器中的文件数量可能超过 500,000 个文件。
在我们公司,我们有一些文件的大小超过了 1.5 GB。通过使用我的代码,它会消耗大量内存并且文件不会从一个目录复制到另一个目录:在更少的代码中,源目录被命名为 "Fichiers" 并且目标目录被命名为 "Sauvegardes".
这是我的代码:
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;
有没有人有这方面的经验?我怎样才能更改我的代码来解决这个问题?
FTP 协议中没有规定,因此 TIdFTP
中没有方法,一次 copy/move 多个 文件.一次只能copy/move个人个文件。
将文件从一个 FTP 文件夹移动 到另一个文件夹很容易,可以使用 TIdFTP.Rename()
方法完成。但是,复制 文件通常需要发出单独的命令,先将文件下载到本地,然后再将其重新上传到新路径。
一些 FTP 服务器支持 复制 文件的自定义命令,因此您不需要在本地 download/upload 它们。例如,ProFTPD 的 mod_copy
模块为此实现了 SITE CPFR/CPTO
命令。如果你的FTP服务器支持这样的命令,你可以使用TIdFTP.Site()
方法,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;
如果这不起作用,另一种避免必须在本地复制每个文件的方法是在到同一 FTP 服务器的 2 个单独的 TIdFTP
连接之间使用站点到站点传输。如果服务器允许,可以使用TIdFTP.SiteToSiteUpload()
和TIdFTP.SiteToSiteDownload()
方法让服务器给自己传输文件,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;
但是,如果根本无法使用此类命令,那么您将不得不求助于将每个文件下载到本地,然后重新上传。当以这种方式复制 一个大文件时,您应该使用TFileStream
(或类似的)而不是TMemoryStream
。不要在内存中存储大文件。如果内存管理器不能分配足够的内存来保存整个文件,你不仅会冒内存错误的风险,而且一旦内存被分配和释放,内存管理器将保留它以供以后重用,它不会得到回到了OS。这就是为什么在传输大文件时内存使用率如此之高,即使在所有传输都已完成之后也是如此。
如果您真的想使用 TMemoryStream
,请仅将其用于较小的文件。您可以在下载文件之前检查服务器上每个文件的大小(通过 TIdFTPListItem.Size
如果可用,否则通过 TIdFTP.Size()
),然后选择合适的 TStream
-派生 class用于该传输,例如:
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;
您还可以对此进行其他优化,例如创建一个具有预先设定大小 Capacity
的单个 TMemoryStream
,然后将其重复用于不会超过 [=29] 的多次传输=].
因此,将所有这些放在一起,您最终可能会得到如下内容:
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;
我正在使用 TIdFTP
(Indy 10.6) 作为客户端应用程序,我需要能够将所有文件从服务器上的一个文件夹复制到另一个文件夹。这能做到吗?
我知道如何重命名或移动文件,我们可以使用 TIdFTP.Rename(Src, Dst)
。
副本呢?我是否需要将 Get()
和 Put()
与新路径/名称一起使用,因为服务器中的文件数量可能超过 500,000 个文件。
在我们公司,我们有一些文件的大小超过了 1.5 GB。通过使用我的代码,它会消耗大量内存并且文件不会从一个目录复制到另一个目录:在更少的代码中,源目录被命名为 "Fichiers" 并且目标目录被命名为 "Sauvegardes".
这是我的代码:
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;
有没有人有这方面的经验?我怎样才能更改我的代码来解决这个问题?
FTP 协议中没有规定,因此 TIdFTP
中没有方法,一次 copy/move 多个 文件.一次只能copy/move个人个文件。
将文件从一个 FTP 文件夹移动 到另一个文件夹很容易,可以使用 TIdFTP.Rename()
方法完成。但是,复制 文件通常需要发出单独的命令,先将文件下载到本地,然后再将其重新上传到新路径。
一些 FTP 服务器支持 复制 文件的自定义命令,因此您不需要在本地 download/upload 它们。例如,ProFTPD 的 mod_copy
模块为此实现了 SITE CPFR/CPTO
命令。如果你的FTP服务器支持这样的命令,你可以使用TIdFTP.Site()
方法,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;
如果这不起作用,另一种避免必须在本地复制每个文件的方法是在到同一 FTP 服务器的 2 个单独的 TIdFTP
连接之间使用站点到站点传输。如果服务器允许,可以使用TIdFTP.SiteToSiteUpload()
和TIdFTP.SiteToSiteDownload()
方法让服务器给自己传输文件,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;
但是,如果根本无法使用此类命令,那么您将不得不求助于将每个文件下载到本地,然后重新上传。当以这种方式复制 一个大文件时,您应该使用TFileStream
(或类似的)而不是TMemoryStream
。不要在内存中存储大文件。如果内存管理器不能分配足够的内存来保存整个文件,你不仅会冒内存错误的风险,而且一旦内存被分配和释放,内存管理器将保留它以供以后重用,它不会得到回到了OS。这就是为什么在传输大文件时内存使用率如此之高,即使在所有传输都已完成之后也是如此。
如果您真的想使用 TMemoryStream
,请仅将其用于较小的文件。您可以在下载文件之前检查服务器上每个文件的大小(通过 TIdFTPListItem.Size
如果可用,否则通过 TIdFTP.Size()
),然后选择合适的 TStream
-派生 class用于该传输,例如:
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;
您还可以对此进行其他优化,例如创建一个具有预先设定大小 Capacity
的单个 TMemoryStream
,然后将其重复用于不会超过 [=29] 的多次传输=].
因此,将所有这些放在一起,您最终可能会得到如下内容:
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;