-- file: FTPUserCommon.mesa - last edit: 
  -- MAS May 19, 1980  6:11 PM  
  -- HGM July 28, 1980  9:16 PM  
  -- JEW October 31, 1978  11:57 AM  

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  FTPDefs,
  FTPPrivateDefs,
  String USING [AppendChar, AppendString, EquivalentString],
  Storage USING [Node, Free];

FTPUserCommon: PROGRAM
  -- import list
    IMPORTS String, Storage, FTPPrivateDefs
  -- export list
    EXPORTS FTPDefs
  -- share list
    SHARES FTPDefs
  = BEGIN OPEN FTPDefs, FTPPrivateDefs;

-- **********************!  Constants  !***********************

ftpsystem: POINTER TO FTPSystem = LocateFtpSystemObject[];
 
-- **********************!  Program Primitives  !***********************

FTPCreateUser: PUBLIC PROCEDURE [filePrimitives: FilePrimitives, communicationPrimitives: CommunicationPrimitives] RETURNS [ftpuser: FTPUser] =
  BEGIN
    empty: PropertyList ← DESCRIPTOR[NIL,6];
  -- verify presence of communication primitives
    IF communicationPrimitives = NIL THEN Abort[communicationPrimitivesNotSpecified];
  -- allocate and initialize ftpuser object
    ftpuser ← Storage.Node[SIZE[FTPUserObject]];
    ftpuser↑ ← FTPUserObject[
      filePrimitives: filePrimitives,
      fileSystem: ,
      communicationPrimitives: communicationPrimitives,
      communicationSystem: NIL,
      remoteFtpSocket: ftpSocket, remoteMtpSocket: mtpSocket,
      ftper: NIL, mtper: NIL,
      propertyList: empty, primaryPropertyList: empty, secondaryPropertyList: empty,
      purpose: filesAndMail,
      state: unconnected,
      intent: ,
      nextBlockType: ,
      numberOfRecipients: , numberOfValidRecipients: ,
      numberOfBytesExpected: , numberOfBytesReceived: ];
  -- create communication system
    BEGIN ENABLE UNWIND => FTPDestroyUser[ftpuser];
    ftpuser.communicationSystem ←
      communicationPrimitives.CreateCommunicationSystem[];
  -- allocate and initialize ftper objects
    ftpuser.ftper ← CreateFTPer[communicationPrimitives, ftpuser.communicationSystem];
    ftpuser.mtper ← CreateFTPer[communicationPrimitives, ftpuser.communicationSystem];
  -- allocate and initialize property list objects
    ftpuser.propertyList ← CreatePropertyList[];
    ftpuser.primaryPropertyList ← CreatePropertyList[];
    ftpuser.secondaryPropertyList ← CreatePropertyList[];
    END; -- enable
  END;

FTPDestroyUser: PUBLIC PROCEDURE [ftpuser: FTPUser] =
  BEGIN OPEN ftpuser;
  -- close network connection if any
    FTPCloseConnection[ftpuser];
  -- release property list objects if any
    IF BASE[propertyList] # NIL THEN DestroyPropertyList[propertyList];
    IF BASE[primaryPropertyList] # NIL THEN DestroyPropertyList[primaryPropertyList];
    IF BASE[secondaryPropertyList] # NIL THEN
      DestroyPropertyList[secondaryPropertyList];
  -- release ftper objects if any
    IF ftper # NIL THEN DestroyFTPer[ftper];
    IF mtper # NIL THEN DestroyFTPer[mtper];
  -- destroy communication system
    IF communicationSystem # NIL THEN
      communicationPrimitives.DestroyCommunicationSystem[communicationSystem];
  -- release ftpuser object
    Storage.Free[ftpuser];
  END;

-- **********************!  Connection Primitives  !***********************

FTPSetContactSocket: PUBLIC PROCEDURE [ftpuser: FTPUser, socket: LONG INTEGER, purpose: Purpose] =
  BEGIN
  -- record remote ftp server's contact socket
    IF purpose # mail THEN
      ftpuser.remoteFtpSocket ← IF socket # 0 THEN socket ELSE ftpSocket;
  -- record remote mtp server's contact socket
    IF purpose # files THEN
      ftpuser.remoteMtpSocket ← IF socket # 0 THEN socket ELSE mtpSocket;
  END;

FTPOpenConnection: PUBLIC PROCEDURE [ftpuser: FTPUser, host: STRING, purpose: Purpose, remoteInsignia: STRING] =
  BEGIN OPEN ftpuser.communicationPrimitives;
  -- forked procedures
    OpenConnectionProcess: PROCEDURE [open: Open] =
      BEGIN OPEN open;
      -- open connection
        xtper.connection ← OpenConnection[
          xtper.communicationSystem, host, socket, responseFromServerSeconds];
      -- exchange version numbers
        PutGetVersion[xtper, version ! UNWIND => CloseConnectionProcess[xtper]]; 
      END;
    CloseConnectionProcess: PROCEDURE [xtper: FTPer] =
      BEGIN
      -- nop unless connection open
        IF xtper.connection = NIL THEN RETURN;
      -- close connection
        CloseConnection[xtper.communicationSystem, xtper.connection]; 
        xtper.connection ← NIL;
      END;
  -- local types
    Open: TYPE = POINTER TO OpenObject;
    OpenObject: TYPE = RECORD [
      xtper: FTPer,
      socket: LONG INTEGER,
      version: Byte];
  -- local constants
    ftper: FTPer = ftpuser.ftper;
    mtper: FTPer = ftpuser.mtper;
  -- local variables
    openObject1: OpenObject ← [
      xtper: ftper,
      socket: ftpuser.remoteFtpSocket,
      version: ftpVersion];
    openObject2: OpenObject ← [
      xtper: mtper,
      socket: ftpuser.remoteMtpSocket,
      version: mtpVersion];
  -- verify state
    IF ftpuser.state # unconnected THEN Abort[connectionAlreadyEstablished];
  -- verify presence of required modules and primitives
    IF purpose # mail THEN
      BEGIN
      IF ~ftpsystem.userFilesLoaded THEN Abort[filesModuleNotLoaded];
      IF ftpuser.filePrimitives = NIL THEN Abort[filePrimitivesNotSpecified];
      END;
    IF purpose # files AND ~ftpsystem.userMailLoaded THEN Abort[mailModuleNotLoaded];
  -- open connection(s) to remote server(s)
    BEGIN ENABLE UNWIND => ForkProcessPair[CloseConnectionProcess, ftper, mtper];
    IF purpose = filesAndMail THEN
      ForkProcessPair[OpenConnectionProcess, @openObject1, @openObject2]
    ELSE OpenConnectionProcess[
      IF purpose = files THEN @openObject1 ELSE @openObject2];
  -- construct remote insignia
    IF remoteInsignia # NIL THEN
      BEGIN
      remoteInsignia.length ← 0;
      IF purpose # mail THEN String.AppendString[remoteInsignia, ftper.inputString];
      IF purpose # files
      AND ~String.EquivalentString[remoteInsignia, mtper.inputString] THEN
        BEGIN
        IF remoteInsignia.length # 0 THEN
          String.AppendChar[remoteInsignia, insigniaSeparator];
        String.AppendString[remoteInsignia, mtper.inputString];
        END;
      END;
  -- create file system
    IF purpose # mail THEN
      ftpuser.fileSystem ← ftpuser.filePrimitives.CreateFileSystem[ftpsystem.bufferSize];
    END; -- enable
  -- record purpose of connection and note connection open
    ftpuser.purpose ← purpose;
    ftpuser.state ← connected; 
  END;

FTPRenewConnection: PUBLIC PROCEDURE [ftpuser: FTPUser] =
  BEGIN OPEN ftpuser;
  -- forked procedure
    RenewConnectionProcess: PROCEDURE [renew: Renew] =
      BEGIN OPEN renew;
      -- renew connection to remote server
        PutGetVersion[xtper, version]; 
      END;
  -- local types
    Renew: TYPE = POINTER TO RenewObject;
    RenewObject: TYPE = RECORD [xtper: FTPer, version: Byte];
  -- local variables
    renewObject1: RenewObject ← [xtper: ftper, version: ftpVersion];
    renewObject2: RenewObject ← [xtper: mtper, version: mtpVersion];
  -- verify purpose and state
    VerifyPurposeAndState[ftpuser, filesAndMail, connected];
  -- renew connection(s) to remote server(s)
    IF purpose = filesAndMail THEN
      ForkProcessPair[RenewConnectionProcess, @renewObject1, @renewObject2]
    ELSE RenewConnectionProcess[
      IF purpose = files THEN @renewObject1 ELSE @renewObject2];
  END;

FTPCloseConnection: PUBLIC PROCEDURE [ftpuser: FTPUser] =
  BEGIN OPEN ftpuser, ftpuser.communicationPrimitives;
  -- forked procedure
    CloseConnectionProcess: PROCEDURE [xtper: FTPer] =
      BEGIN
      -- close connection
        CloseConnection[xtper.communicationSystem, xtper.connection]; 
        xtper.connection ← NIL;
      END;
  -- verify purpose and state
    IF state = unconnected THEN RETURN;
  -- destroy file system
    IF purpose # mail THEN filePrimitives.DestroyFileSystem[fileSystem];
  -- close connection(s) to remote server(s)
    IF purpose = filesAndMail THEN
      ForkProcessPair[CloseConnectionProcess, ftper, mtper]
    ELSE CloseConnectionProcess[IF purpose = files THEN ftper ELSE mtper];
  -- note connection closed
    purpose ← filesAndMail;
    state ← unconnected;
  END;

-- **********************!  Access Primitive  !***********************

FTPSetCredentials: PUBLIC PROCEDURE [ftpuser: FTPUser, status: Status, user, password: STRING] =
  BEGIN OPEN ftpuser;
  -- record user and password in primary property list
    WriteProperty[primaryPropertyList,
      IF status=primary THEN userName ELSE connectName, user];
    WriteProperty[primaryPropertyList,
      IF status=primary THEN userPassword ELSE connectPassword, password];
  END;


-- **********************!  Main Program  !***********************

-- no operation    

END. -- of FTPUserCommon