-- File: FTPUserRetrieve.mesa, Edit:
-- MAS Apr 16, 1980 2:33 PM  
-- HGM July 31, 1980  5:38 PM  

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  FTPDefs,
  FTPPrivateDefs,
  String USING [EquivalentString],
  MDSStorage USING [Node, Free];

FTPUserRetrieve: PROGRAM
  IMPORTS String, Storage: MDSStorage, FTPDefs, FTPPrivateDefs
  EXPORTS FTPDefs
  SHARES FTPDefs =
  BEGIN OPEN FTPDefs, FTPPrivateDefs;


  FTPRetrieveFile: PUBLIC PROCEDURE [
    ftpuser: FTPUser, localFile, remoteFile: STRING, fileType: FileType]
    RETURNS [byteCount: LONG INTEGER] =
    BEGIN OPEN ftpuser;
    -- local constants
    nextFile: STRING = propertyList[serverFilename];
    bufferSize: CARDINAL = maximumDumpBlockSize + minimumDumpBlockSize;
    -- local variables
    retrieveState: {inactive, initiated, waiting, transferring, finishing} ←
      inactive;
    mark, code: Byte;
    fileHandle: FileHandle ← NIL;
    fileInfoObject: FileInfoObject ← [
      fileType: fileType, byteSize: 0, byteCount: 0, creationDate: NIL,
      writeDate: NIL, readDate: NIL, author: NIL];
    dumpStateObject: DumpStateObject ← [
      bufferAddress: NIL, bufferLength: 0, ftper: ftper, blockType: 0];
    -- verify purpose and state
    VerifyPurposeAndState[
      ftpuser, files,
      SELECT state FROM
        connected, enumeratingFiles => state,
        ENDCASE => inventoryingDumpFile];
    -- intercept errors
    BEGIN
    ENABLE
      UNWIND =>
        BEGIN
        ENABLE FTPError => IF ftpError IN CommunicationError THEN CONTINUE;
        IF dumpStateObject.bufferAddress # NIL THEN
          BEGIN
          nextBlockType ← dumpStateObject.blockType;
          Storage.Free[dumpStateObject.bufferAddress];
          END;
        IF fileHandle # NIL THEN
          filePrimitives.CloseFile[ftpuser.fileSystem, fileHandle, TRUE];
        IF retrieveState = transferring THEN
          BEGIN
          SELECT GetCommand[ftper].mark FROM
            markYes => NULL;
            markNo => GetEOC[ftper];
            ENDCASE => Abort[illegalProtocolSequence];
          IF state = connected THEN FinishMultiFileOperation[ftpuser];
          END;
        -- What if loading?
        IF retrieveState = initiated THEN FinishMultiFileOperation[ftpuser];
        END;
    -- initiate remote retrieve
    IF state = connected THEN
      BEGIN
      -- send retrieve command
      PutCommand[ftper, markRetrieve, 0];
      -- construct property list containing absolute and virtual filenames, credentials, and file information
      ResetPropertyList[propertyList];
      WriteFilename[remoteFile, propertyList, NIL, NIL, primaryPropertyList];
      WriteFileInfo[propertyList, @fileInfoObject];
      -- send property list and EOC
      PutPropertyList[ftper, propertyList];
      PutEOC[ftper];
      -- note retrieve initiated
      retrieveState ← initiated;
      END
      -- verify enumeration or inventory intent and filename

    ELSE
      BEGIN
      IF intent # retrieval THEN Abort[illegalProcedureCallSequence];
      IF nextFile = NIL OR ~String.EquivalentString[remoteFile, nextFile] THEN
        Abort[filenameUnexpected];
      WriteProperty[propertyList, serverFilename, NIL];
      END;
    -- sustain remote retrieve
    IF state = connected THEN
      BEGIN
      -- note waiting to transfer
      retrieveState ← waiting;
      -- receive property list and EOC
      [mark, code] ← GetCommand[ftper];
      SELECT mark FROM
        markHereIsPropertyList =>
          BEGIN GetPropertyList[ftper, propertyList]; GetEOC[ftper]; END;
        markNo =>
          BEGIN
          GetEOC[ftper];
          AbortWithExplanation[CodeToSignal[code], ftper.inputString];
          END;
        markEndOfCommand => AbortWithExplanation[noSuchFile, ftper.inputString];
        ENDCASE => Abort[illegalProtocolSequence];
      END;
    -- setup info for OpenFile
    ReadFileInfo[propertyList, @fileInfoObject];
    -- Open local file
    [fileHandle, ] ← filePrimitives.OpenFile[
      ftpuser.fileSystem, localFile, write, FALSE, @fileInfoObject];
    -- accept the offered file
    IF state # inventoryingDumpFile THEN
      BEGIN
      -- request the file and await acknowledgment
      PutCommandAndEOC[ftper, markYes, 0];
      [mark, code] ← GetCommand[ftper];
      SELECT mark FROM
        markHereIsFile => NULL;
        markNo =>
          BEGIN AbortWithExplanation[CodeToSignal[code], ftper.inputString]; END;
        ENDCASE => Abort[illegalProtocolSequence];
      -- note transfer in progress
      retrieveState ← transferring;
      END;
    -- retrieve the file
    byteCount ← ftper.totalByteCount;
    IF state # inventoryingDumpFile THEN
      filePrimitives.WriteFile[
        ftpuser.fileSystem, fileHandle, ReceiveBlock, ftper]
    ELSE
      BEGIN
      -- allocate a buffer
      dumpStateObject.bufferAddress ← Storage.Node[bufferSize];
      -- receive the file
      filePrimitives.WriteFile[
        ftpuser.fileSystem, fileHandle, LoadBlock, @dumpStateObject];
      nextBlockType ← dumpStateObject.blockType;
      -- release the buffer
      Storage.Free[dumpStateObject.bufferAddress];
      dumpStateObject.bufferAddress ← NIL;
      END;
    byteCount ← ftper.totalByteCount - byteCount;
    -- terminate remote retrieve
    retrieveState ← finishing;
    IF state # inventoryingDumpFile THEN GetSpecificCommand[ftper, markYes];
    IF state = connected THEN FinishMultiFileOperation[ftpuser];
    -- close local file

    END;  -- enable
    filePrimitives.CloseFile[ftpuser.fileSystem, fileHandle, FALSE];
    END;


  END. -- of FTPUserRetrieve