20

TIdHttp(Indy10)を使用して単純なhttpダウンローダーを実装したいと思います。インターネットから2種類のコード例を見つけました。残念ながら、それらのどれも私を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;

コードはコンパクトで非常に理解しやすいです。問題は、ダウンロードの開始時にディスク領域が割り当てられることです。もう1つの問題は、コードがバックグラウンドスレッドで実行されない限り、GUIでダウンロードの進行状況を直接表示できないことです(または、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など)が特定の期間内のリクエスト数を制限する可能性があるかどうかはわかりません。


私の期待

  1. ダウンローダーは、サーバーにGETリクエストを1つだけ送信する必要があります。
  2. ダウンロードの開始時にディスク容量を割り当てないでください。

ヒントは大歓迎です。

4

3 に答える 3

27

バリアント#1は最も単純であり、Indyの使用方法です。

ディスク割り当ての問題に関しては、から新しいクラスを派生させTFileStream、そのメソッドをオーバーライドしSetSize()て何もしないようにすることができます。TIdHTTP必要に応じてファイルの事前割り当てを試みますが、実際にはディスク領域を割り当てません。に書き込むとTFileStream、必要に応じてファイルが大きくなります。

ステータスレポートに関してTIdHTTPOnWork...、そのためのイベントがあります。のAWorkCountMaxパラメータは、OnWorkBegin既知の場合は実際のファイルサイズ(応答はチャンク化されていない)、不明の場合は0になります。イベントのAWorkCountパラメータは、OnWorkこれまでに転送された累積バイト数になります。ファイルサイズがわかっている場合は、をで除算し、100を掛けるだけで合計パーセンテージを表示できますAWorkCountAWorkCountMaxそれ以外の場合は、AWorkCount値だけを表示します。転送速度を表示したい場合は、値の差と複数のイベントAWorkCount間の時間間隔からそれを計算できます。OnWork

これを試して:

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;
于 2012-12-01T01:43:37.267 に答える
4

コンポーネントOnWorkを使用してプログレスバーを表示する方法を示す例を次に示します。

DelphiとIndyを使用して、Progressイベントを使用してプログラムでインターネットからファイルをダウンロードします。

ディスクの割り当てについて心配する必要はありません。割り当てられたディスク領域は実際には書き込まれないため、ディスクに損傷を与えることはありません。別のプロセスがディスク容量を要求して容量を使い果たしてしまうことがないように割り当てられていることを嬉しく思います。

于 2012-11-30T08:39:42.350 に答える
2

バリアント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;
于 2013-12-24T15:21:12.820 に答える