Search code examples
httpdelphidownloadararat-synapse

Delphi - Download a File with Progress using Synapse


I have been using Synapse for some time now, to send e-mails mainly. Today I am creating a simple installer, and trying to download the application exe file through HTTP. The file is about 9 MB in size, so I´d like to add a progress status to user, but I do not understand the examples I found. Here is what I got so far:

type
  THookSocketStatus = Procedure(Sender: TObject; Reason: THookSocketReason; const Value: String) of Object;
  CallBack = class
    Class Procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
  end;


Class Procedure CallBack.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
var
  V: String;
Begin
  V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
  Form1.mem1.Lines.Add(V);
  application.ProcessMessages;
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  HTTP: THTTPSend;
  MSTM: TMemoryStream;
begin
  Screen.Cursor := crHourGlass;
  HTTP := THTTPSend.Create;
  MSTM := TMemoryStream.Create;
  Try
    Try
      HTTP.Sock.OnStatus := CallBack.Status;
      If HTTP.HTTPMethod('GET', edt1.Text) Then
      Begin
        MSTM.Seek(0, soFromBeginning);
        MSTM.CopyFrom(HTTP.Document, 0);
        MSTM.SaveToFile(ExtractFilePath(Application.ExeName) + 'test.exe');
      end;
    Except
    end;
  Finally
    MSTM.Free;
    HTTP.Free;
    Screen.Cursor := crDefault;
  end;
end;

In this simple test I got this result:

HR_SocketClose
HR_ResolvingBegin www.website.com:80
HR_ResolvingEnd 176.102.295.18:80
HR_SocketCreate IPv4
HR_Connect www.website.com:80
HR_WriteCount 158
HR_CanRead
HR_ReadCount 288
HR_CanRead
HR_ReadCount 8192
HR_ReadCount 8192
HR_ReadCount 8192
HR_ReadCount 6720
HR_CanRead
HR_ReadCount 3299
.
.
.
HR_ReadCount 8192
HR_ReadCount 8192
HR_ReadCount 7828
HR_SocketClose
HR_SocketClose

Please, what means WriteCount and ReadCount? How can I get total file size to set the progress bar before start the download?

Thank you guys!


Solution

  • I had the same problem and found a solution by extending the code above. The file length was available as suggested above by using the Header information.

    Here is my code:

    unit uhttpdownloader;
    
    
    {$mode Delphi}{$H+}
    
    interface
    
    uses
      Classes, SysUtils, httpsend, blcksock, typinfo;
    
    //Interface for notifications about the progress
    type
      IProgress = interface
        procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
      end;
    
    type
      { THttpDownloader }
    
      THttpDownloader = class
      public
        function DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
      private
        Bytes : Integer;
        MaxBytes : Integer;
        HTTPSender: THTTPSend;
        ProgressMonitor : IProgress;
        procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
        function GetSizeFromHeader(Header: String):integer;
      end;
    
    implementation
    
    function THttpDownloader.DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
    var
      HTTPGetResult: Boolean;
    begin
      Result := False;
      Bytes:= 0;
      MaxBytes:= -1;
      Self.ProgressMonitor:= ProgressMonitor;
    
      HTTPSender := THTTPSend.Create;
      try
        //add callback function for status updates
        HTTPSender.Sock.OnStatus:= Status;
        HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
        if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
          HTTPSender.Document.SaveToFile(TargetFile);
          Result := True;
        end;
      finally
        HTTPSender.Free;
      end;
    end;
    
    //Callback function for status events
    procedure THttpDownloader.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
    var
      V, currentHeader: String;
      i: integer;
    begin
      //try to get filesize from headers
      if (MaxBytes = -1) then
      begin
        for i:= 0 to HTTPSender.Headers.Count - 1 do
        begin
          currentHeader:= HTTPSender.Headers[i];
          MaxBytes:= GetSizeFromHeader(currentHeader);
          if MaxBytes <> -1 then break;
        end;
      end;
    
      V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
    
      //HR_ReadCount contains the number of bytes since the last event
      if Reason = THookSocketReason.HR_ReadCount then
      begin
        Bytes:= Bytes + StrToInt(Value);
        ProgressMonitor.ProgressNotification(V, Bytes, MaxBytes);
      end;
    end;
    
    function THttpDownloader.GetSizeFromHeader(Header: String): integer;
    var
      item : TStringList;
    begin
      //the download size is contained in the header (e.g.: Content-Length: 3737722)
      Result:= -1;
    
      if Pos('Content-Length:', Header) <> 0 then
      begin
        item:= TStringList.Create();
        item.Delimiter:= ':';
        item.StrictDelimiter:=true;
        item.DelimitedText:=Header;
        if item.Count = 2 then
        begin
          Result:= StrToInt(Trim(item[1]));
        end;
      end;
    end;
    end.
    

    The complete source code and example can be downloaded here as well: http://andydunkel.net/lazarus/delphi/2015/09/09/lazarus_synapse_progress.html

    Andy