{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.5    10/26/2004 9:36:26 PM  JPMugaas
  Updated ref.

  Rev 1.4    4/19/2004 5:05:28 PM  JPMugaas
  Class rework Kudzu wanted.

  Rev 1.3    2004.02.03 5:45:22 PM  czhower
  Name changes

  Rev 1.2    10/19/2003 2:08:48 PM  DSiders
  Added localization comments.

  Rev 1.1    4/7/2003 04:03:02 PM  JPMugaas
  User can now descover what output a parser may give.

  Rev 1.0    2/19/2003 04:18:10 AM  JPMugaas
  More things restructured for the new list framework.
}

unit IdFTPListParseAS400;

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdFTPList, IdFTPListParseBase, IdFTPListTypes;

type
  TIdAS400FTPListItem = class(TIdOwnerFTPListItem);
  TIdFTPLPAS400 = class(TIdFTPLineOwnedList)
  protected
    class function MakeNewItem(AOwner : TIdFTPListItems)  : TIdFTPListItem; override;
    class function ParseLine(const AItem : TIdFTPListItem; const APath : String=''): Boolean; override;
  public
    class function GetIdent : String; override;
    class function CheckListing(AListing : TStrings; const ASysDescript : String =''; const ADetails : Boolean = True): boolean; override;
  end;

  // RLebeau 2/14/09: this forces C++Builder to link to this unit so
  // RegisterFTPListParser can be called correctly at program startup...

  {$IFDEF HAS_DIRECTIVE_HPPEMIT_LINKUNIT}
    {$HPPEMIT LINKUNIT}
  {$ELSE}
    {$HPPEMIT '#pragma link "IdFTPListParseAS400"'}
  {$ENDIF}

implementation

uses
  IdGlobal, IdFTPCommon,  IdGlobalProtocols, SysUtils;

const
  DIR_TYPES : array [0..3] of string = ('*DIR','*DDIR','*LIB','*FLR');

{ TIdFTPLPAS400 }

class function TIdFTPLPAS400.CheckListing(AListing: TStrings;
  const ASysDescript: String; const ADetails: Boolean): boolean;
var
  s : TStrings;
begin
  Result := False;
  if AListing.Count > 0 then begin
    s := TStringList.Create;
    try
      SplitDelimitedString(AListing[0], s, True);
      if s.Count > 4 then begin
        Result := CharEquals(s[4], 1, '*') or (s[4]='DIR');  {Do not localize}
      end;
    finally
      FreeAndNil(s);
    end;
  end;
end;

class function TIdFTPLPAS400.GetIdent: String;
begin
  Result := 'AS400';  {do not localize}
end;

class function TIdFTPLPAS400.MakeNewItem(AOwner: TIdFTPListItems): TIdFTPListItem;
begin
  Result := TIdAS400FTPListItem.Create(AOwner);
end;

class function TIdFTPLPAS400.ParseLine(const AItem: TIdFTPListItem;
  const APath: String): Boolean;
var
  LBuffer : String;
  LDate : String;
  LTime : String;
  LObjType : String;
  LI : TIdOwnerFTPListItem;
begin
  try
{  From:
http://groups.google.com/groups?q=AS400+LISTFMT+%3D+0&hl=en&lr=&ie=UTF-8&oe=utf-8&selm=9onmpt%24dhe%2402%241%40news.t-online.com&rnum=1

  ftp> dir qtemp/timestamp
  200 PORT subcommand request successful.
  125 List started.
  drwx---rwx 1 QPGMR 0 20480 Sep 24 18:16 TIMESTAMP
  -rwx---rwx 1 QPGMR 0     0 Sep 24 18:16 TIMESTAMP.TIMESTAMP
  250 List completed.
  FTP: 140 Bytes empfangen in 0.06Sekunden 2.33KB/s

  or

  ftp> dir qtemp/timestamp
  200 PORT subcommand request successful.
  125 List started.

         1         2         3         4         5
  123456789012345678901234567890123456789012345678901234567890
  QPGMR 20480 24.09.01 18:16:20 *FILE QTEMP/TIMESTAMP
  QPGMR                         *MEM  QTEMP/TIMESTAMP.TIMESTAMP
  250 List completed.
  FTP: 146 Bytes empfangen in 0.00Sekunden 146000.00KB/s

It depends qether the SITE param LISTFMT is set to "1" (1st example, *nix-
like) or "0" (2nd example, OS/400-like). I have choosen the 2nd format (I
think it's easier to parse). To get it, submit "QUOTE SITE LISTFMT 0" just
before submitting the DIR command.

From IBM Manual at:
http://publib.boulder.ibm.com/iseries/v5r2/ic2924/index.htm

Here is the original iSeries style format for the LIST subcommand
(when LISTFMT=0):

owner size date time type name
A blank space separates each field.

This is a description of each field:

owner
The 10 character string that represents the user profile which owns the subject.
This string is left justified, and includes blanks. This field is blank for
anonymous FTP sessions.

size
The 10 character number that represents the size of the object. This number is
right justified, and it includes blanks. This field is blank when an object has
no size associated with it.

date
The 8 character modification date in the format that is defined for the server
job. It uses date separators that are defined for the server job. This
modification date is left justified, and it includes blanks.

time
The 8 character modification time that uses the time separator, which the
server job defines.

type
The 10 character OS/400 object type.

name
The variable length name of the object that follows a CRLF (carriage return,
line feed pair). This name may include blanks.

Here is an example of the original iSeries style format:

         1         2         3         4         5
123456789012345678901234567890123456789012345678901234567890
BAILEYSE     5263360 06/11/97 12:27:39 *FILE     BPTFSAVF

Note on name format from (
http://groups.google.com/groups?q=AS400+FTP+LIST+format&hl=
en&lr=&ie=UTF-8&oe=utf-8&selm=3264740F.B52%40mother.com&rnum=4):

Starting in v3r1 you can access the shared folders area or libraries with FTP by using the
"NAMEFMT 1" command. For example:

SYST
215  OS/400 is the remote operating system. The TCP/IP version is "V3R1M0".
SITE NAMEFMT 1
250  Now using naming format "1".
LIST /QDLS

/QDLS/ARM          0 11/09/95 07:19:30 DIR
/QDLS/ARM-VOL1     0 06/23/95 16:39:43 DIR
/QDLS/ARMM         0 08/04/95 14:32:03 DIR

or

SYST
215  OS/400 is the remote operating system. The TCP/IP version is "V3R1M0".
SITE NAMEFMT 1
250  Now using naming format "1".
LIST /QSYS.LIB

QSYS      3584  11/15/95 16:15:33 *FILE    /QSYS.LIB/QSYS.LIB/QPRTRPYL.PRTF
QSYS      18432 11/15/95 16:15:33 *FILE      /QSYS.LIB/QSYS.LIB/QPRTSBSD.PRTF
QSYS      5632  11/15/95 16:15:33 *FILE      /QSYS.LIB/QSYS.LIB/QPRTSPLF.PRTF
QSYS      8704  11/15/95 16:15:33 *FILE      /QSYS.LIB/QSYS.LIB/QPRTSPLQ.PRTF

}
{Notes from   Angus Robertson, Magenta Systems Ltd,

MORE TYPES OF SHIT ON THE AS/400 FILE SYSTEM

 Object types that are commonly used or that you are likely to see on
 this display include the following:
AUTL        Authorization list
BLKSF       Block special file
CFGL        Configuration list
CLS         Class
CMD         Command
CTLD        Controller description
DDIR        Distributed directory
DEVD        Device description
DIR         Directory
DOC         Document
DSTMF       Distributed stream file
FILE        Database file or device file
FLR         Folder
JOBD        Job description
JOBQ        Job queue
LIB         Library
LIND        Line description
MSGQ        Message queue
OUTQ        Output queue
PGM         Program
SBSD        Subsystem description
SOMOBJ      System Object Model object
STMF        Stream file
SYMLNK      Symbolic link
USRPRF      User profile
}
  LI := AItem as TIdOwnerFTPListItem;
  LI.ModifiedAvail := False;
  LI.SizeAvail := False;
  
  LBuffer := AItem.Data;
  LI.OwnerName := Fetch(LBuffer);

  LBuffer := TrimLeft(LBuffer);
  //we have to make sure that the size feild really exists or the
  //the parser is thrown off
  if (LBuffer <> '') and (IsNumeric(LBuffer[1])) then begin
    LI.Size := IndyStrToInt64(FetchLength(LBuffer,9),0);
    LI.SizeAvail := True;
    LBuffer := TrimLeft(LBuffer);
  end;
  //Sometimes the date and time feilds will not present
  if (LBuffer <> '') and (IsNumeric(LBuffer[1])) then begin
    LDate := Trim(StrPart(LBuffer, 8));
    if (LBuffer <> '') and (LBuffer[1] <> ' ') then begin
      LDate := LDate + Fetch(LBuffer);
    end;
    if LDate <> '' then begin
      LI.ModifiedDate := AS400Date(LDate);
       LI.ModifiedAvail := True;
    end;
    LTime := Trim(StrPart(LBuffer, 8));
    if (LBuffer <> '') and (LBuffer[1] <> ' ') then begin
      LTime := LTime + Fetch(LBuffer);
    end;
    if LTime <> '' then begin
      LI.ModifiedDate :=  LI.ModifiedDate + TimeHHMMSS(LTime);
    end;
  end;
  //most of this data is manditory so things are less sensitive to positions
  LBuffer := Trim(LBuffer);
  LObjType := FetchLength(LBuffer,11);
  //A file object is something like a file but it can contain members - treat as dir.
  //  Odd, I know.
  //There are also several types of file objects
  //note that I'm not completely sure about this so it's commented out.  JPM

//  if TextStartsWith(LObjType, '*FILE') then begin {do not localize}
//    LI.ItemType := ditDirectory;
//  end;
  if IdGlobal.PosInStrArray(LObjType,DIR_TYPES)>-1 then begin {do not localize}
    LI.ItemType := ditDirectory;
    if TextEndsWith(LBuffer,'/') then begin
      LBuffer := Fetch(LBuffer,'/');
    end;
  end;
  LI.FileName := TrimLeft(LBuffer);
  if LI.FileName = '' then begin
    LI.FileName := LI.OwnerName;
    LI.OwnerName := '';
  end;

  LI.LocalFileName := LowerCase(StripPath(AItem.FileName, '/'));
  Result := True;
  except
    Result := False;
  end;
end;

initialization
  RegisterFTPListParser(TIdFTPLPAS400);
finalization
  UnRegisterFTPListParser(TIdFTPLPAS400);
end.
