How to download a file from internet with progress bar using threads in Delphi 2009/10 without Indy components?
4
votes
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;