-- file: ChollaMailUtils.mesa
-- edited by Crowther, January 14, 1982 12:49 PM
-- edited by Brotz, October 12, 1982 1:13 PM

DIRECTORY
Ascii,
ccD: FROM "ChollaCmdDefs",
Core,
csD: FROM "CoreStreamDefs",
dsD: FROM "DisplayDefs",
Editor,
exD: FROM "ExceptionDefs",
inD: FROM "InteractorDefs",
intCommon,
MailParse,
MessageParse,
NameInfoDefs,
opD: FROM "OperationsDefs",
Process,
PupDefs,
Storage,
String,
TimeDefs,
tsD: FROM "TOCSelectionDefs",
vmD: FROM "VirtualMgrDefs",
VMDefs;

ChollaMailUtils: MONITOR
IMPORTS ccD, Core, csD, dsD, Editor, exD, inD, intC: intCommon, MessageParse,
NameInfoDefs, opD, Process, PupDefs, Storage, String, TimeDefs, tsD, vmD, VMDefs
EXPORTS ccD =
BEGIN

-- Monitor to protect multiTOCOperation.


first: ccD.UniqueID = [1, 0];
last: ccD.UniqueID = [1, 1];
bad: ccD.UniqueID = [1, 2];
noID: ccD.UniqueID = [1, 3];
illegalID: ccD.UniqueID = [1, 4];
dummyMachine: CARDINAL = 0;
machineString: STRING ← [10];
machineNumber: CARDINAL;
formatString: STRING = "1";

multiTOCOperation: BOOLEAN ← FALSE;

multiTOCCV: CONDITION ← [timeout: Process.SecondsToTicks[1]];

bbRunHintTable: POINTER TO ARRAY [0 .. 0) OF STRING ← NIL;
bbRunHintTableLength: CARDINAL ← 0;


StartMultiTOCOperation: PUBLIC ENTRY PROCEDURE =
-- Used by Cholla operations that need to lock more than one TOC simultaneously.
BEGIN
WHILE multiTOCOperation DO WAIT multiTOCCV ENDLOOP;
multiTOCOperation ← TRUE;
END; -- of StartMultiTOCOperation --


FinishMultiTOCOperation: PUBLIC ENTRY PROCEDURE =
BEGIN
IF ~multiTOCOperation THEN exD.SysBug[];
multiTOCOperation ← FALSE;
NOTIFY multiTOCCV;
END; -- of FinishMultiTOCOperation --


GetIDUsingIndex: PUBLIC PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, index: CARDINAL, idType: ccD.IDType]
RETURNS [u: ccD.UniqueID] =
BEGIN
SELECT TRUE FROM
(toc = NIL) => RETURN[bad];
(index = 0) => RETURN[first];
(index >= toc.indexFF) => RETURN[last];
ENDCASE =>
BEGIN
dm: vmD.DisplayMessagePtr ← vmD.AllocateDisplayMessageObject[];
fieldList: MessageParse.FieldList;
vmD.LoadDisplayMessage[toc, key, index, dm];
fieldList ← MessageParse.MakeFieldList[dm];
u ← GetIDUsingVM
[dm, @fieldList, IF idType = thisID THEN "ThisID"L ELSE "UniqueID"L];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
MessageParse.FreeFieldList[fieldList];
END;
END; -- of GetIDUsingIndex --


GetBothIDsUsingIndex: PUBLIC PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL,
index: CARDINAL, thisID, uniqueID: ccD.UniqueIDPtr] =
BEGIN
IF toc = NIL THEN {thisID↑ ← uniqueID↑ ← bad; RETURN};
SELECT TRUE FROM
(toc = NIL) => thisID↑ ← uniqueID↑ ← bad;
(index = 0) => thisID↑ ← uniqueID↑ ← first;
(index >= toc.indexFF) => thisID↑ ← uniqueID↑ ← last;
ENDCASE =>
BEGIN
dm: vmD.DisplayMessagePtr ← vmD.AllocateDisplayMessageObject[];
fieldList: MessageParse.FieldList;
vmD.LoadDisplayMessage[toc, key, index, dm];
fieldList ← MessageParse.MakeFieldList[dm];
thisID↑ ← GetIDUsingVM[dm, @fieldList, "ThisID"L];
uniqueID↑ ← GetIDUsingVM[dm, @fieldList, "UniqueID"L];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
MessageParse.FreeFieldList[fieldList];
END;
END; -- of GetBothIDsUsingIndex --


GetIDUsingVM: PUBLIC PROCEDURE
[vm: vmD.VirtualMessagePtr, fieldListPtr: MessageParse.FieldListPtr, name: STRING]
RETURNS [u: ccD.UniqueID] =
BEGIN
idFieldList: MessageParse.FieldList;
count: CARDINAL;
[idFieldList, count] ← MessageParse.LocateField[fieldListPtr↑, name];
IF count > 0 THEN
u ← ExtractID[vm, idFieldList
! MessageParse.ParseFault, String.InvalidNumber => {u ← bad; CONTINUE}]
ELSE WITH cm: vm SELECT FROM
CM => BEGIN
u ← MakeUniqueID[FALSE, 1];
FillIDField[vmD.ComposedMessage[vm], fieldListPtr, name, @u];
END;
ENDCASE => exD.SysBug[];
END; -- of GetIDUsingVM --


ExtractID: PUBLIC PROCEDURE
[vm: vmD.VirtualMessagePtr, fieldList: MessageParse.FieldList] RETURNS [u: ccD.UniqueID]=
BEGIN
temp: STRING ← [20];
x: vmD.CharIndex ← MessageParse.GetNextWord
[vm: vm, start: fieldList.valueStart, end: fieldList.valueEnd, s: temp];
IF temp.length = 0 THEN RETURN[bad];
u.machine ← String.StringToDecimal[temp];
[] ← MessageParse.GetNextWord[vm: vm, start: x + 2, end: fieldList.valueEnd, s: temp];
u.time ← String.StringToLongNumber[temp, 10];
END; -- of ExtractID --


FillIDField: PUBLIC PROCEDURE [cm: vmD.ComposedMessagePtr,
fieldListPtr: MessageParse.FieldListPtr, s: STRING, u: ccD.UniqueIDPtr] =
BEGIN
idString: STRING ← [ccD.maxUniqueIDLength];
UniqueIDToString[u↑, idString];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, s, idString];
END; -- of FillIDField --


UniqueIDToString: PUBLIC PROCEDURE [id: ccD.UniqueID, s: STRING] =
BEGIN
s.length ← 0;
String.AppendDecimal[s, id.machine];
String.AppendString[s, " - "L];
String.AppendLongDecimal[s, id.time];
END; -- of UniqueIDToString --


oldTime: LONG CARDINAL ← 0;


GetTime: PROCEDURE [nIDs: CARDINAL] RETURNS [time: LONG CARDINAL] =
BEGIN
time ← TimeDefs.CurrentDayTime[] - ccD.timeOffset;
IF time <= oldTime THEN time ← oldTime + 1;
oldTime ← time + nIDs - 1;
END; -- of GetTime --


MakeUniqueID: PUBLIC PROCEDURE [dummy: BOOLEAN, nIDs: CARDINAL]
RETURNS [ccD.UniqueID] =
{RETURN[[IF dummy THEN dummyMachine ELSE machineNumber, GetTime[nIDs]]]};


NewerUniqueID: PUBLIC PROCEDURE [a, b: ccD.UniqueIDPtr] RETURNS [BOOLEAN] =
{RETURN[a.time > b.time]};


NewMatchesOld: PUBLIC PROCEDURE [new, old: ccD.UniqueIDPtr] RETURNS [BOOLEAN] =
{RETURN[old↑ = new↑ OR old.machine = dummyMachine]};


IsIllegalID: PUBLIC PROCEDURE [id: ccD.UniqueID] RETURNS [BOOLEAN] =
{RETURN[id = illegalID]};


IsDummyID: PUBLIC PROCEDURE [id: ccD.UniqueID] RETURNS [BOOLEAN] =
{RETURN[id.machine = dummyMachine]};


MakeBBRunEntryString: PUBLIC PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, doneFF: vmD.TOCIndex, run, string: STRING] =
-- Creates a string containing details of steps to be displayed in a run BBoard entry.
BEGIN
stepName: STRING ← [50];
specName: STRING ← [ccD.maxSpecNameLength];
dataName: STRING ← [0];
parsedStep: ccD.ParsedStep ← [specName, 0, dataName, 0, stepName, notStarted, 0, 0];
tocString: STRING ← [opD.maxTOCStringLength];
status: STRING ← [20];
string.length ← 0;
String.AppendString[string, run];
String.AppendString[string, " ("L];
String.AppendDecimal[string, doneFF - 1];
String.AppendChar[string, ’/];
String.AppendDecimal[string, toc.indexFF - 1];
String.AppendChar[string, ’)];
FOR i: vmD.TOCIndex IN [1 .. MIN[toc.indexFF - 1, doneFF]] DO
vmD.GetTOCString[toc, key, i, tocString];
ccD.ParseStep[tocString, @parsedStep];
IF (parsedStep.status # rejected AND parsedStep.status # finished)
OR i + 1 >= doneFF THEN
BEGIN
ENABLE String.StringBoundsFault => EXIT;
String.AppendChar[string, Editor.nextCode];
ccD.StatusToString[parsedStep.status, status];
String.AppendString[string, status];
IF string[string.length - 1] # Ascii.SP THEN String.AppendChar[string, Ascii.SP];
String.AppendChar[string, ’/];
String.AppendString[string, stepName];
String.AppendString[string, "/ "L];
String.AppendString[string, specName];
END;
ENDLOOP;
END; -- of MakeBBRunEntryString --


UpdateBBRunEntry: PUBLIC PROCEDURE [run, string: STRING, newID: ccD.UniqueID] =
-- Looks for BB entry for "run". If the tocString is non-NIL, then a new BB entry is
-- constructed, and the old BB entry is replaced. If the old BB entry was not found, a
-- new one is created. If string = NIL, then any existing BB entry for this run is
-- deleted.
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
index: vmD.TOCIndex;
bbRunName: STRING ← [ccD.maxRunNameLength];
fieldList: MessageParse.FieldList;
dm: vmD.DisplayMessagePtr;
cm: vmD.ComposedMessagePtr ← vmD.AllocateComposedMessageObject[];
found: BOOLEAN;
[toc, key] ← ccD.GetTOCForFile["ChollaBulletinBoard"L, bb];
[found, index, dm, fieldList] ← FindBBRunEntry[toc, key, run];
IF found = (dm = NIL) THEN exD.SysBug[];
IF found THEN
BEGIN
vmD.InitComposedMessage[cm, ""L];
vmD.InsertRangeInMessage[0, cm, [0, vmD.GetMessageSize[dm], dm]];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
END
ELSE BEGIN
vmD.InitComposedMessage[cm,
"Subject: xxx

BBEntryType: Run
UniqueID: xxx
ThisID: xxx
"L];
fieldList ← MessageParse.MakeFieldList[cm];
FillIDField[cm, @fieldList, "UniqueID"L, @newID];
END;
FillIDField[cm, @fieldList, "ThisID"L, @newID];
IF string # NIL THEN
BEGIN -- construct new BB entry subject field.
MessageParse.ReplaceOrAppendField[cm, @fieldList, "Subject"L, string];
IF ~opD.ReplaceMailOperation[delete: found, index: index, msg: cm, toc: toc, key: key]
THEN exD.SysBug[];
END
ELSE IF found
AND ~opD.ReplaceMailOperation[delete: TRUE, index: index, msg: NIL, toc: toc, key: key]
THEN exD.SysBug[];
vmD.UnlockTOC[toc, key];
MessageParse.FreeFieldList[fieldList];
vmD.FreeVirtualMessageObject[cm];
END; -- of UpdateBBRunEntry --


FindBBRunEntry: PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL, run: STRING]
RETURNS [found: BOOLEAN, index: vmD.TOCIndex, dm: vmD.DisplayMessagePtr,
fieldList: MessageParse.FieldList] =
-- Returns index of the run’th entry in the BBoard. Returns toc.indexFF if not found.
-- If message is found, returns an allocated dm with fieldList for that message.
BEGIN
bbRunName: STRING ← [ccD.maxRunNameLength];

SearchForRunHint: PROCEDURE [lookForSort: BOOLEAN] RETURNS [found: BOOLEAN] =
BEGIN
sortFound: BOOLEAN ← FALSE;
FOR i: CARDINAL IN [1 .. MIN[bbRunHintTableLength, toc.indexFF]) DO
IF String.EquivalentString[bbRunHintTable[i], run] THEN
{index ← i; RETURN[TRUE]};
IF lookForSort AND ~sortFound AND bbRunHintTable[i] # NIL
AND String.CompareStrings[run, bbRunHintTable[i], TRUE] < 1 THEN
{sortFound ← TRUE; index ← i};
ENDLOOP;
found ← FALSE;
IF ~sortFound THEN index ← toc.indexFF;
END; -- of SearchForRunHint --

IF run = NIL OR run.length = 0 THEN exD.SysBug[];
dm ← vmD.AllocateDisplayMessageObject[];
IF bbRunHintTable # NIL AND SearchForRunHint[FALSE] THEN
BEGIN -- verify that index is truly the one containing run.
vmD.LoadDisplayMessage[toc, key, index, dm];
fieldList ← MessageParse.MakeFieldList[dm];
found ← ccD.ObtainRunName[dm, fieldList, bbRunName]
AND String.EquivalentString[bbRunName, run];
IF found THEN RETURN;
vmD.FlushDisplayMessage[dm, key];
MessageParse.FreeFieldList[fieldList];
END;
IF bbRunHintTable # NIL THEN
BEGIN -- Free the old table --
FOR i: CARDINAL IN [1 .. bbRunHintTableLength) DO
IF bbRunHintTable[i] # NIL THEN Storage.FreeString[bbRunHintTable[i]];
ENDLOOP;
Storage.Free[bbRunHintTable];
END;
-- Make a new table --
bbRunHintTableLength ← toc.indexFF;
bbRunHintTable ← Storage.Node[SIZE[STRING] * bbRunHintTableLength];
FOR i: vmD.TOCIndex IN [1 .. bbRunHintTableLength) DO
vmD.LoadDisplayMessage[toc, key, i, dm];
fieldList ← MessageParse.MakeFieldList[dm];
bbRunHintTable[i] ← IF ~ccD.ObtainRunName[dm, fieldList, bbRunName] THEN NIL
ELSE Storage.CopyString[bbRunName];
vmD.FlushDisplayMessage[dm, key];
MessageParse.FreeFieldList[fieldList];
ENDLOOP;
IF (found ← SearchForRunHint[TRUE]) THEN
{vmD.LoadDisplayMessage[toc, key, index, dm]; fieldList ←MessageParse.MakeFieldList[dm]}
ELSE {vmD.FreeVirtualMessageObject[dm]; fieldList ← NIL; dm ← NIL};
END; -- of FindBBRunEntry --


DoAFileRequest: PUBLIC PROCEDURE
[request: vmD.VirtualMessagePtr, fieldList: MessageParse.FieldList] =
BEGIN
temp: STRING ← [80];
requestor: STRING ← [MailParse.maxRecipientLength];
toc: vmD.TOCHandle;
key: CARDINAL;
response: vmD.ComposedMessagePtr ← vmD.AllocateComposedMessageObject[];
senderFL, fromFL: MessageParse.FieldList;
count: CARDINAL;
filename: STRING ← [ccD.tocCacheFileStringLength];
action: STRING ← [20];
cacheType: ccD.TOCCacheType ← laurel;
useA: BOOLEAN;
inReplyTo: STRING =
"
In-Reply-To: Your message of
"L;

tableOfContentsOf: STRING = "
Table Of Contents of "L;

CopyValueIntoResponse: PROCEDURE [fl: MessageParse.FieldList] =
BEGIN
vmD.InsertRangeInMessage[vmD.GetMessageSize[response], response,
vmD.MessageRange[fl.valueStart, fl.valueEnd, request]];
END; -- of CopyValueIntoResponse --

CopyValueIntoString: PROCEDURE [fl: MessageParse.FieldList, s: STRING] =
BEGIN
FOR index: vmD.CharIndex IN [fl.valueStart .. fl.valueEnd)
UNTIL s.length >= s.maxlength DO
char: CHARACTER = vmD.GetMessageChar[request, index];
IF ~dsD.GetCharProperty[char, white] THEN {s[s.length] ← char; s.length ← s.length + 1};
ENDLOOP;
END; -- of CopyValueIntoString --

Authorized: PROCEDURE [name: STRING, class: {read, write}] RETURNS [ok: BOOLEAN] =
BEGIN
ok ← NameInfoDefs.IsMemberClosure
[IF class = read THEN "Onlookers↑.cholla"L ELSE "Kahunas↑.cholla"L, name] # no;
IF ~ok THEN
BEGIN
vmD.StartMessageInsertion[response, vmD.GetMessageSize[response]];
vmD.InsertStringInMessage[response, "According to information available at this time, you are not authorized to "L];
vmD.InsertStringInMessage[response, IF class = read THEN "read"L ELSE "modify"L];
vmD.InsertStringInMessage[response, " Cholla data.

"L];
vmD.StopMessageInsertion[response];
END;
END; -- of Authorized --

MessageParse.GetStringFromField[request, fieldList, "FileName"L, filename];
MessageParse.GetStringFromField[request, fieldList, "Action"L, action];

vmD.InitComposedMessage[response, "
From: ChollaInfo.cholla ("L];
ccD.InsertString[response, vmD.GetMessageSize[response], intC.workstationName];
ccD.InsertString[response, vmD.GetMessageSize[response], ")

Subject: Your Cholla File Request
To: "L];

[senderFL, count] ← MessageParse.LocateField[fieldList, "Sender"L];
[fromFL, count] ← MessageParse.LocateField[fieldList, "From"L];
IF count > 0 AND senderFL.valueStart < fromFL.valueStart
THEN CopyValueIntoString[senderFL, requestor]
ELSE CopyValueIntoString[fromFL, requestor];
ccD.InsertString[response, vmD.GetMessageSize[response], requestor];
ccD.InsertString[response, vmD.GetMessageSize[response], inReplyTo];
CopyValueIntoResponse[MessageParse.LocateField[fieldList, "Date"L].field];
ccD.InsertString[response, vmD.GetMessageSize[response],
"

"L
];
[cacheType, useA] ← MapFilenameToCacheType[filename];
IF cacheType = laurel THEN
BEGIN
ccD.InsertString[response, vmD.GetMessageSize[response], filename];
ccD.InsertString[response, vmD.GetMessageSize[response],
" is an invalid file name.

"L
];
END
ELSE BEGIN
String.AppendString[filename, ".chml"L]; -- GetNextWord stops before the dot.
SELECT TRUE FROM
String.EquivalentString[action, "SendTOC"L] =>
BEGIN
IF ~Authorized[requestor, read] THEN GO TO Interloper;
ccD.InsertString[response, vmD.GetMessageSize[response], tableOfContentsOf];
ccD.InsertString[response, vmD.GetMessageSize[response], filename];
ccD.InsertString[response, vmD.GetMessageSize[response],
":

"L
];
[toc, key] ← ccD.GetTOCForFile[filename, cacheType, useA];
AppendTOCtoCM[toc, key, response];
vmD.UnlockTOC[toc, key];
END;
String.EquivalentString[action, "SendFile"L] =>
BEGIN
IF ~Authorized[requestor, read] THEN GO TO Interloper;
ccD.InsertString[response, vmD.GetMessageSize[response], "Contents of "L];
ccD.InsertString[response, vmD.GetMessageSize[response], filename];
[toc, key] ← ccD.GetTOCForFile[filename, cacheType, useA];
AppendMailFileToMessage[toc, key, response];
vmD.UnlockTOC[toc, key];
END;
String.EquivalentString[action, "DeleteFile"L] =>
BEGIN
IF ~Authorized[requestor, write] THEN GO TO Interloper;
[] ← ReplaceMailFile[filename, cacheType, useA, FALSE, request];
ccD.InsertString[response, vmD.GetMessageSize[response], "Mail file "L];
ccD.InsertString[response, vmD.GetMessageSize[response], filename];
ccD.InsertString[response, vmD.GetMessageSize[response], " has been deleted.


"L];
END;
String.EquivalentString[action, "ReplaceFile"L] =>
BEGIN
worked: BOOLEAN;
IF ~Authorized[requestor, write] THEN GO TO Interloper;
worked ← ReplaceMailFile[filename, cacheType, useA, TRUE, request];
ccD.InsertString[response, vmD.GetMessageSize[response], "Mail file "L];
ccD.InsertString[response, vmD.GetMessageSize[response], filename];
ccD.InsertString[response, vmD.GetMessageSize[response],
IF worked THEN " has been replaced.


"L ELSE " could not be replaced. There is probably a format error in the file as sent.

The file has been deleted.

"L];
END;
ENDCASE =>
BEGIN
ccD.InsertString[response, vmD.GetMessageSize[response], "Unrecognized file request.

"L];
IF action.length = 0 THEN
ccD.InsertString[response, vmD.GetMessageSize[response], "No Action: field found in your request.


"L]
ELSE BEGIN
ccD.InsertString[response, vmD.GetMessageSize[response], action];
ccD.InsertString[response, vmD.GetMessageSize[response], " is not a valid action.


"L];
END;
END;
EXITS
Interloper => NULL;
END;
ccD.AppendToUnsentMailQueue[response];
vmD.FreeVirtualMessageObject[response];
END; -- of DoAFileRequest --


MapFilenameToCacheType: PROCEDURE [name: STRING]
RETURNS [cacheType: ccD.TOCCacheType, useA: BOOLEAN] =
-- Parses name to determine from which toc cache this file should come. If cacheType
-- returned is laurel, then the name is not a cholla file.
BEGIN
SELECT TRUE FROM
String.EquivalentString[name, "ChollaBulletinBoard"L] => RETURN[bb, TRUE];
EquivalentTail[name, "StepList"L] => RETURN[stepList, FALSE];
EquivalentTail[name, "Done"L] => RETURN[done, FALSE];
EquivalentTail[name, "Spec"L] => RETURN[spec, FALSE];
EquivalentTail[name, "Data"L] => RETURN[data, FALSE];
String.EquivalentString[name, "Pending"L] => RETURN[pending, TRUE];
String.EquivalentString[name, "ChollaUnsentMail"L] => RETURN[unsentMail, FALSE];
String.EquivalentString[name, "Technician"L] => RETURN[technician, TRUE];
String.EquivalentString[name, "StepListPrototypes"L] => RETURN[prototypes, TRUE];
ENDCASE => RETURN[laurel, FALSE];
END; -- of MapFilenameToCacheType --


EquivalentTail: PROCEDURE [string: STRING, tail: STRING] RETURNS [BOOLEAN] =
BEGIN
stringSSD, tailSSD: String.SubStringDescriptor;
IF tail.length > string.length THEN RETURN[FALSE];
stringSSD ← [base: string, offset: string.length - tail.length, length: tail.length];
tailSSD ← [base: tail, offset: 0, length: tail.length];
RETURN[String.EquivalentSubString[@stringSSD, @tailSSD]];
END; -- of EquivalentTail --


AppendTOCtoCM: PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, cm: vmD.ComposedMessagePtr] =
BEGIN
i, count: CARDINAL;
tocString: STRING ← [opD.maxTOCStringLength];
vmD.StartMessageInsertion[cm, vmD.GetMessageSize[cm]];
FOR index: vmD.TOCIndex IN [1 .. toc.indexFF) DO
vmD.GetTOCString[toc, key, index, tocString];
count ← 0;
FOR i IN [0 .. tocString.length) DO
IF tocString[i] = opD.substringSeparator THEN count ← count + 1;
IF count = 2 THEN {i ← i + 1; EXIT};
ENDLOOP;
vmD.InsertSubstringInMessage[cm, tocString, i, tocString.length - i];
IF tocString.length > i THEN vmD.InsertMessageChar[cm, Ascii.CR];
ENDLOOP;
vmD.StopMessageInsertion[cm];
END; -- of AppendTOCtoCM --


AppendMailFileToMessage: PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, message: vmD.ComposedMessagePtr] =
BEGIN
messageStart: vmD.CharIndex ← vmD.GetMessageSize[message];
dm: vmD.DisplayMessagePtr ← vmD.AllocateDisplayMessageObject[];
fileLength: VMDefs.Position = VMDefs.GetFileLength[toc.mailFile];
position: VMDefs.Position ← [0, 0];
bytesRemain: BOOLEAN ← TRUE;
part: CARDINAL ← 1;
partString: STRING ← [6];
IF fileLength = [0, 0] THEN
BEGIN
ccD.InsertString[message, messageStart,
": File is empty.

"L
];
RETURN;
END;
DO
ccD.InsertString[message, vmD.GetMessageSize[message],
":

"L
];
vmD.LoadDisplayMessage[toc, key, 1, dm];
dm.firstPage ← position.page;
dm.firstByte ← 0;
IF fileLength.page > position.page + 120 THEN
{position.page ← position.page + 120; dm.textLength ← 120 * 512}
ELSE BEGIN
dm.textLength ← (fileLength.page - position.page) * 512 + fileLength.byte;
bytesRemain ← FALSE;
END;
vmD.InsertRangeInMessage[vmD.GetMessageSize[message], message, [0, dm.textLength, dm]];
vmD.FlushDisplayMessage[dm, key];
IF ~bytesRemain THEN RETURN;
ccD.AppendToUnsentMailQueue[message];
vmD.DeleteRangeInMessage[[messageStart, vmD.GetMessageSize[message], message]];
ccD.InsertString[message, messageStart,
", Part "L];
part ← part + 1;
partString.length ← 0;
String.AppendDecimal[partString, part];
ccD.InsertString[message, vmD.GetMessageSize[message],
partString];
ENDLOOP;
END; -- of AppendMailFileToMessage --


ReplaceMailFile: PROCEDURE [filename: STRING, cacheType: ccD.TOCCacheType,
useA: BOOLEAN, replace: BOOLEAN, vm: vmD.VirtualMessagePtr]
RETURNS [worked: BOOLEAN] =
BEGIN
dm: inD.MessageTextNbrPtr = intC.dmTextNbr;
tnp: inD.TOCTextNbrPtr = intC.tocTextNbr;
displayMessage: vmD.DisplayMessagePtr = vmD.DisplayMessage[dm.message];
toc: vmD.TOCHandle;
key: CARDINAL;

[toc, key] ← ccD.GetTOCForFile[filename, cacheType, useA];
FOR dmList: vmD.DMList ← toc.dmList, dmList.next UNTIL dmList = NIL DO
dispMess: vmD.DisplayMessagePtr = dmList.dm;
IF dispMess # NIL THEN
BEGIN
vmD.FlushDisplayMessage[dispMess, key];
IF dm.haveMessage AND dispMess = vmD.DisplayMessage[dm.message] THEN
BEGIN
dm.haveMessage ← FALSE;
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, dm.topY, dm.bottomY];
exD.DisplayExceptionString["The displayed message has been deleted."L];
END
ELSE exD.SysBug[]; -- someone else is holding onto a deleted display message.
END;
ENDLOOP;
tsD.ResetTOCSelection[toc, key];
VMDefs.SetFileLength[toc.mailFile, [0, 0]];
IF replace THEN
BEGIN
found: BOOLEAN;
at: vmD.CharIndex;
vmSize: vmD.CharIndex = vmD.GetMessageSize[vm];
[found, at, , ] ← Editor.FindOperation["’*start’*"L, 0, vmSize, vm];
IF found THEN
BEGIN
sh: csD.StreamHandle = csD.Open[toc.mailFile, byte, write];
UNTIL at >= vmSize DO
[] ← vmD.GetMessageChar[vm, at]; -- MAGIC: set up msg.get.
csD.WriteBlock[sh, vm.buffer, vm.get.floor + (at - vm.get.first), vm.get.free - at];
at ← vm.get.free;
ENDLOOP;
csD.Close[sh];
END;
END;
Core.Close[toc.mailFile];
vmD.CleanupTOC[toc, key, delete];
worked ← TRUE;
[] ← opD.GetMailFileOperation[toc, key, filename
! opD.MailFileError => {worked ← FALSE; CONTINUE}];
IF ~worked THEN
BEGIN
mailFile: VMDefs.FileHandle ← Core.Open[filename, update];
Core.Delete[mailFile];
[] ← opD.GetMailFileOperation[toc, key, filename];
END;
IF toc = tnp.toc THEN
BEGIN
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, tnp.topY, tnp.bottomY];
inD.DisplayTOCTail[tnp, key, tnp.lines, 1, 1];
inD.UpdateTOCThumbLine[tnp, key];
END;
vmD.UnlockTOC[toc, key];
END; -- of ReplaceMailFile --


StringToType: PUBLIC PROCEDURE [s: STRING] RETURNS [ccD.ChollaFileType] =
BEGIN
RETURN[SELECT TRUE FROM
String.EquivalentString[s, "Done"L] => done,
String.EquivalentString[s, "BB"L] => bb,
String.EquivalentString[s, "Step"L] => step,
String.EquivalentString[s, "Technician"L] => technician,
String.EquivalentString[s, "Spec"L] => spec,
String.EquivalentString[s, "Data"L] => data,
String.EquivalentString[s, "Prototypes"L] => prototypes,
ENDCASE => none];
END; -- of StringToType --


StringToAction: PUBLIC PROCEDURE [s: STRING] RETURNS [ccD.Action] =
BEGIN
RETURN[SELECT TRUE FROM
String.EquivalentString[s, "Insert"L] => insert,
String.EquivalentString[s, "Delete"L]=> delete,
String.EquivalentString[s, "Update"L]=> replace,
ENDCASE=> none];
END; -- of StringToAction --


deletionList: POINTER TO ccD.DeletionList ← NIL;


InsertInDeletionList: PUBLIC PROCEDURE [uniqueID: ccD.UniqueID] =
BEGIN
deletionListSH: csD.StreamHandle;
IF deletionList = NIL THEN OpenDeletionList[];
deletionList.item[(deletionList.first + deletionList.nItems) MOD ccD.maxItemsInDeletionList]
← uniqueID;
IF deletionList.nItems = ccD.maxItemsInDeletionList THEN
deletionList.first ← (deletionList.first + 1) MOD ccD.maxItemsInDeletionList
ELSE deletionList.nItems ← deletionList.nItems + 1;
deletionListSH ← csD.OpenFromName
["ChollaDeletionList.DontDeleteThisFile"L, word, overwrite];
csD.WriteBlock[deletionListSH, deletionList, 0, SIZE[ccD.DeletionList]];
csD.Close[deletionListSH];
END; -- of InsertInDeletionList --


UniqueIDIsInDeletionList: PUBLIC PROCEDURE [uniqueID: ccD.UniqueID]
RETURNS [BOOLEAN] =
BEGIN
IF deletionList = NIL THEN OpenDeletionList[];
FOR index: CARDINAL IN [0 .. deletionList.nItems) DO
IF deletionList.item[(deletionList.first + index) MOD ccD.maxItemsInDeletionList] = uniqueID
THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END; -- of UniqueIDIsInDeletionList --


OpenDeletionList: PROCEDURE =
BEGIN
deletionListSH: csD.StreamHandle
← csD.OpenFromName["ChollaDeletionList.DontDeleteThisFile"L, word, write];
deletionList ← Storage.Node[SIZE[ccD.DeletionList]];
deletionList.first ← deletionList.nItems ← 0;
[] ← csD.ReadBlock[deletionListSH, deletionList, 0, SIZE[ccD.DeletionList]];
csD.Close[deletionListSH];
END; -- of OpenDeletionList --


Machine: PROCEDURE =
BEGIN
address: PupDefs.PupAddress;
PupDefs.GetPupAddress[@address, "ME"L];
machineNumber ← 256 * address.net + address.host;
machineString.length ← 0;
String.AppendDecimal[machineString, machineNumber];
END; -- of Machine --


Machine[];


END. -- of ChollaMailUtils --