-- FTPUserMailOut.mesa, Edited by: HGM July 28, 1980  9:29 PM  

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  FTPDefs,
  FTPPrivateDefs,
  String USING [AppendChar, StringToDecimal];

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

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

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

FTPBeginDeliveryOfMessage: PUBLIC PROCEDURE [ftpuser: FTPUser] =
  BEGIN OPEN ftpuser;
  -- verify purpose and state
    VerifyPurposeAndState[ftpuser, mail, connected];
  -- If the message gets forwarded, IFSs want to know where to send complains.
    IF primaryPropertyList[userName]=NIL THEN Abort[credentialsMissing];
  -- send store mail command
    PutCommand[mtper, markStoreMail, 0];
  -- initialize number of recipients and valid recipients to zero
    numberOfRecipients ← numberOfValidRecipients ← 0;
  -- initialize property list for non-standard use
  -- Note:  FTPSendRecipientOfMessage circumvents WriteProperty
  --   to avoid allocation and release of main storage in this inner loop.
    ResetPropertyList[propertyList];
  -- note readiness to send recipients
    state ← messageRecipientsBeingSent;
  END;

FTPSendRecipientOfMessage: PUBLIC PROCEDURE [ftpuser: FTPUser, mailboxName, mailboxHostName, dmsName: STRING] =
  BEGIN OPEN ftpuser;
  propertyList: PropertyList ← ftpuser.propertyList;
  -- verify purpose and state
    VerifyPurposeAndState[ftpuser, mail, messageRecipientsBeingSent];
  -- store mailbox name in property list
  -- Note:  WriteProperty is circumvented
  --   to avoid allocation and release of main storage in this inner loop.
    propertyList[mailbox] ← mailboxName;
    IF numberOfRecipients=0 THEN propertyList[sender] ← primaryPropertyList[userName];
  -- send the property list
    PutPropertyList[mtper, propertyList ! UNWIND =>
      propertyList[mailbox] ← propertyList[sender] ← NIL];
    propertyList[mailbox] ← propertyList[sender] ← NIL;
  -- increment number of recipients and valid recipients
    numberOfRecipients ← numberOfRecipients + 1;
    numberOfValidRecipients ← numberOfValidRecipients + 1;
  END;

FTPIdentifyNextRejectedRecipient: PUBLIC PROCEDURE [ftpuser: FTPUser, errorMessage: STRING] RETURNS [recipientNumber: CARDINAL, recipientError: RecipientError] =
  BEGIN OPEN ftpuser;
  -- Note: Returns recipientNumber=0 if no more mailbox exceptions.
  -- local constants
    inputString: STRING = mtper.inputString;
    textualRecipientNumber: STRING = [maxStringLength];
  -- local variables
    mark, code: Byte;
    string: STRING;
    i: CARDINAL;
    shouldBe: UserState;
  -- verify purpose and state
    shouldBe ← SELECT state FROM
      messageRecipientsBeingSent,
        initialMailboxExceptionsBeingReceived,
          messageTextBeingSent => state,
      ENDCASE => finalMailboxExceptionsBeingReceived;
    VerifyPurposeAndState[ftpuser, mail, shouldBe];
  -- signal end of recipient list or text as necessary
    SELECT state FROM
      messageRecipientsBeingSent =>
        BEGIN
        PutEOC[mtper];
        state ← initialMailboxExceptionsBeingReceived;
        END;
      messageTextBeingSent =>
        BEGIN
        PutCommandAndEOC[mtper, markYes, 0];
        state ← finalMailboxExceptionsBeingReceived;
        END;
      ENDCASE;
  -- fetch next mailbox exception if any
    [mark, code] ← GetCommand[mtper];
    SELECT mark FROM
      markMailboxException =>
        BEGIN
        -- separate recipient number and error message
          string ← textualRecipientNumber;
          errorMessage.length ← 0;
          FOR i IN [0..inputString.length) DO
            IF string = textualRecipientNumber
            AND inputString[i] = mailboxExceptionIndexTerminator THEN
              string ← errorMessage
            ELSE String.AppendChar[string, inputString[i]];
            ENDLOOP;
        -- decode and verify recipient number
          recipientNumber ← String.StringToDecimal[textualRecipientNumber];
          IF recipientNumber ~IN [1..numberOfRecipients] THEN
            Abort[noSuchRecipientNumber];
        -- decode and verify error code
          recipientError ← ExceptionCodeToRecipientError[code];
        -- supply error message if none provided
          IF errorMessage.length = 0 AND ftpsystem.accessoriesLoaded THEN
            VerbalizeRecipientError[recipientError, errorMessage];
        -- decrement number of valid recipients
          IF numberOfValidRecipients = 0 THEN Abort[duplicateMailboxException];
          numberOfValidRecipients ← numberOfValidRecipients - 1;
        END;
      markYes =>
        BEGIN
        -- receive EOC
          GetEOC[mtper];
        -- lay groundwork for transmission of message
          IF state = initialMailboxExceptionsBeingReceived THEN
            BEGIN
            PutCommand[mtper, markHereIsFile, 0];
            state ← messageTextBeingSent;
            END
        -- note mail delivery complete
          ELSE state ← messageDelivered;
        -- inform caller of end of mailbox exception run
          recipientNumber ← 0;
        END;
      markNo => 
        BEGIN
        -- receive EOC
          GetEOC[mtper];
        -- note mail delivery complete
          state ← messageDelivered;
        -- abort
          AbortWithExplanation[CodeToSignal[code], inputString];
        END;
      ENDCASE => Abort[illegalProtocolSequence];
  END;

FTPSendBlockOfMessage: PUBLIC PROCEDURE [ftpuser: FTPUser, source: POINTER, byteCount: CARDINAL] =
  BEGIN OPEN ftpuser;
  -- local variables
    bytePointerObject: BytePointerObject ← [source, FALSE, byteCount];
  -- verify purpose and state
    VerifyPurposeAndState[ftpuser, mail, messageTextBeingSent];
  -- send block of message
  -- Note:  The FTPUtilities procedure, SendBytes, is here replicated in-line
  --   to reduce Alto working sets.
    IF bytePointerObject.count > 0 THEN
      BEGIN OPEN mtper;
      communicationPrimitives.SendBytes[
        communicationSystem, connection, @bytePointerObject];
      END;
  END;

FTPEndDeliveryOfMessage: PUBLIC PROCEDURE [ftpuser: FTPUser] =
  BEGIN OPEN ftpuser;
  -- verify purpose and state
    VerifyPurposeAndState[ftpuser, mail, messageDelivered];
  -- restore state
    state ← connected;
  END;


END. -- of FTPUserMailOut