使用TIdHttp逐步下载文件 [英] Download file progressively using TIdHttp
问题描述
我想使用TIdHttp(Indy10)实现一个简单的http下载。我从互联网找到了两种代码示例。不幸的是,他们都不满足于100%。这是代码,我想要一些建议。
I want to implement a simple http downloader using TIdHttp (Indy10). I found two kind of code examples from the internet. Unfortunately none of them satisfy me 100%. Here is the code and I want some advise.
变式1
var
Buffer: TFileStream;
HttpClient: TIdHttp;
begin
Buffer := TFileStream.Create('somefile.exe', fmCreate or fmShareDenyWrite);
try
HttpClient := TIdHttp.Create(nil);
try
HttpClient.Get('http://somewhere.com/somefile.exe', Buffer); // wait until it is done
finally
HttpClient.Free;
end;
finally
Buffer.Free;
end;
end;
代码紧凑,非常容易理解。问题是它在下载开始时分配磁盘空间。另一个问题是我们无法直接在GUI中显示下载进度,除非代码在后台线程中执行(或者我们可以绑定HttpClient.OnWork事件)。
The code is compact and very easy to understand. The problem is that it allocates disk space when downloading begins. Another problem is that we cannot show the download progress in GUI directly, unless the code is executed in a background thread (alternatively we can bind HttpClient.OnWork event).
变式2:
const
RECV_BUFFER_SIZE = 32768;
var
HttpClient: TIdHttp;
FileSize: Int64;
Buffer: TMemoryStream;
begin
HttpClient := TIdHttp.Create(nil);
try
HttpClient.Head('http://somewhere.com/somefile.exe');
FileSize := HttpClient.Response.ContentLength;
Buffer := TMemoryStream.Create;
try
while Buffer.Size < FileSize do
begin
HttpClient.Request.ContentRangeStart := Buffer.Size;
if Buffer.Size + RECV_BUFFER_SIZE < FileSize then
HttpClient.Request.ContentRangeEnd := Buffer.Size + RECV_BUFFER_SIZE - 1
else
HttpClient.Request.ContentRangeEnd := FileSize;
HttpClient.Get(HttpClient.URL.URI, Buffer); // wait until it is done
Buffer.SaveToFile('somefile.exe');
end;
finally
Buffer.Free;
end;
finally
HttpClient.Free;
end;
end;
首先,我们从服务器查询文件大小,然后下载文件内容。检索到的文件内容将被完全收到磁盘上。潜在的问题是我们必须向服务器发送多个GET请求。我不确定某些服务器(如megaupload)是否可能限制特定时间段内的请求数。
First we query the file size from the server and then we download file contents in pieces. Retrieved file contents will be save to disk when they are received completely. The potential problem is we have to send multiple GET requests to the server. I am not sure if some servers (such as megaupload) might limit the number of requests within particular time period.
我的预期
- 下载程序只能向服务器发送一个GET请求。
- 下载开始时不能分配磁盘空间。
赞赏任何提示。 >
Any hints are appreciated.
推荐答案
变体#1是最简单的,就是Indy是如何使用的。
Variant #1 is the simpliest, and is how Indy is meant to be used.
关于磁盘分配问题,您可以从 TFileStream
导出一个新类,并覆盖其 SetSize()
方法什么都不做。 TIdHTTP
仍然会尝试在适当的时候预分配文件,但实际上并不会分配任何磁盘空间。写入 TFileStream
将根据需要增长文件。
Regarding the disk allocation issue, you can derive a new class from TFileStream
and override its SetSize()
method to do nothing. TIdHTTP
will still attempt to pre-allocate the file when appropriate, but it will not actually allocate any disk space. Writing to TFileStream
will grow the file as needed.
关于状态报告, TIdHTTP
有 OnWork ...
为此目的的事件。 AWorkCountMax
参数 OnWorkBegin
将是实际的文件大小(如果已知(响应未分块))或0如果不知道。 OnWork
事件的 AWorkCount
参数将是到目前为止传输的累计字节数。如果文件大小已知,您可以通过简单地将 AWorkCount
除以 AWorkCountMax
并乘以100,否则只能显示 AWorkCount
值。如果要显示传输速度,可以从 AWorkCount
值的差值和多个 OnWork
事件
Regarding status reporting, TIdHTTP
has OnWork...
events for that purpose. The AWorkCountMax
parameter of the OnWorkBegin
will be the actual file size if known (the response is not chunked), or 0 if not known. The AWorkCount
parameter of the OnWork
event will be the cumulative number of bytes that have been transferred so far. If the file size is known, you can display the total percentage by simply dividing the AWorkCount
by the AWorkCountMax
and multiplying by 100, otherwise just display the AWorkCount
value by itself. If you want to display the speed of the transfer, you can calculate that from the difference of AWorkCount
values and the time intervals between multiple OnWork
events.
尝试这样:
type
TNoPresizeFileStream = class(TFileStream)
procedure
procedure SetSize(const NewSize: Int64); override;
end;
procedure TNoPresizeFileStream.SetSize(const NewSize: Int64);
begin
end;
。
type
TSomeClass = class(TSomething)
...
TotalBytes: In64;
LastWorkCount: Int64;
LastTicks: LongWord;
procedure Download;
procedure HttpWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
procedure HttpWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure HttpWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
...
end;
procedure TSomeClass.Download;
var
Buffer: TNoPresizeFileStream;
HttpClient: TIdHttp;
begin
Buffer := TNoPresizeFileStream.Create('somefile.exe', fmCreate or fmShareDenyWrite);
try
HttpClient := TIdHttp.Create(nil);
try
HttpClient.OnWorkBegin := HttpWorkBegin;
HttpClient.OnWork := HttpWork;
HttpClient.OnWorkEnd := HttpWorkEnd;
HttpClient.Get('http://somewhere.com/somefile.exe', Buffer); // wait until it is done
finally
HttpClient.Free;
end;
finally
Buffer.Free;
end;
end;
procedure TSomeClass.HttpWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
if AWorkMode <> wmRead then Exit;
// initialize the status UI as needed...
//
// If TIdHTTP is running in the main thread, update your UI
// components directly as needed and then call the Form's
// Update() method to perform a repaint, or Application.ProcessMessages()
// to process other UI operations, like button presses (for
// cancelling the download, for instance).
//
// If TIdHTTP is running in a worker thread, use the TIdNotify
// or TIdSync class to update the UI components as needed, and
// let the OS dispatch repaints and other messages normally...
TotalBytes := AWorkCountMax;
LastWorkCount := 0;
LastTicks := Ticks;
end;
procedure TSomeClass.HttpWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
var
PercentDone: Integer;
ElapsedMS: LongWord;
BytesTransferred: Int64;
BytesPerSec: Int64;
begin
if AWorkMode <> wmRead then Exit;
ElapsedMS := GetTickDiff(LastTicks, Ticks);
if ElapsedMS = 0 then ElapsedMS := 1; // avoid EDivByZero error
if TotalBytes > 0 then
PercentDone := (Double(AWorkCount) / TotalBytes) * 100.0;
else
PercentDone := 0.0;
BytesTransferred := AWorkCount - LastWorkCount;
// using just BytesTransferred and ElapsedMS, you can calculate
// all kinds of speed stats - b/kb/mb/gm per sec/min/hr/day ...
BytesPerSec := (Double(BytesTransferred) * 1000) / ElapsedMS;
// update the status UI as needed...
LastWorkCount := AWorkCount;
LastTicks := Ticks;
end;
procedure TSomeClass.HttpWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
if AWorkMode <> wmRead then Exit;
// finalize the status UI as needed...
end;
这篇关于使用TIdHttp逐步下载文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!