2012-02-06 26 views
9

Tôi nhận được nguồn bên dưới từ trang web của bên thứ ba giải thích cách tải xuống tệp từ internet bằng cách sử dụng WinInet. Tôi không quá quen thuộc với API, và tôi đã nhìn vào đơn vị WinInet nhưng không thấy bất kỳ cuộc gọi API nào giống như những gì tôi cần.Sử dụng WinInet để xác định tổng kích cỡ tệp trước khi tải xuống

Điều tôi đang làm là thêm khả năng báo cáo tiến trình tải xuống tệp. Thủ tục này tôi đã bọc bên trong của TThread và mọi thứ hoạt động tốt. Tuy nhiên, chỉ một phần bị thiếu: Tìm tổng kích thước của tệp nguồn trước khi tải xuống.

Xem bên dưới nơi tôi có nhận xét //HOW TO GET TOTAL SIZE? Đây là nơi tôi cần tìm hiểu tổng kích thước của tệp TRƯỚC KHI tôi bắt đầu tải xuống tệp đó là gì. Làm thế nào để tôi làm việc này? Bởi vì mã này có vẻ như không biết kích thước tệp cho đến khi tệp được tải xuống xong - điều đó làm cho việc thêm này không liên quan.

procedure TInetThread.Execute; 
const 
    BufferSize = 1024; 
var 
    hSession, hURL: HInternet; 
    Buffer: array[1..BufferSize] of Byte; 
    BufferLen: DWORD; 
    f: File; 
    S: Bool; 
    D: Integer; 
    T: Integer; 
    procedure DoWork(const Amt: Integer); 
    begin 
    if assigned(FOnWork) then 
     FOnWork(Self, FSource, FDest, Amt, T); 
    end; 
begin 
    S:= False; 
    try 
    try 
     if not DirectoryExists(ExtractFilePath(FDest)) then begin 
     ForceDirectories(ExtractFilePath(FDest)); 
     end; 
     hSession:= InternetOpen(PChar(FAppName), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); 
     try 
     hURL:= InternetOpenURL(hSession, PChar(FSource), nil, 0, 0, 0); 
     try 
      AssignFile(f, FDest); 
      Rewrite(f, 1); 
      T:= 0; //HOW TO GET TOTAL SIZE? 
      D:= 0; 
      DoWork(D); 
      repeat 
      InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen); 
      BlockWrite(f, Buffer, BufferLen); 
      D:= D + BufferLen; 
      DoWork(D); 
      until BufferLen = 0; 
      CloseFile(f); 
      S:= True; 
     finally 
      InternetCloseHandle(hURL); 
     end 
     finally 
     InternetCloseHandle(hSession); 
     end; 
    except 
     on e: exception do begin 
     S:= False; 
     end; 
    end; 
    finally 
    if assigned(FOnComplete) then 
     FOnComplete(Self, FSource, FDest, S); 
    end; 
end; 
+3

tôi thực hiện chỉ là một tính năng như vậy và thấy rằng việc sử dụng WinInet gây ra oai "timeout lỗi" xảy ra trong ứng dụng của tôi. Http-Head yêu cầu thường mất 100 mSec, mất đến 15 giây để quay trở lại. Đó là một vấn đề được biết đến trong việc gọi WinInet từ Delphi trên một số phiên bản của Windows/WinInet. Tôi đang đề cập đến điều này trong trường hợp bạn gặp phải những trục trặc kỳ lạ như vậy. Nếu bạn có thể đi Indy ở đây hoặc một cái gì đó không phải WinInet (như WinHttp), hãy xem xét nó! :-) –

+0

'..Đó là một vấn đề đã biết khi gọi WinInet từ Delphi trên một số phiên bản Windows/WinInet' @WarrenP Tôi chưa bao giờ gặp vấn đề này khi sử dụng WinInet từ delphi.Bạn có thể chỉ ra một số tài liệu hoặc liên kết về chủ đề này không? – RRUZ

+0

Đây là một liên kết: http://jgobserve.blogspot.com/2009/03/wininet-timeout-issue-and-solution.html - Quan sát của tôi là các vấn đề không bị giới hạn trong thực tế là khi mạng cơ bản bị lỗi , bạn sẽ chờ đợi lâu. Đôi khi tất cả mọi thứ xuất hiện tốt WINCnet EXCEPT có thời gian chờ mà tôi không thể giải thích khác. Mã tôi đã viết bằng Python, hoặc trong Delphi sử dụng INDY hoặc ICS KHÔNG biểu hiện cùng một mẫu lỗi. –

Trả lời

16

Bạn có thể sử dụng phương thức HEAD và kiểm tra Content-Length để lấy kích thước tập tin của một tập tin từ xa

Kiểm tra hai phương pháp này

WinInet

Nếu bạn muốn thực hiện một Phương thức HEAD, bạn phải sử dụng các hàm HttpOpenRequest, HttpSendRequestHttpQueryInfo WinInet.

uses 
SysUtils, 
Windows, 
WinInet; 

function GetWinInetError(ErrorCode:Cardinal): string; 
const 
    winetdll = 'wininet.dll'; 
var 
    Len: Integer; 
    Buffer: PChar; 
begin 
    Len := FormatMessage(
    FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_FROM_SYSTEM or 
    FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY, 
    Pointer(GetModuleHandle(winetdll)), ErrorCode, 0, @Buffer, SizeOf(Buffer), nil); 
    try 
    while (Len > 0) and {$IFDEF UNICODE}(CharInSet(Buffer[Len - 1], [#0..#32, '.'])) {$ELSE}(Buffer[Len - 1] in [#0..#32, '.']) {$ENDIF} do Dec(Len); 
    SetString(Result, Buffer, Len); 
    finally 
    LocalFree(HLOCAL(Buffer)); 
    end; 
end; 


procedure ParseURL(const lpszUrl: string; var Host, Resource: string); 
var 
    lpszScheme  : array[0..INTERNET_MAX_SCHEME_LENGTH - 1] of Char; 
    lpszHostName : array[0..INTERNET_MAX_HOST_NAME_LENGTH - 1] of Char; 
    lpszUserName : array[0..INTERNET_MAX_USER_NAME_LENGTH - 1] of Char; 
    lpszPassword : array[0..INTERNET_MAX_PASSWORD_LENGTH - 1] of Char; 
    lpszUrlPath  : array[0..INTERNET_MAX_PATH_LENGTH - 1] of Char; 
    lpszExtraInfo : array[0..1024 - 1] of Char; 
    lpUrlComponents : TURLComponents; 
begin 
    ZeroMemory(@lpszScheme, SizeOf(lpszScheme)); 
    ZeroMemory(@lpszHostName, SizeOf(lpszHostName)); 
    ZeroMemory(@lpszUserName, SizeOf(lpszUserName)); 
    ZeroMemory(@lpszPassword, SizeOf(lpszPassword)); 
    ZeroMemory(@lpszUrlPath, SizeOf(lpszUrlPath)); 
    ZeroMemory(@lpszExtraInfo, SizeOf(lpszExtraInfo)); 
    ZeroMemory(@lpUrlComponents, SizeOf(TURLComponents)); 

    lpUrlComponents.dwStructSize  := SizeOf(TURLComponents); 
    lpUrlComponents.lpszScheme  := lpszScheme; 
    lpUrlComponents.dwSchemeLength := SizeOf(lpszScheme); 
    lpUrlComponents.lpszHostName  := lpszHostName; 
    lpUrlComponents.dwHostNameLength := SizeOf(lpszHostName); 
    lpUrlComponents.lpszUserName  := lpszUserName; 
    lpUrlComponents.dwUserNameLength := SizeOf(lpszUserName); 
    lpUrlComponents.lpszPassword  := lpszPassword; 
    lpUrlComponents.dwPasswordLength := SizeOf(lpszPassword); 
    lpUrlComponents.lpszUrlPath  := lpszUrlPath; 
    lpUrlComponents.dwUrlPathLength := SizeOf(lpszUrlPath); 
    lpUrlComponents.lpszExtraInfo  := lpszExtraInfo; 
    lpUrlComponents.dwExtraInfoLength := SizeOf(lpszExtraInfo); 

    InternetCrackUrl(PChar(lpszUrl), Length(lpszUrl), ICU_DECODE or ICU_ESCAPE, lpUrlComponents); 

    Host := lpszHostName; 
    Resource := lpszUrlPath; 
end; 

function GetRemoteFileSize(const Url : string): Integer; 
const 
    sUserAgent = 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101'; 

var 
    hInet : HINTERNET; 
    hConnect : HINTERNET; 
    hRequest : HINTERNET; 
    lpdwBufferLength: DWORD; 
    lpdwReserved : DWORD; 
    ServerName: string; 
    Resource: string; 
    ErrorCode : Cardinal; 
begin 
    ParseURL(Url,ServerName,Resource); 
    Result:=0; 

    hInet := InternetOpen(PChar(sUserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); 
    if hInet=nil then 
    begin 
    ErrorCode:=GetLastError; 
    raise Exception.Create(Format('InternetOpen Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
    end; 

    try 
    hConnect := InternetConnect(hInet, PChar(ServerName), INTERNET_DEFAULT_HTTP_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0); 
    if hConnect=nil then 
    begin 
     ErrorCode:=GetLastError; 
     raise Exception.Create(Format('InternetConnect Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
    end; 

    try 
     hRequest := HttpOpenRequest(hConnect, PChar('HEAD'), PChar(Resource), nil, nil, nil, 0, 0); 
     if hRequest<>nil then 
     begin 
      try 
      lpdwBufferLength:=SizeOf(Result); 
      lpdwReserved :=0; 
      if not HttpSendRequest(hRequest, nil, 0, nil, 0) then 
      begin 
       ErrorCode:=GetLastError; 
       raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
      end; 

      if not HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, @Result, lpdwBufferLength, lpdwReserved) then 
      begin 
       Result:=0; 
       ErrorCode:=GetLastError; 
       raise Exception.Create(Format('HttpQueryInfo Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
      end; 
      finally 
      InternetCloseHandle(hRequest); 
      end; 
     end 
     else 
     begin 
      ErrorCode:=GetLastError; 
      raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
     end; 
    finally 
     InternetCloseHandle(hConnect); 
    end; 
    finally 
    InternetCloseHandle(hInet); 
    end; 

end; 

Indy

Ngoài ra kiểm tra mã này sử dụng indy.

function GetRemoteFilesize(const Url :string) : Integer; 
var 
    Http: TIdHTTP; 
begin 
    Http := TIdHTTP.Create(nil); 
    try 
    Http.Head(Url); 
    result:= Http.Response.ContentLength; 
    finally 
    Http.Free; 
    end; 
end; 
+2

+1 Điểm được sử dụng, tôi nên sử dụng Indy thay thế: D Đơn giản chỉ cần để mã sạch –

+3

Nếu bạn biết * bạn sẽ tải xuống tài nguyên, bạn không thể gửi yêu cầu GET và đọc tiêu đề Nội dung dài từ thay vào đó? Nó sẽ giúp bạn tiết kiệm thêm một kết nối HTTP. –

+6

@RobKennedy - vâng, bạn có thể, miễn là dữ liệu không được gửi theo khối bằng cách sử dụng tiêu đề 'Transfer-Encoding: chunked', trong trường hợp tiêu đề' Content-Length' không được sử dụng và không có cách nào để biết tổng kích thước cho đến khi đoạn cuối cùng được nhận. –

3

Trả lời câu hỏi về cách tải kích thước tải xuống bằng WinInet. Đây là một trong những trình tải xuống tệp của tôi dựa trên WinInet.

Đây là phương pháp tôi sử dụng để có được kích thước tải về:

function TWebDownloader.GetContentLength(URLHandle: HINTERNET): Int64; 
// returns the expected download size. Returns -1 if one not provided 
    var 
    SBuffer: Array[1..20] of char; 
    SBufferSize: Integer; 
    srv: integer; 
    begin 
    srv := 0; 
    SBufferSize := 20; 
    if HttpQueryInfo(URLHandle, HTTP_QUERY_CONTENT_LENGTH, @SBuffer, SBufferSize, srv) then 
     Result := StrToFloat(String(SBuffer)) 
    else 
     Result := -1; 
    end; 

Sử dụng phương pháp này đòi hỏi phải có một xử lý yêu cầu mở, và không cần đọc bất kỳ dữ liệu:

URLHandle := HttpOpenRequest(ConnectHandle, 'GET', Pchar(sitepath), nil, 
        nil, nil, INTERNET_FLAG_NO_CACHE_WRITE, 0); 
... 
DownloadSize := GetContentLength(URLHandle); 

HTH

+0

+1 Công cụ tuyệt vời và 1/6 mã như câu trả lời khác :) btw dự án tải xuống khổng lồ của bạn đến cùng như thế nào? –

+0

Tôi thực sự tò mò về cách thức hoạt động của nó, bởi vì điều kiện là phương thức loại trừ lẫn nhau) là GET b) không chuyển giao tài nguyên được yêu cầu. Tôi đoán nó đóng kết nối khi tiêu đề đã được recv'd. – OnTheFly

+0

@ JerryDodge nó đã hoàn thành đủ cho nhu cầu của tôi và tôi chuyển sang những thứ khác. Nó vẫn cần nhiều công việc làm sạch mặc dù. – Glenn1234

0

sau khi sửa các loại có vẻ tốt hơn như sau:

function GetContentLength(URLHandle:HINTERNET):Int64; 
// returns the expected download size. Returns -1 if one not provided 
var 
SBufferSize, srv:Cardinal; 
begin 
srv:=0; 
SBufferSize:=20; 
if Not HttpQueryInfo(URLHandle, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, {@SBuffer} @Result, SBufferSize, srv) then Result:=-1; 
end; 

gọi nó là:

{get the file handle} 
hURL:=InternetOpenURL(hSession, PChar(URL), nil, 0, 0, 0); 
if hURL=Nil then 
begin 
InternetCloseHandle(hSession); 
ShowMessage('The link is incorrect!'); 
exit; 
end; 
{get the file size} 
filesize:=GetContentLength(hURL); 
Các vấn đề liên quan