-- ReturnOp.mesa
-- edited by Schroeder, January 18, 1981  3:06 PM
-- edited by Brotz, August 15, 1980  5:48 PM
-- edited by Levin, January 12, 1981  3:09 PM
DIRECTORY
  crD: FROM "CoreDefs",
  exD: FROM "ExceptionDefs",
  gsD: FROM "GlobalStorageDefs",
  intCommon: FROM "IntCommon",
  mfD: FROM "MailFormatDefs",
  opD: FROM "OperationsDefs",
  ovD: FROM "OverviewDefs",
  vmD: FROM "VirtualMgrDefs";
ReturnOp: PROGRAM
  IMPORTS crD, exD, gsD, intC:intCommon, mfD, vmD
  EXPORTS opD
  SHARES opD =
BEGIN
fileAccessError: ERROR = CODE; -- Had error return trying to read or write a file
ReturnMailFileOperation: PUBLIC PROCEDURE [buffer: POINTER TO opD.BufferRecord]
          RETURNS [ovD.ErrorCode] =
BEGIN
-- variables of ReturnMailFileOperation
out: RECORD[  --output buffer
  ptr: POINTER TO UNSPECIFIED,  --pointer to buffer start
  limit: CARDINAL,  --size of buffer in bytes (a multiple of 512)
  byteCount: CARDINAL,  --number of bytes to write out from the buffer
  firstPage: crD.PageNumber,  --number of the first page in the buffer
  string: STRING,  --a string overlayed on the buffer area
  isGSPage: BOOLEAN  --buffer page was gotten from global storage division
  ] ← [NIL, , 0, , , FALSE];
in: RECORD[  --input buffer
  ptr: POINTER TO UNSPECIFIED,  --pointer to buffer start
  limit: CARDINAL,  --size of buffer in bytes (a multiple of 512)
  firstPage: crD.PageNumber,  --number of the first page in the buffer
  firstByte: CARDINAL,  --starting point for a copy
  string: STRING,  --a string overlayed on the buffer area
  isGSPage: BOOLEAN  --buffer page was gotten from global storage division
  ] ← [ , , , , , FALSE];
errorCode: ovD.ErrorCode;
tOCEntry: vmD.TOCFixedPart;
tb: vmD.TOCFixedPartPtr = @tOCEntry;
stateOfPreviousEntry: {initialDeletions, deleted, notDeleted};
changingInPlace: BOOLEAN ← TRUE; --becomes FALSE when first deletion found 
replacePosition,  --next place for ReplaceCharInOutBuffer to put character in output buffer
i, p, b, appendCharCount, firstFree, eofByte, firstByteOfFirstDeletion: CARDINAL;
eofPage, firstPageOfFirstDeletion: crD.PageNumber;
tocValid: BOOLEAN ← TRUE;
-- internal procedures of ReturnMailFileOperation
AdvanceOutBufferPosition: PROCEDURE[page: crD.PageNumber, byte: CARDINAL] =
  BEGIN
  IF out.ptr = NIL THEN
    BEGIN  -- need to set up the buffers
    buffer.s1 ← (buffer.s1 / 256) * 512;  --normalize size to n*512 bytes
    buffer.s2 ← (buffer.s2 / 256) * 512;  --normalize size to n*512 bytes
    IF buffer.s1 > buffer.s2 THEN
      {in.ptr ← buffer.b2; in.limit ← buffer.s2; out.ptr ← buffer.b1; out.limit ← buffer.s1}
    ELSE {in.ptr ← buffer.b1; in.limit ← buffer.s1; out.ptr ← buffer.b2; out.limit← buffer.s2};
    IF out.limit = 0 THEN  -- larger buffer didn’t contain a full page
      {out.ptr ← gsD.GetMemoryPages[1]; out.limit ← 512; out.isGSPage ← TRUE};
    out.string ← LOOPHOLE[out.ptr - 2, STRING];
    -- make sure that requested page doesn’t appear to be in buffer
    out.firstPage ← page + 1;
    END;  -- need to set up the buffers
  IF out.firstPage # page THEN
    BEGIN  --desired page not in the output buffer
    IF out.byteCount # 0 THEN WriteOutputBuffer[];
    IF byte # 0 OR changingInPlace THEN
      BEGIN
      [errorCode, out.byteCount] ← crD.ReadPages[out.ptr, 512, page, intC.mailFileHandle];
      IF errorCode # ovD.ok THEN ERROR fileAccessError;
      END;
    out.firstPage ← page;
    END;
  IF changingInPlace THEN replacePosition ← byte ELSE out.byteCount ← byte;
  END;  -- of AdvanceOutBufferPosition --
ReplaceCharInOutBuffer: PROCEDURE[c: CHARACTER] =
  BEGIN
  IF replacePosition >= 512 THEN
    BEGIN
    WriteOutputBuffer[];
    out.firstPage ← out.firstPage + 1;
    replacePosition ← 0;
    [errorCode, out.byteCount] ←
        crD.ReadPages[out.ptr, 512, out.firstPage, intC.mailFileHandle];
    IF errorCode # ovD.ok THEN ERROR fileAccessError;
    END;
  out.string[replacePosition] ← c;
  replacePosition ← replacePosition + 1;
  END;  -- of ReplaceCharInOutBuffer --
AppendCharToOutBuffer: PROCEDURE[c: CHARACTER] =
  BEGIN
  IF out.byteCount >= out.limit THEN
    BEGIN
    WriteOutputBuffer[];
    out.firstPage ← out.firstPage + out.limit / 512;
    out.byteCount ← 0;
    END;
  out.string[out.byteCount] ← c;
  out.byteCount ← out.byteCount + 1;
  appendCharCount ← appendCharCount + 1;
  END;  -- of AppendCharToOutBuffer --
WriteOutputBuffer: PROCEDURE =
  BEGIN
  IF (errorCode ← crD.WritePages[out.ptr, out.byteCount, out.firstPage, intC.mailFileHandle])
    # ovD.ok THEN ERROR fileAccessError;
  END;  -- of WriteOutputBuffer --
SetUpInBufferAndIndicateCompactionUnderway: PROCEDURE =
  BEGIN
  --set up the input buffer
  IF in.limit = 0 THEN  -- smaller buffer didn’t contain a full page
    {in.ptr ← gsD.GetMemoryPages[1]; in.limit ← 512; in.isGSPage ← TRUE};
  in.string ← LOOPHOLE[in.ptr - 2, STRING];
  -- put compaction mark in toc
  vmD.SetTOCValidity[FALSE];
  tocValid ← FALSE;
  END; --of SetUpInBufferAndIndicateCompactionUnderway--
CopyTo: PROCEDURE [page: crD.PageNumber, byte: CARDINAL] =
  BEGIN
  pagesLeft, index, firstCopyCount, byteCount: CARDINAL;
  --get page and byte of last byte to copy
  IF tocValid THEN exD.SysBug[]; -- make sure in buffer set up--
  IF byte = 0 THEN {page ← page -1; byte ← 511} ELSE byte ← byte - 1;
  pagesLeft ← page - in.firstPage + 1;
  UNTIL pagesLeft = 0 DO
    --calculate number of bytes to read this time
    byteCount ← IF pagesLeft < in.limit / 512 THEN pagesLeft * 512 ELSE in.limit;
    --do the read
    [errorCode, ] ← crD.ReadPages[in.ptr, byteCount, in.firstPage, intC.mailFileHandle];
    IF errorCode # ovD.ok THEN ERROR fileAccessError;
    --calculate pages left to read and first page of in buffer for next iteration
    in.firstPage ← in.firstPage + byteCount / 512;
    pagesLeft ← page - in.firstPage + 1;
    --set byteCount to be the number of bytes to copy for this iteration
    IF pagesLeft = 0 THEN byteCount ← byteCount - (511 - byte);
    byteCount ← byteCount - in.firstByte;
    --copy to the output buffer
    firstCopyCount ← MIN[byteCount, out.limit - out.byteCount];
    FOR index IN [0 .. firstCopyCount) DO
      out.string[out.byteCount + index] ← in.string[in.firstByte + index];
      ENDLOOP;
    IF (out.byteCount ← out.byteCount + firstCopyCount) >= out.limit THEN
      BEGIN
      WriteOutputBuffer[];
      out.firstPage ← out.firstPage + out.limit / 512;
      out.byteCount ← byteCount - firstCopyCount;
      in.firstByte ← in.firstByte + firstCopyCount; 
      FOR index IN [0 .. out.byteCount) DO
        out.string[index] ← in.string[in.firstByte + index];
        ENDLOOP;
      END;
    in.firstByte ← 0;
    ENDLOOP;
  END;  -- of CopyTo --
-- Code for ReturnMailFileOperation
BEGIN -- block for EXITS and signals
ENABLE fileAccessError => GOTO errorReturn;
IF (firstFree ← vmD.GetFirstFreeTOCIndex[]) = 1 THEN
  BEGIN
  vmD.CleanupTOC[delete];
  errorCode ← crD.DeleteFile[intC.mailFileHandle];
  GOTO normalReturn;
  END;
FOR i IN [vmD.GetFirstChangedTOCIndex[] .. firstFree) DO
  vmD.GetTOCFixedPart[i,tb]; --first argument should be of type TOCIndex
  IF tb.deleted THEN EXIT;
  IF tb.changed THEN
    BEGIN
    AdvanceOutBufferPosition[tb.firstPage, tb.firstByte];
    mfD.CreateStamp[tb, ReplaceCharInOutBuffer];
    END;
  REPEAT
  FINISHED =>
    BEGIN -- no deleted entries were found, so we are done
    IF out.byteCount # 0 THEN WriteOutputBuffer[];
    errorCode ← crD.CloseFile[intC.mailFileHandle];
    vmD.CleanupTOC[resetChanges];
    GOTO normalReturn;
    END;
  ENDLOOP;
-- a deleted entry exists
-- remember page and byte of last character before first deleted message
firstPageOfFirstDeletion ← tb.firstPage;
firstByteOfFirstDeletion ← tb.firstByte;
stateOfPreviousEntry ← initialDeletions;
changingInPlace ← FALSE;
[errorCode, eofPage, eofByte] ← crD.UFileLength[intC.mailFileHandle];
IF errorCode # ovD.ok THEN GOTO errorReturn;
-- process remaining messages in the toc; remember that message i is deleted
FOR i IN (i .. firstFree + 1] DO
  SELECT i FROM
    < firstFree => vmD.GetTOCFixedPart[i, tb];  --get next entry from TOC
    = firstFree =>
      BEGIN  -- calculate distance, in pages and bytes, from last toc message to EOF
      b ← tb.firstByte + tb.offsetToHeader + tb.textLength;
      tb.firstPage ← tb.firstPage + (b / 512);
      tb.firstByte ← b MOD 512;
      -- now tb.firstPage and tb.firstByte show end of mailfile as described by the toc
      IF tb.firstByte = eofByte AND tb.firstPage = eofPage THEN  -- no more to copy
        BEGIN
        IF stateOfPreviousEntry = notDeleted THEN CopyTo[tb.firstPage, tb.firstByte];
        EXIT;
        END
      ELSE BEGIN -- more to copy because of a partial TOC
        --makeup entry describing message starting after last TOCed message
        tb.deleted ← FALSE;
        tb.changed ← FALSE;
        END;
      END;
    ENDCASE =>  -- > firstFree --
      BEGIN
      --makeup an entry describing deleted message starting at old EOF for mailfile
      tb.firstPage ← eofPage;
      tb.firstByte ← eofByte;
      tb.deleted ← TRUE;
      END;
  IF tb.deleted THEN  --this entry is deleted
    BEGIN
    IF stateOfPreviousEntry = notDeleted THEN
      {CopyTo[tb.firstPage, tb.firstByte]; stateOfPreviousEntry ← deleted}
    END
  ELSE BEGIN  --this entry is not deleted
    IF tb.changed THEN  --this entry has been changed
      BEGIN
      SELECT stateOfPreviousEntry FROM
        = notDeleted => CopyTo[tb.firstPage, tb.firstByte];
        = deleted => NULL;
        ENDCASE  -- = initialDeletions -- =>
          BEGIN
          AdvanceOutBufferPosition[firstPageOfFirstDeletion, firstByteOfFirstDeletion];
          SetUpInBufferAndIndicateCompactionUnderway[];
          END;
      appendCharCount ← 0;
      mfD.CreateStamp[tb, AppendCharToOutBuffer];
      -- record the copy starting point
      b ← tb.firstByte + appendCharCount;
      in.firstPage ← tb.firstPage + (b / 512);
      in.firstByte ← b MOD 512;
      END  --this entry has been changed
    ELSE BEGIN  --this entry has not been changed
      IF stateOfPreviousEntry # notDeleted THEN
        BEGIN  -- = initialDeletions OR = deleted --
        IF stateOfPreviousEntry = initialDeletions THEN
          BEGIN
          AdvanceOutBufferPosition[firstPageOfFirstDeletion, firstByteOfFirstDeletion];
          SetUpInBufferAndIndicateCompactionUnderway[];
          END;
        -- record the copy starting point
        in.firstPage ← tb.firstPage;
        in.firstByte ← tb.firstByte;
        END;
      END;  --this entry has not been changed
    stateOfPreviousEntry ← notDeleted;
    END;  --this entry is not deleted
  ENDLOOP;
IF stateOfPreviousEntry = initialDeletions THEN --new EOF is start of first deletion
  {p ← firstPageOfFirstDeletion; b ← firstByteOfFirstDeletion}
ELSE --new EOF is end of last copy
  {p ← out.firstPage + out.byteCount / 512; b ← out.byteCount MOD 512};
IF p # 0 OR b # 0 THEN -- something is left
  BEGIN
  IF out.byteCount # 0 THEN WriteOutputBuffer[];
  errorCode ← crD.UFileTruncate[p, b, intC.mailFileHandle];
  IF errorCode # ovD.ok THEN GOTO errorReturn;
  errorCode ← crD.CloseFile[intC.mailFileHandle];
  IF NOT tocValid THEN vmD.SetTOCValidity[TRUE];
  vmD.CleanupTOC[resetChanges]; 
  END
ELSE  -- whole file was deleted
  {errorCode ← crD.DeleteFile[intC.mailFileHandle]; vmD.CleanupTOC[delete]};
EXITS
normalReturn => NULL;
errorReturn =>
    {[] ← crD.CloseFile[intC.mailFileHandle]; vmD.CleanupTOC[dontResetChanges]};
END; -- of EXITS block
IF out.isGSPage THEN gsD.ReturnMemoryPages[1, out.ptr];
IF in.isGSPage THEN gsD.ReturnMemoryPages[1, in.ptr];
intC.mailFileHandle ← NIL;
RETURN[errorCode];
END; -- of ReturnMailFileOperation --
END.  -- of ReturnOp --