-- file: ChollaMail.mesa
-- edited by Crowther, January 14, 1982 12:49 PM
-- edited by Brotz, March 4, 1983 10:13 AM
-- edited by White, August 27, 1982 5:03 PM

DIRECTORY
Ascii,
ccD: FROM "ChollaCmdDefs",
csD: FROM "CoreStreamDefs",
Core,
dsD: FROM "DisplayDefs",
Editor,
exD: FROM "ExceptionDefs",
inD: FROM "InteractorDefs",
intCommon,
MessageParse,
mfD: FROM "MailFormatDefs",
opD: FROM "OperationsDefs",
Process,
RetrieveDefs,
Storage,
String,
vmD: FROM "VirtualMgrDefs";

ChollaMail: MONITOR
IMPORTS ccD, csD, dsD, Editor, exD, inD, intC: intCommon, MessageParse, mfD, opD,
Process, RetrieveDefs, Storage, String, vmD
EXPORTS ccD =
BEGIN

thereIsSomethingToDo: BOOLEAN ← FALSE;
SomethingToDo: CONDITION ← [timeout: Process.SecondsToTicks[30]];
haveNewMail: BOOLEAN ← FALSE;
haveUnsentMail: BOOLEAN ← TRUE;
chollaRetrieveHandle: RetrieveDefs.Handle;
chollaRetrieveRegistry: STRING;
chollaRetrieveName: STRING;
chollaRetrievePassword: STRING = "cholla"L;
chollaUserBlk: Core.DMSUserBlk;
chollaDLSimpleName: STRING;
formatString: STRING = "1";

-- SendDataMail takes a composed message which must be in the third window.
-- to that message it adds (or modifies) the following fields:
-- MessageType {BB or Done or Step, or Spec, or Technician}
-- Action{ replace = update, insert, delete}
-- ThisID {ID of new message for replace or insert. Unused for delete
-- LeftID {ID of left guy for insert or delete. Unused for replace
-- RightID {ID of right guy for insert or delete. Unused for replace
-- To (Cholla↑)
-- From (for shortcut)
-- it then fires off the message, using a shunt to deal with self(mode S).

-- ReceiveDataMail takes a virtual message and finds the corresponding fields
-- if the from field is self, then it discards (mode S)
-- it asks the appropiate type guy for the TOC and index
-- if it can’t get it, then put it on pending.
-- Three cases:
-- Update possible - do it
-- Update out of date - discard it
-- Update impossible - put on pending

--Delete takes: toc key index
--Insert takes: toc key index cm
--Update takes: toc key index cm


SendChollaMail: PUBLIC PROCEDURE
[action: ccD.Action, type: ccD.ChollaFileType, toc: vmD.TOCHandle, key: CARDINAL,
index: vmD.TOCIndex, cm: vmD.ComposedMessagePtr,
fieldListPtr: MessageParse.FieldListPtr, dummy: BOOLEAN ← FALSE,
nIDs: CARDINAL ← 1]
RETURNS [worked: BOOLEAN] =
BEGIN
indexString: STRING ← [10];
callToc: vmD.TOCHandle ← toc; --we’re going to change toc, so GetIDs do replace right.
newID: ccD.UniqueID ← ccD.MakeUniqueID[dummy, nIDs];
id: ccD.UniqueID;
cmcopy: vmD.ComposedMessagePtr;
IF cm = NIL THEN exD.SysBug[];
-- We don’t care about neighbor ID’s if replace, done, or bboard.
-- toc ← NIL makes GetIDxx return badID.
IF action = replace OR type = done OR type = bb THEN toc ← NIL
ELSE IF toc = NIL THEN exD.SysBug[];
String.AppendDecimal[indexString, index];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "From"L, intC.workstationName, TRUE];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "To"L, intC.chollaDL, TRUE];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "
User"L, intC.user.name];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "Index"L, indexString];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "Type"L, SELECT type FROM
done => "Done"L, bb => "BB"L, step => "Step"L, technician => "Technician"L,
spec => "Spec"L, data => "Data"L, prototypes => "Prototypes"L, ENDCASE => "None"L];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "Action"L,
SELECT action FROM insert => "Insert"L, replace => "Update"L, ENDCASE => "Delete"L];
id ← ccD.GetIDUsingIndex[toc, key, index - 1, uniqueID];
ccD.FillIDField[cm, fieldListPtr, "LeftID"L, @id];
id ← ccD.GetIDUsingIndex[toc, key, index + (IF action = insert THEN 0 ELSE 1), uniqueID];
ccD.FillIDField[cm, fieldListPtr, "RightID"L, @id];
IF action = insert THEN ccD.FillIDField[cm, fieldListPtr, "UniqueID"L, @newID];
ccD.FillIDField[cm, fieldListPtr, "ThisID"L, @newID];
MessageParse.ReplaceOrAppendField[cm, fieldListPtr, "Format"L, formatString];
cmcopy ← vmD.AllocateComposedMessageObject[];
vmD.InitComposedMessage[cmcopy, ""L];
vmD.InsertRangeInMessage[0, cmcopy, [0, vmD.GetMessageSize[cm], cm]];
IF (worked ← ReceiveChollaMail[cm, fieldListPtr, FALSE, callToc, key]) THEN
AppendToUnsentMailQueue[cmcopy]; -- ReceiveChollaMail removes inserted fields.
vmD.FreeVirtualMessageObject[cmcopy];
IF cm = intC.cmTextNbr.message THEN
BEGIN
MessageParse.DeleteField[cm, fieldListPtr, "UniqueID"L];
MessageParse.DeleteField[cm, fieldListPtr, "ThisID"L];
MessageParse.DeleteField[cm, fieldListPtr, "Format"L];
ccD.ResetTheEditor[FALSE, fieldListPtr↑];
END;
END; -- of SendChollaMail --


AppendToUnsentMailQueue: PUBLIC PROCEDURE [cm: vmD.ComposedMessagePtr] =
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
[toc, key] ← ccD.GetTOCForFile["ChollaUnsentMail"L, unsentMail, FALSE];
IF ~opD.ReplaceMailOperation[delete: FALSE, index: vmD.FirstFreeTOCIndex[toc, key],
msg: cm, toc: toc, key: key] THEN exD.SysBug[];
haveUnsentMail ← TRUE;
vmD.UnlockTOC[toc, key];
CallSomethingToDo[];
END; -- of AppendToUnsentMailQueue --


CallSomethingToDo: PUBLIC ENTRY PROCEDURE =
BEGIN
thereIsSomethingToDo ← TRUE;
NOTIFY SomethingToDo;
END; -- of CallSomethingToDo --


WaitForSomethingToDo: ENTRY PROCEDURE =
BEGIN
UNTIL thereIsSomethingToDo DO WAIT SomethingToDo ENDLOOP;
thereIsSomethingToDo ← FALSE;
END; -- of WaitForSomethingToDo --


ChollaMailProcess: PUBLIC PROCEDURE =
-- This procedure is FORKed early on. Its purpose is to remove messages from the unsent
-- mail queue and from the pending mail queue.
BEGIN
IF intC.workstationName = NIL OR intC.chollaDL = NIL THEN GO TO profileError;
FOR i: CARDINAL DECREASING IN [0 .. intC.workstationName.length) DO
IF intC.workstationName[i] = ’. THEN
BEGIN
length: CARDINAL = intC.workstationName.length - i - 1;
chollaRetrieveRegistry ← Storage.String[length];
FOR j: CARDINAL IN [0 .. length) DO
chollaRetrieveRegistry[j] ← intC.workstationName[i + j + 1];
ENDLOOP;
chollaRetrieveRegistry.length ← length;
chollaRetrieveName ← Storage.String[i];
FOR j: CARDINAL IN [0 .. i) DO
chollaRetrieveName[j] ← intC.workstationName[j];
ENDLOOP;
chollaRetrieveName.length ← i;
EXIT;
END;
REPEAT
FINISHED => GO TO profileError;
ENDLOOP;
chollaUserBlk ← [name: chollaRetrieveName, registry: chollaRetrieveRegistry,
password: chollaRetrievePassword];
FOR i: CARDINAL IN [0 .. intC.chollaDL.length) DO
IF intC.chollaDL[i] = ’↑ THEN
BEGIN
chollaDLSimpleName ← Storage.String[i];
FOR j: CARDINAL IN [0 .. i) DO
chollaDLSimpleName[j] ← intC.chollaDL[j];
ENDLOOP;
chollaDLSimpleName.length ← i;
EXIT;
END;
REPEAT
FINISHED => GO TO profileError;
ENDLOOP;
Process.Yield[];
chollaRetrieveHandle ← RetrieveDefs.Create
[pollingInterval: 30 --seconds --, reportChanges: TellMeAboutMyMail];
RetrieveDefs.NewUser[chollaRetrieveHandle, intC.workstationName, "Cholla"L];
haveNewMail ← TRUE;
UNTIL ccD.shutDownCholla DO
IF haveUnsentMail THEN FireOffUnsentMail[];
IF haveNewMail THEN RetrieveChollaMail[];
WaitForSomethingToDo[];
Process.Yield[];
ENDLOOP;
RetrieveDefs.Destroy[chollaRetrieveHandle];
Storage.FreeString[chollaRetrieveRegistry];
Storage.FreeString[chollaRetrieveName];
EXITS
profileError =>
BEGIN
exD.DisplayExceptionString["No registry found in Workstation or ChollaDL profile entry!"L];
intC.isCholla ← FALSE;
END;
END; -- of ChollaMailProcess --


FireOffUnsentMail: PROCEDURE =
BEGIN
cm: vmD.ComposedMessagePtr ← NIL;
DO
toc: vmD.TOCHandle;
key: CARDINAL;
[toc, key] ← ccD.GetTOCForFile["ChollaUnsentMail"L, unsentMail, FALSE];
IF toc.indexFF = 1 THEN {haveUnsentMail ← FALSE; vmD.UnlockTOC[toc, key]; EXIT};
IF cm = NIL THEN cm ← vmD.AllocateComposedMessageObject[];
ccD.LoadComposedMessage[toc, key, 1, cm];
vmD.UnlockTOC[toc, key];
Process.Yield[]; Process.Yield[]; Process.Yield[];
SELECT inD.SendOperation[cm, @chollaUserBlk, Editor.FormatMessage,TRUE,FALSE] FROM
ok => NULL;
commFailure => EXIT;
badMessage => BEGIN
bogusMessageHeader: STRING = "Subject: Bogus outgoing Cholla mail

To: ChollaSupport.pa

"L;
exD.DisplayExceptionString
["Malformed outgoing message. Please notify ChollaSupport."L];
ccD.InsertString[cm, 0, bogusMessageHeader];
AppendToUnsentMailQueue[cm];
vmD.DeleteRangeInMessage[[0, bogusMessageHeader.length, cm]];
END;
ENDCASE => exD.SysBug[];
[toc, key] ← ccD.GetTOCForFile["ChollaUnsentMail"L, unsentMail, FALSE];
IF ~opD.ReplaceMailOperation[delete: TRUE, index: 1, msg: NIL, toc: toc, key: key]
THEN exD.SysBug[];
vmD.UnlockTOC[toc, key];
Process.Yield[];
ENDLOOP;
IF cm # NIL THEN vmD.FreeVirtualMessageObject[cm];
END; -- of FireOffUnsentMail --


TellMeAboutMyMail: PROCEDURE [mbxState: RetrieveDefs.MBXState] =
BEGIN
SELECT mbxState FROM
badName, badPwd => exD.DisplayExceptionString["Bad workstation name in your profile"L];
notEmpty => {haveNewMail ← TRUE; CallSomethingToDo[]};
ENDCASE;
END; -- TellMeAboutMyMail --


-- ///////RECEIVE SIDE///////


MessageDescriptorPtr: TYPE = POINTER TO MessageDescriptor;
MessageDescriptor: TYPE= RECORD
[cm: vmD.ComposedMessagePtr,
fieldListPtr: MessageParse.FieldListPtr,
type: ccD.ChollaFileType,
action: ccD.Action,
index: CARDINAL,
oldID, leftID, rightID, thisID: ccD.UniqueID,
toc: vmD.TOCHandle,
key: CARDINAL,
remote, done: BOOLEAN];


RetrieveChollaMail: PROCEDURE =
BEGIN
completed: CARDINAL ← 0;
cm: vmD.ComposedMessagePtr;
toc: vmD.TOCHandle;
key: CARDINAL;
tnp: inD.TOCTextNbrPtr = intC.tocTextNbr;

ResetMessages: PROCEDURE =
BEGIN
fp: vmD.TOCFixedPart;
FOR i: vmD.TOCIndex DECREASING IN [1 .. toc.indexFF) DO
vmD.GetTOCFixedPart[toc, key, i, @fp];
IF fp.deleted THEN
{IF ~opD.ReplaceMailOperation[delete: TRUE, index: i, msg: NIL, toc: toc, key: key]
THEN exD.SysBug[]}
ELSE {fp.mark ← Ascii.SP; vmD.PutTOCFixedPart[toc, key, i, @fp]};
ENDLOOP;
END; -- of ResetMessages --

FindUntriedMessage: PROCEDURE RETURNS [index: vmD.TOCIndex] =
BEGIN
fp: vmD.TOCFixedPart;
FOR i: vmD.TOCIndex IN [1 .. toc.indexFF) DO
vmD.GetTOCFixedPart[toc, key, i, @fp];
IF ~fp.deleted AND fp.mark = Ascii.SP THEN RETURN[i];
ENDLOOP;
RETURN[0];
END; -- of FindUntriedMessage --

MarkCurrentMessage: PROCEDURE [index: vmD.TOCIndex] =
BEGIN
fp: vmD.TOCFixedPart;
vmD.GetTOCFixedPart[toc, key, index, @fp];
fp.mark ← ’c;
vmD.PutTOCFixedPart[toc, key, index, @fp];
END; -- of MarkCurrentMessage --

FindCurrentMessage: PROCEDURE RETURNS [index: vmD.TOCIndex] =
BEGIN
fp: vmD.TOCFixedPart;
index ← 0;
FOR i: vmD.TOCIndex IN [1 .. toc.indexFF) DO
vmD.GetTOCFixedPart[toc, key, i, @fp];
IF ~fp.deleted AND fp.mark = ’c THEN
{IF index = 0 THEN index ← i ELSE RETURN[0]};
ENDLOOP;
END; -- of FindCurrentMessage --

[toc, key] ← ccD.GetTOCForFile["Pending"L, pending];
haveNewMail ← FALSE;
IF ~opD.AccessNewMailOperation
[toc, key, chollaRetrieveHandle, chollaRetrieveRegistry, TRUE]
AND toc.indexFF = 1
THEN {vmD.UnlockTOC[toc, key]; RETURN};
IF tnp.toc = toc THEN
BEGIN
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, tnp.topY, tnp.bottomY];
inD.TOCTextPainter[tnp, key];
END;
ResetMessages[];
cm ← vmD.AllocateComposedMessageObject[];
DO
didAMessage: BOOLEAN ← FALSE;
didThisMessage: BOOLEAN;
fp: vmD.TOCFixedPart;
DO
index: vmD.TOCIndex ← FindUntriedMessage[];
fieldList: MessageParse.FieldList;
Process.Yield[]; Process.Yield[];
IF index = 0 THEN
BEGIN
ResetMessages[];
IF didAMessage THEN EXIT
ELSE {vmD.FreeVirtualMessageObject[cm]; vmD.UnlockTOC[toc, key]; RETURN};
END;
ccD.LoadComposedMessage[toc, key, index, cm];
MarkCurrentMessage[index];
vmD.UnlockTOC[toc, key];
fieldList ← MessageParse.MakeFieldList[cm];
didThisMessage ← ReceiveChollaMail[cm, @fieldList, TRUE, NIL, 0];
MessageParse.FreeFieldList[fieldList];
[toc, key] ← ccD.GetTOCForFile["Pending"L, pending];
index ← FindCurrentMessage[];
IF index = 0 THEN {ResetMessages[]; EXIT};
vmD.GetTOCFixedPart[toc, key, index, @fp];
IF didThisMessage THEN fp.deleted ← didAMessage ← TRUE
ELSE fp.mark ← ’n; -- something other than ’c (current) or SP (untried).
vmD.PutTOCFixedPart[toc, key, index, @fp];
IF tnp.toc = toc THEN inD.RefreshTOCChange[toc, key, index, replace];
ENDLOOP;
ENDLOOP;
END; -- of RetrieveChollaMail --


ReceiveChollaMail: PROCEDURE
[cm: vmD.ComposedMessagePtr, fieldListPtr: MessageParse.FieldListPtr, remote: BOOLEAN,
toc: vmD.TOCHandle, key: CARDINAL]
RETURNS [done: BOOLEAN] =
BEGIN ENABLE MessageParse.ParseFault => GOTO Bad;
m: MessageDescriptor;
temp: STRING ← [50];
FieldType: TYPE =
{date, sender, from, type, action, index, leftId, rightId, to, user, thisId, uniqueId};
fields: ARRAY FieldType OF STRING ←
["Date"L, "Sender"L, "From"L, "Type"L, "Action"L, "Index"L, "LeftID"L, "RightID"L,
"To"L, "User"L, "ThisID"L, "UniqueID"L];
legal: BOOLEAN;

ReadOne: PROCEDURE [fieldType: FieldType] RETURNS [STRING] =
BEGIN
fl: MessageParse.FieldList ← MessageParse.LocateField[fieldListPtr↑, fields[fieldType]].field;
[] ← MessageParse.GetNextWord[vm: cm, start: fl.valueStart, end: fl.valueEnd, s: temp];
RETURN[temp];
END; -- of ReadOne --

IF remote AND String.EquivalentString[ReadOne[from], chollaRetrieveName] THEN
RETURN[TRUE];
IF MessageParse.LocateField[fieldListPtr↑, fields[type]].count = 0 THEN GO TO Bad;
IF String.EquivalentString[ReadOne[type], "FileRequest"L] THEN
{ccD.DoAFileRequest[cm, fieldListPtr↑]; RETURN[TRUE]};
m.type ← ccD.StringToType[temp];
IF m.type = none THEN GO TO Bad;
FOR i: FieldType IN FieldType DO
n: CARDINAL ← MessageParse.LocateField[fieldListPtr↑, fields[i]].count;
IF n > 1 OR (i > sender AND n = 0) THEN GOTO Bad;
ENDLOOP;
m.toc ← toc;
m.key ← key;
m.cm ← cm;
m.fieldListPtr ← fieldListPtr;
m.remote ← remote;
m.action ← ccD.StringToAction[ReadOne[action]];
IF m.action = none THEN GO TO Bad;
m.index ← String.StringToDecimal[ReadOne[index]];
m.leftID ← ccD.GetIDUsingVM[cm, fieldListPtr, fields[leftId]];
m.rightID ← ccD.GetIDUsingVM[cm, fieldListPtr, fields[rightId]];
m.thisID ← ccD.GetIDUsingVM[cm, fieldListPtr, fields[thisId]];
m.oldID ← ccD.GetIDUsingVM[cm, fieldListPtr, fields[uniqueId]];
FOR i: FieldType IN [date .. user] DO
MessageParse.DeleteField[cm, fieldListPtr, fields[i]];
ENDLOOP;
legal ← SELECT m.type FROM
bb => DoABBMessage[@m],
done => DoADoneMessage[@m],
step => DoAStepListMessage[@m],
technician => FALSE,
spec, data => DoSpecOrDataMessage[@m],
prototypes => DoAPrototypesMessage[@m],
ENDCASE => FALSE;
IF ~ legal THEN GO TO Bad;
RETURN[m.done];
EXITS
Bad => BEGIN
originalSize: vmD.CharIndex = vmD.GetMessageSize[cm];
bogusMessageHeader: STRING = "

Subject: Bogus incoming Cholla mail
To: ChollaSupport.pa

"L;
done ← remote;
IF remote THEN exD.DisplayExceptionString["Bogus mail received!"L];
vmD.StartMessageInsertion[cm, 0];
vmD.InsertStringInMessage[cm, "From: "L];
vmD.InsertStringInMessage[cm, intC.workstationName];
vmD.InsertStringInMessage[cm, bogusMessageHeader];
vmD.StopMessageInsertion[cm];
AppendToUnsentMailQueue[cm];
vmD.DeleteRangeInMessage[[0, vmD.GetMessageSize[cm] - originalSize, cm]];
END;
END; -- of ReceiveChollaMail --


DoADoneMessage: PROCEDURE [m: MessageDescriptorPtr] RETURNS [legal: BOOLEAN] =
-- Sets m.done to TRUE if this message is no longer to be kept in Pending.mail.
BEGIN
string: STRING ← [ccD.tocCacheFileStringLength];
statusString: STRING ← [15];
status: ccD.StepStatus;
subjectString: STRING ← [opD.maxTOCStringLength - ccD.maxRunNameLength - 2];
stepListDotMail: STRING = "StepList"L;
doneDotMail: STRING = "Done"L;
toc: vmD.TOCHandle;
key: CARDINAL;
success: BOOLEAN ← FALSE;
changedCurrentDoneFile: BOOLEAN;
technician: STRING ← [ccD.maxUserNameLength];
step: vmD.TOCIndex = MessageParse.GetNumberFromField[m.cm, m.fieldListPtr↑, "Step"L];
doneTOCIndexFF: vmD.TOCIndex;
IF step = 0 OR step # m.index THEN RETURN[FALSE];
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Run"L, string];
IF string.length = 0 THEN RETURN[FALSE];
String.AppendString[string, doneDotMail];
legal ← TRUE;
IF m.remote THEN ccD.StartMultiTOCOperation[];
IF m.toc = NIL THEN [toc, key] ← ccD.GetTOCForFile[string, done, FALSE]
ELSE {toc ← m.toc; key ← m.key};
IF m.action = delete THEN legal ← success ← FALSE
ELSE SELECT TRUE FROM
step < toc.indexFF =>
{m.action ← replace; success ← DoActionOnDoneMessage[m, toc, key]};
step = toc.indexFF =>
{m.action ← insert; success ← DoActionOnDoneMessage[m, toc, key]};
ENDCASE => m.done ← FALSE;
doneTOCIndexFF ← toc.indexFF;
IF m.toc = NIL THEN vmD.UnlockTOC[toc, key];
IF ~success OR ccD.IsDummyID[m.oldID] THEN
{IF m.remote THEN ccD.FinishMultiTOCOperation[]; RETURN};
-- success means that we changed the done file. Now store new state.
changedCurrentDoneFile ← String.EquivalentString[ccD.currentDoneFileName, string];
string.length ← string.length - doneDotMail.length;
String.AppendString[string, stepListDotMail];
[toc, key] ← ccD.GetTOCForFile[string, stepList, FALSE];
IF toc = NIL THEN exD.SysBug[];
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Status"L, statusString];
status ← ccD.StringToStatus[statusString];
ccD.SetStepStatus[toc, key, step, status];
string.length ← string.length - stepListDotMail.length;
ccD.MakeBBRunEntryString[toc, key, doneTOCIndexFF, string, subjectString];
vmD.UnlockTOC[toc, key];
ccD.UpdateBBRunEntry[run: string, string: subjectString, newID: m.thisID];
IF status = started THEN
BEGIN
version: CARDINAL = MessageParse.GetNumberFromField[m.cm, m.fieldListPtr↑, "Version"L];
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Spec"L, string];
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Started-By"L, technician];
ccD.UpdateTechnician[technician, string, version];
END;
IF m.remote AND changedCurrentDoneFile AND ccD.currentStepNumber = step THEN
exD.DisplayExceptionString
["The lower window may be out of date. Try invoking ""Step""."L];
IF m.remote THEN ccD.FinishMultiTOCOperation[];
END; -- of DoADoneMessage --


DoABBMessage: PROCEDURE [m: MessageDescriptorPtr] RETURNS [legal: BOOLEAN] =
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
tocString: STRING ← [opD.maxTOCStringLength];
run: STRING ← [ccD.tocCacheFileStringLength];
stepListDotMail: STRING = "StepList"L;
doneDotMail: STRING = "Done"L;
success: BOOLEAN;
isRunDelete: BOOLEAN;
legal ← TRUE;
IF ccD.UniqueIDIsInDeletionList[m.oldID] THEN {m.done ← TRUE; RETURN};
IF m.toc = NIL THEN
[toc, key] ← ccD.GetTOCForFile["ChollaBulletinBoard"L, bb]
ELSE {toc ← m.toc; key ← m.key};
[success, isRunDelete] ← DoActionOnBBMessage[m, toc, key, run];
IF m.toc = NIL THEN vmD.UnlockTOC[toc, key];
-- Note: no multiTOC operation here on remote case since files are locked only one at a time.
IF success AND isRunDelete THEN
BEGIN -- destroy the run files.
String.AppendString[run, stepListDotMail];
[toc, key] ← ccD.GetTOCForFile[run, stepList, FALSE];
FOR index: vmD.TOCIndex DECREASING IN [1 .. toc.indexFF) DO
IF ~opD.ReplaceMailOperation[delete: TRUE, index: index, msg: NIL, toc: toc, key: key]
THEN exD.SysBug[];
ENDLOOP;
vmD.UnlockTOC[toc, key];
run.length ← run.length - stepListDotMail.length;
String.AppendString[run, doneDotMail];
[toc, key] ← ccD.GetTOCForFile[run, done, FALSE];
FOR index: vmD.TOCIndex DECREASING IN [1 .. toc.indexFF) DO
IF ~opD.ReplaceMailOperation[delete: TRUE, index: index, msg: NIL, toc: toc, key: key]
THEN exD.SysBug[];
ENDLOOP;
vmD.UnlockTOC[toc, key];
END;
IF success AND m.action = delete THEN ccD.InsertInDeletionList[m.oldID];
END; -- of DoABBMessage --


DoSpecOrDataMessage: PROCEDURE [m: MessageDescriptorPtr] RETURNS [legal: BOOLEAN] =
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
fileName: STRING ← [ccD.tocCacheFileStringLength];
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "FileName"L, fileName];
IF fileName.length = 0 THEN RETURN[FALSE];
legal ← TRUE;
IF m.toc = NIL THEN
[toc, key] ← ccD.GetTOCForFile[fileName, IF m.type = spec THEN spec ELSE data, FALSE]
ELSE {toc ← m.toc; key ← m.key};
MessageParse.DeleteField[m.cm, m.fieldListPtr, "FileName"L];
SELECT TRUE FROM
~DoActionOnMessage[m, toc, key] => NULL;
m.index = 1 => UpdateCurrentVersion[toc, key];
m.action = replace => MarkIfArchived[toc, key, m];
ENDCASE;
IF m.toc = NIL THEN vmD.UnlockTOC[toc, key];
END; -- of DoSpecOrDataMessage --


MarkIfArchived: PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, m: MessageDescriptorPtr] =
BEGIN
archivedValue: STRING ← [5];
fp: vmD.TOCFixedPart;
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Archived"L, archivedValue];
vmD.GetTOCFixedPart[toc, key, m.index, @fp];
fp.mark ← IF String.EquivalentString[archivedValue, "TRUE"L] THEN ’a ELSE Ascii.SP;
vmD.PutTOCFixedPart[toc, key, m.index, @fp];
END; -- of MarkIfArchived --


UpdateCurrentVersion: PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL] =
BEGIN
fp: vmD.TOCFixedPart;
dm: vmD.DisplayMessagePtr;
fieldList: MessageParse.FieldList;
messageID: vmD.TOCIndex;
firstFree: vmD.TOCIndex = vmD.FirstFreeTOCIndex[toc, key];
IF firstFree <= 2 THEN RETURN;
FOR i: vmD.TOCIndex IN [1 .. firstFree) DO
vmD.GetTOCFixedPart[toc, key, i, @fp];
IF fp.mark = ’* THEN {fp.mark ← Ascii.SP; vmD.PutTOCFixedPart[toc, key, i, @fp]};
ENDLOOP;
dm ← vmD.AllocateDisplayMessageObject[];
vmD.LoadDisplayMessage[toc, key, 1, dm];
fieldList ← MessageParse.MakeFieldList[dm];
messageID ← MessageParse.GetNumberFromField[dm, fieldList, "CurrentVersion"L];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
MessageParse.FreeFieldList[fieldList];
vmD.GetTOCFixedPart[toc, key, messageID + 1, @fp];
fp.mark ← ’*;
vmD.PutTOCFixedPart[toc, key, messageID + 1, @fp];
END; -- of UpdateCurrentVersion --


DoAStepListMessage: PROCEDURE [m: MessageDescriptorPtr] RETURNS [legal: BOOLEAN] =
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
string: STRING ← [ccD.tocCacheFileStringLength];
stepListDotMail: STRING = "StepList"L;
doneDotMail: STRING = "Done"L;
doneTOCIndexFF: vmD.TOCIndex;
MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Run"L, string];
IF string.length = 0 THEN RETURN[FALSE];
legal ← TRUE;
String.AppendString[string, doneDotMail];
[toc, key] ← ccD.GetTOCForFile[string, done, FALSE];
doneTOCIndexFF ← toc.indexFF;
vmD.UnlockTOC[toc, key];
string.length ← string.length - doneDotMail.length;
IF m.remote THEN ccD.StartMultiTOCOperation[];
IF m.toc = NIL THEN
BEGIN
String.AppendString[string, stepListDotMail];
[toc, key] ← ccD.GetTOCForFile[string, stepList, FALSE];
string.length ← string.length - stepListDotMail.length;
END
ELSE {toc ← m.toc; key ← m.key};
IF DoActionOnMessage[m, toc, key] THEN
BEGIN
subjectString: STRING ← [opD.maxTOCStringLength - 2];
IF toc.indexFF = 1 THEN subjectString ← NIL
ELSE ccD.MakeBBRunEntryString[toc, key, doneTOCIndexFF, string, subjectString];
ccD.UpdateBBRunEntry[run: string, string: subjectString, newID: m.thisID];
END;
IF m.toc = NIL THEN vmD.UnlockTOC[toc, key];
IF m.remote THEN ccD.FinishMultiTOCOperation[];
END; -- of DoAStepListMessage --


DoAPrototypesMessage: PROCEDURE [m: MessageDescriptorPtr] RETURNS [legal: BOOLEAN] =
BEGIN
toc: vmD.TOCHandle;
key: CARDINAL;
string: STRING ← [ccD.tocCacheFileStringLength];
IF m.toc = NIL THEN
[toc, key] ← ccD.GetTOCForFile["StepListPrototypes"L, prototypes, FALSE]
ELSE {toc ← m.toc; key ← m.key};
[] ← DoActionOnMessage[m, toc, key];
IF m.toc = NIL THEN vmD.UnlockTOC[toc, key];
RETURN[TRUE];
END; -- of DoAPrototypesMessage --


DoActionOnMessage: PROCEDURE
[m: MessageDescriptorPtr, toc: vmD.TOCHandle, key: CARDINAL]
RETURNS [success: BOOLEAN] =
BEGIN
thisIDforIndex, uniqueIDforIndex, id: ccD.UniqueID;
success ← FALSE;
IF toc = NIL THEN exD.SysBug[];
m.done ← TRUE;
ccD.GetBothIDsUsingIndex[toc, key, m.index, @thisIDforIndex, @uniqueIDforIndex];
SELECT m.action FROM
insert => BEGIN
IF m.thisID = thisIDforIndex THEN RETURN; -- duplicate --
id ← ccD.GetIDUsingIndex[toc, key, m.index - 1, uniqueID];
SELECT TRUE FROM
~ccD.NewMatchesOld[@m.leftID, @id]
OR ~ccD.NewMatchesOld[@m.rightID, @uniqueIDforIndex] => m.done ← FALSE;
m.type = step => success ← DoStepListInsertion[m, toc, key];
~opD.ReplaceMailOperation
[delete: FALSE, index: m.index, msg: m.cm, toc: toc, key: key] =>
exD.DisplayExceptionString["Can’t insert in file"L];
ENDCASE => success ← TRUE;
END;
delete => SELECT TRUE FROM
m.leftID # ccD.GetIDUsingIndex[toc, key, m.index - 1, uniqueID]
OR m.rightID # ccD.GetIDUsingIndex[toc, key, m.index + 1, uniqueID]
OR m.oldID # uniqueIDforIndex
=> m.done ← FALSE;
~opD.ReplaceMailOperation
[delete: TRUE, index: m.index, msg: NIL, toc: toc, key: key] =>
exD.DisplayExceptionString["Can’t delete in file"L];
ENDCASE => success ← TRUE;
replace => BEGIN
SELECT TRUE FROM
~ccD.NewMatchesOld[@m.oldID, @uniqueIDforIndex] => m.done ← FALSE;
ccD.IsIllegalID[thisIDforIndex] => m.done ← FALSE;
~ccD.NewerUniqueID[@m.thisID, @thisIDforIndex] => NULL;
ENDCASE =>
IF ~(success ← opD.ReplaceMailOperation
[delete: TRUE, index: m.index, msg: m.cm, toc: toc, key: key])
THEN exD.DisplayExceptionString["Can’t insert in done file"L];
END;
ENDCASE => exD.SysBug[];
END; -- of DoActionOnMessage --


DoActionOnDoneMessage: PROCEDURE
[m: MessageDescriptorPtr, toc: vmD.TOCHandle, key: CARDINAL]
RETURNS [success: BOOLEAN] =
BEGIN
doneFileThisID: ccD.UniqueID;
success ← FALSE;
IF toc = NIL THEN exD.SysBug[];
m.done ← TRUE;
SELECT m.action FROM
insert =>
BEGIN -- Only occurs on append to done file.
success ← opD.ReplaceMailOperation
[delete: FALSE, index: m.index, msg: m.cm, toc: toc, key: key];
IF ~success THEN exD.DisplayExceptionString["Can’t insert in file"L];
END;
replace =>
BEGIN -- we already have something in the done file.
IF ccD.IsDummyID[m.thisID] THEN RETURN;
doneFileThisID ← ccD.GetIDUsingIndex[toc, key, m.index, thisID];
IF ccD.IsDummyID[doneFileThisID] OR ccD.NewerUniqueID[@m.thisID, @doneFileThisID]
THEN BEGIN
success ← opD.ReplaceMailOperation
[delete: TRUE, index: m.index, msg: m.cm, toc: toc, key: key];
IF ~success THEN exD.DisplayExceptionString["Can’t insert in done file"L];
END;
END;
ENDCASE => exD.SysBug[];
END; -- of DoActionOnDoneMessage --


DoActionOnBBMessage: PROCEDURE
[m: MessageDescriptorPtr, toc: vmD.TOCHandle, key: CARDINAL, run: STRING]
RETURNS [success, isRunDelete: BOOLEAN] =
BEGIN
thisID: ccD.UniqueID;
index: vmD.TOCIndex;
found: BOOLEAN ← TRUE;
success ← FALSE;
IF toc = NIL THEN exD.SysBug[];
m.done ← TRUE;
isRunDelete ← FALSE;
FOR index IN [1 .. toc.indexFF) DO
IF m.oldID = ccD.GetIDUsingIndex[toc, key, index, uniqueID] THEN EXIT;
REPEAT
FINISHED => found ← FALSE;
ENDLOOP;
SELECT m.action FROM
insert =>
BEGIN
IF found THEN RETURN; -- duplicate --
success ← opD.ReplaceMailOperation
[delete: FALSE, index: 1, msg: m.cm, toc: toc, key: key];
IF ~success THEN exD.DisplayExceptionString["Can’t insert in Bulletin Board!"L];
END;
delete =>
BEGIN
dm: vmD.DisplayMessagePtr;
fieldList: MessageParse.FieldList;
IF ~found THEN {m.done ← FALSE; RETURN[FALSE, FALSE]};
dm ← vmD.AllocateDisplayMessageObject[];
vmD.LoadDisplayMessage[toc, key, index, dm];
fieldList ← MessageParse.MakeFieldList[dm];
isRunDelete ← ccD.ObtainRunName[dm, fieldList, run];
MessageParse.FreeFieldList[fieldList];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
success ← opD.ReplaceMailOperation
[delete: TRUE, index: index, msg: NIL, toc: toc, key: key];
IF ~success THEN exD.DisplayExceptionString["Can’t delete in Bulletin Board!"L];
END;
replace =>
BEGIN
IF ~found THEN {m.done ← FALSE; RETURN[FALSE, FALSE]};
thisID ← ccD.GetIDUsingIndex[toc, key, index, thisID];
IF ccD.NewerUniqueID[@m.thisID, @thisID] THEN
BEGIN
success ← opD.ReplaceMailOperation
[delete: TRUE, index: index, msg: m.cm, toc: toc, key: key];
IF ~success THEN exD.DisplayExceptionString["Can’t replace in Bulletin Board!"L];
END;
END;
ENDCASE => exD.SysBug[];
END; -- of DoActionOnBBMessage --


DoStepListInsertion: PROCEDURE
[m: MessageDescriptorPtr, toc: vmD.TOCHandle, key: CARDINAL]
RETURNS [success: BOOLEAN] =
BEGIN
IF MessageParse.LocateField[m.fieldListPtr↑, "Subject"L].count # 0 THEN
BEGIN
success ← opD.ReplaceMailOperation
[delete: FALSE, index: m.index, msg: m.cm, toc: toc, key: key];
IF ~success THEN exD.DisplayExceptionString["Can’t insert in file"L];
END
ELSE BEGIN
tocIndex: vmD.TOCIndex ← m.index;
appending: BOOLEAN = (m.index = toc.indexFF);
cmIndex: vmD.CharIndex ← 0;
cmSize: vmD.CharIndex;
char: CHARACTER;
entry: STRING ← [250];
entryIndex: CARDINAL;
id: ccD.UniqueID ← m.thisID;
runName: STRING ← [ccD.tocCacheFileStringLength];
newcm: vmD.ComposedMessagePtr = vmD.AllocateComposedMessageObject[];
sh: csD.StreamHandle;
nmIndex: vmD.CharIndex;

AppendMessageToStepList: PROCEDURE =
BEGIN
fp: vmD.TOCFixedPart;
tocString: STRING = [opD.maxTOCStringLength];
fp.deleted ← fp.changed ← fp.bogus ← FALSE;
fp.seen ← TRUE;
fp.mark ← Ascii.SP;
fp.offsetToHeader ← 0;
fp.textLength ← vmD.GetMessageSize[newcm];
[fp.firstPage, fp.firstByte] ← csD.MapPositionToPageByte[csD.GetPosition[sh]];
mfD.CreateStamp[@fp, StampPutChar];
nmIndex ← 0;
UNTIL nmIndex >= fp.textLength DO
[] ← vmD.GetMessageChar[newcm, nmIndex];
csD.WriteBlock[sh, newcm.buffer, newcm.get.floor + (nmIndex - newcm.get.first),
newcm.get.free - nmIndex];
nmIndex ← newcm.get.free;
ENDLOOP;
nmIndex ← 0;
mfD.ParseHeaderForTOC[tocString, ParseReadChar];
vmD.ExtendTOC[toc, key, @fp, tocString];
END; -- of AppendMessageToStepList --

ParseReadChar: PROCEDURE RETURNS [char: CHARACTER] =
BEGIN
char ← vmD.GetMessageChar[newcm, nmIndex];
nmIndex ← nmIndex + 1;
END; -- of ParseReadChar --

StampPutChar: PROCEDURE [c: CHARACTER] = {csD.Write[sh, c]};

MessageParse.GetStringFromField[m.cm, m.fieldListPtr↑, "Run"L, runName];
MessageParse.DeleteField[m.cm, m.fieldListPtr, "Run"L];
MessageParse.DeleteField[m.cm, m.fieldListPtr, "Format"L];
MessageParse.DeleteField[m.cm, m.fieldListPtr, "ThisID"L];
MessageParse.DeleteField[m.cm, m.fieldListPtr, "UniqueID"L];
cmSize ← vmD.GetMessageSize[m.cm];
IF appending THEN sh ← csD.Open[toc.mailFile, byte, append];
UNTIL cmIndex >= cmSize DO
bogus: BOOLEAN ← FALSE;
slashCount: CARDINAL ← 0;
entryIndex ← 0;
UNTIL cmIndex >= cmSize DO
char ← vmD.GetMessageChar[m.cm, cmIndex];
cmIndex ← cmIndex + 1;
SELECT char FROM
Ascii.CR => EXIT;
’/ => slashCount ← slashCount + 1;
’: => IF slashCount < 2 THEN bogus ← TRUE;
ENDCASE;
IF entryIndex < entry.maxlength THEN
{entry[entryIndex] ← char; entryIndex ← entryIndex + 1};
ENDLOOP;
IF entryIndex = 0 OR bogus OR slashCount = 0 OR slashCount MOD 2 = 1 THEN LOOP;
entry.length ← entryIndex;
MakeMessageFromEntry[entry, newcm, @id, runName];
IF appending THEN AppendMessageToStepList[]
ELSE [] ← opD.ReplaceMailOperation
[delete: FALSE, index: tocIndex, msg: newcm, toc: toc, key: key];
tocIndex ← tocIndex + 1;
id.time ← id.time + 1;
ENDLOOP;
IF appending THEN
BEGIN
tnp: inD.TOCTextNbrPtr = intC.tocTextNbr;
csD.Close[sh];
IF tnp.toc = toc THEN
BEGIN
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, tnp.topY, tnp.bottomY];
inD.TOCTextPainter[tnp, key];
END;
END;
vmD.FreeVirtualMessageObject[newcm];
success ← TRUE;
END;
END; -- of DoStepListInsertion --


MakeMessageFromEntry: PROCEDURE
[entry: STRING, msg: vmD.ComposedMessagePtr, id: ccD.UniqueIDPtr, runName: STRING] =
-- Constructs in "msg" a message suitable for insertion in a Step List file. The status section
-- of "entry" will be replaced with NotStarted. N.B. "entry" may be modified.
BEGIN
fieldList: MessageParse.FieldList;
template: STRING = "Subject: xxx


Run: xxx
UniqueID: xxx
ThisID: xxx
Format: xxx
"L;
slashIndex: CARDINAL ← 0;
subject: STRING;
notStarted: STRING =
"Not Started "L;
FOR i: CARDINAL IN [0 .. entry.length) DO
IF entry[i] = ’/ THEN {slashIndex ← i; EXIT};
ENDLOOP;
IF slashIndex > 0 THEN
BEGIN
FOR i: CARDINAL IN [slashIndex .. entry.length) DO
entry[i - slashIndex] ← entry[i];
ENDLOOP;
entry.length ← entry.length - slashIndex;
END;
Process.Yield[]; Process.Yield[];
vmD.InitComposedMessage[msg, template];
subject ← Storage.String[notStarted.length + entry.length];
String.AppendString[subject, notStarted];
String.AppendString[subject, entry];
fieldList ← MessageParse.MakeFieldList[msg];
MessageParse.ReplaceOrAppendField[msg, @fieldList, "
Subject"L, subject];
MessageParse.ReplaceOrAppendField[msg, @fieldList, "
Run"L, runName];
ccD.FillIDField[msg, @fieldList, "UniqueID"L, id];
ccD.FillIDField[msg, @fieldList, "ThisID"L, id];
MessageParse.ReplaceOrAppendField[msg, @fieldList, "Format"L, formatString];
Storage.FreeString[subject];
MessageParse.FreeFieldList[fieldList];
END; -- of MakeMessageFromEntry --


END. -- of ChollaMail --