-- 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],
  MDSStorage USING [Node, Free];

FTPUserCommon: PROGRAM
  IMPORTS String, Storage: MDSStorage, FTPPrivateDefs
  EXPORTS FTPDefs
  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