534 lines
17 KiB
ObjectPascal
534 lines
17 KiB
ObjectPascal
{-----------------------------------------------------------------------------
|
|
The contents of this file are subject to the Mozilla Public License
|
|
Version 1.1 (the "License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
The Original Code is: JvMultiHTTPGrabber.PAS, released on 2001-02-28.
|
|
|
|
The Initial Developer of the Original Code is Sébastien Buysse [sbuysse att buypin dott com]
|
|
Portions created by Sébastien Buysse are Copyright (C) 2001 Sébastien Buysse.
|
|
All Rights Reserved.
|
|
|
|
Contributor(s): Michael Beck [mbeck att bigfoot dott com].
|
|
|
|
Last Modified: 2000-02-28
|
|
|
|
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
|
|
located at http://jvcl.sourceforge.net
|
|
|
|
Known Issues:
|
|
-----------------------------------------------------------------------------}
|
|
|
|
{$I jvcl.inc}
|
|
|
|
{$HPPEMIT '#pragma link "wininet.lib"'}
|
|
|
|
unit JvMultiHTTPGrabber;
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, SysUtils, Classes, WinInet,
|
|
JvTypes, JvComponent;
|
|
|
|
type
|
|
TUrlEvent = procedure(Sender: TObject; UserData: Integer; Url: string) of object;
|
|
TUrlEventError = procedure(Sender: TObject; UserData: Integer;
|
|
Url: string; Error: string) of object;
|
|
TUrlResolved = procedure(Sender: TObject; UserData: Integer; Url: string; Name: string) of object;
|
|
TUrlRedirect = procedure(Sender: TObject; UserData: Integer;
|
|
Url: string; NewUrl: string) of object;
|
|
TUrlSent = procedure(Sender: TObject; UserData: Integer;
|
|
Url: string; DataSize: Integer) of object;
|
|
TJvDoneFileEvent = procedure(Sender: TObject; UserData: Integer; FileName: string;
|
|
FileSize: Integer; Url: string) of object;
|
|
TJvDoneStreamEvent = procedure(Sender: TObject; UserData: Integer; Stream: TStream;
|
|
StreamSize: Integer; Url: string) of object;
|
|
TDateEvent = procedure(Sender: TObject; UserData: Integer; FileDate: TDateTime;
|
|
Url: string) of object;
|
|
|
|
TJvMultiHTTPGrabber = class(TJvComponent)
|
|
private
|
|
FAgent: string;
|
|
FUrl: string;
|
|
FReferer: string;
|
|
FPassword: string;
|
|
FUserName: string;
|
|
FOutputMode: TJvOutputMode;
|
|
FFileName: TFileName;
|
|
FOnDoneFile: TJvDoneFileEvent;
|
|
FOnDoneStream: TJvDoneStreamEvent;
|
|
FOnProgress: TJvHTTPProgressEvent;
|
|
FOnReceived: TUrlSent;
|
|
FOnReceivingResponse: TUrlEvent;
|
|
FOnClosed: TUrlEvent;
|
|
FOnConnecting: TUrlEvent;
|
|
FOnResolving: TUrlEvent;
|
|
FOnRedirect: TUrlRedirect;
|
|
FOnConnected: TUrlEvent;
|
|
FOnResolved: TUrlResolved;
|
|
FOnClosing: TUrlEvent;
|
|
FOnRequestComplete: TUrlEvent;
|
|
FOnRequestSent: TUrlSent;
|
|
FOnSendingRequest: TUrlEvent;
|
|
FOnError: TUrlEventError;
|
|
FCount: Integer;
|
|
FOnDateRetrieved: TDateEvent;
|
|
function GetWorking: Boolean;
|
|
procedure RaiseWebError(Infos: Pointer);
|
|
protected
|
|
procedure RaiseError(Value: Pointer);
|
|
function StartConnection(UserData: Integer; IgnoreMessages: Boolean = False): Pointer;
|
|
procedure StopConnection(Infos: Pointer);
|
|
procedure ThreadTerminated(Sender: TObject);
|
|
procedure ThreadDateTerminated(Sender: TObject);
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure Download(UserData: Integer = 0);
|
|
procedure GetFileAge(UserData: Integer = 0);
|
|
property DownloadCount: Integer read FCount;
|
|
published
|
|
property Agent: string read FAgent write FAgent;
|
|
property FileName: TFileName read FFileName write FFileName;
|
|
property OutputMode: TJvOutputMode read FOutputMode write FOutputMode default omStream;
|
|
property Password: string read FPassword write FPassword;
|
|
property Referer: string read FReferer write FReferer;
|
|
property Url: string read FUrl write FUrl;
|
|
property UserName: string read FUserName write FUserName;
|
|
property Working: Boolean read GetWorking;
|
|
property OnClosingConnection: TUrlEvent read FOnClosing write FOnClosing;
|
|
property OnClosedConnection: TUrlEvent read FOnClosed write FOnClosed;
|
|
property OnConnectingToServer: TUrlEvent read FOnConnecting write FOnConnecting;
|
|
property OnConnectedToServer: TUrlEvent read FOnConnected write FOnConnected;
|
|
property OnDoneFile: TJvDoneFileEvent read FOnDoneFile write FOnDoneFile;
|
|
property OnDoneStream: TJvDoneStreamEvent read FOnDoneStream write FOnDoneStream;
|
|
property OnError: TUrlEventError read FOnError write FOnError;
|
|
property OnProgress: TJvHTTPProgressEvent read FOnProgress write FOnProgress;
|
|
property OnReceivingResponse: TUrlEvent read FOnReceivingResponse write FOnReceivingResponse;
|
|
property OnReceivedResponse: TUrlSent read FOnReceived write FOnReceived;
|
|
property OnRedirect: TUrlRedirect read FOnRedirect write FOnRedirect;
|
|
property OnRequestComplete: TUrlEvent read FOnRequestComplete write FOnRequestComplete;
|
|
property OnRequestSent: TUrlSent read FOnRequestSent write FOnRequestSent;
|
|
property OnResolvingName: TUrlEvent read FOnResolving write FOnResolving;
|
|
property OnResolvedName: TUrlResolved read FOnResolved write FOnResolved;
|
|
property OnSendingRequest: TUrlEvent read FOnSendingRequest write FOnSendingRequest;
|
|
property OnDateRetrieved: TDateEvent read FOnDateRetrieved write FOnDateRetrieved;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
JvResources;
|
|
|
|
type
|
|
PRequestInfos = ^TRequestInfos;
|
|
TRequestInfos = record
|
|
Url: string;
|
|
Filename:string;
|
|
OutputMode: TJvOutputMode;
|
|
hSession: HINTERNET;
|
|
hHostConnect: HINTERNET;
|
|
hRequest: HINTERNET;
|
|
FileSize: Integer;
|
|
IgnoreMsg: Boolean;
|
|
Grabber: TJvMultiHTTPGrabber;
|
|
UserData: Integer;
|
|
end;
|
|
|
|
TJvMultiHttpThread = class(TThread)
|
|
private
|
|
FInfos: Pointer;
|
|
FPosition: Integer;
|
|
FContinue: Boolean;
|
|
FStream: TMemoryStream;
|
|
protected
|
|
procedure Execute; override;
|
|
procedure Progress;
|
|
procedure Error;
|
|
public
|
|
constructor Create(Value: Pointer);
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
TJvMultiDateHttpThread = class(TThread)
|
|
private
|
|
FInfos: Pointer;
|
|
FValue: TDateTime;
|
|
protected
|
|
procedure Execute; override;
|
|
procedure Error;
|
|
public
|
|
constructor Create(Value: Pointer);
|
|
end;
|
|
|
|
//=== TJvMultiHTTPGrabber ====================================================
|
|
|
|
constructor TJvMultiHTTPGrabber.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FOutputMode := omStream;
|
|
FAgent := RsMultiAgent;
|
|
FCount := 0;
|
|
end;
|
|
|
|
procedure StatusCallback(Handle: HInternet; Context: DWord;
|
|
Status: DWord; Info: Pointer; StatLen: DWord); stdcall;
|
|
begin
|
|
with PRequestInfos(Context)^ do
|
|
if not IgnoreMsg then
|
|
case Status of
|
|
INTERNET_STATUS_CLOSING_CONNECTION:
|
|
if Assigned(Grabber.FOnClosing) then
|
|
Grabber.FOnClosing(Grabber, UserData, Url);
|
|
INTERNET_STATUS_CONNECTED_TO_SERVER:
|
|
if Assigned(Grabber.FOnConnected) then
|
|
Grabber.FOnConnected(Grabber, UserData, Url);
|
|
INTERNET_STATUS_CONNECTING_TO_SERVER:
|
|
if Assigned(Grabber.FOnConnecting) then
|
|
Grabber.FOnConnecting(Grabber, UserData, Url);
|
|
INTERNET_STATUS_NAME_RESOLVED:
|
|
if Assigned(Grabber.FOnResolved) then
|
|
Grabber.FOnResolved(Grabber, UserData, Url, StrPas(PChar(Info)));
|
|
INTERNET_STATUS_RECEIVING_RESPONSE:
|
|
if Assigned(Grabber.FOnReceivingResponse) then
|
|
Grabber.FOnReceivingResponse(Grabber, UserData, Url);
|
|
INTERNET_STATUS_REDIRECT:
|
|
if Assigned(Grabber.FOnRedirect) then
|
|
Grabber.FOnRedirect(Grabber, UserData, Url, StrPas(PChar(Info)));
|
|
INTERNET_STATUS_REQUEST_COMPLETE:
|
|
if Assigned(Grabber.FOnRequestComplete) then
|
|
Grabber.FOnRequestComplete(Grabber, UserData, Url);
|
|
INTERNET_STATUS_REQUEST_SENT:
|
|
if Assigned(Grabber.FOnRequestSent) then
|
|
Grabber.FOnRequestSent(Grabber, UserData, Url, DWORD(Info^));
|
|
INTERNET_STATUS_RESOLVING_NAME:
|
|
if Assigned(Grabber.FOnResolving) then
|
|
Grabber.FOnResolving(Grabber, UserData, Url);
|
|
INTERNET_STATUS_RESPONSE_RECEIVED:
|
|
if Assigned(Grabber.FOnReceived) then
|
|
Grabber.FOnReceived(Grabber, UserData, Url, DWORD(Info^));
|
|
INTERNET_STATUS_SENDING_REQUEST:
|
|
if Assigned(Grabber.FOnSendingRequest) then
|
|
Grabber.FOnSendingRequest(Grabber, UserData, Url);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.Download(UserData: Integer);
|
|
var
|
|
Infos: PRequestInfos;
|
|
begin
|
|
Infos := StartConnection(UserData, False);
|
|
if Infos <> nil then
|
|
with TJvMultiHttpThread.Create(Infos) do
|
|
begin
|
|
OnTerminate := ThreadTerminated;
|
|
FreeOnTerminate := true;
|
|
Resume;
|
|
Inc(FCount);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.GetFileAge(UserData: Integer = 0);
|
|
var
|
|
Infos: PrequestInfos;
|
|
begin
|
|
Infos := StartConnection(UserData, True);
|
|
if Infos <> nil then
|
|
with TJvMultiDateHttpThread.Create(Infos) do
|
|
begin
|
|
OnTerminate := ThreadDateTerminated;
|
|
FreeOnTerminate := true;
|
|
Resume;
|
|
end;
|
|
end;
|
|
|
|
function TJvMultiHTTPGrabber.GetWorking: Boolean;
|
|
begin
|
|
Result := FCount > 0;
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.RaiseError(Value: Pointer);
|
|
var
|
|
Msg: array [0..256] of Char;
|
|
begin
|
|
if Assigned(FOnError) then
|
|
with PRequestInfos(Value)^ do
|
|
begin
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, GetLastError, 0, Msg,
|
|
SizeOf(Msg), nil);
|
|
FOnError(Self, UserData, Url, Msg);
|
|
end;
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.RaiseWebError(Infos: Pointer);
|
|
var
|
|
dwIndex, dwBufLen: DWORD;
|
|
Buf: array [0..1024] of Char;
|
|
begin
|
|
if Assigned(FOnError) then
|
|
begin
|
|
dwIndex := 0;
|
|
dwBufLen := SizeOf(Buf);
|
|
InternetGetLastResponseInfo(dwIndex, Buf, dwBufLen);
|
|
with PRequestInfos(Infos)^ do
|
|
FOnError(Self, UserData, Url, StrPas(buf));
|
|
end;
|
|
end;
|
|
|
|
function TJvMultiHTTPGrabber.StartConnection(UserData: Integer; IgnoreMessages: Boolean): Pointer;
|
|
var
|
|
Infos: PRequestInfos;
|
|
HostName, FilePath: string;
|
|
HostPort: Word;
|
|
|
|
procedure ParseUrl(Value: string);
|
|
begin
|
|
HostName := '';
|
|
FilePath := '';
|
|
if Pos('HTTP://', UpperCase(Value)) <> 0 then
|
|
Value := Copy(Value, 8, Length(Value));
|
|
if Pos('/', Value) <> 0 then
|
|
begin
|
|
HostName := Copy(Value, 1, Pos('/', Value) - 1);
|
|
FilePath := Copy(Value, Pos('/', Value) + 1, Length(Value));
|
|
end
|
|
else
|
|
HostName := Value;
|
|
|
|
if Pos(':', HostName) <> 0 then
|
|
begin // If URL contains a non-standard Port number, attempt to use it
|
|
HostPort := StrToIntDef(Copy(HostName, Pos(':', HostName) + 1, Length(HostName)), INTERNET_DEFAULT_HTTP_PORT);
|
|
HostName := Copy(HostName, 1, Pos(':', HostName) - 1);
|
|
end
|
|
else // If not, use the standard one
|
|
HostPort := INTERNET_DEFAULT_HTTP_PORT;
|
|
end;
|
|
|
|
begin
|
|
Result := nil;
|
|
|
|
Infos := New(PRequestInfos);
|
|
Infos^.Url := Url;
|
|
Infos^.Filename := FileName;
|
|
Infos^.OutputMode := OutputMode;
|
|
Infos^.UserData := UserData;
|
|
Infos^.Grabber := Self;
|
|
Infos^.IgnoreMsg := IgnoreMessages;
|
|
|
|
//Opening the web session with the server
|
|
Infos^.hSession := InternetOpen(PChar(FAgent), INTERNET_OPEN_TYPE_PRECONFIG,
|
|
nil, nil, 0);
|
|
if Infos^.hSession = nil then
|
|
begin
|
|
RaiseError(Infos);
|
|
Dispose(Infos);
|
|
Exit;
|
|
end;
|
|
|
|
//Setting callback function
|
|
InternetSetStatusCallback(Infos^.hSession, PFNInternetStatusCallback(@StatusCallback));
|
|
|
|
//Open the internet connection
|
|
ParseUrl(Url);
|
|
Infos^.hHostConnect := InternetConnect(Infos^.hSession, PChar(HostName),
|
|
HostPort, PChar(FUserName), PChar(FPassword), INTERNET_SERVICE_HTTP,
|
|
0, Cardinal(Infos));
|
|
if Infos^.hHostConnect = nil then
|
|
begin
|
|
RaiseWebError(Infos);
|
|
InternetCloseHandle(Infos^.hSession);
|
|
Dispose(Infos);
|
|
Exit;
|
|
end;
|
|
|
|
//prepare the GET order
|
|
Infos^.hRequest := HttpOpenRequest(Infos^.hHostConnect, 'GET', PChar(FilePath),
|
|
'HTTP/1.0', PChar(FReferer), nil, INTERNET_FLAG_RELOAD, 0);
|
|
|
|
Result := Infos;
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.StopConnection(Infos: Pointer);
|
|
begin
|
|
InternetCloseHandle(PRequestInfos(Infos)^.hRequest);
|
|
InternetCloseHandle(PRequestInfos(Infos)^.hHostConnect);
|
|
InternetCloseHandle(PRequestInfos(Infos)^.hSession);
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.ThreadDateTerminated(Sender: TObject);
|
|
begin
|
|
with Sender as TJvMultiDateHttpThread do
|
|
begin
|
|
with PRequestInfos(FInfos)^ do
|
|
if Assigned(FOnDateRetrieved) then
|
|
FOnDateRetrieved(Self, UserData, FValue, Url);
|
|
|
|
StopConnection(FInfos);
|
|
Dispose(FInfos);
|
|
// Free; // (p3) FreeOnTerminate is set when creating, so don't free here
|
|
end;
|
|
end;
|
|
|
|
procedure TJvMultiHTTPGrabber.ThreadTerminated(Sender: TObject);
|
|
var
|
|
TT: TJvMultiHttpThread;
|
|
begin
|
|
TT := Sender as TJvMultiHttpThread; // need this for debugging purposes
|
|
try
|
|
if (TT.FStream <> nil) and (TT.FStream.Size > 0) then
|
|
begin
|
|
if OutputMode = omStream then
|
|
begin
|
|
if Assigned(FOnDoneStream) then
|
|
FOnDoneStream(Self, PRequestInfos(TT.FInfos)^.UserData, TT.FStream, TT.FStream.Size, PRequestInfos(TT.FInfos)^.Url);
|
|
end
|
|
else
|
|
begin
|
|
TT.FStream.SaveToFile(PRequestInfos(TT.FInfos)^.FileName);
|
|
if Assigned(FOnDoneFile) then
|
|
FOnDoneFile(Self, PRequestInfos(TT.FInfos)^.UserData, PRequestInfos(TT.FInfos)^.FileName, TT.FStream.Size, PRequestInfos(TT.FInfos)^.Url);
|
|
end;
|
|
end;
|
|
|
|
StopConnection(PRequestInfos(TT.FInfos));
|
|
Dispose(PRequestInfos(TT.FInfos));
|
|
Dec(FCount);
|
|
finally
|
|
// TT.Free; // (p3) FreeOnTerminate is set when creating, so don't free here
|
|
end;
|
|
end;
|
|
|
|
//=== TJvMultiHttpThread =====================================================
|
|
|
|
constructor TJvMultiHttpThread.Create(Value: Pointer);
|
|
begin
|
|
inherited Create(True);
|
|
FInfos := Value;
|
|
FPosition := 0;
|
|
FContinue := True;
|
|
FStream := nil;
|
|
end;
|
|
|
|
destructor TJvMultiHttpThread.Destroy;
|
|
begin
|
|
FreeAndNil(FStream);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TJvMultiHttpThread.Error;
|
|
var
|
|
Infos: TRequestInfos;
|
|
begin
|
|
Infos := PRequestInfos(FInfos)^;
|
|
if Assigned(Infos.Grabber.FOnError) then
|
|
Infos.Grabber.FOnError(Self, Infos.UserData, Infos.Url, RsErrorConnection);
|
|
end;
|
|
|
|
procedure TJvMultiHttpThread.Execute;
|
|
var
|
|
Infos: PRequestInfos;
|
|
Buffer: array [0..512] of Byte;
|
|
BytesRead: DWORD;
|
|
dLength, dReserved, dSize: DWORD;
|
|
begin
|
|
// (p3) avoid memory leaks
|
|
FreeAndNil(FStream);
|
|
try
|
|
Infos := PRequestInfos(FInfos);
|
|
|
|
//Send the request
|
|
if not HttpSendRequest(Infos^.hRequest, nil, 0, nil, 0) then
|
|
begin
|
|
Synchronize(Error);
|
|
Exit;
|
|
end;
|
|
|
|
// Get the Size
|
|
dLength := SizeOf(dSize);
|
|
dReserved := 0;
|
|
if HttpQueryInfo(Infos^.hRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER,
|
|
@dSize, dLength, dReserved) then
|
|
Infos^.FileSize := dSize
|
|
else
|
|
Infos^.FileSize := -1;
|
|
|
|
// Download the stuff
|
|
Synchronize(Progress);
|
|
if not FContinue then
|
|
Exit;
|
|
|
|
FStream := TMemoryStream.Create;
|
|
repeat
|
|
if not InternetReadFile(Infos^.hRequest, @Buffer[0], SizeOf(Buffer), BytesRead) then
|
|
BytesRead := 0
|
|
else
|
|
begin
|
|
Inc(FPosition, BytesRead);
|
|
FStream.Write(buffer, BytesRead);
|
|
Synchronize(Progress);
|
|
if not FContinue then
|
|
Exit;
|
|
end;
|
|
until BytesRead = 0;
|
|
FStream.Position := 0;
|
|
except
|
|
end;
|
|
Terminate;
|
|
end;
|
|
|
|
procedure TJvMultiHttpThread.Progress;
|
|
begin
|
|
with PRequestInfos(FInfos)^ do
|
|
if Assigned(Grabber.OnProgress) then
|
|
Grabber.OnProgress(Grabber, UserData, FPosition, FileSize, Url, FContinue);
|
|
end;
|
|
|
|
//=== TJvMultiDateHttpThread =================================================
|
|
|
|
constructor TJvMultiDateHttpThread.Create(Value: Pointer);
|
|
begin
|
|
inherited Create(True);
|
|
FInfos := Value;
|
|
end;
|
|
|
|
procedure TJvMultiDateHttpThread.Error;
|
|
var
|
|
Infos: TRequestInfos;
|
|
begin
|
|
Infos := PRequestInfos(FInfos)^;
|
|
if Assigned(Infos.Grabber.FOnError) then
|
|
Infos.Grabber.FOnError(Self, Infos.UserData, Infos.Url, RsErrorConnection);
|
|
end;
|
|
|
|
procedure TJvMultiDateHttpThread.Execute;
|
|
var
|
|
Infos: PRequestInfos;
|
|
STime: TSystemTime;
|
|
dLength, dReserved: DWORD;
|
|
begin
|
|
// (rom) secure thread against exceptions
|
|
try
|
|
Infos := PRequestInfos(FInfos);
|
|
|
|
dLength := SizeOf(TSystemTime);
|
|
dReserved := 0;
|
|
|
|
HttpSendRequest(Infos^.hRequest, nil, 0, nil, 0);
|
|
|
|
if HttpQueryInfo(Infos^.hRequest, HTTP_QUERY_LAST_MODIFIED or HTTP_QUERY_FLAG_SYSTEMTIME,
|
|
@STime, dLength, dReserved) then
|
|
FValue := SystemTimeToDateTime(STime)
|
|
else
|
|
FValue := -1;
|
|
except
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|