2012-02-06 15 views
9

Dostałem poniższe źródło z witryny innej firmy wyjaśniającej, jak pobrać plik z Internetu, używając WinInet. Nie jestem zbyt obeznany z interfejsem API i przyjrzałem się jednostce WinInet, ale nie widziałem żadnych wywołań API, takich jak to, czego potrzebuję.Używanie WinInet do określenia całkowitego rozmiaru pliku przed jego pobraniem.

Co robię, to dodawanie możliwości zgłaszania postępu pobierania pliku. Ta procedura już zawinęłam wewnątrz TThread i wszystko działa dobrze. Jednak tylko jeden brakujący element: Znalezienie całkowitego rozmiaru pliku źródłowego przed pobraniem.

Zobacz poniżej, gdzie mam komentarz //HOW TO GET TOTAL SIZE? Tutaj muszę się dowiedzieć, jaki jest całkowity rozmiar pliku PRZED rozpoczęciem pobierania go. Jak mam to zrobić? Ponieważ kod ten wydaje się nie wiem rozmiar pliku, dopóki nie zostanie pobrany - i to sprawia, że ​​ten dodatek nie ma znaczenia.

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

I wdrożone właśnie taką funkcję, a okazało się, że za pomocą WinInet powoduje strach "Timeout bug" wydarzy się w moim app. Żądania Http-Head, które zwykle wynoszą 100 mSec, trwały do ​​15 sekund, aby powrócić. Jest to znany problem podczas wywoływania WinInet z Delphi w niektórych wersjach Windows/WinInet. Wspominam o tym na wypadek, gdyby później doświadczyłeś takich dziwnych błędów. Jeśli możesz wybrać tutaj Indy lub coś innego niż WinInet (np. WinHttp), rozważ to! :-) –

+0

'' Jest to znany problem z wywołaniem WinInet z Delphi w niektórych wersjach Windows/WinInet' @WarrenP Nigdy nie miałem problemu z WinInet z Delphi.Czy możesz wskazać jakąś dokumentację lub linki na ten temat? – RRUZ

+0

Oto link: http://jgobserve.blogspot.com/2009/03/wininet-timeout-issue-and-solution.html - Moje obserwacje wskazują, że problemy nie ograniczają się do faktu, że gdy sieć bazowa zawiedzie dostajesz długie oczekiwanie. Czasami wszystko wydaje się w porządku, Z WYJĄTKIEM WinInet, który ma limity czasu, których nie potrafię inaczej wytłumaczyć. Kod napisany w Pythonie lub w Delphi przy użyciu INDY lub ICS NIE wykazuje tego samego wzorca awarii. –

Odpowiedz

16

Można użyć metody głowę i sprawdzić Content-Length pobrać rozmiar pliku zdalnego pliku

Sprawdź te dwie metody

WinInet

Jeśli chcesz wykonać Metoda HEAD musi używać funkcji WinInet HttpOpenRequest, HttpSendRequest i 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

również sprawdzić ten kod przy użyciu 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 Punkt zajęty, zamiast tego powinienem użyć Indy: D Po prostu dla czystego kodu –

+3

Jeśli wiesz * i tak pobierzesz zasób, czy nie możesz wysłać żądania GET i przeczytać nagłówek Content-Length z zamiast tego? Pozwoli to zaoszczędzić dodatkowe połączenie HTTP. –

+6

@RobKennedy - tak, możesz, dopóki dane nie są przesyłane w porcjach za pomocą nagłówka 'Transfer-Encoding: chunked', w którym to przypadku nagłówek' Content-Length' nie jest używany i nie ma mowy znać całkowity rozmiar do momentu otrzymania ostatniej porcji. –

3

Odpowiadając na pytanie, jak uzyskać rozmiaru pobieranej z WinInet. To jest jeden z moich downloaderów plików opartych na WinInet.

Jest to metoda używam, aby uzyskać rozmiar pobierania:

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; 

Zastosowanie tej metody wymaga otwartego uchwyt żądanie, i nie wymaga czytania jakichkolwiek danych:

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

HTH

+0

+1 Świetne rzeczy, a 1/6 kodu jako druga odpowiedź :) btw jaki jest twój wielki projekt downloadera? –

+0

Jestem bardzo ciekawa, jak to działa, ponieważ warunki wzajemnie się wykluczają a) metoda jest GET b) brak transferu żądanego zasobu. Domyślam się, że zamyka połączenie, gdy nagłówki zostały odzyskane. – OnTheFly

+0

@JerryDodge to wystarczająco dużo dla moich potrzeb i przeniosłem się na inne rzeczy. Nadal jednak wymaga wielu prac porządkowych. – Glenn1234

0

po ustalające rodzaje wygląda lepiej tak:

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; 

nazwać:

{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); 
Powiązane problemy