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

FTPUserRetrieve: PROGRAM
  IMPORTS String, Storage, 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