{$mode objfpc}
{$h+}
unit pkglnet;

interface

uses
  SysUtils, Classes,
  uriparser,
  lnet, lftp, lhttp, pkgdownload,pkgoptions, fprepos;

Type

  { TLNetDownloader }

  TLNetDownloader = Class(TBaseDownloader)
   private
    FQuit: Boolean;
    FFTP: TLFTPClient;
    FHTTP: TLHTTPClient;
    FOutStream: TStream;
    URI: TURI;
   protected
    // callbacks
    function OnHttpClientInput(ASocket: TLHTTPClientSocket; ABuffer: pchar;
      ASize: Integer): Integer;
    procedure OnLNetDisconnect(aSocket: TLSocket);
    procedure OnHttpDoneInput(aSocket: TLHTTPClientSocket);
    procedure OnLNetError(const msg: string; aSocket: TLSocket);
    procedure OnFTPControl(aSocket: TLSocket);
    procedure OnFTPReceive(aSocket: TLSocket);
    procedure OnFTPSuccess(aSocket: TLSocket; const aStatus: TLFTPStatus);
    procedure OnFTPFailure(aSocket: TLSocket; const aStatus: TLFTPStatus);
    // overrides
    procedure FTPDownload(Const URL : String; Dest : TStream); override;
    procedure HTTPDownload(Const URL : String; Dest : TStream); override;
   public
    constructor Create(AOwner : TComponent); override;
  end;

implementation

uses
  pkgglobals,
  pkgmessages;

{ TLNetDownloader }

function TLNetDownloader.OnHttpClientInput(ASocket: TLHTTPClientSocket;
  ABuffer: pchar; ASize: Integer): Integer;
begin
  Result:=FOutStream.Write(aBuffer[0], aSize);
end;

procedure TLNetDownloader.OnLNetDisconnect(aSocket: TLSocket);
begin
  FQuit:=True;
end;

procedure TLNetDownloader.OnHttpDoneInput(aSocket: TLHTTPClientSocket);
begin
  ASocket.Disconnect;
  FQuit:=True;
end;

procedure TLNetDownloader.OnLNetError(const msg: string; aSocket: TLSocket);
begin
  Error(msg);
  FQuit:=True;
end;

procedure TLNetDownloader.OnFTPControl(aSocket: TLSocket);
var
  s: string;
begin
  FFTP.GetMessage(s); // have to empty OS buffer, write the info if you wish to debug
end;

procedure TLNetDownloader.OnFTPReceive(aSocket: TLSocket);
const
  BUF_SIZE = 65536; // standard OS recv buffer size
var
  Buf: array[1..BUF_SIZE] of Byte;
begin
  FOutStream.Write(Buf[1], FFTP.GetData(Buf[1], BUF_SIZE));
end;

procedure TLNetDownloader.OnFTPSuccess(aSocket: TLSocket;
  const aStatus: TLFTPStatus);
begin
  FFTP.Disconnect;
  FQuit:=True;
end;

procedure TLNetDownloader.OnFTPFailure(aSocket: TLSocket;
  const aStatus: TLFTPStatus);
begin
  FFTP.Disconnect;
  Error(SErrDownloadFailed,['FTP',EncodeURI(URI),'']);
  FQuit:=True;
end;

procedure TLNetDownloader.FTPDownload(const URL: String; Dest: TStream);
begin
  FOutStream:=Dest;
  Try
    { parse URL }
    URI:=ParseURI(URL);

    if URI.Port = 0 then
      URI.Port := 21;

    FFTP.Connect(URI.Host, URI.Port);
    while not FFTP.Connected and not FQuit do
      FFTP.CallAction;

    if not FQuit then begin
      FFTP.Authenticate(URI.Username, URI.Password);
      FFTP.ChangeDirectory(URI.Path);
      FFTP.Retrieve(URI.Document);
      while not FQuit do
        FFTP.CallAction;
    end;
  finally
    FOutStream:=nil;
  end;
end;

procedure TLNetDownloader.HTTPDownload(const URL: String; Dest: TStream);
begin
  FOutStream:=Dest;
  Try
    { parse aURL }
    URI := ParseURI(URL);

    if URI.Port = 0 then
      URI.Port := 80;

    FHTTP.Host := URI.Host;
    FHTTP.Method := hmGet;
    FHTTP.Port := URI.Port;
    FHTTP.URI := URI.Path + URI.Document;
    FHTTP.SendRequest;

    FQuit:=False;
    while not FQuit do
      FHTTP.CallAction;
    if FHTTP.Response.Status<>HSOK then
      Error(SErrDownloadFailed,['HTTP',EncodeURI(URI),FHTTP.Response.Reason]);
  Finally
    FOutStream:=nil; // to be sure
  end;
end;

constructor TLNetDownloader.Create(AOwner: TComponent);
begin
  inherited;

  FFTP:=TLFTPClient.Create(Self);
  FFTP.Timeout:=1000;
  FFTP.StatusSet:=[fsRetr]; // watch for success/failure of retreives only
  FFTP.OnError:=@OnLNetError;
  FFTP.OnControl:=@OnFTPControl;
  FFTP.OnReceive:=@OnFTPReceive;
  FFTP.OnSuccess:=@OnFTPSuccess;
  FFTP.OnFailure:=@OnFTPFailure;

  FHTTP:=TLHTTPClient.Create(Self);
  FHTTP.Timeout := 1000; // go by 1s times if nothing happens
  FHTTP.OnDisconnect := @OnLNetDisconnect;
  FHTTP.OnDoneInput := @OnHttpDoneInput;
  FHTTP.OnError := @OnLNetError;
  FHTTP.OnInput := @OnHttpClientInput;
end;

initialization
  RegisterDownloader('lnet',TLNetDownloader);
end.
