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リクエストを送信する必要があることです。一部のサーバー(メガアップロードなど)が特定の期間内のリクエスト数を制限する可能性があるかどうかはわかりません。
私の期待
ヒントをいただければ幸いです。
バリアント#1は最も単純であり、Indyの使用方法です。
ディスク割り当ての問題に関しては、TFileStream
から新しいクラスを派生させ、そのSetSize()
メソッドをオーバーライドして何もしないようにすることができます。 TIdHTTP
は、必要に応じてファイルの事前割り当てを試みますが、実際にはディスク領域を割り当てません。 TFileStream
に書き込むと、必要に応じてファイルが大きくなります。
ステータスレポートに関しては、TIdHTTP
にはその目的のためにOnWork...
イベントがあります。 AWorkCountMax
のOnWorkBegin
パラメータは、わかっている場合は実際のファイルサイズ(応答はチャンクされていない)、不明な場合は0になります。 AWorkCount
イベントのOnWork
パラメータは、これまでに転送された累積バイト数になります。ファイルサイズがわかっている場合は、AWorkCount
をAWorkCountMax
で割り、100を掛けるだけで合計パーセンテージを表示できます。それ以外の場合は、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;
これは、コンポーネントOnWorkを使用してプログレスバーを表示する方法を示す例です。
DelphiとIndyを使用してProgressイベントを使用してプログラムでインターネットからファイルをダウンロードします
ディスクの割り当てについて心配する必要はありません。割り当てられたディスク領域は実際には書き込まれないため、ディスクに損傷を与えることはありません。別のプロセスがディスクスペースを要求してスペースが不足する可能性がないように割り当てられていることを嬉しく思います。
バリアント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;