2012-11-30 33 views
19

Tôi muốn triển khai trình tải xuống http đơn giản bằng TIdHttp (Indy10). Tôi tìm thấy hai loại ví dụ mã từ internet. Thật không may, không ai trong số họ thỏa mãn tôi 100%. Đây là mã và tôi muốn một số lời khuyên.Tải tệp xuống dần dần bằng TIdHttp


Variant 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; 

Mã này là nhỏ gọn và rất dễ hiểu. Vấn đề là nó phân bổ không gian đĩa khi tải xuống bắt đầu. Một vấn đề khác là chúng ta không thể hiển thị tiến trình tải xuống trong GUI trực tiếp, trừ khi mã được thực hiện trong một luồng nền (theo cách khác chúng ta có thể ràng buộc sự kiện HttpClient.OnWork).


Biến thể 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; 

Đầu tiên chúng ta truy vấn kích thước tập tin từ máy chủ và sau đó chúng tôi tải nội dung tập tin trong miếng. Nội dung tập tin được tải sẽ được lưu vào đĩa khi chúng được nhận hoàn toàn. Vấn đề tiềm năng là chúng ta phải gửi nhiều yêu cầu GET đến máy chủ. Tôi không chắc chắn nếu một số máy chủ (chẳng hạn như megaupload) có thể giới hạn số lượng yêu cầu trong khoảng thời gian cụ thể.


sự mong đợi của tôi

  1. Các downloader nên chỉ gửi một GET-yêu cầu đến máy chủ.
  2. Không gian đĩa phải được cấp phát khi quá trình tải xuống bắt đầu.

Bất kỳ gợi ý nào được đánh giá cao.

+4

Nếu bạn muốn có một bộ nhớ đệm 'TFileStream', nhìn vào Đóng góp của David ở đây: [Các tệp đệm (để truy cập đĩa nhanh hơn)] (http://stackoverflow.com/a/5639712/576719). –

Trả lời

26

Biến thể số 1 là đơn giản nhất và cách Indy được sử dụng.

Về vấn đề phân bổ đĩa, bạn có thể lấy được một lớp mới từ TFileStream và ghi đè phương thức SetSize() của mình để không làm gì cả. TIdHTTP sẽ vẫn cố gắng phân bổ trước tệp khi thích hợp, nhưng nó sẽ không thực sự phân bổ bất kỳ dung lượng đĩa nào. Viết đến TFileStream sẽ phát triển tệp nếu cần.

Về báo cáo trạng thái, TIdHTTPOnWork... sự kiện cho mục đích đó. Tham số AWorkCountMax của OnWorkBegin sẽ là kích thước tệp thực nếu được biết (phản hồi không được chunked) hoặc 0 nếu không biết. Thông số AWorkCount của sự kiện OnWork sẽ là số lượng byte tích lũy đã được chuyển cho đến thời điểm này.Nếu kích thước tệp được biết, bạn có thể hiển thị tổng tỷ lệ phần trăm bằng cách chỉ cần chia AWorkCount cho số AWorkCountMax và nhân với 100, nếu không chỉ hiển thị giá trị AWorkCount. Nếu bạn muốn hiển thị tốc độ truyền, bạn có thể tính toán điều đó từ sự khác biệt của các giá trị AWorkCount và khoảng thời gian giữa nhiều sự kiện OnWork.

Hãy thử điều này:

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

Chỉ cần một cái gì đó không liên quan nhưng tôi thấy trong mã của bạn: Bạn sử dụng Ticks để tính toán khoảng thời gian. Nó không phải là vấn đề lớn trong ví dụ này. Nhưng tôi đề nghị sử dụng TDateTime để đại diện cho StartTime và StopTime và sử dụng TTimeSpan.Subtract (StopTime, StartTime) để tính toán khoảng thời gian. Bởi vì MSDN cho biết các ve sẽ được đặt lại nếu hệ thống chạy liên tục trong 49,7 ngày. Nếu ứng dụng của bạn chạy trên máy chủ, thời lượng có thể được tính sai. – stanleyxu2005

+6

Tôi cố ý chọn không sử dụng 'TDateTime' vì tôi không muốn mã bị ảnh hưởng bởi các thay đổi đồng hồ có thể (tiết kiệm ánh sáng ban ngày, thao tác người dùng, v.v.). Ngoài ra, mã là thời gian các khoảng thời gian xảy ra giữa các sự kiện, mà sẽ không bao giờ đến gần giới hạn 49 ngày của 'LongWord'. Các tài khoản 'GetTickDiff()' cho lớp bọc xung quanh xảy ra bất cứ khi nào 'GetTickCount()' kết thúc tốt đẹp về 0, do đó, đó không phải là vấn đề. –

+3

@RemyLebeau Giống như tôi đã nói hôm qua tôi đã thử Indy một lần nữa, lần này phiên bản 10 và tôi cảm thấy như v10 thực sự tốt hơn nhiều so với 9 và hoạt động độc đáo. Tôi đã tiếp tục, đăng nhập qua HTTP POST và tôi đã sử dụng các ý tưởng từ mã của bạn ở trên để thực hiện một số thống kê để có - Indy rất tuyệt vời, cảm ơn người đàn ông! – Tom

4

Dưới đây là một ví dụ cho thấy làm thế nào để sử dụng các thành phần OnWork để hiển thị một thanh tiến trình:

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

Bạn không nên lo lắng về việc phân bổ đĩa. Không gian đĩa được cấp phát không thực sự được ghi vào, vì vậy nó sẽ không làm hỏng đĩa của bạn. Hãy vui vẻ rằng nó được phân bổ để không thể có một quy trình khác tuyên bố không gian đĩa và cho phép bạn hết dung lượng!

2

Đừng quên để thêm video này cho Variant 2

: Else HttpClient.Request.ContentRangeEnd := FileSize; 

Replace

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

By

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

Cảm ơn, bạn đã đúng. Mã được cập nhật. – stanleyxu2005

Các vấn đề liên quan