2012-02-06 14 views
9

Ho ricevuto la fonte di seguito da un sito di terze parti che spiega come scaricare un file da Internet utilizzando WinInet. Non ho molta familiarità con l'API, e ho dato un'occhiata all'unità WinInet ma non ho visto alcuna chiamata API come quello di cui ho bisogno.Utilizzo di WinInet per identificare la dimensione totale del file prima di scaricarlo

Quello che sto facendo è aggiungere la possibilità di riportare l'avanzamento del download di un file. Questa procedura ho già inserito all'interno di un TThread e tutto funziona correttamente. Tuttavia, un solo pezzo mancante: trovare la dimensione totale del file sorgente prima del download.

Vedere sotto dove ho un commento //HOW TO GET TOTAL SIZE? Questo è dove ho bisogno di scoprire qual è la dimensione totale del file PRIMA che inizi a scaricarlo. Come procedo a fare questo? Poiché questo codice sembra che non conosca la dimensione del file finché non viene scaricato, e ciò rende questa aggiunta irrilevante.

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

ho implementato solo una tale caratteristica ed ha trovato che utilizzando WinInet provoca un terrore "timeout bug" avvenga nella mia app. Le richieste Http-Head che normalmente richiedono 100 mSec, richiedevano fino a 15 secondi per la restituzione. È un problema noto chiamare WinInet da Delphi su alcune versioni di Windows/WinInet. Sto menzionando questo nel caso in cui successivamente si verificano tali anomalie strane. Se puoi andare a Indy qui o qualcosa di non WinInet (come WinHttp), ti preghiamo di prenderlo in considerazione! :-) –

+0

'..E 'un problema noto chiamare WinInet da Delphi su alcune versioni di Windows/WinInet' @WarrenP Non ho mai avuto questo problema usando WinInet da Delphi.Puoi indicare qualche documentazione o link su questo argomento? – RRUZ

+0

Ecco un link: http://jgobserve.blogspot.com/2009/03/wininet-timeout-issue-and-solution.html - Le mie osservazioni sono che i problemi non sono limitati al fatto che quando la rete sottostante fallisce , hai lunghe attese. A volte tutto sembra a posto TRANNE winInet che ha timeout che non posso altrimenti spiegare. Il codice che ho scritto in Python, o in Delphi usando INDY o ICS, NON presenta lo stesso modello di errore. –

risposta

16

È possibile utilizzare il metodo HEAD e controllare il Content-Length per recuperare la dimensione del file di un file remoto

controllare questi due metodi

WinInet

Se si desidera eseguire un Metodo HEAD è necessario utilizzare le funzioni HttpOpenRequest, HttpSendRequest e HttpQueryInfo 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

controllare anche questo codice utilizzando 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 Punto preso, dovrei usare Indy invece: D Semplicemente per codice pulito –

+3

Se * sai * stai per scaricare comunque la risorsa, non potresti semplicemente inviare una richiesta GET e leggere l'intestazione Content-Length da quello invece? Ti farebbe risparmiare una connessione HTTP extra. –

+6

@RobKennedy - sì, è possibile, a condizione che i dati non vengano inviati in blocchi usando l'intestazione 'Transfer-Encoding: chunked', nel qual caso l'intestazione 'Content-Length' non è usata, e non c'è modo per conoscere la dimensione totale fino a quando l'ultimo pezzo è stato ricevuto. –

3

Rispondere alla domanda su come ottenere una dimensione di download con WinInet. Questo è uno dei miei downloader di file basati su WinInet.

questo è il metodo che uso per ottenere la dimensione del download:

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; 

L'utilizzo di questo metodo richiede una maniglia richiesta aperta, e non richiede la lettura di qualsiasi dei dati:

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

HTH

+0

+1 Grandi cose, e 1/6 il codice come l'altra risposta :) A proposito, com'è che il tuo enorme progetto di downloader sta arrivando? –

+0

Sono davvero curioso di sapere come funziona, perché le condizioni sono mutuamente esclusive a) il metodo è GET b) nessun trasferimento della risorsa richiesta. Suppongo che chiuda la connessione quando le intestazioni sono state ripristinate. – OnTheFly

+0

@JerryDodge è finito abbastanza per i miei bisogni e sono passato ad altro. Tuttavia ha ancora bisogno di molto lavoro di pulizia. – Glenn1234

0

dopo che fissa i tipi sembra meglio così:

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; 

chiamarlo:

{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);