2012-02-06 27 views
9

Obtuve el siguiente origen de un sitio de terceros que explica cómo descargar un archivo de Internet usando WinInet. No estoy muy familiarizado con API, y eché un vistazo a la unidad WinInet, pero no vi ninguna llamada API como la que necesito.Uso de WinInet para identificar el tamaño total del archivo antes de descargarlo

Lo que estoy haciendo es agregar la capacidad de informar el progreso de la descarga de un archivo. Este procedimiento ya lo envolví dentro de un TThread y todo funciona bien. Sin embargo, solo falta una pieza: encontrar el tamaño total del archivo fuente antes de descargarlo.

Vea a continuación donde tengo un comentario //HOW TO GET TOTAL SIZE? Aquí es donde tengo que averiguar cuál es el tamaño total del archivo ANTES de comenzar a descargarlo. ¿Cómo voy a hacer esto? Debido a que este código parece no saber el tamaño del archivo hasta que se haya terminado de descargar, y eso hace que esta adición sea irrelevante.

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

he implementado sólo una característica tal y encontraron que el uso de WinInet provoca un temor "error de tiempo de espera" que suceda en mi aplicación. Las solicitudes de Http-Head que normalmente toman 100 mSec, tardaron hasta 15 segundos en regresar. Es un problema conocido al llamar a WinInet desde Delphi en algunas versiones de Windows/WinInet. Menciono esto en caso de que más tarde experimentes problemas técnicos tan extraños. Si puede ir a Indy aquí o algo que no sea WinInet (como WinHttp), ¡considérelo! :-) –

+0

'..Es un problema conocido al llamar a WinInet desde Delphi en algunas versiones de Windows/WinInet' @WarrenP Nunca tuve este problema al utilizar WinInet desde delphi.¿Puedes señalar alguna documentación o enlaces sobre este tema? – RRUZ

+0

Aquí hay un enlace: http://jgobserve.blogspot.com/2009/03/wininet-timeout-issue-and-solutions.html - Mis observaciones son que los problemas no están restringidos al hecho de que cuando falla la red subyacente , tienes largas esperas. A veces todo parece estar bien EXCEPTO winInet, que tiene tiempos de espera que de otro modo no puedo explicar. El código que escribí en Python, o en Delphi usando INDY o ICS NO exhibe el mismo patrón de falla. –

Respuesta

16

Se puede utilizar el método HEAD y comprobar el Content-Length para recuperar el tamaño de archivo de un archivo remoto

Comprobar estos dos métodos

WinInet

Si desea ejecutar una El método HEAD debe usar las funciones WinInet HttpOpenRequest, HttpSendRequest y HttpQueryInfo.

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

También puedes ver el código usando 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 tomado, debería usar Indy en su lugar: D Simplemente para limpiar el código –

+3

Si * sabes * vas a descargar el recurso de todos modos, ¿no podrías simplemente enviar una solicitud GET y leer el encabezado Content-Length de eso en cambio? Le ahorraría una conexión HTTP adicional. –

+6

@RobKennedy - sí, puede hacerlo, siempre que los datos no se envíen en fragmentos utilizando el encabezado 'Transfer-Encoding: chunked', en cuyo caso el encabezado' Content-Length' no se usa, y no hay forma de hacerlo para saber el tamaño total hasta que se haya recibido el último trozo. –

3

Respondiendo a la pregunta de cómo obtener un tamaño de descarga con WinInet. Esto está fuera de uno de mis descargadores de archivos que se basa en WinInet.

Este es el método que utilizo para obtener el tamaño de la descarga:

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; 

El uso de este método requiere un gestor de solicitud abierta, y no requiere la lectura de cualquiera de los datos:

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

HTH

+0

+1 Cosas grandiosas, y 1/6 el código como la otra respuesta :) btw ¿cómo está avanzando ese gran proyecto de descarga? –

+0

Tengo mucha curiosidad sobre cómo funciona, porque las condiciones son mutuamente excluyentes a) el método es GET b) no se transfiere el recurso solicitado. Creo que cierra la conexión cuando se han recibido los encabezados. – OnTheFly

+0

@JerryDodge está terminado lo suficiente para mis necesidades y pasé a otras cosas. Sin embargo, todavía necesita mucho trabajo de limpieza. – Glenn1234

0

después de la fijación de los tipos que se ve mejor así:

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; 

llamarlo:

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