-- 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