WalnutNotifierImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Willie-Sue, April 15, 1986 3:17:19 pm PST
Donahue, May 20, 1985 1:53:31 pm PDT
Pavel, February 6, 1986 2:59:42 pm PST
Contents: Notifier & restart code
created July, 1983 by Willie-Sue
Last edit by:
Willie-Sue on: January 3, 1985 9:26:50 am PST
Donahue on: December 13, 1984 10:22:29 am PST
(to be consistent with new WalnutDisplayerInternal)
DIRECTORY
Booting USING [RegisterProcs, CheckpointProc, RollbackProc],
FS USING [Error, ErrorDesc, ErrorFromStream],
GVBasics USING [RName],
GVRetrieve USING [MBXState],
IO,
Labels USING [Set],
MBQueue USING [Action, DequeueAction, FlushWithCallback, QueueClientAction],
Menus USING [MenuProc],
Process USING [Detach, Pause, SecondsToTicks],
Rope,
UserCredentials USING [Get],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc, Token],
ViewerClasses USING [Viewer],
ViewerLocks USING [CallUnderWriteLock],
ViewerSpecs USING [openTopY],
ViewerOps,
WalnutDefs USING [Error, VersionMismatch],
WalnutOps USING [ActiveMsgSetName, DeletedMsgSetName,
 FileName, MsgExists, MsgSetsInfo, Scavenge, Shutdown,
 SizeOfDatabase, SizeOfMsgSet, Startup],
WalnutNewMail USING [GetLastMailBoxStatus],
WalnutControlInternal USING [
forceQuitMenu, mailNotifyLabel, maybeQuitMenu, mustQuitWalnut, previousUser,
readOnlyDBMenu, mailDBMenu, nonMailDBMenu,
scavMenu, WaitCallOutcome, walnutMenu, walnutRootFile, workingMenu,
ChangeMenu, GetUserResponse, FixUpWalnutViewers],
WalnutDisplayerInternal USING [
tocDefaultLooks, tocSelectedLooks, tocUnreadLooks, userWantsQMs, plainTextStyle, displayMsgSetInIcon,
QDisplayMsgSet, InternalAddToMsgMenu],
WalnutWindowInternal USING [initialActiveIconic, initialActiveOpen, initialActiveRight,
msgNamePrefix, msgSetNamePrefix, msgSetBorders, msgSetsVersion,
msbDefaultLooks, msbSelectedLooks,
activeMsgSetButton, personalMailDB, readOnlyAccess,
walnut, walnutQueue, walnutRulerAfter,
CloseDownWalnut, CloseTS, DisableNewMail, EnableNewMail,
GetButton, OpenTS, Report, SetMailState, ShowMsgSetButtons, TakeDownWalnutViewers],
WalnutWindow USING [EnumWalnutViewers];
WalnutNotifierImpl: CEDAR MONITOR
IMPORTS
Booting, FS, IO, Process, Rope,
Labels, MBQueue,
UserCredentials, UserProfile, ViewerLocks, ViewerOps, ViewerSpecs,
WalnutDefs, WalnutOps, WalnutNewMail,
WalnutControlInternal, WalnutDisplayerInternal, WalnutWindow, WalnutWindowInternal
EXPORTS WalnutControlInternal, WalnutDisplayerInternal, WalnutWindow =
BEGIN OPEN WalnutControlInternal, WalnutWindowInternal;
Walnut Viewers types and global data
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
scavengeMsg: ROPE = "Click Scavenge or Quit";
forceQuitRope: ROPE = "You must quit out of Walnut; Click Quit when ready";
wDefsError: ROPE = "\n **** WalnutDefs.Error: code: %g, info: %g at %g";
wVersError: ROPE = "\n **** WalnutDefs.VersionMismatch: explanation: %g at %g";
IOErr: ROPE = "\n *** IO.Error: ";
FSErr: ROPE = "\n *** FS.Error: ";
WalnutState: TYPE = {running, stopped, waitingForUser};
walnutState: WalnutState ← stopped;
WaitForNewMailLabel: ROPE = "Checking for new mail ...";
NoMailLabel: ROPE = "Cannot retrieve mail using this database";
ROAccessLabel: ROPE = "You only have Read access to this database";
WaitCallFinished: CONDITION;
* * * * * * * * * * exported to WalnutWindow
MsgMenuClientData: TYPE = REF MsgMenuClientDataRec;
MsgMenuClientDataRec: TYPE =
RECORD[mProc: Menus.MenuProc, data: REF ANY, doReset: BOOL];
AddToMsgMenu: PUBLIC ENTRY PROC[label: ROPE, proc: Menus.MenuProc,
 clientData: REF ANYNIL, onQueue: BOOLFALSE, doReset: BOOLTRUE] = {
mmcd: MsgMenuClientData;
IF ~ onQueue THEN {
WalnutDisplayerInternal.InternalAddToMsgMenu[label, proc, clientData, onQueue];
RETURN
};
mmcd ← NEW[MsgMenuClientDataRec ← [proc, clientData, doReset]];
WalnutDisplayerInternal.InternalAddToMsgMenu[label, MsgMenuProc, mmcd, onQueue];
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
GetWalnutState: ENTRY PROC RETURNS[WalnutState] = { RETURN[walnutState] };
WalnutNotifier: PUBLIC PROC = {
OPEN MBQueue;
Wn: PROC = {
action: MBQueue.Action ← MBQueue.DequeueAction[walnutQueue];
IF GetWalnutState[] = running THEN {
WITH action SELECT FROM
e1: Action.user =>
e1.proc[e1.parent, e1.clientData, e1.mouseButton, e1.shift, e1.control];
e2: Action.client =>
IF e2.proc # NIL THEN e2.proc[e2.data]
ELSE {
wqe: WalnutQueueEntry ← NARROW[e2.data];
WaitForWaitCall: ENTRY PROC =
{ENABLE UNWIND => NULL; NOTIFY wqe.condition; WAIT WaitCallFinished};
IF wqe # NIL THEN WaitForWaitCall[];
};
ENDCASE => ERROR;
}
ELSE
WITH action SELECT FROM  -- walnut is not running
e1: Action.user => NULL;
e2: Action.client =>
IF e2.proc # NIL THEN
{ IF e2.proc = QuitWalnut THEN e2.proc[e2.data] ELSE NULL }
ELSE {
wqe: WalnutQueueEntry ← NARROW[e2.data];
Notify: ENTRY PROC = {ENABLE UNWIND => NULL; NOTIFY wqe.condition};
IF wqe # NIL THEN
{ wqe.outcome ← notRunning; Notify[] };
};
ENDCASE => ERROR;
};
DO
[] ← CarefullyApply[Wn];
ENDLOOP;
};
WhatHappened: TYPE = {ok, flushAndContinue, quitting, restarted};
CarefullyApply: PROC[proc1: PROCNIL, proc2: PROC[BOOL] ← NIL]
RETURNS[result: WhatHappened] = {
ENABLE BEGIN
ABORTED => {
Report["\n Catching ABORTED\n"];
GOTO failure;
};
WalnutDefs.Error => {
IF code = $AlreadyStarted THEN GOTO ignore;
IF code = $DBInternalError THEN GOTO internalError;
Report[IO.PutFR[wDefsError, IO.atom[code], IO.rope[explanation], IO.time[]]];
GOTO failure;
};
WalnutDefs.VersionMismatch => {
Report[IO.PutFR[wVersError, IO.rope[explanation], IO.time[]]];
GOTO mismatch;
};
FS.Error => {
Report[FSErr, error.explanation];
GOTO failure;
};
IO.Error => {
ed: FS.ErrorDesc;
ed ← FS.ErrorFromStream[stream ! IO.Error, FS.Error => CONTINUE];
IF ed.explanation # NIL THEN Report[IOErr, ed.explanation];
IF ec = Failure THEN {
IF ed.code = $quotaExceeded THEN GOTO outOfSpace ELSE {
Report[IO.PutFR["IO Failure with code %g", IO.atom[ed.code]] ];
GOTO failure;
};
}
ELSE {
IF ed.explanation = NIL THEN Report["Other IO error"];
GOTO failure; -- better this than nothing
};
};
END;
IF proc1 # NIL THEN proc1[] ELSE proc2[walnutState = running];
RETURN[ok];
EXITS
internalError => {
Report["\n DB reported an internal error; you'll have to quit"];
Report["\nThen type WalnutScavenge to the commandTool"];
ViewerOps.BlinkIcon[walnut];
MBQueue.FlushWithCallback[walnutQueue, FlushingQueue];
Report[forceQuitRope];
[] ← GetUserResponse[forceQuitMenu];
MBQueue.QueueClientAction[walnutQueue, QuitWalnut, NIL];
RETURN[quitting];
};
ignore => RETURN[ok];
mismatch =>
IF MismatchHandled[] THEN RETURN[flushAndContinue]
ELSE RETURN[HandleFailure[]];
failure => RETURN[HandleFailure[]];
outOfSpace => {
Report["\n Out of space"];
ViewerOps.BlinkIcon[walnut];
MBQueue.FlushWithCallback[walnutQueue, FlushingQueue];
Report[forceQuitRope];
[] ← GetUserResponse[forceQuitMenu];
MBQueue.QueueClientAction[walnutQueue, QuitWalnut, NIL];
RETURN[quitting];
};
};
MismatchHandled: PROC RETURNS[ok: BOOL] = {
IF (ok ← WalnutOps.MsgSetsInfo[].version = WalnutWindowInternal.msgSetsVersion) THEN
WalnutWindowInternal.ShowMsgSetButtons[];
RETURN[ok];
};
HandleFailure: PROC RETURNS[WhatHappened] = {
doQuit: BOOL;
SetWalnutState[waitingForUser];
MBQueue.FlushWithCallback[walnutQueue, FlushingQueue];
WalnutOps.Shutdown[];
IF walnut = NIL THEN {
MBQueue.QueueClientAction[walnutQueue, QuitWalnut, NIL];
RETURN[quitting];
};
Report["\nClick Quit or Retry"];
IF (doQuit ← GetUserResponse[maybeQuitMenu]) THEN {
Report[" quitting ..."];
MBQueue.QueueClientAction[walnutQueue, QuitWalnut, NIL];
RETURN[quitting];
}
ELSE {
Report[" retrying ..."];
TRUSTED { Process.Detach[FORK WaitForRestart[walnutRootFile]] };
RETURN[restarted];
}
};
QueueWasFlushed can't be an ENTRY proc
WalnutQueueEntry: TYPE = REF QueueEntryObject;
QueueEntryObject: TYPE = RECORD[outcome: WaitCallOutcome ← ok, condition: CONDITION];
FlushingQueue: PROC[action: MBQueue.Action] = {
WITH action SELECT FROM
e1: MBQueue.Action.user => NULL;
e2: MBQueue.Action.client => {
wqe: WalnutQueueEntry ← NARROW[e2.data];
Notify: ENTRY PROC = { NOTIFY wqe.condition };
IF wqe # NIL THEN {
wqe.outcome ← flushed;
Notify[]
};
};
ENDCASE => ERROR;
};
FlushWQueue: PUBLIC INTERNAL PROC =  -- for WalnutWindowInternalImpl to call
{ MBQueue.FlushWithCallback[walnutQueue, FlushingQueue] };
DoWaitCall: PUBLIC PROC[proc: PROC[]] RETURNS[outcome: WaitCallOutcome] = {
puts proc on Walnut's queue and waits for its execution to finish
result: WhatHappened;
wqe: WalnutQueueEntry = NEW[QueueEntryObject ← []];
QueueAndWait[wqe];
IF wqe.outcome = ok THEN  -- ~flushed or ~notRunning
IF (result ← CarefullyApply[proc1: proc]) # ok THEN wqe.outcome ← flushed;
NotifyWaitCallWaiter[];
RETURN[wqe.outcome]
};
QueueAndWait: ENTRY PROC[wqe: WalnutQueueEntry] = {
ENABLE UNWIND => NULL;
MBQueue.QueueClientAction[walnutQueue, NIL, wqe];
WAIT wqe.condition
};
NotifyWaitCallWaiter: ENTRY PROC =
{ ENABLE UNWIND => NULL; NOTIFY WaitCallFinished };
DoStartupCall: PUBLIC PROC[proc: PROC[isRunning: BOOL]]
RETURNS[outcome: WaitCallOutcome] = {
puts proc on Walnut's queue and waits for its execution to finish; calls proc even if walnut is not running
result: WhatHappened;
wqe: WalnutQueueEntry = NEW[QueueEntryObject ← []];
QueueAndWait[wqe];
IF wqe.outcome # flushed THEN  -- ok or notRunning
IF (result ← CarefullyApply[proc2: proc]) # ok THEN wqe.outcome ← flushed;
NotifyWaitCallWaiter[];
RETURN[wqe.outcome]
};
MsgMenuProc: Menus.MenuProc = {
mmcd: MsgMenuClientData ← NARROW[clientData];
mmcd.mProc[parent, mmcd.data, mouseButton, shift, control];
IF ~mmcd.doReset THEN RETURN;
WalnutWindowInternal.ShowMsgSetButtons[];
WalnutControlInternal.FixUpWalnutViewers[];
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WaitForRestart: PROC[rootFile: ROPE, exp: ROPENIL] = {
Rw: PROC[BOOL] = {
IF RestartWalnut[rootFile, FALSE, FALSE] THEN Report["Restart finished"]
};
OpenTS[exp];  -- displays exp if ts not already open
[] ← DoStartupCall[Rw];
};
RestartWalnut: PUBLIC PROC[rootFile: ROPE, scavengeFirst, firstTime: BOOL]
RETURNS[restarted: BOOL] = {
DO
BEGIN
ENABLE BEGIN
ABORTED, UNWIND => GOTO retry;
WalnutDefs.Error => {
IF code = $AlreadyStarted THEN GOTO ignore;
Report[IO.PutFR[wDefsError, IO.atom[code], IO.rope[explanation], IO.time[]]];
IF code = $BadLog OR code = $SchemaMismatch OR
code = $WrongRootFile OR code = $DBInternalError
THEN GOTO needScav ELSE GOTO retry
};
IO.Error => {
ed: FS.ErrorDesc;
ed ← FS.ErrorFromStream[stream ! IO.Error, FS.Error => CONTINUE];
IF ed.explanation # NIL THEN Report[IOErr, ed.explanation];
IF ec = Failure THEN
IF ed.code = $quotaExceeded THEN Report["\n Out of space"]
ELSE Report[IO.PutFR["IO Failure with code %g", IO.atom[ed.code]] ];
Report["Other IO Error"];
GOTO retry;
};
FS.Error =>
{ Report[FSErr, error.explanation]; GOTO retry};
END;
dbFileName, key: ROPE;
wasReadOnly: BOOL ← readOnlyAccess;
notFoundRope: ROPE = " not found and can't be created";
mailFor: GVBasics.RName;
newMailExists: BOOL;
readOnlyAccess ← FALSE;
IF scavengeFirst THEN {
Report[IO.PutFR["\n Starting scavenge at %g ...\n", IO.time[]]];
[newMailExists, mailFor, key] ← WalnutOps.Scavenge[rootFile: rootFile];
Report[IO.PutFR["\n Scavenge finished at %g\n", IO.time[]]];
}
ELSE [readOnlyAccess, newMailExists, mailFor, key] ← WalnutOps.Startup[
rootFile: rootFile, wantReadOnly: readOnlyAccess];
dbFileName← WalnutOps.FileName[];
msgNamePrefix ← dbFileName.Concat["!Msg!"];
msgSetNamePrefix ← dbFileName.Concat["!MsgSet!"];
walnutRootFile ← rootFile;
personalMailDB ← mailFor.Equal[UserCredentials.Get[].name, FALSE];
IF readOnlyAccess THEN
{ walnutMenu ← readOnlyDBMenu; personalMailDB ← FALSE }
ELSE walnutMenu ← IF personalMailDB THEN mailDBMenu ELSE nonMailDBMenu;
Labels.Set[mailNotifyLabel,
IF personalMailDB THEN WaitForNewMailLabel ELSE
IF readOnlyAccess THEN ROAccessLabel ELSE NoMailLabel];
IF personalMailDB THEN EnableNewMail[];
ShowMsgSetButtons[];
IF ~walnut.iconic THEN ViewerOps.PaintViewer[walnut, client];
check how much space is left (if any) in the control window for a typescript)
IF firstTime THEN
{ dif: INTEGER;
wH: INTEGER ← ViewerSpecs.openTopY/4;
msgsInDeleted: INT← WalnutOps.SizeOfMsgSet[WalnutOps.DeletedMsgSetName].messages;
total: INT ← WalnutOps.SizeOfDatabase[].messages;
LockedSetHeight: PROC = {ViewerOps.SetOpenHeight[walnut, wH - dif]};
IF (dif ← (wH-walnutRulerAfter.wy) - 100) # 0 THEN
{ ViewerLocks.CallUnderWriteLock[LockedSetHeight, walnut];
IF ~walnut.iconic THEN ViewerOps.ComputeColumn[walnut.column]
};
IF msgsInDeleted > total/8 THEN
Report[IO.PutFR[
"There are %g deleted msgs (%g total); consider doing an expunge", IO.int[msgsInDeleted], IO.int[total]] ];
};
FixUpWalnutViewers[];
IF initialActiveOpen AND firstTime THEN
{ IF personalMailDB OR
WalnutOps.SizeOfMsgSet[WalnutOps.ActiveMsgSetName].messages # 0 THEN
[] ← WalnutDisplayerInternal.QDisplayMsgSet[activeMsgSetButton];
};
IF personalMailDB THEN
IF newMailExists THEN SetMailState[thereIsMail]
ELSE {
mbxState: GVRetrieve.MBXState ← WalnutNewMail.GetLastMailBoxStatus[].mbxState;
IF mbxState = unknown THEN {
SetMailState[gvWaiting];
Process.Pause[Process.SecondsToTicks[15]];
mbxState ← WalnutNewMail.GetLastMailBoxStatus[].mbxState;
};
SELECT mbxState FROM
unknown => SetMailState[noGV];
allEmpty, someEmpty => SetMailState[noMail];
allDown => SetMailState[noServers];
notEmpty => SetMailState[gvMail];
ENDCASE => SetMailState[noMail];
};
walnut.inhibitDestroy ← FALSE;
SetWalnutState[running];
ChangeMenu[walnutMenu, FALSE];
RETURN[TRUE];
EXITS
ignore => RETURN[TRUE];
retry =>
{ Report["Start or Restart failed; click Quit or Retry"];
SetWalnutState[waitingForUser];
IF walnut = NIL THEN
{ CloseDownWalnut[]; RETURN[FALSE]};
IF GetUserResponse[maybeQuitMenu] THEN
{ CloseDownWalnut[]; RETURN[FALSE]};
WalnutOps.Shutdown[];  -- before trying to restart
scavengeFirst ← FALSE;
};
needScav =>
{ Report["A scavenge is necessary\n", scavengeMsg];
IF ~GetUserResponse[scavMenu] THEN
{ CloseDownWalnut[]; SetWalnutState[stopped]; RETURN[FALSE]};
WalnutOps.Shutdown[];  -- not needed before doing scavenge
Report["\n Starting scavenge ...\n"];
scavengeFirst ← TRUE;
}
END;
ENDLOOP;
};
FixUpWalnutViewers: PUBLIC PROC = {
msgSetList, msgList: LIST OF Viewer;
v: Viewer;
name: ROPE;
[msgSetList, msgList] ← WalnutWindow.EnumWalnutViewers[TRUE];
FOR vL: LIST OF Viewer ← msgSetList, vL.rest UNTIL vL=NIL DO
name ← NARROW[ViewerOps.FetchProp[v ← vL.first, $WalnutMsgSetName]];
[]← WalnutDisplayerInternal.QDisplayMsgSet[GetButton[name], v];
ENDLOOP;
FOR vL: LIST OF Viewer← msgList, vL.rest UNTIL vL=NIL DO
name ← NARROW[ViewerOps.FetchProp[v ← vL.first, $WalnutMsgName]];
IF ~WalnutOps.MsgExists[name] THEN {
IF ViewerOps.FetchProp[v, $Frozen] # NIL THEN LOOP;
WalnutWindowInternal.Report["Msg: ", name, " doesn't exist; destroying viewer"];
ViewerOps.DestroyViewer[v];
};
ENDLOOP;
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
QuitWalnut: PUBLIC PROC[ra: REF ANY] = {
IF GetWalnutState[] = stopped THEN RETURN;  -- ignore
SetWalnutState[waitingForUser];
IF ra # NIL THEN {
msg: ROPE = NARROW[ra];
walnut.inhibitDestroy ← TRUE;
OpenTS[];   -- if after rollback, may not be open
TakeDownWalnutViewers[];  -- make the user notice
ViewerOps.BlinkIcon[walnut, IF walnut.iconic THEN 0 ELSE 1];
Report["\n **********", msg];
Report["You MUST quit out of Walnut; Click Quit when ready"];
[] ← GetUserResponse[forceQuitMenu];
};
CloseDownWalnut[];
SetWalnutState[stopped];
};
SetWalnutState: ENTRY PROC[newState: WalnutState] =
{ walnutState ← newState};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
userChangedMessage: ROPE = "Logged-in user changed";
displayMsgSetInIcon: PUBLIC BOOL
UserProfile.Boolean[key: "Walnut.DisplayMsgSetInIcon", default: FALSE];
This is declared here because of a compiler/loader incompatibility
SetWalnutProfileVars: UserProfile.ProfileChangedProc = {
curUser: ROPE = UserCredentials.Get[].name;
initialActiveIconic ← UserProfile.Boolean[key: "Walnut.InitialActiveIconic", default: FALSE];
initialActiveRight ← UserProfile.Boolean[key: "Walnut.InitialActiveRight", default: TRUE];
initialActiveOpen ← UserProfile.Boolean[key: "Walnut.InitialActiveOpen", default: FALSE];
msgSetBorders ← UserProfile.Boolean[key: "Walnut.MsgSetButtonBorders", default: FALSE];
msbDefaultLooks ←
UserProfile.Token[key: "Walnut.MsgSetButtonDefaultLooks", default: ""];
msbSelectedLooks ←
UserProfile.Token[key: "Walnut.MsgSetButtonSelectedLooks", default: "bi"];
WalnutDisplayerInternal.tocDefaultLooks ←
UserProfile.Token[key: "Walnut.TOCDefaultLooks", default: ""];
WalnutDisplayerInternal.tocSelectedLooks ←
UserProfile.Token[key: "Walnut.TOCSelectedLooks", default: "sb"];
WalnutDisplayerInternal.tocUnreadLooks ←
UserProfile.Token[key: "Walnut.TOCUnreadLooks", default: "i"];
WalnutDisplayerInternal.userWantsQMs ←
UserProfile.Boolean[key: "Walnut.ShowUnreadWithQMs", default: TRUE];
WalnutDisplayerInternal.plainTextStyle ←
UserProfile.Token[key: "Walnut.PlainTextStyle", default: "cedar"];
WalnutDisplayerInternal.displayMsgSetInIcon ←
UserProfile.Boolean[key: "Walnut.DisplayMsgSetInIcon", default: FALSE];
IF GetWalnutState[] # running THEN {previousUser ← curUser; RETURN};
IF ~Rope.Equal[previousUser, curUser, FALSE] THEN
{ mustQuitWalnut ← userChangedMessage;
MBQueue.FlushWithCallback[walnutQueue, FlushingQueue];
MBQueue.QueueClientAction[walnutQueue, QuitWalnut, mustQuitWalnut];
previousUser ← curUser;
};
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WalnutCheckpointProc: Booting.CheckpointProc = {
ENABLE UNWIND => { CloseTS[]};
Wcp: PROC = {
ChangeMenu[workingMenu, FALSE];
Report["\nDoing Checkpoint ... "];
DisableNewMail[];
WalnutOps.Shutdown[ ! ABORTED => CONTINUE];
CloseTS[];
};
IF walnut = NIL THEN RETURN;
MBQueue.FlushWithCallback[walnutQueue, FlushingQueue];
IF DoWaitCall[Wcp] # ok THEN RETURN["Walnut's Checkpoint Proc didn't get done"];
};
WalnutRollbackProc: Booting.RollbackProc = {
IF GetWalnutState[] # running THEN RETURN;
CloseTS[];  -- to be very sure
IF ~Rope.Equal[UserCredentials.Get[].name, previousUser, FALSE] THEN
MBQueue.QueueClientAction[walnutQueue, QuitWalnut, userChangedMessage]
ELSE TRUSTED
{ Process.Detach[FORK WaitForRestart[walnutRootFile, "Restarting after Rollback"]] };
};
clean up Walnut at checkpoint time
TRUSTED {
Booting.RegisterProcs[c: WalnutCheckpointProc, r: WalnutRollbackProc];
Process.Detach[FORK WalnutNotifier[] ];
};
UserProfile.CallWhenProfileChanges[SetWalnutProfileVars];
END.