(06-22-2023, 03:51 PM)Neerarawat Wrote: The issue you're experiencing ...
First off, that felt like a ChatGPT-generated answer to me, but I'm going to address it anyway, because it provided a lot of misinformation that will get you into trouble.
(06-22-2023, 03:51 PM)Neerarawat Wrote: Here's an example of how you can achieve this using TThread
DO NOT call
TIdFTP.Connected() from a different thread than the one that is performing the FTP operations.
Connected() determines the socket state by reading from the socket, so you would be reading socket data from 2 threads at the same time, and thus risking the
IOHandler.InputBuffer becoming corrupted, which would screw up the FTP operations.
A safer approach would be to wait for the thread itself to terminate after the FTP work is finished. Note that
TThread.CreateAnonymousThread() creates a
TThread object whose
FreeOnTerminate property is set to
True, which means you would not be able to use the
TThread.WaitFor() method by default without crashing your code. But, you can just turn off
FreeOnTerminate before starting the anonymous thread.
Also, since the FTP operations are being executed in a worker thread, the
FTP.OnWork... event handlers will be called in the context of that thread, not in the context of the main UI thread. So, you would need to synchronize with the main thread when accessing UI controls.
Try something more like this instead:
Code:
procedure TForm1.DownloadFile(const AHost, AUsername, APassword, ARemoteFile, ALocalFile: string);
var
LThread: TThread;
procedure WaitForThread;
var
H: THandle;
begin
// LThread.WaitFor;
H := LThread.Handle;
repeat
case MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0+0: Break;
WAIT_OBJECT_0+1: Application.ProcessMessages;
else
RaiseLastOSError;
end;
until False;
end;
begin
// Connect and download the file in a separate thread
LThread := TThread.CreateAnonymousThread(
procedure
var
FTP: TIdFTP;
begin
FTP := TIdFTP.Create(nil);
try
FTP.Host := AHost;
FTP.Username := AUsername;
FTP.Password := APassword;
// Set up event handlers to track progress
FTP.OnWork := Self.FTPWork;
FTP.OnWorkBegin := Self.FTPWorkBegin;
FTP.OnWorkEnd := Self.FTPWorkEnd;
FTP.Connect;
try
FTP.Get(ARemoteFile, ALocalFile);
finally
FTP.Disconnect;
end;
finally
FTP.Free;
end;
end
);
try
LThread.FreeOnTerminate := False;
LThread.Start;
// Wait for the download to complete
WaitForThread;
finally
LThread.Free;
end;
// You can perform any post-download actions here
end;
procedure TForm1.FTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
// Update the progress bar based on the current work count
TThread.Queue(nil,
procedure
begin
ProgressBar1.Position := AWorkCount;
end
);
end;
procedure TForm1.FTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
// Show the progress bar and set its maximum value
TThread.Queue(nil,
procedure
begin
ProgressBar1.Visible := True;
ProgressBar1.Position := 0;
ProgressBar1.Max := AWorkCountMax;
end
);
end;
procedure TForm1.FTPWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
// Hide the progress bar when the FTP work ends
TThread.Queue(nil,
procedure
ProgressBar1.Visible := False;
end
);
end;
Which, to be honest, is really no better than simply eliminating the anonymous thread altogether and just having
DownloadFile() use
TIdFTP directly in the context of the calling thread, and using
TIdAntiFreeze to avoid blockage if
DownloadFile() is being called in the context of the main UI thread:
Code:
procedure TForm1.DownloadFile(const AHost, AUsername, APassword, ARemoteFile, ALocalFile: string);
var
FTP: TIdFTP;
begin
FTP := TIdFTP.Create(nil);
try
FTP.Host := AHost;
FTP.Username := AUsername;
FTP.Password := APassword;
// Set up event handlers to track progress
FTP.OnWork := FTPWork;
FTP.OnWorkBegin := FTPWorkBegin;
FTP.OnWorkEnd := FTPWorkEnd;
FTP.Connect;
try
FTP.Get(ARemoteFile, ALocalFile);
finally
FTP.Disconnect;
end;
finally
FTP.Free;
end;
// You can perform any post-download actions here
end;
That being said, regardless of whichever thread ends up using
TIdFTP, do note that for
TIdFTP.Get() (but not for
TIdFTP.Put()), the
AWorkCountMax parameter of the
TIdFTP.OnWorkBegin event will
always be 0, because the FTP protocol does not advertise data sizes during transfers. So, you would need to use either
TIdFTP.Size(),
TIdFTP.List(), or
TIdFTP.ExtListItem() to determine a file's size
before you then use
TIdFTP.Get() to download the file. I'll leave that as an exercise for you to add that to the above example.