4
votes

How to download a file from internet with progress bar using threads in Delphi 2009/10 without Indy components?

2
Show us what you've tried so far. And why don't you want to use Indy?Steve Mayne
There are alternatives like ICS, or you could use raw tcp sockets, if you like to make your life hard. But whats wrong with Indy?BugFinder
It can be done with Indy in less then 50 lines of code, I assume you have a very good reason to avoid it. What might that reason be?Cosmin Prund
I'm very curious why you don't want to use a mature/full of examples suits like Indy? Why you want to re-invent the wheel?RBA

2 Answers

6
votes

I don't like to use indy either, my reason is it is too large. You could also use wininet. I have written the following for a small project required small app size.

unit wininetUtils;

interface

uses Windows, WinInet
{$IFDEF KOL}
,KOL
{$ELSE}
,Classes
{$ENDIF}
;

type

{$IFDEF KOL}
  _STREAM = PStream;
  _STRLIST = PStrList;
{$ELSE}
  _STREAM = TStream;
  _STRLIST = TStrings;
{$ENDIF}

TProgressCallback = function (ATotalSize, ATotalRead, AStartTime: DWORD): Boolean;

function DownloadToFile(const AURL: String; const AFilename: String;
  const  AAgent: String = '';
  const AHeaders: _STRLIST = nil;
  const ACallback: TProgressCallback = nil
  ) : LongInt;

function DownloadToStream(AURL: String; AStream: _STREAM;
  const  AAgent: String = '';
  const AHeaders: _STRLIST = nil;
  const ACallback: TProgressCallback = nil
  ) : LongInt;

implementation

function DownloadToFile(const AURL: String; const AFilename: String;
  const  AAgent: String = '';
  const AHeaders: _STRLIST = nil;
  const ACallback: TProgressCallback = nil
  ) : LongInt;
var
  FStream: _STREAM;
begin
  {$IFDEF KOL}
//    fStream := NewFileStream(AFilename, ofCreateNew or ofOpenWrite);
//    fStream := NewWriteFileStream(AFilename);
    fStream := NewMemoryStream;
  {$ELSE}
    fStream := TFileStream.Create(AFilename, fmCreate);
//    _STRLIST = TStrings;
  {$ENDIF}
  try
    Result := DownloadToStream(AURL, FStream, AAgent, AHeaders, ACallback);
    fStream.SaveToFile(AFilename, 0, fStream.Size);
  finally
    fStream.Free;
  end;
end;

function StrToIntDef(const S: string; Default: Integer): Integer;
var
  E: Integer;
begin
  Val(S, Result, E);
  if E <> 0 then Result := Default;
end;

function DownloadToStream(AURL: String; AStream: _STREAM;
  const  AAgent: String = '';
  const AHeaders: _STRLIST = nil;
  const ACallback: TProgressCallback = nil
  ) : LongInt;

  function _HttpQueryInfo(AFile: HINTERNET; AInfo: DWORD): string;
  var
    infoBuffer: PChar;
    dummy: DWORD;
    err, bufLen: DWORD;
    res: LongBool;
  begin
    Result := '';
    bufLen := 0;
    dummy := 0;
    infoBuffer := nil;
    res := HttpQueryInfo(AFile, AInfo, infoBuffer, bufLen, dummy);
    if not res then
    begin
      // Probably working offline, or no internet connection.
      err := GetLastError;
      if err = ERROR_HTTP_HEADER_NOT_FOUND then
      begin
        // No headers
      end else if err = ERROR_INSUFFICIENT_BUFFER then
      begin
        GetMem(infoBuffer, bufLen);
        try
          HttpQueryInfo(AFile, AInfo, infoBuffer, bufLen, dummy);
          Result := infoBuffer;
        finally
          FreeMem(infoBuffer);
        end;
      end;
    end;
  end;

  procedure ParseHeaders;
  begin

  end;


const
  BUFFER_SIZE = 16184;
var
  buffer: array[1..BUFFER_SIZE] of byte;
  Totalbytes, Totalread, bytesRead, StartTime: DWORD;
  hInet: HINTERNET;
  reply: String;
  hFile: HINTERNET;
begin
  Totalread := 0;
  Result := 0;
  hInet := InternetOpen(PChar(AAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil,nil,0);
  if hInet = nil then Exit;

  try
    hFile := InternetOpenURL(hInet, PChar(AURL), nil, 0, 0, 0);
    if hFile = nil then Exit;
    StartTime := GetTickCount;
    try
      if AHeaders <> nil then
      begin
        AHeaders.Text := _HttpQueryInfo(hFile, HTTP_QUERY_RAW_HEADERS_CRLF);
        ParseHeaders;
      end;

      Totalbytes := StrToIntDef(_HttpQueryInfo(hFile,
        HTTP_QUERY_CONTENT_LENGTH), 0);

      reply := _HttpQueryInfo(hFile, HTTP_QUERY_STATUS_CODE);
      if reply = '200' then
        // File exists, all ok.
        result := 200
      else if reply = '401' then
        // Not authorised. Assume page exists,
        // but we can't check it.
        result := 401
      else if reply = '404' then
        // No such file.
        result := 404
      else if reply = '500' then
        // Internal server error.
        result := 500
      else
        Result := StrToIntDef(reply, 0);

      repeat
        InternetReadFile(hFile, @buffer, SizeOf(buffer), bytesRead);
        if bytesRead > 0 then
        begin
          AStream.Write(buffer, bytesRead);
          Inc(Totalread, bytesRead);
          if Assigned(ACallback) then
          begin
            if not ACallback(TotalBytes, Totalread, StartTime) then Break;
          end;
          Sleep(10);
        end;
    //    BlockWrite(localFile, buffer, bytesRead);
      until bytesRead = 0;

    finally
      InternetCloseHandle(hFile);
    end;
  finally
    InternetCloseHandle(hInet);
  end;
end;


end.
1
votes

This uses the clever internet suite to handle the download, I haven't so much as checked it in the IDE so I wouldn't expect it to compile and no doubt it's full of errors but it should be enough to get you started.

I don't know why you don't want to use Indy but I would strongly advise getting some components to help with the Http download... there really is no need to reinvent the wheel.

interface
type
    TMyDownloadThread= Class(TThread)
    private
        FUrl: String;
        FFileName: String;
        FProgressHandle: HWND;
        procedure GetFile (Url: String; Stream: TStream; ReceiveProgress: TclSocketProgressEvent);
        procedure OnReceiveProgress(Sender: TObject; ABytesProceed, ATotalBytes: Integer);
        procedure SetPercent(Percent: Double);
    protected
        Procedure Execute; Override;
    public
        Constructor Create(Url, FileName: String; PrograssHandle: HWND);
    End;

implementation

constructor TMyDownloadThread.Create(Url, FileName: String; PrograssHandle: HWND);
begin
    Inherited Create(True);
    FUrl:= Url;
    FFileName:= FileName;
    FProgressHandle:= PrograssHandle;
    Resume;
end;


procedure TMyDownloadThread.GetFile(Url: String; Stream: TStream; ReceiveProgress: TclSocketProgressEvent);
var
    Http: TclHttp;
begin
    Http := TclHTTP.Create(nil);
    try
        try
            Http.OnReceiveProgress := ReceiveProgress;
            Http.Get(Url, Stream);
        except
        end;
    finally
        Http.Free;
    end;
end;

procedure TMyDownloadThread.OnReceiveProgress(Sender: TObject; ABytesProceed, ATotalBytes: Integer);
begin
    SetPercent((ABytesProceed / ATotalBytes) * 100);
end;

procedure TMyDownloadThread.SetPercent(Percent: Double);
begin
    PostMessage(FProgressHandle, AM_DownloadPercent, LowBytes(Percent), HighBytes(Percent));
end;

procedure TMyDownloadThread.Execute;
var
    FileStream: TFileStream;
begin
    FileStream := TFileStream.Create(FFileName, fmCreate);
    try
        GetFile(FUrl, FileStream, OnReceiveProgress);
    finally
        FileStream.Free;
    end;        
end;