-- MailFileOp.mesa
-- Edited by Schroeder, March 3, 1981 10:05 AM.
-- Edited by Brotz, March 4, 1983 9:52 AM.
-- Edited by Levin, February 24, 1981 4:49 PM.
-- Edited by Taft, May 27, 1983 5:42 PM

DIRECTORY
AltoFile USING [DiskFull],
Core USING [Close, Delete, Open],
csD: FROM "CoreStreamDefs" USING [Close, Destroy, GetLength, GetPosition,
MapPageByteToPosition, MapPositionToPageByte, Open, Position, Read, SetPosition,
StreamCopy, StreamHandle, Write],
DiskIODefs USING [DiskError],
Inline USING [LowHalf],
MailParseDefs USING [endOfInput],
mfD: FROM "MailFormatDefs" USING [CreateStamp, ParseHeaderForTOC, ParseStamp],
opD: FROM "OperationsDefs" USING [MailFileProblem, maxTOCStringLength],
Process USING [Yield],
String USING [AppendString],
vmD: FROM "VirtualMgrDefs" USING [CleanupTOC, ExtendTOC, FirstChangedTOCIndex,
FirstFreeTOCIndex, GetTOCFixedPart, LoadTOC, PageNumber, SetTOCValidity,
PutTOCFixedPart, TOCFixedPart, TOCFixedPartPtr, TOCHandle, TOCIndex],
VMDefs USING [Error, FileHandle, GetFileLength, PageNumber, Position, SetFileLength];

MailFileOp: PROGRAM
IMPORTS AltoFile, Core, csD, DiskIODefs, Inline, mfD, Process, String, vmD, VMDefs
EXPORTS opD =

BEGIN


MailFileError: PUBLIC ERROR [reason: opD.MailFileProblem] = CODE;


GetMailFileOperation: PUBLIC PROCEDURE [toc: vmD.TOCHandle,
key: CARDINAL, mailFile: STRING] RETURNS [firstUnseen: vmD.TOCIndex] =
BEGIN
-- variables for GetMailFileOperation
mailFileHandle: VMDefs.FileHandle ← NIL;
mailStream: csD.StreamHandle ← NIL;
tOCEntry1, tOCEntry2: vmD.TOCFixedPart; -- Buffers for TOC entries
tb1: vmD.TOCFixedPartPtr = @tOCEntry1;
tb2: vmD.TOCFixedPartPtr = @tOCEntry2;
TOCFileName: STRING;
lastTOCIndex: vmD.TOCIndex;
TOCString: STRING ← [opD.maxTOCStringLength];
fName: STRING ← [75];
i: CARDINAL;
code: opD.MailFileProblem ← ok;
eofPosition, currentPosition, nextCharLimit: csD.Position;

parseStampLimit: CARDINAL = 24;

-- internal procedures for GetMailFileOperation

Cleanup: PROCEDURE =
BEGIN
IF mailStream # NIL THEN {csD.Destroy[mailStream]; mailStream ← NIL};
END; -- of Cleanup --

ErrorCleanup: PROCEDURE =
BEGIN
Cleanup[];
IF toc.mailFile#NIL THEN
(IF eofPosition # 0 THEN Core.Close ELSE Core.Delete)[toc.mailFile];
vmD.CleanupTOC[toc, key, delete]; -- a nop if no toc exists yet
END; -- of ErrorCleanup --

PositionNextChar: PROCEDURE [page: VMDefs.PageNumber, byte, limit: CARDINAL] =
--sets buffer management variables so NextChar will produce char at specified position
BEGIN
currentPosition ← csD.MapPageByteToPosition[page, byte];
IF currentPosition <= eofPosition THEN csD.SetPosition[mailStream, currentPosition];
nextCharLimit ← MIN[currentPosition + limit, eofPosition];
END; -- of PositionNextChar --

NextChar: PROCEDURE RETURNS [lastChar: CHARACTER] =
BEGIN
lastChar ← IF currentPosition >= nextCharLimit
THEN MailParseDefs.endOfInput ELSE csD.Read[mailStream];
currentPosition ← currentPosition + 1;
END; -- of NextChar --

-- code of GetMailFileOperation

BEGIN -- block for EXITS
fName.length ← 0;
String.AppendString[fName, mailFile];
mailFile ← fName;
FOR i IN [0 .. fName.length) DO
IF fName[i] = ’. THEN EXIT;
REPEAT
FINISHED => String.AppendString[fName, ".mail"L];
ENDLOOP;

mailFileHandle ← Core.Open[mailFile, update];

Process.Yield[];
Process.Yield[];

TOCString.length ← 0;
String.AppendString[TOCString, mailFile];
String.AppendString[TOCString, "-dmsTOC"L];
TOCFileName ← TOCString;

mailStream ← csD.Open[mailFileHandle, byte, read];
eofPosition ← csD.GetLength[mailStream];
IF eofPosition = 0 THEN
BEGIN
firstUnseen ← vmD.LoadTOC[toc, key, TOCFileName, mailFileHandle, new];
GOTO simpleReturn;
END;

firstUnseen ← vmD.LoadTOC[toc, key, TOCFileName, mailFileHandle, old
! UNWIND => ErrorCleanup[]];

lastTOCIndex ← vmD.FirstFreeTOCIndex[toc, key] - 1;
IF lastTOCIndex = 0 THEN PositionNextChar[0, 0, parseStampLimit] -- no toc entries yet
ELSE BEGIN -- TOC has some entries already
vmD.GetTOCFixedPart[toc, key, lastTOCIndex, tb1];
PositionNextChar[tb1.firstPage, tb1.firstByte, tb1.offsetToHeader];
IF ~mfD.ParseStamp[NextChar, tb2 ! UNWIND => ErrorCleanup[]]
OR tb2.textLength # tb1.textLength OR tb2.offsetToHeader # tb1.offsetToHeader
OR vmD.FirstChangedTOCIndex[toc] <= lastTOCIndex THEN
BEGIN -- last TOC entry is not reasonable
vmD.CleanupTOC[toc, key, delete];
firstUnseen ← vmD.LoadTOC[toc, key, TOCFileName, mailFileHandle, new
! UNWIND => ErrorCleanup[]];
PositionNextChar[0, 0, parseStampLimit];
END -- last TOC entry is not reasonable
ELSE -- TOC is reasonable
PositionNextChar
[tb1.firstPage, tb1.firstByte + tb1.offsetToHeader + tb1.textLength, parseStampLimit];
END; -- TOC has some entries already

WHILE currentPosition < eofPosition DO
Process.Yield[];
Process.Yield[];
[tb1.firstPage, tb1.firstByte] ← csD.MapPositionToPageByte[currentPosition];
tb1.changed ← FALSE;
IF ~mfD.ParseStamp[NextChar, tb1 ! UNWIND => ErrorCleanup[]] THEN
{code ← notAMailFile; ErrorCleanup[]; GO TO simpleReturn};
IF ~tb1.deleted THEN
BEGIN
PositionNextChar[tb1.firstPage, tb1.firstByte + tb1.offsetToHeader, tb1.textLength];
mfD.ParseHeaderForTOC[TOCString, NextChar ! UNWIND => Cleanup[]];
IF firstUnseen = 0 AND ~tb1.seen THEN
firstUnseen ← vmD.FirstFreeTOCIndex[toc, key];
vmD.ExtendTOC[toc, key, tb1, TOCString ! UNWIND => Cleanup[]];
END;
PositionNextChar[tb1.firstPage, tb1.firstByte+ tb1.offsetToHeader+ tb1.textLength,
parseStampLimit];
ENDLOOP;

IF currentPosition > eofPosition THEN
BEGIN
-- last stamp pointed beyond the end of the file, so contract last stamp
lastMessageStart: csD.Position ← csD.MapPageByteToPosition[tb1.firstPage, tb1.firstByte];
WriteStampChar: PROCEDURE [c: CHARACTER] = {csD.Write[mailStream, c]};
tb1.textLength ← Inline.LowHalf[eofPosition - lastMessageStart - tb1.offsetToHeader];
vmD.PutTOCFixedPart[toc, key, lastTOCIndex, tb1];
csD.Destroy[mailStream];
mailStream ← csD.Open[mailFileHandle, byte, write];
csD.SetPosition[mailStream, lastMessageStart];
mfD.CreateStamp[tb1, WriteStampChar];
code ← lastStampTooLong;
END;
EXITS
simpleReturn => NULL;
END; -- of EXITS block
Cleanup[];
Process.Yield[];
Process.Yield[];
IF code # ok THEN ERROR MailFileError[code];
END; -- of GetMailFileOperation --


ReturnMailFileOperation: PUBLIC PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL] =
BEGIN
TOCEntry: vmD.TOCFixedPart;
fp: vmD.TOCFixedPartPtr = @TOCEntry;
stateOfPreviousEntry: {initialDeletions, deleted, notDeleted};
changingInPlace: BOOLEAN ← TRUE; --becomes FALSE when first deletion found
i, b, appendCharCount, firstFree: CARDINAL;
positionOfFirstDeletion, eof: VMDefs.Position;
tocValid: BOOLEAN ← TRUE;
in, out: csD.StreamHandle ← NIL;

-- internal procedures of ReturnMailFileOperation

AppendCharToOutBuffer: PROCEDURE [c: CHARACTER] =
BEGIN
csD.Write[out, c];
appendCharCount ← appendCharCount + 1;
END; -- of AppendCharToOutBuffer --

CopyTo: PROCEDURE [page: VMDefs.PageNumber, byte: CARDINAL] =
-- Copies bytes between the current in position and page, byte to the current out position.
BEGIN
csD.StreamCopy[in, out, csD.MapPageByteToPosition[page, byte] - csD.GetPosition[in]];
END; -- of CopyTo --

Cleanup: PROCEDURE =
BEGIN
IF in = NIL THEN {csD.Destroy[in]; in ← NIL};
IF out = NIL THEN {csD.Destroy[out]; out ← NIL};
END; -- of Destroy --

-- Code for ReturnMailFileOperation

BEGIN -- block for EXITS and signals
ENABLE VMDefs.Error, DiskIODefs.DiskError, AltoFile.DiskFull => GOTO errorReturn;

IF (firstFree ← vmD.FirstFreeTOCIndex[toc, key]) = 1 THEN
{Core.Delete[toc.mailFile]; vmD.CleanupTOC[toc, key, delete]; RETURN};

out ← csD.Open[toc.mailFile, byte, write];
in ← csD.Open[toc.mailFile, byte, read];
FOR i IN [vmD.FirstChangedTOCIndex[toc] .. firstFree) DO
vmD.GetTOCFixedPart[toc, key, i, fp];
IF fp.deleted THEN {toc.firstChange ← i; EXIT};
IF fp.changed THEN
BEGIN
csD.SetPosition[out, csD.MapPageByteToPosition[fp.firstPage, fp.firstByte]];
mfD.CreateStamp[fp, AppendCharToOutBuffer];
vmD.PutTOCFixedPart[toc, key, i, fp, FALSE];
END;
REPEAT
FINISHED =>
BEGIN -- no deleted entries were found, so we are done
csD.Close[out];
csD.Destroy[in];
Core.Close[toc.mailFile];
toc.firstChange ← 0;
vmD.CleanupTOC[toc, key, resetChanges];
RETURN;
END;
ENDLOOP;

-- a deleted entry exists
-- remember page and byte of last character before first deleted message
positionOfFirstDeletion ← [fp.firstPage, fp.firstByte];
stateOfPreviousEntry ← initialDeletions;
changingInPlace ← FALSE;
eof ← VMDefs.GetFileLength[toc.mailFile];

-- 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[toc, key, i, fp]; --get next entry from TOC
= firstFree =>
BEGIN -- calculate distance, in pages and bytes, from last toc message to EOF
b ← fp.firstByte + fp.offsetToHeader + fp.textLength;
fp.firstPage ← fp.firstPage + (b / 512);
fp.firstByte ← b MOD 512;
-- now fp.firstPage and fp.firstByte show end of mailfile as described by the toc
IF fp.firstByte = eof.byte AND fp.firstPage = eof.page THEN -- no more to copy
BEGIN
IF stateOfPreviousEntry = notDeleted THEN CopyTo[fp.firstPage, fp.firstByte];
EXIT;
END
ELSE BEGIN -- more to copy because of a partial TOC
--makeup entry describing message starting after last TOCed message
fp.deleted ← FALSE;
fp.changed ← FALSE;
END;
END;
ENDCASE => -- > firstFree --
BEGIN
--makeup an entry describing deleted message starting at old EOF for mailfile
fp.firstPage ← eof.page;
fp.firstByte ← eof.byte;
fp.deleted ← TRUE;
END;
IF fp.deleted THEN --this entry is deleted
BEGIN
IF stateOfPreviousEntry = notDeleted THEN
{CopyTo[fp.firstPage, fp.firstByte]; stateOfPreviousEntry ← deleted}
END
ELSE BEGIN --this entry is not deleted
IF fp.changed THEN --this entry has been changed
BEGIN
SELECT stateOfPreviousEntry FROM
= notDeleted => CopyTo[fp.firstPage, fp.firstByte];
= deleted => NULL;
ENDCASE -- = initialDeletions -- =>
BEGIN
csD.SetPosition[out, csD.MapPageByteToPosition
[positionOfFirstDeletion.page, positionOfFirstDeletion.byte]];
vmD.SetTOCValidity[toc, FALSE];
tocValid ← FALSE;
END;
appendCharCount ← 0;
mfD.CreateStamp[fp, AppendCharToOutBuffer];
-- record the copy starting point
csD.SetPosition
[in, csD.MapPageByteToPosition[fp.firstPage, fp.firstByte + appendCharCount]];
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
csD.SetPosition[out, csD.MapPageByteToPosition
[positionOfFirstDeletion.page, positionOfFirstDeletion.byte]];
vmD.SetTOCValidity[toc, FALSE];
tocValid ← FALSE;
END;
-- record the copy starting point
csD.SetPosition[in, csD.MapPageByteToPosition[fp.firstPage, fp.firstByte]];
END;
END; --this entry has not been changed
stateOfPreviousEntry ← notDeleted;
END; --this entry is not deleted
ENDLOOP;

IF stateOfPreviousEntry = initialDeletions
THEN eof ← positionOfFirstDeletion --new EOF is start of first deletion --
ELSE [eof.page, eof.byte]
← csD.MapPositionToPageByte[csD.GetPosition[out]]; -- new EOF is end of last copy --
IF eof # [0, 0] THEN -- something is left
BEGIN
csD.Destroy[in];
csD.Close[out];
VMDefs.SetFileLength[toc.mailFile, eof];
Core.Close[toc.mailFile];
IF NOT tocValid THEN vmD.SetTOCValidity[toc, TRUE];
vmD.CleanupTOC[toc, key, resetChanges];
END
ELSE -- whole file was deleted
{Cleanup[]; Core.Delete[toc.mailFile]; vmD.CleanupTOC[toc, key, delete]};

EXITS
errorReturn =>
{Cleanup[]; Core.Close[toc.mailFile]; vmD.CleanupTOC[toc, key, dontResetChanges]};
END; -- of EXITS block

END; -- of ReturnMailFileOperation --


END. -- of MailFileOp --