首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用TIdHttp渐进式下载文件

使用TIdHttp渐进式下载文件
EN

Stack Overflow用户
提问于 2012-11-30 15:53:12
回答 3查看 15.4K关注 0票数 20

我想用TIdHttp (Indy10)实现一个简单的http下载器。我在网上找到了两种代码示例。不幸的是,没有一个能让我100%满意。这是代码,我需要一些建议。

变体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;

代码很紧凑,很容易理解。问题是它会在下载开始时分配磁盘空间。另一个问题是,我们不能在图形用户界面中直接显示下载进度,除非代码在后台线程中执行(或者我们可以绑定HttpClient.OnWork事件)。

变体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)是否会在特定时间段内限制请求的数量。

My expectations

  1. 下载程序应该只向服务器发送一个GET-request。
  2. 下载开始时不能分配磁盘空间。

任何提示都是值得感谢的。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-12-01 09:43:37

变体#1是最简单的,也是如何使用Indy的。

关于磁盘分配问题,您可以从TFileStream派生一个新类,并覆盖它的SetSize()方法而不执行任何操作。TIdHTTP仍会在适当的时候尝试预分配文件,但不会实际分配任何磁盘空间。写入TFileStream将根据需要增大文件。

关于状态报告,TIdHTTP提供了用于此目的的OnWork...事件。如果已知(响应未分块),OnWorkBeginAWorkCountMax参数将为实际文件大小;如果未知,则为0。OnWork事件的AWorkCount参数将是到目前为止已传输的累积字节数。如果文件大小已知,您可以通过简单地将AWorkCount除以AWorkCountMax并乘以100来显示总百分比,否则仅显示AWorkCount值本身。如果要显示传输速度,可以根据多个OnWork事件之间的AWorkCount值和时间间隔的差异来计算。

试试这个:

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;
票数 27
EN

Stack Overflow用户

发布于 2012-11-30 16:39:42

下面的示例展示了如何使用components OnWork显示进度条:

Download a File from internet programatically with an Progress event using Delphi and Indy

您不必担心磁盘分配的问题。分配的磁盘空间实际上并未写入,因此不会损坏您的磁盘。值得庆幸的是,它被分配了,这样就不会有另一个进程占用磁盘空间并让您耗尽空间!

票数 4
EN

Stack Overflow用户

发布于 2013-12-24 23:21:13

不要忘记为变体2添加以下内容

 : Else HttpClient.Request.ContentRangeEnd := FileSize;

替换

   if Buffer.Size + RECV_BUFFER_SIZE < FileSize then
  HttpClient.Request.ContentRangeEnd := Buffer.Size + RECV_BUFFER_SIZE - 1;

通过

   if Buffer.Size + RECV_BUFFER_SIZE < FileSize then
  HttpClient.Request.ContentRangeEnd := Buffer.Size + RECV_BUFFER_SIZE - 1;
   Else HttpClient.Request.ContentRangeEnd := FileSize;
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13641055

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档