{
  $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

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdFTPList, IdFTPListParseBase, IdFTPListTypes;

{
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: TCollection); 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: TCollection); 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 : TStrings;
    procedure SetDetails(AValue : TStrings);
  public
    constructor Create(AOwner: TCollection); override;
    destructor Destroy; override;
    property Details : TStrings 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 : TStrings; const ASysDescript : String = ''; const ADetails : Boolean = True): Boolean; override;
    class function ParseListing(AListing : TStrings; 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 : TStrings; const ASysDescript : String = ''; const ADetails : Boolean = True): Boolean; override;
    class function ParseListing(AListing : TStrings; ADir : TIdFTPListItems) : Boolean; override;
  end;

implementation

uses
  IdGlobal, IdFTPCommon, IdGlobalProtocols, IdStrings, SysUtils;

{ 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;
begin
  {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.}
  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 := TextStartsWith(AData, 'Migrated') or TextStartsWith(AData, '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 := TextStartsWith(AData, 'Pseudo Directory');  {do not localize}
  end;

  function CanGetAttributes(const AData : String) : Boolean;
  begin
    Result := (not IsMVSMigrated(AData)) and (not IsPseudoDir(AData)) and
      (not TextStartsWith(AData, 'Error'));  {do not localize}
  end;

var
  i : Integer;
  s : TStrings;
  LI : TIdMVSFTPListItem;
begin
  //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.
  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 := TStringList.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 := IndyStrToInt(s[3], 0);
      end;
      if s.Count >4 then begin
        LI.NumberTracks := IndyStrToInt(s[4], 0);
      end;
      if s.Count > 5 then begin
        LI.RecFormat := s[5];
      end;
      if s.Count >6 then begin
        LI.RecLength := IndyStrToInt(s[6], 0);
      end;
      if s.Count > 7 then begin
        LI.BlockSize := IndyStrToInt(s[7], 0);
      end;
      if s.Count > 8 then
      begin
        LI.Org := s[8];
        if LI.Org = 'PO' then begin {do not localize}
          LI.ItemType :=  ditDirectory;
        end else begin
          LI.ItemType :=  ditFile;
        end;
      end;
    finally
      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 (TextEndsWith(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, MaxInt);
  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;
var
  LPName, LPSize : Integer;
begin
  //Name     VV.MM  Created     Changed     Size  Init   Mod   Id
  //
  //or
  //
  //Name      Size   TTR   Alias-of AC --------- Attributes --------- Amode Rmode
  //if there are loaded moduals
  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;
var
  s : TStrings;
//MVS Particianed data sets must be treated differently than
//the regular MVS catalog.

//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 := TStringList.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
    FreeAndNil(s);
  end;
  Result := True;
end;

{ TIdFTPLPMVSJESInterface1 }

class function TIdFTPLPMVSJESInterface1.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
      SplitColumns(AListing[0], s);
      Result := (s.Count > 2) and (PosInStrArray(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 not Result then begin
        Result := IsMVS_JESNoJobsMsg(AListing[0]);
      end;
    finally
      FreeAndNil(s);
    end;
  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;
var
  LBuf : String;
  LI : TIdMVSJESFTPListItem;
begin
  {
  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
  }
  AItem.ModifiedAvail := False;
  AItem.SizeAvail := False;
  LI := AItem as TIdMVSJESFTPListItem;
  //owner
  LBuf := AItem.Data;
  LI.OwnerName := Fetch(LBuf);
  LBuf := TrimLeft(LBuf);
  //filename
  LI.FileName := Fetch(LBuf);
  LBuf := 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 := TrimLeft(LBuf);
  LI.JobSpoolFiles := IndyStrToInt(Fetch(LBuf), 0);
  Result := True;
end;

class function TIdFTPLPMVSJESInterface1.ParseListing(AListing: TStrings;
  ADir: TIdFTPListItems): Boolean;
var
  LItem : TIdFTPListItem;
  i : Integer;
begin
  Result := False;
  if AListing.Count > 0 then
  begin
    if not IsMVS_JESNoJobsMsg(Alisting[0]) 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: TStrings;
  const ASysDescript: String; const ADetails: Boolean): Boolean;
begin
  Result := False;
  if AListing.Count > 0 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;
var
  LBuf, LNo : String;
  LPos, LPos2 : Integer;
  LI : TIdMVSJESIntF2FTPListItem;
begin
  {
  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
  }
  LI := AItem as TIdMVSJESIntF2FTPListItem;
  LI.ModifiedAvail := False;
  LI.SizeAvail := False;
  LI.FileName := Trim(Copy(AItem.Data, 10, 8));
  LI.OwnerName := Trim(Copy(AItem.Data, 19, 7));
  if IsLineStr(LI.OwnerName) then begin
    LI.OwnerName := '';
  end;
  case PosInStrArray(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 := Trim(Copy(AItem.Data, 35, MaxInt));
  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;
    LNo := LBuf[LPos2] + LNo;
  end;
  LI.JobSpoolFiles := IndyStrToInt(LNo, 0);
  Result := True;
end;

class function TIdFTPLPMVSJESInterface2.ParseListing(AListing: TStrings;
  ADir: TIdFTPListItems): Boolean;
var
  LItem : TIdFTPListItem;
  i : Integer;
  LStartLine : Integer;
  LDetailFlag : Boolean;
  LI : TIdMVSJESIntF2FTPListItem;
begin
  Result := False;
  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 if AListing[i] = '--------' then begin
        LDetailFlag := True;
      end else
      begin
        LItem := MakeNewItem(ADir);
        LItem.Data := AListing[i];
        ParseLine(LItem);
      end;
    end;
    Result := True;
  end;
end;

{ TIdMVSFTPListItem }

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

{ TIdMVSJESIntFFTPListItem }

constructor TIdMVSJESIntF2FTPListItem.Create(AOwner: TCollection);
begin
  inherited Create(AOwner);
  FDetails := TStringList.Create;
end;

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

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

{ TIdMVSJESFTPListItem }

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

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

end.
