Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ProgressBar while FTP retreive
#7
(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.

Reply


Messages In This Thread
ProgressBar while FTP retreive - by zsleo - 05-18-2023, 03:51 AM
RE: ProgressBar while FTP retreive - by rlebeau - 05-18-2023, 07:54 PM
RE: ProgressBar while FTP retreive - by zsleo - 05-19-2023, 03:11 AM
RE: ProgressBar while FTP retreive - by rlebeau - 05-19-2023, 04:25 PM
RE: ProgressBar while FTP retreive - by zsleo - 05-22-2023, 06:04 AM
RE: ProgressBar while FTP retreive - by rlebeau - 06-22-2023, 05:19 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)