-- file: ChollaCmd.mesa
-- edited by Barth, August 20, 1981 11:45 AM
-- edited by Brotz, October 22, 1982 11:45 AM
-- edited by Crowther, December 22, 1981 10:23 AM

DIRECTORY
ccD: FROM "ChollaCmdDefs",
dsD: FROM "DisplayDefs",
Editor,
exD: FROM "ExceptionDefs",
inD: FROM "InteractorDefs",
intCommon,
lmD: FROM "LaurelMenuDefs",
MailParse,
NameInfoDefs,
opD: FROM "OperationsDefs",
Process,
RetrieveDefs,
String,
Storage,
tsD: FROM "TOCSelectionDefs",
vmD: FROM "VirtualMgrDefs",
VMDefs;

ChollaCmd: MONITOR
IMPORTS ccD, dsD, exD, inD, intC: intCommon, lmD, NameInfoDefs, opD, Process,
RetrieveDefs, String, Storage, tsD, vmD, VMDefs
EXPORTS ccD
SHARES vmD =

BEGIN
OPEN ccD;

activeStepSpecName: PUBLIC STRING ← [tocCacheFileStringLength];
-- last one displayed by step or spec

cacheTable: PUBLIC ARRAY TOCCacheType OF TOCCache
← [[], [], [], [], [], [], [], [], [], [], []];

stepListRunName: PUBLIC STRING ← [maxRunNameLength];
stepListLowerHp: PUBLIC inD.HousePtr;

iOwnThisStep: PUBLIC BOOLEAN ← FALSE;
currentStepNumber: PUBLIC vmD.TOCIndex ← 0;
displayedStep: PUBLIC vmD.TOCIndex;
currentDoneFileName: PUBLIC STRING ← [tocCacheFileStringLength];
shutDownCholla: PUBLIC BOOLEAN;

mailProcess: PROCESS;

AuthorizationState: TYPE = {unknown, no, yes};
authorizationTable: ARRAY AuthorizationClass OF AuthorizationState;


StartChollaMailProcess: PUBLIC PROCEDURE =
BEGIN
shutDownCholla ← FALSE;
mailProcess ← FORK ChollaMailProcess[];
END; -- of StartChollaMailProcess --


FinishChollaMailProcess: PUBLIC PROCEDURE =
BEGIN
shutDownCholla ← TRUE;
CallSomethingToDo[];
JOIN mailProcess;
END; -- of StartChollaMailProcess --


BBoardCommand: PUBLIC inD.CommandProcedure =
BEGIN OPEN inD;
tnp: TOCTextNbrPtr = intC.tocTextNbr;
dm: MessageTextNbrPtr = intC.dmTextNbr;
IF ~intC.isCholla THEN
{exD.DisplayExceptionString["BBoard not permitted if profile is in error"L]; RETURN};
IF ~IsAuthorized[onlooker, hp] THEN RETURN;
intC.tocTextNbr.displayFormatted ← FALSE;
intC.deliverCommandVisible ← FALSE;
CleanupDisplayMessage[];
dsD.ClearRectangle[leftMargin, rightMargin, tnp.topY, tnp.bottomY];
[ , ] ← GetTOCForFile[NIL, laurel];
tnp.toc ← NIL;
tnp.haveToc ← FALSE;
InstallBB[];
hp.command ← inD.NullCommand;
END; -- of BulletinBoardCommand --


RestoreLaurel: PUBLIC PROCEDURE =
BEGIN
tnp: inD.TOCTextNbrPtr = intC.tocTextNbr;
CleanupDisplayMessage[];
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, tnp.topY, tnp.bottomY];
lmD.ChangeEditorMenu[singleLine];
intC.bboardCommandHouse.command ← BBoardCommand;
intC.userBracketsHouse.nextHouse ← intC.newMailCommandHouse;
inD.ChangeCommandMenu
[cnp: intC.mailCommandNbr, region: intC.mailCommandRegion, linesToKeep: 2];
intC.tocCommandNbr.houses ← intC.displayCommandHouse;
inD.ChangeCommandMenu
[cnp: intC.tocCommandNbr, region: intC.TOCCommandRegion, linesToKeep: 0];
intC.cmCommandNbr.houses ← intC.newFormCommandHouse;
inD.ChangeCommandMenu
[cnp: intC.cmCommandNbr, region: intC.CMCommandRegion, linesToKeep: 0];
intC.deliverCommandVisible ← TRUE;
tnp.displayFormatted ← TRUE;
tnp.haveToc ← FALSE;
tnp.toc ← NIL;
IF intC.mailFileCommandHouse.text.length > 0 THEN
BEGIN
inD.IndicateCommandBusy[intC.mailFileCommandHouse];
inD.GetMailFileCommand[intC.mailFileCommandHouse, TRUE];
inD.IndicateCommandFinished[intC.mailFileCommandHouse];
END;
END; -- of RestoreLaurel --


CleanUpTOCCaches: PUBLIC PROCEDURE =
-- Returns all tocs in all toc caches except for the ones held in the laurel toc cache.
BEGIN
FOR ct: TOCCacheType IN (FIRST[TOCCacheType] .. LAST[TOCCacheType]] DO
[ , ] ← GetTOCForFile[NIL, ct];
ENDLOOP;
END; -- of CleanUpTOCCaches --


GetTOCForFile: PUBLIC PROCEDURE
[fileName: STRING, cacheType: TOCCacheType, usingA: BOOLEAN ← TRUE]
RETURNS [toc: vmD.TOCHandle, key: CARDINAL] =
BEGIN
worked: BOOLEAN;
DO
[toc, key, worked] ← GetTOCForFileInner[fileName, cacheType, usingA];
IF worked THEN EXIT;
Process.Yield[]; Process.Yield[];
ENDLOOP;
END; -- of GetTOCForFile --


GetTOCForFileInner: ENTRY PROCEDURE
[fileName: STRING, cacheType: TOCCacheType, usingA: BOOLEAN ← TRUE]
RETURNS [toc: vmD.TOCHandle, key: CARDINAL, worked: BOOLEAN] =
-- Returns a toc corresponding to a mail file with "filename". ".mail" is added if no dot is
-- present in the "filename" parameter. "cacheType" determines which category of mail
-- file is requested; different categories do not interfere with each others replacement
-- strategy. "usingA" TRUE means that the toc is requested for user interaction; FALSE
-- means that the Cholla mail process is requesting this toc. Normally, the toc is returned
-- locked. If anything goes wrong, "toc" retruned is NIL.
-- A new toc requested from the same cacheType with the same "usingA" will cause the
-- previous toc held in that cacheType to be returned. To force a return of both tocs held
-- in the cacheType without getting a toc on a new file, call GetTOCForFile with
-- "filename" = NIL.
-- A note on which tocs are contained in which positions:
-- (Conservation of TOC data structure law.)
-- Motivation: there are several (9) distinct classes of mail file used by Cholla. Each class
-- generally needs two tocs (called a and b) available at all times. One of these tocs (a)
-- is used directly by the user, the other (b) is used by the Cholla mail process.
-- Sometimes a = b, in which case the "other" toc data structure is saved in the spare slot.
-- laurel cache is different because it might match any of the tocs in the other categories!
-- For all toc caches except the laurel toc cache,
-- if tocCache.spare = NIL, then a and b are different tocs.
-- if tocCache.spare # NIL, then a and b are the same, and tocCache.spare holds the
-- "other" toc not currently in use by this tocCache.
-- For the laurel toc cache, b is never used. If a matches any other toc in any other
-- cache, then spare contains the toc displaced from a.
-- In any case, a toc in the spare slot is closed.
BEGIN

MatchOtherCache: PROCEDURE RETURNS [cache: POINTER TO TOCCacheX] =
-- Returns the TOCCacheX that contains the toc we’re looking for.
BEGIN -- for laurel cache diddling only
FOR ct: TOCCacheType IN (FIRST[TOCCacheType] .. LAST[TOCCacheType]] DO
cache ← @cacheTable[ct].a;
IF cache.file # NIL AND String.EquivalentString[fname, cache.file]
THEN RETURN;
cache ← @cacheTable[ct].b;
IF cache.file # NIL AND String.EquivalentString[fname, cache.file]
THEN RETURN;
ENDLOOP;
RETURN[NIL];
END; -- of MatchOtherCache --

tocCache: TOCCachePtr ← @cacheTable[cacheType];
laurelCache: TOCCachePtr = @cacheTable[laurel];
match: POINTER TO TOCCacheX ← @laurelCache.a;
myT: POINTER TO TOCCacheX = IF usingA THEN @tocCache.a ELSE @tocCache.b;
hisT: POINTER TO TOCCacheX = IF usingA THEN @tocCache.b ELSE @tocCache.a;
myTocIsOpen: BOOLEAN ← TRUE;
fname: STRING ← [tocCacheFileStringLength];
laurel: BOOLEAN = (cacheType = laurel);
sameAsLaurelsFile: BOOLEAN;
IF laurel AND ~usingA THEN exD.SysBug[];
worked ← TRUE;
IF fileName # NIL THEN
BEGIN
String.AppendString[fname, fileName];
FOR i: CARDINAL IN [0 .. fname.length) DO
IF fname[i] = ’. THEN EXIT;
REPEAT
FINISHED => String.AppendString[fname, IF laurel THEN ".mail"L ELSE ".chml"L];
ENDLOOP;
END;
sameAsLaurelsFile
← laurelCache.a.file # NIL AND String.EquivalentString[fname, laurelCache.a.file];
IF fileName = NIL OR (laurel AND sameAsLaurelsFile) THEN
BEGIN
keyA, keyB, keySpare: CARDINAL ← 0;
bNotEqualA: BOOLEAN ← tocCache.b.toc # NIL AND tocCache.b.toc # tocCache.a.toc;

UnlockABAndSpare: PROCEDURE =
BEGIN
IF keyA # 0 THEN vmD.UnlockTOC[tocCache.a.toc, keyA];
IF keyB # 0 THEN vmD.UnlockTOC[tocCache.b.toc, keyB];
IF keySpare # 0 THEN vmD.UnlockTOC[tocCache.spare, keySpare];
END; -- of UnlockABAndSpare --

IF tocCache.a.toc # NIL THEN
BEGIN
keyA ← vmD.LockTOC[tocCache.a.toc];
IF keyA = 0 THEN {UnlockABAndSpare[]; RETURN[NIL, 0, FALSE]};
END;
IF bNotEqualA THEN
BEGIN
keyB ← vmD.LockTOC[tocCache.b.toc];
IF keyB = 0 THEN {UnlockABAndSpare[]; RETURN[NIL, 0, FALSE]};
END;
IF tocCache.spare # NIL THEN
BEGIN
keySpare ← vmD.LockTOC[tocCache.spare];
IF keySpare = 0 THEN {UnlockABAndSpare[]; RETURN[NIL, 0, FALSE]};
END;
SELECT TRUE FROM
laurelCache.spare = NIL => NULL; -- laurelCache.a.toc # other toc cache’s toc.
laurel => -- some other cache holds laurelCache.a.toc.
BEGIN
IF keyA # 0 THEN {vmD.UnlockTOC[tocCache.a.toc, keyA]; keyA ← 0};
tocCache.a.toc ← NIL;
END;
ENDCASE =>
BEGIN -- not laurel’s toc cache. Check if this duplicates laurel’s toc cache
IF String.EquivalentString[tocCache.a.file, laurelCache.a.file] THEN
BEGIN
IF keyA # 0 THEN {vmD.UnlockTOC[tocCache.a.toc, keyA]; keyA ← 0};
tocCache.a.toc ← laurelCache.spare;
IF tocCache.a.toc # NIL THEN
{keyA ← vmD.LockTOC[tocCache.a.toc]; IF keyA = 0 THEN exD.SysBug[]};
laurelCache.spare ← NIL;
END;
IF String.EquivalentString[tocCache.b.file, laurelCache.a.file] THEN
BEGIN
IF keyB # 0 THEN {vmD.UnlockTOC[tocCache.b.toc, keyB]; keyB ← 0};
tocCache.b.toc ← laurelCache.spare;
IF tocCache.b.toc # NIL THEN
{keyB ← vmD.LockTOC[tocCache.b.toc]; IF keyB = 0 THEN exD.SysBug[]};
laurelCache.spare ← NIL;
END;
-- Note cleverness if a=b=laurel.a : result is laurel.spare = NIL, b = NIL, a = old spare,
-- original 3-way shared toc remains in laurel.a.
END;
IF tocCache.spare # NIL THEN ReturnAndDestroyTOC[tocCache.spare, keySpare];
IF tocCache.a.toc # NIL THEN ReturnAndDestroyTOC[tocCache.a.toc, keyA];
IF tocCache.b.toc # NIL AND tocCache.b.toc # tocCache.a.toc THEN
ReturnAndDestroyTOC[tocCache.b.toc, keyB];
IF myT.file # NIL THEN Storage.FreeString[myT.file];
IF hisT.file # NIL THEN Storage.FreeString[hisT.file];
tocCache↑ ← [];
IF fileName = NIL THEN RETURN[NIL, 0, TRUE];
END;
IF myT.toc = NIL THEN
BEGIN
myT.toc ← vmD.CreateTOC[];
myT.file ← Storage.String[tocCacheFileStringLength];
myTocIsOpen ← FALSE;
END;
toc ← myT.toc;
key ← vmD.LockTOC[toc];
IF key = 0 THEN RETURN[NIL, 0, FALSE];
IF myTocIsOpen THEN
SELECT TRUE FROM
String.EquivalentString[myT.file, fname] => RETURN;
toc = hisT.toc
OR (laurel AND laurelCache.spare # NIL)
OR (~laurel AND toc = laurelCache.a.toc) =>
BEGIN -- toc is a duplicate of some other toc. Don’t return it, get a toc from a spare.
cacheForSpare: TOCCachePtr ← IF toc = hisT.toc THEN tocCache ELSE laurelCache;
myT.toc ← cacheForSpare.spare;
cacheForSpare.spare ← NIL;
vmD.UnlockTOC[toc, key];
toc ← myT.toc;
key ← vmD.LockTOC[toc];
IF key = 0 THEN exD.SysBug[];
END;
myT.file.length = 0 => exD.SysBug[];
ENDCASE => opD.ReturnMailFileOperation[toc, key];
myT.file.length ← 0;
IF toc.open THEN exD.SysBug[];
SELECT TRUE FROM
hisT.toc # NIL AND String.EquivalentString[fname, hisT.file] =>
BEGIN
vmD.UnlockTOC[toc, key];
tocCache.spare ← toc;
toc ← myT.toc ← hisT.toc;
key ← vmD.LockTOC[toc];
END;
(laurel AND (match ← MatchOtherCache[]) # NIL) OR (~laurel AND sameAsLaurelsFile)
=>
BEGIN
vmD.UnlockTOC[toc, key];
laurelCache.spare ← toc;
toc ← myT.toc ← match.toc;
key ← vmD.LockTOC[toc];
END;
~CallGetOp[toc, key, fname] =>
BEGIN
vmD.DestroyTOC[toc, key];
myT.toc ← NIL;
Storage.FreeString[myT.file];
myT.file ← NIL;
RETURN[NIL, 0, TRUE];
END;
ENDCASE;
String.AppendString[myT.file, fname]; --must be here!
IF key = 0 THEN RETURN[NIL, 0, FALSE];
END; -- of GetTOCForFileInner --


ReturnAndDestroyTOC: PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL] =
BEGIN
IF toc = NIL THEN RETURN;
IF toc.open THEN opD.ReturnMailFileOperation[toc, key];
vmD.DestroyTOC[toc, key];
END; -- of ReturnAndDestroyTOC --


CallGetOp: PROCEDURE [toc: vmD.TOCHandle, key: CARDINAL, fileName: STRING]
RETURNS [successful: BOOLEAN] =
-- Encapsulates GetMailFileOperation call and Error catches. Returns LAST[CARDINAL]
-- in "firstUnSeenTOCIndex" if the operation failes.
BEGIN
firstUnSeenTOCIndex: vmD.TOCIndex ← LAST[CARDINAL];
i: vmD.TOCIndex;
tsD.ResetTOCSelection[toc, key];
firstUnSeenTOCIndex ← opD.GetMailFileOperation
[toc, key, fileName
! VMDefs.CantOpen =>
BEGIN
SELECT reason FROM
illegalFileName => exD.DisplayException[exD.illegalMailFileName];
alreadyExists => exD.DisplayException[exD.fileInUse];
accessDenied =>
exD.DisplayExceptionString["User credentials insufficient to access this file."L];
io => exD.DisplayExceptionString["Cannot connect to remote server."L];
ENDCASE => REJECT;
CONTINUE;
END;
VMDefs.Error =>
BEGIN
IF reason = resources THEN exD.DisplayException[exD.diskFullSomeNotIndexed];
CONTINUE;
END;
opD.MailFileError =>
BEGIN
SELECT reason FROM
notAMailFile => exD.DisplayException[exD.formatErrorCantGet];
lastStampTooLong =>
{firstUnSeenTOCIndex ← 0; exD.DisplayException[exD.mayBeTruncated]};
ENDCASE;
CONTINUE;
END;
vmD.TOCOverflow =>
BEGIN
exD.DisplayException[exD.tocOverflowSomeNotIndexed];
firstUnSeenTOCIndex ← toc.indexFF - 1;
CONTINUE;
END];
IF firstUnSeenTOCIndex = LAST[CARDINAL] THEN RETURN[FALSE];
IF firstUnSeenTOCIndex # 0 THEN -- some unseen entry exists
tsD.SetTOCSelection[toc, key, firstUnSeenTOCIndex]
ELSE IF (i ← vmD.FirstFreeTOCIndex[toc, key]) > 1 THEN
tsD.SetTOCSelection[toc, key, i - 1];
RETURN[TRUE];
END; -- of CallGetOp --


LoadComposedMessage: PUBLIC PROCEDURE
[toc: vmD.TOCHandle, key, index: CARDINAL, cm: vmD.ComposedMessagePtr] =
BEGIN
dm: vmD.DisplayMessagePtr ← vmD.AllocateDisplayMessageObject[];
vmD.LoadDisplayMessage[toc, key, index, dm];
vmD.InitComposedMessage[cm, ""L];
vmD.InsertRangeInMessage[0, cm, [0, vmD.GetMessageSize[dm], dm]];
vmD.FlushDisplayMessage[dm, key];
vmD.FreeVirtualMessageObject[dm];
END; -- of LoadComposedMessage --

CleanupDisplayMessage: PUBLIC PROCEDURE =
BEGIN
key: CARDINAL;
mnp: inD.MessageTextNbrPtr = intC.dmTextNbr;
dm: vmD.DisplayMessagePtr = vmD.DisplayMessage[mnp.message];
toc: vmD.TOCHandle = dm.toc;
IF ~mnp.haveMessage THEN RETURN;
key ← vmD.WaitForLock[toc];
dsD.ClearRectangle[inD.leftMargin, inD.rightMargin, mnp.topY, mnp.bottomY];
vmD.FlushDisplayMessage[dm, key];
vmD.UnlockTOC[toc, key];
mnp.haveMessage ← FALSE;
END; -- of CleanupDisplayMessage --


IsAuthorized: PUBLIC PROCEDURE
[class: AuthorizationClass, hp: inD.HousePtr, displayNotAuthMessage: BOOLEAN ← TRUE]
RETURNS [ok: BOOLEAN] =
BEGIN
IF authorizationTable[class] = unknown THEN
SELECT RetrieveDefs.MailboxState[intC.retrieveHandle] FROM
badName, badPwd =>
FOR c: AuthorizationClass IN AuthorizationClass DO
authorizationTable[c] ← no;
ENDLOOP;
cantAuth => NULL;
ENDCASE =>
BEGIN
name: STRING ← [MailParse.maxRecipientLength];
String.AppendString[name, intC.user.name ! String.StringBoundsFault => CONTINUE];
String.AppendChar[name, ’. ! String.StringBoundsFault => CONTINUE];
String.AppendString[name, intC.user.registry ! String.StringBoundsFault => CONTINUE];
UNTIL authorizationTable[class] # unknown DO
classToCheck: AuthorizationClass ← class;
IF class = onlooker AND authorizationTable[technician] = unknown THEN
classToCheck ← technician;
SELECT NameInfoDefs.IsMemberClosure
[SELECT classToCheck FROM
kahuna => "Kahunas↑.cholla"L,
maven => "Mavens↑.cholla"L,
technician => "Technicians↑.cholla"L,
ENDCASE => "Onlookers↑.cholla"L, name] FROM
no =>
FOR c: AuthorizationClass IN [FIRST[AuthorizationClass] .. classToCheck] DO
authorizationTable[c] ← no;
ENDLOOP;
yes =>
FOR c: AuthorizationClass IN [classToCheck .. LAST[AuthorizationClass]] DO
authorizationTable[c] ← yes;
ENDLOOP;
ENDCASE => EXIT;
ENDLOOP;
END;
ok ← authorizationTable[class] # no;
IF ~ok THEN
BEGIN
IF displayNotAuthMessage THEN
exD.DisplayExceptionString["You are not authorized to use this command!"L];
inD.IndicateCommandFinished[hp];
END;
END; -- of IsAuthorized --


ResetAuthorization: PUBLIC PROCEDURE =
BEGIN
FOR class: AuthorizationClass IN AuthorizationClass DO
authorizationTable[class] ← unknown;
ENDLOOP;
END; -- of ResetAuthorization --


StringToStatus: PUBLIC PROCEDURE [s: STRING] RETURNS [StepStatus] =
BEGIN
RETURN[SELECT TRUE FROM
String.EquivalentString[s, "Finished"L] => finished,
String.EquivalentString[s, "NotStarted"L] => notStarted,
String.EquivalentString[s, "Started"L] => started,
String.EquivalentString[s, "InProcess"L] => inProcess,
String.EquivalentString[s, "Processed"L] => processDone,
String.EquivalentString[s, "Rejected"L] => rejected,
ENDCASE => none];
END; -- of StringToStatus --


ResetAuthorization[];


END. -- of ChollaCmd --