unit LNetHTTPDataProvider;

{$mode objfpc}{$H+}

interface

uses
  Forms, Classes, SysUtils, IpHtml, IpMsg, IpUtils, lnetcomponents, Graphics, lhttp, lnet;
  
  type

  TIpHTTPDataProvider = class;

  TGettingURLCB = procedure(AProvider: TIpHTTPDataProvider; AURL: String) of object;
  
  { TIpHTTPDataProvider }

  TIpHTTPDataProvider = class(TIpAbstractHtmlDataProvider)
  private
    fLastType: String;
    fCachedStreams: TStringList;
    fCachedEmbeddedObjects: TStringList;
    procedure AddObjectToCache(ACache: TStringList; AURL: String; AStream: TStream);
    procedure ClearCache;
    procedure ClearCachedObjects;
    function GetCachedURL(AURL: String): TStream;
    function GetCachedObject(AURL: String): TStream;
    procedure HttpError(const msg: string; aSocket: TLSocket);
    function HttpInput(ASocket: TLHTTPClientSocket; ABuffer: pchar; ASize: LongInt): LongInt;
    procedure HttpInputDone(ASocket: TLHTTPClientSocket);
    procedure HttpProcessHeader(ASocket: TLHTTPClientSocket);
    procedure HttpCanWrite(ASocket: TLHTTPClientSocket; var OutputEof: TWriteBlockStatus);
    procedure HttpDisconnect(aSocket: TLSocket);
    
    function GetURL(const AURL: String; JustHeader: Boolean = False): TStream;
    function GetHostAndURI(const fURL: String; var AHost: String; var AURI: String): Boolean;
  protected
    function DoGetHtmlStream(const URL: string;
      PostData: TIpFormDataEntity) : TStream; override;
    function DoCheckURL(const URL: string;
      var ContentType: string): Boolean; override;
    procedure DoLeave(Html: TIpHtml); override;
    procedure DoReference(const URL: string); override;
    procedure DoGetImage(Sender: TIpHtmlNode; const URL: string;
      var Picture: TPicture); override;
    function DoGetStream(const URL: string): TStream; override;
    function CanHandle(const URL: string): Boolean; override;
    function BuildURL(const OldURL, NewURL: string): string; override;
  public
    constructor Create(AOwner: TComponent);
    destructor Destroy; override;
  end;
  
  TLHttpClientEx = class(TLHTTPClientComponent)
  //TLHttpClientEx = class(TLHTTPClient)
  private
    Stream: TStream;
    Waiting: Boolean;
    HeaderOnly: Boolean;
  end;


implementation

uses
  FPImage,
  {$IF FPC_FULLVERSION>=20602} //fpreadgif exists since at least this version
  FPReadgif,
  {$ENDIF}
  FPReadbmp,
  FPReadxpm,
  FPReadJPEG,
  FPReadpng,
  FPWritebmp,
  IntFGraphics;

{ TIpHTTPDataProvider }

procedure TIpHTTPDataProvider.AddObjectToCache ( ACache: TStringList;
  AURL: String; AStream: TStream ) ;
var
  TmpStream: TStream;
begin
  TmpStream := TMemoryStream.Create;
  AStream.Position := 0;
  TmpStream.CopyFrom(AStream, AStream.Size);
  ACache.AddObject(AURL, TmpStream);
  AStream.Position := 0;
end;

procedure TIpHTTPDataProvider.ClearCache;
var
  i: Integer;
begin
  for i := 0 to fCachedStreams.Count-1 do
    if fCachedStreams.Objects[i] <> nil then
      fCachedStreams.Objects[i].Free;
  fCachedStreams.Clear;

end;

procedure TIpHTTPDataProvider.ClearCachedObjects;
var
  i: Integer;
begin
  for i := 0 to fCachedStreams.Count-1 do
    if fCachedEmbeddedObjects.Objects[i] <> nil then
      fCachedEmbeddedObjects.Objects[i].Free;
  fCachedEmbeddedObjects.Clear;


end;

function TIpHTTPDataProvider.GetCachedURL ( AURL: String ) : TStream;
var
  i: Integer;
begin
  Result := nil;
  if Trim(AURL) = '' then
    Exit;
  for i := 0 to fCachedStreams.Count-1 do
    if fCachedStreams.Strings[i] = AURL then
    begin
      if fCachedStreams.Objects[i] = nil then break;
      Result := TMemoryStream.Create;
      TStream(fCachedStreams.Objects[i]).Position := 0;
      Result.CopyFrom(TStream(fCachedStreams.Objects[i]), TStream(fCachedStreams.Objects[i]).Size);
      Result.Position := 0;
      break;
    end;
  //WriteLn(AURL,' in cache = ', Result <> nil);
  if Result = nil then
    Result := GetCachedObject(AURL);

end;

function TIpHTTPDataProvider.GetCachedObject ( AURL: String ) : TStream;
var
  i: Integer;
begin
  Result := nil;
  if Trim(AURL) = '' then
    Exit;
  for i := 0 to fCachedEmbeddedObjects.Count-1 do
    if fCachedEmbeddedObjects.Strings[i] = AURL then
    begin
      if fCachedEmbeddedObjects.Objects[i] = nil then break;
      Result := TMemoryStream.Create;
      TStream(fCachedEmbeddedObjects.Objects[i]).Position := 0;
      Result.CopyFrom(TStream(fCachedEmbeddedObjects.Objects[i]), TStream(fCachedEmbeddedObjects.Objects[i]).Size);
      Result.Position := 0;
      break;
    end;
  //WriteLn(AURL,' in cached objects = ', Result <> nil);

end;

procedure TIpHTTPDataProvider.HttpError(const msg: string; aSocket: TLSocket);
begin
  TLHttpClientEx(ASocket.Creator).Waiting := False;
  //writeLn('Error occured: ', msg);

end;

function TIpHTTPDataProvider.HttpInput(ASocket: TLHTTPClientSocket;
  ABuffer: pchar; ASize: LongInt): LongInt;
begin
  //WriteLN(ASocket.Creator.ClassName);
  if TLHttpClientEx(ASocket.Creator).Stream = nil then
    TLHttpClientEx(ASocket.Creator).Stream := TMemoryStream.Create;
  Result := TLHttpClientEx(ASocket.Creator).Stream.Write(ABuffer^, ASize);


end;

procedure TIpHTTPDataProvider.HttpInputDone(ASocket: TLHTTPClientSocket);
begin
  TLHttpClientEx(ASocket.Creator).Waiting := False;
  aSocket.Disconnect;
  //WriteLn('InputDone');
end;

procedure TIpHTTPDataProvider.HttpProcessHeader(ASocket: TLHTTPClientSocket);
var
  i: TLHTTPParameter;
begin
  //WriteLn('Process Header');
  //for i := Low(TLHTTPParameterArray) to High(TLHTTPParameterArray) do
  //  if ASocket.Parameters[i] <> ''  then
  //  WriteLn(ASocket.Parameters[i]);
  //WriteLn(ASocket.Parameters[hpContentType]);
  fLastType := ASocket.Parameters[hpContentType];
  if TLHttpClientEx(ASocket.Creator).HeaderOnly then
    TLHttpClientEx(ASocket.Creator).Waiting := False;
end;

procedure TIpHTTPDataProvider.HttpCanWrite(ASocket: TLHTTPClientSocket;
  var OutputEof: TWriteBlockStatus);
begin
    //WriteLn('OnCanWrite');
end;

procedure TIpHTTPDataProvider.HttpDisconnect(aSocket: TLSocket);
begin
  TLHttpClientEx(ASocket.Creator).Waiting := False;
  //WriteLn('Disconnected');
end;


function TIpHTTPDataProvider.GetURL(const AURL: String; JustHeader: Boolean = False): TStream;
var
  fHost, fURI: String;
  fHttpClient: TLHttpClientEx;
begin
  Result := nil;

  if JustHeader = False then
    Result := GetCachedURL(AURL);
  //WriteLN('Getting: ', AURL);
  if Result = nil then
  begin
    if not GetHostAndURI(AURL, fHost, fURI) then Exit(nil);
    //WriteLn('Result := True');
    fHttpClient := TLHttpClientEx.Create(Owner);
    fHttpClient.OnInput := @HttpInput;
    fHttpClient.OnError := @HttpError;
    fHttpClient.OnDoneInput := @HttpInputDone;
    fHttpClient.OnProcessHeaders := @HttpProcessHeader;
    fHttpClient.OnCanWrite := @HttpCanWrite;
    fHttpClient.OnDisconnect := @HttpDisconnect;

    fHttpClient.Host := fHost;
    fHttpClient.Port := 80;
    fHttpClient.HeaderOnly := JustHeader;
    if JustHeader then
      fHttpClient.Method := hmHead
    else
      fHttpClient.Method := hmGet;
    fHttpClient.URI := fURI;

    fHttpClient.SendRequest;
    //WriteLn('Sending Request');

    fHttpClient.Waiting := True;
    {while fHttpClient.Waiting = True do
      begin
        fHttpClient.CallAction;
        Sleep(1);
      end;}

    while fHttpClient.Waiting do begin
      //WriteLn('InFirstLoop');
      Application.HandleMessage;
      if csDestroying in ComponentState then Exit;
    end;
    //WriteLn('LeftLoop');

    Result:= fHttpClient.Stream;
    if Result <> nil then
      Result.Position := 0;
    //fDataStream.SaveToFile('temp.txt');
    //Application.Terminate;
    fHttpClient.Free;
  end;
end;

function TIpHTTPDataProvider.GetHostAndURI(const fURL: String; var AHost: String; var AURI: String): Boolean;
var
  fPos: Integer;
begin
  fPos := Pos('://', fUrl);
  if fPos = 0 then Exit(False);
  Result := True;
  AHost := Copy(fURL, fPos+3, Length(fURL));
  
  
  fPos := Pos('/', AHost);
  if fPos = 0 then begin
    AURI:='/';
    Exit(True);
  end;
  AURI := Copy(AHost, fPos, Length(AHost));
  AHost := Copy(AHost, 1, fPos-1);
  //WriteLn('Got Host: ',AHost);
  //WriteLn('Got URI : ',AURI);
end;

function TIpHTTPDataProvider.DoGetHtmlStream(const URL: string;
  PostData: TIpFormDataEntity): TStream;
begin
  Result := GetCachedURL(URL);
  if Result = nil then
  begin
    Result := GetURL(URL);
    if Result <> nil then
      AddObjectToCache(fCachedStreams, URL, Result);
  end;
end;

function TIpHTTPDataProvider.DoCheckURL(const URL: string;
  var ContentType: string): Boolean;
var
  TmpStream: TStream;
begin
  //WriteLn('Want content type: "', ContentType,'" for Url:',URL);
  Result := True;
  //TmpStream := GetCachedURL(URL);
  //if TmpStream = nil then
  //begin
    TmpStream := GetURL(URL, True);
  //  if TmpStream <> nil then
  //    AddObjectToCache(fCachedStreams, URL, TmpStream);
  //end;

  if TmpStream <> nil then FreeAndNil(TmpStream);
  ContentType := fLastType;//}'text/html';
end;

procedure TIpHTTPDataProvider.DoLeave(Html: TIpHtml);
begin
  ClearCache;
end;

procedure TIpHTTPDataProvider.DoReference(const URL: string);
begin

end;

procedure TIpHTTPDataProvider.DoGetImage(Sender: TIpHtmlNode;
  const URL: string; var Picture: TPicture);
var
  Stream: TStream;
  FileExt: String;
begin
  //DebugLn('Getting Image ',(Url));
  Picture := nil;

  FileExt := ExtractFileExt(URL);

  Picture := TPicture.Create;
  try
    Stream := GetCachedObject(URL);
    if Stream = nil then
    begin
      Stream := GetURL(URL);
      if Stream <> nil then
        AddObjectToCache(fCachedEmbeddedObjects, URL, Stream);
    end;

    if Assigned(Stream) then
    begin
      Stream.Position := 0;
      Picture.LoadFromStreamWithFileExt(Stream, FileExt);
    end
    else
      Picture.Graphic := TBitmap.Create;
  except
    try
      Picture.Free;
    finally
      Picture := TPicture.Create;
      Picture.Graphic := TBitmap.Create;
    end;
  end;
  Stream.Free;
end;

function TIpHTTPDataProvider.DoGetStream ( const URL: string ) : TStream;
begin
  Result := GetCachedObject(URL);
  if Result = nil then
  begin
    Result := GetURL(URL);
    if Result <> nil then
      AddObjectToCache(fCachedEmbeddedObjects, URL, Result);
  end;
end;

function TIpHTTPDataProvider.CanHandle(const URL: string): Boolean;
begin
  //WriteLn('Can Handle: ', URL);
  Result := True;
end;

function TIpHTTPDataProvider.BuildURL(const OldURL, NewURL: string): string;
begin
  Result := Iputils.BuildURL(OldURL, NewURL);
end;

constructor TIpHTTPDataProvider.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fCachedEmbeddedObjects := TStringList.Create;
  fCachedStreams := TStringList.Create;
end;

destructor TIpHTTPDataProvider.Destroy;
begin
  ClearCache;
  ClearCachedObjects;
  fCachedStreams.Free;
  fCachedEmbeddedObjects.Free;
  inherited Destroy;
end;

end.

