unit KolSimpleftp;
//
// purpose: KOL Ftp component using wininet api
//  author: © 2005, Thaddy de Koning
// Remarks: Based on an example by Charly Calvert
//          
interface

uses
  Windows,
  Kol,
  WinINet;

type
  PFtp = ^TFtp;
  TFtp = object(Tobj)
  private
    FContext: Integer;
    FINet: HInternet;
    FFtpHandle: HInternet;
    FCurFiles: PStrList;
    FServer: string;
    FOnNewDir: TOnEvent;
    FCurDir: string;
    FUserID: string;
    FPassword: string;
    FConnected: Boolean;
    Fport: Dword;
    FAnonymous: boolean;
    function GetCurrentDirectory: string;
    procedure SetUpNewDir;
    procedure setconnected(const Value: Boolean);
  protected
    destructor Destroy; virtual;
    function  Connect: Boolean;
    procedure Disconnect;
  public
    function FindFiles(const mask:string): PStrlist;
    function ChangeDirExact(S: string): Boolean;
    function ChangeDirCustom(S: string): Boolean;
    function BackOneDir: Boolean;
    function GetFile(FTPFile, NewFile: string): Boolean;
    function SendFile1(FTPFile, NewFile: string): Boolean;
    function SendFile2(FTPFile, NewFile: string): Boolean;
    function CustomToFileName(S: string): string;
    property CurFiles: PStrlist read FCurFiles;
    property CurDir: string read FCurDir;
    property UserID: string read FUserID write FUserID;
    property Password: string read FPassword write FPassword;
    property Server: string read FServer write FServer;
    property OnNewDir: TOnEvent read FOnNewDir
                write FOnNewDir;
    property Connected:Boolean read FConnected write setconnected;
    property Port:Dword read Fport write Fport;
    property Files:PStrlist read FCurFiles;
    property Anonymous:boolean read FAnonymous write FAnonymous;
  end;


function NewFtp(AOwner:PControl):PFtp;

implementation


// A few utility functions

function GetFirstToken(S: string; Token: Char): string;
var
  Temp: string;
  Index: INteger;
begin
  Index := Pos(Token, S);
  if Index < 1 then begin
    GetFirstToken := '';
    Exit;
  end;
  Dec(Index);
  SetLength(Temp, Index);
  Move(S[1], Temp[1], Index);
  GetFirstToken := Temp;
end;

function StripFirstToken(S: string; Ch: Char): string;
var
  i, Size: Integer;
begin
  i := Pos(Ch, S);
  if i = 0 then begin
    StripFirstToken := S;
    Exit;
  end;
  Size := (Length(S) - i);
  Move(S[i + 1], S[1], Size);
  SetLength(S, Size);
  StripFirstToken := S;
end;

function ReverseStr(S: string): string;
var
  Len: Integer;
  Temp: String;
  i,j: Integer;
begin
  Len := Length(S);
  SetLength(Temp, Len);
  j := Len;
  for i := 1 to Len do begin
    Temp[i] := S[j];
    dec(j);
  end;
  ReverseStr := Temp;
end;

function StripLastToken(S: string; Token: Char): string;
var
  Temp: string;
  Index: INteger;
begin
  SetLength(Temp, Length(S));
  S := ReverseStr(S);
  Index := Pos(Token, S);
  Inc(Index);
  Move(S[Index], Temp[1], Length(S) - (Index - 1));
  SetLength(Temp, Length(S) - (Index - 1));
  StripLastToken := ReverseStr(Temp);
end;



function NewFtp(AOwner:PControl):PFtp;
begin
  New(Result,Create);
  AOwner.add2autofree(Result);
  Result.FCurFiles := NewStrlist;
  Result.FINet := InternetOpen('WinINet1', 0, nil, 0, 0);
  Result.Fport:=0;//default port 21
end;

destructor TFtp.Destroy;
begin
  if FINet <> nil then
    InternetCloseHandle(FINet);
  if FFtpHandle <> nil then
    InternetCloseHandle(FFtpHandle);
  FCurfiles.Free;
  inherited Destroy;
end;

function TFtp.Connect: Boolean;
begin
  FContext := 255;
  if Fanonymous then
    FftpHandle := InternetConnect(FINet, PChar(FServer), Fport,
     nil, nil, Internet_Service_Ftp, 0, FContext)
  else
    FftpHandle := InternetConnect(FINet, PChar(FServer), Fport,
     PChar(FUserID), PChar(FPassWord), Internet_Service_Ftp, 0, FContext);
  if FFtpHandle = nil then
  begin
    Result := False;
    Messagebox(0,'Connection failed','Error',MB_ICONERROR or MB_OK);
  end
  else begin
    SetUpNewDir;
    Result := True;
  end;
end;

function TFtp.GetCurrentDirectory: string;
var
  Len: cardinal;
  S: string;
begin
  Len := 0;
  ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
  SetLength(S, Len);
  ftpGetCurrentDirectory(FFTPHandle, PChar(S), Len);
  Result := S;
end;

procedure TFtp.SetUpNewDir;
begin
  FCurDir := GetCurrentDirectory;
  if Assigned(FOnNewDir) then
    FOnNewDir(@Self);
end;

function GetDots(NumDots: Integer): string;
var
  S: string;
  i: Integer;
begin
  S := '';
  for i := 1 to NumDots do
    S := S + ' ';
  Result := S;
end;

function GetFindDataStr(FindData: TWin32FindData): string;
var
  S: string;
  Temp: string;
begin
  case FindData.dwFileAttributes of
    FILE_ATTRIBUTE_ARCHIVE: S := 'A';
//    FILE_ATTRIBUTE_COMPRESSED: S := 'C';
    FILE_ATTRIBUTE_DIRECTORY: S := 'D';
    FILE_ATTRIBUTE_HIDDEN: S := 'H';
    FILE_ATTRIBUTE_NORMAL: S := 'N';
    FILE_ATTRIBUTE_READONLY: S := 'R';
    FILE_ATTRIBUTE_SYSTEM: S := 'S';
    FILE_ATTRIBUTE_TEMPORARY: S := 'T';
  else
    S := Int2Str(FindData.dwFileAttributes);
  end;
  S := S + GetDots(75);
  Move(FindData.CFilename[0], S[6], StrLen(FindData.CFileName));
  Temp := Int2Str(FindData.nFileSizeLow);
  Move(Temp[1], S[25], Length(Temp));
  Result := S;
end;

function TFtp.FindFiles(const mask:string): PStrlist;
var
  FindData: TWin32FindData;
  FindHandle: HInternet;
begin
   FindHandle := FtpFindFirstFile(FFtphandle,Pchar(Mask),
     FindData, 0, 0);
   if FindHandle = nil then begin
     Result := nil;
     Exit;
   end;
   FCurFiles.Clear;
   FCurFiles.Add(GetFindDataStr(FindData));
   while InternetFindnextFile(FindHandle, @FindData) do
     FCurFiles.Add(GetFindDataStr(FindData));
   InternetCloseHandle(Findhandle);
   GetCurrentDirectory;
   FCurFiles.Sort(false);
   Result := FCurFiles;
end;

function TFtp.CustomToFileName(S: string): string;
const
  PreSize = 6;
var
  Temp: string;
  TempSize: Integer;
begin
  Temp := '';
  TempSize := Length(S) - PreSize; 
  SetLength(Temp, TempSize);
  Move(S[PreSize], Temp[1], TempSize);
  Temp := GetFirstToken(Temp, ' ');
  Result := Temp;
end;

function TFtp.BackOneDir: Boolean;
var
  S: string;
begin
  S := FCurDir;
  S := StripLastToken(S, '/');
  if S = '/' then begin
    Result := False;
    Exit;
  end;

  if S <> '' then begin
    ChangeDirExact(S);
    Result := True;
  end else begin
    ChangeDirExact('/');
    Result := True;
  end;

end;

// Changes to specific directory in S
function TFtp.ChangeDirExact(S: string): Boolean;
begin
  if S <> '' then
    FtpSetCurrentDirectory(FFTPHandle, PChar(S));
  Result := True;
  Fcurfiles.Clear;
  FindFiles('*.*');
  SetUpNewDir;
end;

// Assumes S has been returned by GetFindDataString;
function TFtp.ChangeDirCustom(S: string): Boolean;
begin
  S := CustomToFileName(S);
  if S <> '' then
    FtpSetCurrentDirectory(FFTPHandle, PChar(S));
  Result := True;
  FCurfiles.Clear;
  FindFiles('*.*');
  SetUpNewDir;
end;

function TFtp.GetFile(FTPFile, NewFile: string): Boolean;
begin
  Result := FtpGetFile(FFTPHandle, PChar(FTPFile), PChar(NewFile),
               False, File_Attribute_Normal,
               Ftp_Transfer_Type_Binary, 0);
end;

function TFtp.SendFile1(FTPFile, NewFile: string): Boolean;
const
  Size:DWord = 3000;
var
  Transfer: Bool;
  Error: DWord;
  S: string;
begin
  Transfer := FtpPutFile(FFTPHandle, PChar(FTPFile), 
                         PChar(NewFile),
                         Ftp_Transfer_Type_Binary, 0);

  if not Transfer then begin
    Error := GetLastError;
    Messagebox(0, PChar(Format('Error Number: %d. Hex: %x',[error,error])),'Error',MB_ICONERROR or MB_OK);
    SetLength(S, Size);
    if not InternetGetLastResponseInfo(Error, PChar(S), Size) then
    begin
      Error := GetLastError;
      Messagebox(0, PChar(Format('Error Number: %d. Hex: %x',[error,error])),'Error',MB_ICONERROR or MB_OK);
    end;
    Messagebox(0, PChar(Format('Error Number: %d. Hex: %x',[error,error])),'Error',MB_ICONERROR or MB_OK);

  end else
    Messagebox(0, 'Success','Information',MB_ICONINFORMATION or MB_OK);
  Result := Transfer;
end;

function TFtp.SendFile2(FTPFile, NewFile: string): Boolean;
var
  FHandle: HInternet;
begin
  FHandle :=  FtpOpenFile(FFTPHandle, 'sam.txt', GENERIC_READ,
                           FTP_TRANSFER_TYPE_BINARY, 0);
  if FHandle <> nil then
  InternetCloseHandle(FHandle)
  else
    Messagebox(0, 'Send file failed','Error',MB_ICONERROR or MB_OK);
  Result := True;
end;

procedure TFtp.Disconnect;
begin
  if FFtpHandle <> nil then
  begin
   InternetCloseHandle(FFtpHandle);
   FFtpHandle:=nil;
  end;
end;

procedure TFtp.setconnected(const Value: Boolean);
begin
  if Value = True then Fconnected:=Connect else
  begin
    Disconnect;
    FConnected:= Value;
  end;
end;

end.

