-- FTPPair.mesa, Edit: HGM July 31, 1980  5:23 PM  

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  FTPDefs,
  FTPPrivateDefs,
  String USING [AppendString];

FTPPair: PROGRAM
  -- import list


  IMPORTS String, FTPDefs, FTPPrivateDefs
  -- export list

  EXPORTS FTPPrivateDefs
  -- share list

  SHARES FTPDefs, FTPPrivateDefs =
  BEGIN OPEN FTPDefs, FTPPrivateDefs;


  ForkTransferPair: PUBLIC PROCEDURE [
    fileSystem: FileSystem,
    readFile: PROCEDURE [
      FileSystem, FileHandle, PROCEDURE [UNSPECIFIED, POINTER, CARDINAL],
      UNSPECIFIED], srcFileHandle: FileHandle,
    writeFile: PROCEDURE [
      FileSystem, FileHandle, PROCEDURE [UNSPECIFIED, POINTER, CARDINAL]
      RETURNS [CARDINAL], UNSPECIFIED], dstFileHandle: FileHandle] =
    BEGIN
    -- Note:  Does NOT double buffer.
    -- input or output process
    IOProcess: PROCEDURE [input: BOOLEAN] =
      BEGIN
      -- catchphrase
      ENABLE
	UNWIND =>
	  BEGIN
	  abort ← TRUE;
	  PostEvent[IF input THEN @outputBufferFull ELSE @outputBufferEmpty];
	  END;
      -- SendBlock procedure
      SendBlock: PROCEDURE [
	unused: UNSPECIFIED, source: POINTER, byteCount: CARDINAL] =
	BEGIN
	-- fill output buffer
	sBPO ← [source, FALSE, byteCount];
	-- post full output buffer
	PostEvent[@outputBufferFull];
	-- await empty output buffer
	AwaitEvent[@outputBufferEmpty];
	IF abort THEN Abort[unidentifiedError];
	END;
      -- ReceiveBlock procedure
      ReceiveBlock: PROCEDURE [
	unused: UNSPECIFIED, destination: POINTER, maxWordCount: CARDINAL]
	RETURNS [actualByteCount: CARDINAL] =
	BEGIN
	-- local variables
	dBPO: BytePointerObject ← [destination, FALSE, bytesPerWord*maxWordCount];
	-- await full output buffer
	IF ~dataRemains THEN
	  BEGIN
	  AwaitEvent[@outputBufferFull];
	  IF abort THEN Abort[unidentifiedError];
	  END;
	-- empty output buffer
	TransferBytes[@sBPO, @dBPO];
	dataRemains ← sBPO.count # 0;
	-- notify empty output buffer
	IF ~dataRemains THEN PostEvent[@outputBufferEmpty];
	-- return actual byte count to caller
	actualByteCount ← bytesPerWord*maxWordCount - dBPO.count;
	END;
      -- read/write file
      IF input THEN readFile[fileSystem, srcFileHandle, SendBlock, NIL]
      ELSE writeFile[fileSystem, dstFileHandle, ReceiveBlock, NIL];
      END;
    -- local variables
    abort, dataRemains: BOOLEAN ← FALSE;
    outputBufferEmpty, outputBufferFull: EventObject;
    sBPO: BytePointerObject;
    -- prepare events
    PrepareEvent[@outputBufferEmpty];
    PrepareEvent[@outputBufferFull];
    -- transfer file
    ForkProcessPair[IOProcess, TRUE, FALSE];
    END;

  ForkProcessPair: PUBLIC PROCEDURE [
    process: PROCEDURE [UNSPECIFIED], parameter1, parameter2: UNSPECIFIED] =
    BEGIN
    -- ProcessRoot procedure
    ProcessRoot: PROCEDURE [parameter: UNSPECIFIED, processMessage: STRING]
      RETURNS [processFtpError: FtpError] =
      BEGIN
      -- initialize outcome
      processFtpError ← ok;
      -- call process procedure
      process[
	parameter !
	FTPError =>
	  BEGIN
	  processFtpError ← ftpError;
	  IF message # NIL THEN String.AppendString[processMessage, message];
	  CONTINUE;
	  END;
	ANY =>
	  BEGIN
	  ftpsystem: POINTER TO FTPSystem = LocateFtpSystemObject[];
	  IF ftpsystem↑.catchUnidentifiedErrors THEN
	    BEGIN processFtpError ← unidentifiedError; CONTINUE; END;
	  END];
      END;
    -- local constants
    message1: STRING = [maxStringLength];
    message2: STRING = [maxStringLength];
    -- local variables
    ftpError1, ftpError2: FtpError;
    pH: PROCESS RETURNS [processFtpError: FtpError];
    -- fork one root procedure and call the other
    pH ← FORK ProcessRoot[parameter1, message1];
    ftpError2 ← ProcessRoot[parameter2, message2];
    ftpError1 ← JOIN pH;
    -- report (first) error if any
    IF ftpError1 # ok THEN AbortWithExplanation[ftpError1, message1];
    IF ftpError2 # ok THEN AbortWithExplanation[ftpError2, message2];
    END;


  END.. -- FTPPair