2012-11-30 51 views
19

我想使用TIdHttp(Indy10)實現簡單的http下載程序。我從互聯網上找到了兩種代碼示例。不幸的是他們沒有一個能讓我滿意。這裏是代碼,我想要一些建議。使用TIdHttp逐步下載文件


變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事件)。


變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請求到服務器。
  2. 下載開始時,不得分配磁盤空間。

任何提示表示讚賞。

+4

如果你想有一個緩存'TFileStream',看大衛的貢獻在這裏:[緩衝文件(用於更快的磁盤訪問)](http:/ /stackoverflow.com/a/5639712/576719)。 –

回答

26

變體#1是最簡單的,並且Indy是如何使用的。

關於磁盤分配問題,您可以從TFileStream中派生一個新類,並覆蓋其方法以使其不做任何操作。 TIdHTTP仍然會在適當時嘗試預先分配文件,但它實際上不會分配任何磁盤空間。寫入TFileStream將根據需要增加文件。

關於狀態報告,TIdHTTPOnWork...事件爲此目的。如果已知(響應未分塊),則的AWorkCountMax參數將爲實際文件大小,如果未知,則參數爲0。 OnWork事件的參數AWorkCount將是到目前爲止已傳輸的累積字節數。如果文件大小已知,則可以通過將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; 
+0

只是沒有關係,但我在你的代碼中看到:你使用Ticks來計算時間跨度。這個例子中沒有什麼大不了的。但我建議使用TDateTime來表示StartTime和StopTime,並使用TTimeSpan.Subtract(StopTime,StartTime)來計算時間跨度。因爲MSDN表示如果系統連續運行49.7天,則重置將被重置。如果您的應用程序在服務器上運行,則持續時間可能計算錯誤。 – stanleyxu2005

+6

我故意選擇不使用'TDateTime',因爲我不希望受到可能的時鐘變化(夏時制,用戶操作等)影響的代碼。此外,代碼還會計算事件間發生的時間間隔,這些間隔絕不會接近「LongWord」的49.7天限制。 'GetTickDiff()'說明每當'GetTickCount()'回到零時發生的繞回,所以這不是問題。 –

+3

@RemyLebeau就像我昨天說的,我再次嘗試Indy,這次是10版,我覺得V10比9好多了,而且效果很好。我已經恢復,通過HTTP POST進行日誌記錄,並使用上述代碼中的想法來做一些統計,所以是的 - Indy很棒,謝謝你! – Tom

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; 
+0

謝謝,你是對的。代碼已更新。 – stanleyxu2005