{
  $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.7    10/26/2004 9:46:36 PM  JPMugaas
  Updated refs.

  Rev 1.6    6/8/2004 12:42:22 PM  JPMugaas
  Fixed an Invalid Type Cast problem.

  Rev 1.5    4/19/2004 5:05:36 PM  JPMugaas
  Class rework Kudzu wanted.

  Rev 1.4    2004.02.03 5:45:24 PM  czhower
  Name changes

  Rev 1.3    10/19/2003 3:36:02 PM  DSiders
  Added localization comments.

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

  Rev 1.1    2/19/2003 05:53:20 PM  JPMugaas
  Minor restructures to remove duplicate code and save some work with some
  formats.  The Unix parser had a bug that caused it to give a False positive
  for Xercom MicroRTOS.

  Rev 1.0    2/19/2003 06:04:36 AM  JPMugaas
  IBM MVS parser has been ported to new design.
}

unit IdFTPListParseMVS;

interface

uses
  IdFTPList, IdFTPListParseBase, IdFTPListTypes, IdObjs;

{
  This should work with IBM MVS, OS/390, and z/OS.

  Note that in z/OS, there is no need for a parser for the HFS (hierarchical file
  system) because the server would present a Unix-like list for that file system.
}

type
  TIdJESJobStatus = (IdJESNotApplicable, IdJESReceived, IdJESHold, IdJESRunning, IdJESOuptutAvailable);

  TIdMVSFTPListItem = class(TIdRecFTPListItem)
  protected
    FBlockSize : Integer;
    FMigrated : Boolean;
    FVolume : String;
    FUnit : String;
    FOrg : String; //data set organization
        FMVSNumberExtents: Integer;
    FMVSNumberTracks: Integer;
  public
    constructor Create(AOwner: TIdCollection); override;
    property Migrated : Boolean read FMigrated write FMigrated;
    property BlockSize : Integer read FBlockSize write FBlockSize;
    property RecLength;
    property RecFormat;
    property NumberRecs;
    property Volume : String read FVolume write FVolume;
    //can't be unit because that's a reserved word
    property Units : String read FUnit write FUnit;
    property Org : String read FOrg write FOrg; //data set organization
    property NumberExtents: Integer read FMVSNumberExtents write FMVSNumberExtents;
    property NumberTracks: Integer read FMVSNumberTracks write FMVSNumberTracks;
  end;
  TIdMVSJESFTPListItem = class(TIdOwnerFTPListItem)
  protected
    FMVSJobStatus : TIdJESJobStatus;
    FMVSJobSpoolFiles : Integer;
  public
    constructor Create(AOwner: TIdCollection); override;
    property JobStatus : TIdJESJobStatus read FMVSJobStatus write FMVSJobStatus;
    property JobSpoolFiles : Integer read FMVSJobSpoolFiles write FMVSJobSpoolFiles;
  end;
  TIdMVSJESIntF2FTPListItem = class(TIdOwnerFTPListItem)
  protected
    FJobStatus : TIdJESJobStatus;
    FJobSpoolFiles : Integer;
    FDetails : TIdStrings;
    procedure SetDetails(AValue : TIdStrings);
  public
    constructor Create(AOwner: TIdCollection); override;
    destructor Destroy; override;
    property Details : TIdStrings read FDetails write SetDetails;
    property JobStatus : TIdJESJobStatus read FJobStatus write FJobStatus;
    property JobSpoolFiles : Integer read FJobSpoolFiles write FJobSpoolFiles;
  end;

  TIdFTPLPMVS = class(TIdFTPListBaseHeader)
  protected
    class function MakeNewItem(AOwner : TIdFTPListItems)  : TIdFTPListItem; override;
    class function IsHeader(const AData: String): Boolean; override;
    class function ParseLine(const AItem : TIdFTPListItem; const APath : String=''): Boolean; override;
  public
    class function GetIdent : String; override;
  end;
  TIdFTPLPMVSPartitionedDataSet = class(TIdFTPListBaseHeader)
  protected
    class function IsHeader(const AData: String): Boolean;  override;
    class function ParseLine(const AItem : TIdFTPListItem; const APath : String=''): Boolean; override;
  public
    class function GetIdent : String; override;
  end;
  //Jes queues
  TIdFTPLPMVSJESInterface1 = class(TIdFTPListBase)
  protected
    class function MakeNewItem(AOwner : TIdFTPListItems)  : TIdFTPListItem; override;
    class function IsMVS_JESNoJobsMsg(const AData: String): Boolean; virtual;
    class function ParseLine(const AItem : TIdFTPListItem; const APath : String=''): Boolean; override;
  public
    class function GetIdent : String; override;
    class function CheckListing(AListing : TIdStrings; const ASysDescript : String =''; const ADetails : Boolean = True): boolean; override;
    class function ParseListing(AListing : TIdStrings; ADir : TIdFTPListItems) : boolean; override;
  end;
  TIdFTPLPMVSJESInterface2 = class(TIdFTPListBase)
  protected
    class function MakeNewItem(AOwner : TIdFTPListItems)  : TIdFTPListItem; override;
    class function IsMVS_JESIntF2Header(const AData: String): Boolean;
    class function ParseLine(const AItem : TIdFTPListItem; const APath : String=''): Boolean; override;
  public
    class function GetIdent : String; override;
    class function CheckListing(AListing : TIdStrings; const ASysDescript : String =''; const ADetails : Boolean = True): boolean; override;
    class function ParseListing(AListing : TIdStrings; ADir : TIdFTPListItems) : boolean; override;
  end;

implementation

uses
  IdGlobal, IdFTPCommon, IdGlobalProtocols, IdStrings, IdSys;

{ TIdFTPLPMVS }

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

class function TIdFTPLPMVS.IsHeader(const AData: String): Boolean;
//Volume Unit    Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname
//Volume Unit  Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname
//Volume Unit     Date  Ext Used Recfm Lrecl BlkSz Dsorg Dsname
var lvolp, lunp, lrefp, lextp, lusedp,
    lrecp, lBlkSz, lDsorg, lDsnp : Integer;

{Note that this one is a little more difficult because I could not find
 a MVS machine that accepts anonymous FTP.  So I have to do the best I can
 with some old posts where people had posted dir structures they got from FTP.}
begin
  lvolp := IndyPos('Volume',AData);  {Do not translate}
  lunp := IndyPos('Unit',AData);    {Do not translate}
  lrefp := IndyPos('Referred',AData);  {Do not translate}
  if lrefp = 0 then
  begin
    lrefp := IndyPos('Date',AData);  {Do not translate}
  end;
  lextp := IndyPos('Ext',AData);     {Do not translate}
  lusedp := IndyPos('Used',AData);   {Do not translate}
  lrecp := IndyPos('Lrecl',AData);   {Do not translate}
  lBlkSz := IndyPos('BlkSz',AData);   {Do not translate}
  lDsorg := IndyPos('Dsorg',AData);    {Do not translate}
  lDsnp := IndyPos('Dsname',AData);   {Do not translate}
  Result := (lvolp <> 0) and (lunp > lvolp) and
            (lrefp > lunp) and (lextp > lrefp) and
            (lusedp > lextp) and (lrecp > lusedp) and
            (lBlkSz > lrecp) and (lDsorg > lBlkSz) and
            (lDsnp > lDsorg);
end;

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

class function TIdFTPLPMVS.ParseLine(const AItem: TIdFTPListItem;
  const APath: String): Boolean;
{Much of this is based on a thread at:

http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&selm=DLspv2.G2w%40epsilon.com&rnum=2

and

http://www.snee.com/bob/opsys/part6mvs.pdf

Note:  Thread concerning MVS Data Set Size
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&threadm=jcmorris.767551300%40mwunix&rnum=15&prev=/groups%3Fq%3DMVS%2BRecfm%2BV%26start%3D10%26hl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3Dutf-8%26selm%3Djcmorris.767551300%2540mwunix%26rnum%3D15
http://groups.google.com/groups?q=MVS+Recfm+V&start=10&hl=en&lr=&ie=UTF-8&oe=utf-8&selm=jcmorris.767551300%40mwunix&rnum=15

http://www.isc.ucsb.edu/tsg/ftp-to-mvs.html
http://www.lsu.edu/ocs/tsc/os390doc/mvsftp.html
}

  function IsMVSMigrated(const AData : String) : Boolean;
  begin
    Result := (Copy(AData,1,8) = 'Migrated') or {do not localize}
      ( Copy(AData,1,6) = 'MIGRAT');  {do not localize}
  end;

  function IsPseudoDir(const AData : String) : Boolean;
  begin
  //In newer implementations, a directory mode is available, see if
  //item is a Pseudo-directory
    Result := (Copy(AData,1,16)='Pseudo Directory');  {do not localize}
  end;

  function CanGetAttributes(const AData : String) : Boolean;
  begin
    Result := (IsMVSMigrated(AData)=False) and ( Copy(AData,1,5) <> 'Error')  {do not localize}
      and (IsPseudoDir(AData)=False);
  end;

var i : Integer;
    s : TIdStrings;
    LI : TIdMVSFTPListItem;
//NOTE:  File Size is not supported at all
//because the file size is calculated with something like this:
//      BlkSz * Blks/Trk * Trks
//but you can not get MVS DEVINFO macro so you do not have enough information
//to work with.
begin
  AItem.ModifiedAvail := False;
  AItem.SizeAvail := False;
  LI := AItem as TIdMVSFTPListItem;
  if IsMVSMigrated(AItem.Data) then
  begin
    LI.Migrated := True;
  end;
  if IsPseudoDir(AItem.Data) then
  begin
    LI.ItemType :=  ditDirectory;
  end;
  if CanGetAttributes(AItem.Data) then
  begin
    s := TIdStringList.Create;
    try
      SplitColumns(AItem.Data,s);
      if s.Count >0 then
      begin
        LI.Volume := s[0];
      end;
      if s.Count >1 then
      begin
        LI.Units := s[1];
      end;
      if s.Count >2 then
      begin
        //Sometimes, the Referred Column will contain a date.
        //e.g. **NONE**
        //Documented in: Communications Server for z/OS V1R2 TCP/IP Implementation Guide Volume 2: UNIX Applications
        //URL: http://www.redbooks.ibm.com/pubs/pdfs/redbooks/sg245228.pdf
        if IsNumeric(s[2][1]) then
        begin
          LI.ModifiedDate :=  MVSDate(s[2]);
            LI.ModifiedAvail := True;
        end;
      end;
      if s.Count >3 then
      begin
        LI.NumberExtents := Sys.StrToInt(s[3],0);
      end;
      if s.Count >4 then
      begin
        LI.NumberTracks := Sys.StrToInt(s[4],0);
      end;
      if s.Count >5 then
      begin
        LI.RecFormat := s[5];
      end;
      if s.Count >6 then
      begin
        LI.RecLength := Sys.StrToInt(s[6],0);
      end;
      if s.Count >7 then
      begin
        LI.BlockSize := Sys.StrToInt(s[7],0);
      end;
      if s.Count >8 then
      begin
        LI.Org := s[8];
        if (LI.Org = 'PO') then {do not localize}
        begin
          LI.ItemType :=  ditDirectory;
        end
        else
        begin
          LI.ItemType :=  ditFile;
        end;
      end;
    finally
      Sys.FreeAndNil(s);
    end;
  end;
  //Note that spaces are illegal in MVS file names (Data set namess)
  //http://www.snee.com/bob/opsys/part6mvs.pdf
  //but for filenames enclosed in '', we should tolorate spaces.
  if (AItem.Data<>'') and (AItem.Data[Length(AItem.Data)]='''') then
  begin
    i := IndyPos('''',AItem.Data)+1;
    AItem.FileName := Copy(AItem.Data,i,Length(AItem.Data)-i-1);
  end
  else
  begin
    i := RPos(' ',AItem.Data) +1;
    AItem.FileName := Copy(AItem.Data,i,Length(AItem.Data));
  end;
  Result := True;
end;

{ TIdFTPLPMVSPartitionedDataSet }

class function TIdFTPLPMVSPartitionedDataSet.GetIdent: String;
begin
  Result := 'MVS:  Partitioned Data Set'; {do not localize}
end;

class function TIdFTPLPMVSPartitionedDataSet.IsHeader(
  const AData: String): Boolean;
//Name     VV.MM  Created     Changed     Size  Init   Mod   Id
//
//or
//
//Name      Size   TTR   Alias-of AC --------- Attributes --------- Amode Rmode
//if there are loaded moduals

var LPName,
  LPSize : Integer;

begin
  LPName := IndyPos('Name', AData); {do not localize}
  LPSize := IndyPos('Size', AData); {do not localize}
  Result := (LPName>0) and (LPSize > LPName);
end;

class function TIdFTPLPMVSPartitionedDataSet.ParseLine(
  const AItem: TIdFTPListItem; const APath: String): Boolean;
//MVS Particianed data sets must be treated differently than
//the regular MVS catalog.
var s : TIdStrings;

//NOTE:  File Size is not supported at all.  Size is usually size in records, not bytes
//  This is based on stuff at:
// http://publibz.boulder.ibm.com:80/cgi-bin/bookmgr_OS390/BOOKS/F1AA2032/1.5.15?SHELF=&DT=20001127174124
// and
// http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=utf-8&selm=DLspv2.G2w%40epsilon.com&rnum=2

//From Google: http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=4e7k0p%24t1v%40blackice.winternet.com&rnum=6&prev=/groups%3Fq%3DMVS%2BPartitioned%2Bdata%2Bset%2Bdirectory%26hl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26selm%3D4e7k0p%2524t1v%2540blackice.winternet.com%26rnum%3D6

{
From: Ralph Goers (rgoer@rgoer.candle.com)
Subject: Re: FTP -- VM, MVS, VMS Questions 

 	
View this article only	
Newsgroups: comp.os.os2.networking.tcp-ip
Date: 1996/01/27 
	

In message <4e7k0p$t1v@blackice.winternet.com> - frickson@gibbon.com (John C. F
rickson) writes:
:>
:>MVS directory lines look like this:
:>
:>Volume Unit  Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname
:>OVH025 3380   12/29/95  1   60  FB      80 23200  PO  MICSFILE.CLIST
:>USS018 3380   12/18/95  1   15  VB     259  8000  PS  NFS.DOC
:>Migrated                                              OAQPS.INTERIM.CNTYIM.V1.DATA.D052093
:>AIR030 3380   12/13/95  1   18  FB      80  3600  PS  OAQPS.INTERIM.PLNTNAME.DATA
:>
:>1) Ext is number of extents? Yes.
:>2) Used is number of tracks? Yes
:>3) Is the volume name important or can I ignore it?
It is important only if it is MIGRAT or migrated. This indicates that the 
dataset has not been accessed recently and has been migrated to an HSM 
volume (DASD or tape).
:>4) Is there any way to calculate filesizes?  I think I would have to know
:>   the capacity of the device (i.e. BlkSz * Blks/Trk * Trks)
I'm not sure this would do you any good. Just because you know how many bytes 
are in a track doesn't mean that each track is full. In the case of an FB 
file you can figure out how many blocks will fit in a track.  This may not be 
true of V type files.
:>5) Dsorg -- PO seems to equate to partioned data set.  Is that always true?
:>   Are any values other than PO and PS possible?
Yes - you can also have VS files (VSAM) - however I have no idea why anyone 
would try to FTP one.
:>6) What is "Migrated"?  Moved to tape?  Is the file still available for
:>   download, or should I simply ignore these lines?
If you reference the file it will automatically be brought back to a DASD 
volume.
:>7) After loggin in, the server reports the current directory as "PA2",
:>   but in response to a "PWD" command, it says "PUBLIC.". ??? Not sure about this. Usually, it defaults to "USERID.".
:>8) Is the "CD" command useful for anything other than getting into
:>   partitioned data sets?
Sure. If you have a bunch of sequential datasets starting with A.B then 
cd'ing to that and then doing MGET * should download all the sequential files.
PDSes should not be downloaded but should be treated as a subdirectory.
:>9) After CD'ing into a PDS, a directory looks like this:
:>    Name     VV.MM  Created     Changed     Size  Init   Mod   Id
:>   $README   01.10 89/04/19 94/12/15 18:55    90     1     0 EWZ
:>   What are the Size, Init, Mod and ID fields?
:>   Is VV.MM version information?
:>
Size is the number of records in the file. init is the number of lines that 
were in the file when statistics were first set and mod is the number of 
lines modified since then. VV.MM is a version and modification level. These 
SPF statistics can be reset by going into edit on the member and typing STATS 
OFF, SAVE, STATS ON, SAVE. They can also be reset via option 3.5 - Member 
statistics panel.  Some programs also modify them for their own use.

You should count on these statistics even being present.  They are created 
only by SPF. Other programs use the same area in the directory entry for 
their own purposes. When you link a load module, for example, information 
about it is stored there.  When you FTP files up to MVS FTP will not create 
statistics for PDS members. 

Hope this is helpful.

Ralph
}
begin
  AItem.ModifiedAvail := False;
  AItem.SizeAvail := False;
  s := TIdStringList.Create;
  try
    SplitColumns(AItem.Data,s);
    if s.Count > 0 then
    begin
      AItem.FileName := s[0];
      //in some particianed data sets, dates are missing.
      if (s.Count > 7) and (s[3]<>'') and IsNumeric(s[3][1]) and (IndyPos('/',s[3])>0) then
      begin

        AItem.ModifiedDate := MVSDate(s[3]);
        //    Name     VV.MM  Created     Changed     Size  Init   Mod   Id
        //$README   01.10 89/04/19 94/12/15 18:55    90     1     0 EWZ
        if s.Count > 4 then
        begin
          AItem.ModifiedDate := AItem.ModifiedDate + TimeHHMMSS(s[4]);
          AItem.ModifiedAvail := True;
        end;
      end;
    end;
  finally
    Sys.FreeAndNil(s);
  end;
  Result := True;
end;

{ TIdFTPLPMVSJESInterface1 }

class function TIdFTPLPMVSJESInterface1.CheckListing(AListing: TIdStrings;
  const ASysDescript: String; const ADetails: Boolean): boolean;
var s : TIdStrings;
begin
  if AListing.Count > 0 then
  begin
    s := TIdStringList.Create;
    try
      SplitColumns(AListing[0],s);
      Result := (s.Count >2) and (PosInStrArray(Sys.Trim(s[2]),MVS_JES_Status)>-1);
      if Result and (s.Count > 3) then
      begin
        Result := IsNumeric(s[3]) or (s[3][1]='-');
      end;
      if Result = False then
      begin
        Result := IsMVS_JESNoJobsMsg(AListing[0]);
      end;
    finally
      Sys.FreeAndNil(s);
    end;
  end
  else
  begin
    Result := False;
  end;
end;

class function TIdFTPLPMVSJESInterface1.GetIdent: String;
begin
  Result := 'MVS:  JES Queue Interface 1';  {do not localize}
end;

class function TIdFTPLPMVSJESInterface1.IsMVS_JESNoJobsMsg(
  const AData: String): Boolean;
begin
  Result := (AData = 'No jobs found on JES queue'); {do not localize}
end;

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

class function TIdFTPLPMVSJESInterface1.ParseLine(
  const AItem: TIdFTPListItem; const APath: String): Boolean;
{
ALFREDCA  JOB03192  OUTPUT    3 Spool Files
ALFREDCA  JOB03193  OUTPUT    0 Spool Files
ALFREDCA  JOB03194  INPUT
12345678901234567890123456789012345678901234567890
         1         2         3         4         5
#From: IBM Communications Server for OS/390 V2R10 TCP/IP Implementation Guide Volume 2: UNIX Applications
#Obtained at: http://www.redbooks.ibm.com/pubs/pdfs/redbooks/sg245228.pdf
}
var LBuf : String;
  LI : TIdMVSJESFTPListItem;
begin
  AItem.ModifiedAvail := False;
  AItem.SizeAvail := False;
  LI := AItem as TIdMVSJESFTPListItem;
  //owner
  LBuf := AItem.Data;
  LI.OwnerName := Fetch(LBuf);
  LBuf := Sys.TrimLeft(LBuf);
  //filename
  LI.FileName  := Fetch(LBuf);
  LBuf := Sys.TrimLeft(LBuf);
  case PosInStrArray (Fetch(LBuf),MVS_JES_Status) of
    0 : LI.JobStatus := IdJESReceived;        // 'INPUT'  job received but not run yet
    1 : LI.JobStatus := IdJESHold;            // 'HELD'   job is in hold status
    2 : LI.JobStatus := IdJESRunning;         // 'ACTIVE' job is running
    3 : LI.JobStatus := IdJESOuptutAvailable; // 'OUTPUT' job has finished and has output available
  end;
  //spool file output if available
  LBuf := Sys.TrimLeft(LBuf);
  LI.JobSpoolFiles := Sys.StrToInt(Fetch(LBuf),0);
  Result := True;
end;

class function TIdFTPLPMVSJESInterface1.ParseListing(AListing: TIdStrings;
  ADir: TIdFTPListItems): boolean;
var LItem : TIdFTPListItem;
  i : Integer;
begin
  Result := False;
  if AListing.Count >0 then
  begin
    if IsMVS_JESNoJobsMsg(Alisting[0])=False then
    begin
      for i := 0 to AListing.Count -1 do
      begin
        LItem := MakeNewItem(ADir);
        LItem.Data := AListing[i];
        ParseLine(LItem);
      end;
    end
    else
    begin
      Result := True;
    end;
  end;
end;

{ TIdFTPLPMVSJESInterface2 }

class function TIdFTPLPMVSJESInterface2.CheckListing(AListing: TIdStrings;
  const ASysDescript: String; const ADetails: Boolean): boolean;
begin
  Result := False;
  if AListing.Count > -1 then
  begin
    Result := IsMVS_JESIntF2Header(AListing[0]);
  end;
end;

class function TIdFTPLPMVSJESInterface2.GetIdent: String;
begin
  Result := 'MVS:  JES Queue Interface 2';  {do not localize}
end;

class function TIdFTPLPMVSJESInterface2.IsMVS_JESIntF2Header(
  const AData: String): Boolean;
begin
  Result := (AData = 'JOBNAME  JOBID    OWNER    STATUS CLASS');  {do not localize}
end;

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

class function TIdFTPLPMVSJESInterface2.ParseLine(
  const AItem: TIdFTPListItem; const APath: String): Boolean;
{
JOBNAME  JOBID    OWNER    STATUS CLASS
BPXAS    STC02133 ++++++++ OUTPUT STC      RC=000 2 spool
BPXAS    STC00916 ++++++++ OUTPUT STC      RC=000 2 spool
BPXAS    STC02132 ++++++++ ACTIVE STC
BPXAS    STC02131 ++++++++ ACTIVE STC
123456789012345678901234567890123456789012345678901234567890
         1         2         3         4         5         6
}
var LBuf, LNo : String;
    LPos, LPos2 : Integer;
  LI : TIdMVSJESIntF2FTPListItem;
begin
  LI := AItem as TIdMVSJESIntF2FTPListItem;
  LI.ModifiedAvail := False;
  LI.SizeAvail := False;
  LI.FileName := Sys.Trim(Copy(AItem.Data,10,8));
  LI.OwnerName := Sys.Trim(Copy(AItem.Data,19,7));
  if IsLineStr(LI.OwnerName) then
  begin
    LI.OwnerName := '';
  end;
  case PosInStrArray (Sys.Trim(Copy(AItem.Data,28,7)),MVS_JES_Status) of
    0 : LI.JobStatus := IdJESReceived;          // 'INPUT'   job received but not run yet
    1 : LI.JobStatus := IdJESHold;              // 'HELD'    job is in hold status
    2 : LI.JobStatus := IdJESRunning;           // 'ACTIVE'  job is running
    3 : LI.JobStatus := IdJESOuptutAvailable;   // 'OUTPUT'  job has finished and has output available
  end;
  LBuf := Sys.Trim(Copy(AItem.Data,35,Length(AItem.Data)));
  LPos := IndyPos(' spool',LBuf); {do not localize}
  if LPos = 0 then
  begin
    Result := False;
    Exit;
  end;
  LNo := '';
  for LPos2 := LPos-1 downto 1 do
  begin
    if (LBuf[LPos2]=' ') then
    begin
      break;
    end
    else
    begin
      LNo := LBuf[LPos2] + LNo;
    end;
  end;
  LI.JobSpoolFiles := Sys.StrToInt(LNo,0);
  Result := True;
end;

class function TIdFTPLPMVSJESInterface2.ParseListing(AListing: TIdStrings;
  ADir: TIdFTPListItems): boolean;
var LItem : TIdFTPListItem;
  i : Integer;
  LStartLine : Integer;
  LDetailFlag : Boolean;
  LI : TIdMVSJESIntF2FTPListItem;
begin
  if AListing.Count >0 then
  begin
    if IsMVS_JESIntf2Header(AListing[0]) then
    begin
      LStartLine := 1;
    end
    else
    begin
      LStartLine := 0;
    end;
    LDetailFlag := False;
    for i := LStartLine to AListing.Count -1 do
    begin
      if LDetailFlag then
      begin
        if ADir.Count > 0 then
        begin
          LI := ADir.Items[ADir.Count -1] as TIdMVSJESIntF2FTPListItem;
          LI.Details.Add(AListing[i]);
        end;
      end
      else
      begin
        if AListing[i] = '--------' then
        begin
          LDetailFlag := True;
        end
        else
        begin
          LItem := MakeNewItem(ADir);
          LItem.Data := AListing[i];
          ParseLine(LItem);
        end;
      end;
    end;
    Result := True;
  end
  else
  begin
    Result := False;
  end;
end;

{ TIdMVSFTPListItem }

constructor TIdMVSFTPListItem.Create(AOwner: TIdCollection);
begin
  inherited Create(AOwner);
  FSizeAvail := False;  //we can't get the file size from a MVS system
end;

{ TIdMVSJESIntFFTPListItem }

constructor TIdMVSJESIntF2FTPListItem.Create(AOwner: TIdCollection);
begin
  inherited Create(AOwner);
  FDetails := TIdStringList.Create;
end;

destructor TIdMVSJESIntF2FTPListItem.Destroy;
begin
  Sys.FreeAndNil(FDetails);
  inherited Destroy;
end;

procedure TIdMVSJESIntF2FTPListItem.SetDetails(AValue: TIdStrings);
begin
  FDetails.Assign(AValue);
end;

{ TIdMVSJESFTPListItem }

constructor TIdMVSJESFTPListItem.Create(AOwner: TIdCollection);
begin
  inherited Create(AOwner);
   Self.JobStatus := IdJESNotApplicable;
end;

initialization
  RegisterFTPListParser(TIdFTPLPMVS);
  RegisterFTPListParser(TIdFTPLPMVSPartitionedDataSet);
  RegisterFTPListParser(TIdFTPLPMVSJESInterface1);
  RegisterFTPListParser(TIdFTPLPMVSJESInterface2);
finalization
  UnRegisterFTPListParser(TIdFTPLPMVS);
  UnRegisterFTPListParser(TIdFTPLPMVSPartitionedDataSet);
  UnRegisterFTPListParser(TIdFTPLPMVSJESInterface1);
  UnRegisterFTPListParser(TIdFTPLPMVSJESInterface2);
end.

