Synapas http Download from internet using threads in Free Pascal

In our previous example we have used Synapse  ThttpSend to download files from the web using blocking mode.

Now we want to unfreeze this blocking of application while downloading large files. We have used threads in this example. Before using thread we need to enable it first if we are using Linux.

By default we will find this compiler directive in main project source code:


uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}

Remove useCThreads part to be like this:


uses
  {$IFDEF UNIX}
  cthreads,
  {$ENDIF}

This is the form:

And this is the code of form unit:

unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, process, FileUtil, Forms, Controls, Graphics, Dialogs,
  StdCtrls, Buttons, httpSend;

type

  { THTTPDownload }

  THTTPDownload = class(TThread)
    private
      fURL: string;
      fFileName: string;
    public
      constructor Create(URL, FileName: string);
      procedure Execute; override;
  end;

  { TfmMain }

  TfmMain = class(TForm)
    btDownload: TBitBtn;
    btGet: TButton;
    edURL: TEdit;
    Label1: TLabel;
    Memo1: TMemo;
    SaveDialog1: TSaveDialog;
    procedure btDownloadClick(Sender: TObject);
    procedure btGetClick(Sender: TObject);
  private
    procedure DownloadTerminiated(Sender: TObject);
    { private declarations }
  public
    { public declarations }
  end;

var
  fmMain: TfmMain;

implementation

{ THTTPDownload }

constructor THTTPDownload.Create(URL, FileName: string);
begin
  inherited Create(True);
  fURL:= URL;
  fFileName:= FileName;
end;

procedure THTTPDownload.Execute;
var
  httpClient: THTTPSend;
begin
  httpClient:= THTTPSend.Create;
  if httpClient.HTTPMethod('GET', fURL) then
    httpClient.Document.SaveToFile(fFileName);
      fmMain.Memo1.Lines.Add(fFileName + ', has been downloaded');

  httpClient.Free;
end;

{$R *.lfm}

{ TfmMain }

procedure TfmMain.btGetClick(Sender: TObject);
var
  httpClient: THTTPSend;
begin
  httpClient:= THTTPSend.Create;
  if httpClient.HTTPMethod('GET', edURL.Text) then
  begin
    Memo1.Lines.LoadFromStream(httpClient.Document);
  end;
  httpClient.Free;
end;

procedure TfmMain.DownloadTerminiated(Sender: TObject);
begin
  Memo1.Lines.Add('Thread finished');
  btDownload.Enabled:= True;
end;

procedure TfmMain.btDownloadClick(Sender: TObject);
var
  httpClient: THTTPSend;
  download: THTTPDownload;
begin
  SaveDialog1.FileName:= ExtractFileName(edURL.Text);
  if SaveDialog1.Execute then
  begin
    btDownload.Enabled:= False;
    download:= THTTPDownload.Create(edURL.Text, SaveDialog1.FileName);
    download.OnTerminate:= @DownloadTerminiated;
    download.Resume;
    memo1.Lines.Add('Download started');
  end;

end;

end.

9 thoughts on “Synapas http Download from internet using threads in Free Pascal

  1. …Thanks for the insight. I tried your concept and replaced the HTTP download functionality with a mysql query and it works pretty well. The application remains responsive as expected.

    Then I tried the same concept with a synapse based “PING” function. For some reason the non-blocking delay of ping makes the application no responsive for a pc host that is not found. Is there something I am overlooking?

  2. Thanks, below please find the relevant sections of the code:

    
      { TPingBox }
       TFNPingBox = class(TThread)
          private
    
            fServerAddress: string;    //ip address of server to be pinged
            fPilotLight   : TShape;    //shape control to be used as a pilot light
          public
            constructor Create(serverAddress: string;pilotLight:TShape);
            procedure Execute; override;
        end;
    
    constructor TFNPingBox.Create(serverAddress: string;pilotLight:TShape);
    begin
      inherited Create(True);
      fServerAddress:= serverAddress;
      fPilotLight   := pilotLight;
    end;
    
    procedure TFNPingBox.Execute;
    begin
      if (pinghost(fServerAddress)  -1) then
      begin
       form1.Shape1.Brush.Color:= clLime;
      end
      else
      begin
       form1.Shape1.Brush.Color:= clRed;
      end;
    
      form1.Memo1.Lines.Add('Pinger execution completed.');
    end;
    
    procedure TForm1.SqlQueryTerminated(Sender: TObject);
    begin
      form1.Timer2.Enabled:= false;
      Memo1.Lines.Add('Mysql query thread finished.');
    end;
    
    procedure TForm1.Button4Click(Sender: TObject);
    var pinger: TFNPingBox;
    begin
      pinger := TFNPingBox.Create(form1.Edit1.Text,form1.Shape1);
      pinger.OnTerminate:= @PingTerminated;
      try
       pinger.Resume;
      finally
        pinger.Free;
      end;
    
    end;
    
    
  3. You shouldn’t call pinger.Free at this position, you are trying to free the thread while it is working. so your code will be like this:

    procedure TForm1.Button4Click(Sender: TObject);
    var pinger: TFNPingBox;
    begin
      pinger := TFNPingBox.Create(form1.Edit1.Text,form1.Shape1);
      Pinger.FreeOnTerminate:= True;
      pinger.OnTerminate:= @PingTerminated;
    
      pinger.Resume;
    	 
    end;
    
  4. That sorted out the problem. I guess I actually introduced the anomaly myself. My attempt was to ensure that “pinger” is freed upon completion of the thread. But now I remember that the thread is actually freed by the “pinger.OnTerminate:= @PingTerminated” statement.

    Thank you very much indeed.

  5. not that Pinger.Resume will start the thread but it will not wait until execution or termination, it will go to the next step, which was Pinger.Free in your previous code.

    Another note:
    Don’t write immediately from Thread to Memo, specially if you have multiple instances of the thread. Instead put the code of display in a method like:

    
    procedure TFNPingBox.Display(AText: string);
    begin
      form1.Memo1.Lines.Add(AText);
    end;
    
    

    And call it using Synchronize from the thread:

    	procedure TFNPingBox.Execute;
    	begin
    	  if (pinghost(fServerAddress)  -1) then
    	  begin
    	   form1.Shape1.Brush.Color:= clLime;
    	  end
    	  else
    	  begin
    	   form1.Shape1.Brush.Color:= clRed;
    	  end;
    	 
    	  Synchronize(Display('Pinger execution completed.'));
    	end;
    

    Synchornize will ensure that no multiple instances of this thread will access Memo1 at the same time

  6. I have implemented the Synchronize(Display) procedure as proposed and it also works. However, I had to remove the “(AText: String)” argument for the procedure to make it compile successfully.

    I am satisfied with the progress. Thank you.

  7. Hello, Thanks for a nice tutorial.

    I wanted to know is there any way to show a progressbar showing the percentage of the file being downloaded? I didn’t find any working example.

Leave a comment