<<>> <> <> <> <> <> <<>> <> <> <> <> <> DIRECTORY BasicTime USING [Now], FS USING [Error, ErrorDesc], MailRetrieve USING [MBXState], Icons USING [IconFlavor], IO, Labels USING [Set], Menus USING [MenuProc], Process USING [Detach], Rope, SendMailOps USING [UnregisterReporter], SystemNames USING [UserName], TBQueue USING [Action, DequeueAction, FlushWithCallback, QueueClientAction], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc, Token], ViewerClasses USING [Viewer], ViewerEvents USING [EventRegistration, UnRegisterEventProc], ViewerLocks USING [CallUnderWriteLock], ViewerSpecs USING [openTopY], ViewerOps, Wallaby USING [ShutDownWallaby], WalnutDefs USING [Error, VersionMismatch], WalnutOps USING [ActiveName, MsgExists, MsgSetsInfo, Scavenge, Shutdown, SizeOfMsgSet, Startup, UnregisterReporter, WalnutOpsHandle], WalnutNewMail USING [GetLastMailBoxStatus], WalnutWindowPrivate USING [MsgAndHandle, MsgSetInfo, WalnutHandle, WalnutHandleRec, WalnutState], WalnutInternal USING [ChangeMenu, DisableNewMail, EnableNewMail, GetButton, GetUserResponse, initialActiveIconic, initialActiveOpen, InternalAddToMsgMenu, InternalReplaceInMsgMenu, msbDefaultLooks, msbSelectedLooks, OpenTS, plainTextStyle, previousUser, QDisplayMsgSet, SetMailState, ShowMsgSetButtons, TakeDownWalnutViewers, tocDefaultLooks, tocSelectedLooks, tocUnreadLooks, userWantsQMs, WaitCallOutcome, workingMenu], WalnutWindow USING [GetHandleList, Report, ReportRope, ReportFormat]; WalnutNotifierImpl: CEDAR MONITOR IMPORTS BasicTime, FS, IO, Process, Rope, Labels, SendMailOps, SystemNames, TBQueue, UserProfile, ViewerLocks, ViewerEvents, ViewerOps, ViewerSpecs, Wallaby, WalnutDefs, WalnutOps, WalnutNewMail, WalnutInternal, WalnutWindow EXPORTS WalnutInternal, WalnutWindow = BEGIN OPEN WalnutInternal; <> ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; MsgAndHandle: TYPE = WalnutWindowPrivate.MsgAndHandle; MsgSetInfo: TYPE = WalnutWindowPrivate.MsgSetInfo; WalnutState: TYPE = WalnutWindowPrivate.WalnutState; WalnutHandle: TYPE = WalnutWindowPrivate.WalnutHandle; WalnutHandleRec: PUBLIC TYPE = WalnutWindowPrivate.WalnutHandleRec; 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\n"; wVersError: ROPE = "\n **** WalnutDefs.VersionMismatch: explanation: %g at %g\n"; IOErr: ROPE = "\n *** IO.Error: "; FSErr: ROPE = "\n *** FS.Error: "; debugging: BOOL ¬ FALSE; WalnutDefsError: SIGNAL = CODE; IOError: SIGNAL = CODE; FSError: SIGNAL = CODE; WaitForNewMailLabel: ROPE = "Checking for new mail ..."; NoMailLabel: ROPE = "Cannot retrieve mail using this database"; ROAccessLabel: ROPE = "You only have Read access to this database"; <<* * * * * * * * * * exported to WalnutWindow>> MsgMenuClientData: TYPE = REF MsgMenuClientDataRec; MsgMenuClientDataRec: TYPE = RECORD[wH: WalnutHandle, mProc: Menus.MenuProc, data: REF ANY, doReset: BOOL]; AddToMsgMenu: PUBLIC ENTRY PROC[wH: WalnutHandle, label: ROPE, proc: Menus.MenuProc, clientData: REF ANY ¬ NIL, onQueue: BOOL ¬ FALSE, doReset: BOOL ¬ TRUE] = { mmcd: MsgMenuClientData; IF NOT onQueue THEN { InternalAddToMsgMenu[wH, label, proc, clientData, onQueue]; RETURN }; FOR whL: LIST OF WalnutHandle ¬ (IF wH # NIL THEN LIST[wH] ELSE WalnutWindow.GetHandleList[]), whL.rest UNTIL whL = NIL DO mmcd ¬ NEW[MsgMenuClientDataRec ¬ [whL.first, proc, clientData, doReset]]; InternalAddToMsgMenu[whL.first, label, MsgMenuProc, mmcd, onQueue]; ENDLOOP; }; ReplaceInMsgMenu: PUBLIC ENTRY PROC[wH: WalnutHandle, label: ROPE, proc: Menus.MenuProc, clientData: REF ANY ¬ NIL, onQueue: BOOL ¬ FALSE, doReset: BOOL ¬ TRUE] RETURNS[oldFound: BOOL]= { mmcd: MsgMenuClientData; IF NOT onQueue THEN { oldFound ¬ InternalReplaceInMsgMenu[wH, label, proc, clientData, onQueue]; RETURN }; FOR whL: LIST OF WalnutHandle ¬ (IF wH # NIL THEN LIST[wH] ELSE WalnutWindow.GetHandleList[]), whL.rest UNTIL whL = NIL DO mmcd ¬ NEW[MsgMenuClientDataRec ¬ [whL.first, proc, clientData, doReset]]; oldFound ¬ InternalReplaceInMsgMenu[whL.first, label, MsgMenuProc, mmcd, onQueue]; ENDLOOP; }; <<>> <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> GetWalnutState: ENTRY PROC[wH: WalnutHandle] RETURNS[WalnutState] = { RETURN[wH.walnutState] }; WalnutNotifier: PUBLIC PROC[wH: WalnutHandle] = { OPEN TBQueue; Wn: PROC = { action: TBQueue.Action ¬ TBQueue.DequeueAction[wH.walnutQueue]; IF GetWalnutState[wH] = running THEN { WITH action SELECT FROM e1: Action.tbUser => e1.proc[e1.button, e1.clientData, e1.mouseButton, e1.shift, e1.control]; e2: Action.mbUser => e2.proc[e2.parent, e2.clientData, e2.mouseButton, e2.shift, e2.control]; e3: Action.client => IF e3.proc # NIL THEN e3.proc[e3.data] ELSE { wqe: WalnutQueueEntry ¬ NARROW[e3.data]; WaitForWaitCall: ENTRY PROC = { ENABLE UNWIND => NULL; NOTIFY wqe.condition; WAIT wqe.waitCallFinished }; IF wqe # NIL THEN WaitForWaitCall[]; }; ENDCASE => ERROR; } ELSE WITH action SELECT FROM -- walnut is not running e1: Action.tbUser => NULL; e2: Action.mbUser => NULL; e3: Action.client => IF e3.proc # NIL THEN { IF e3.proc = QuitWalnut THEN e3.proc[e3.data] ELSE NULL } ELSE { wqe: WalnutQueueEntry ¬ NARROW[e3.data]; Notify: ENTRY PROC = {ENABLE UNWIND => NULL; NOTIFY wqe.condition}; IF wqe # NIL THEN { wqe.outcome ¬ notRunning; Notify[] }; }; ENDCASE => ERROR; }; DO [] ¬ CarefullyApply[wH, Wn]; ENDLOOP; }; WhatHappened: TYPE = {ok, flushAndContinue, quitting, restarted}; CarefullyApply: PROC[wH: WalnutHandle, proc1: PROC ¬ NIL, proc2: PROC[BOOL] ¬ NIL] RETURNS[result: WhatHappened] = { ENABLE BEGIN ABORTED => { WalnutWindow.Report[wH, "\n Catching ABORTED\n"]; GOTO failure; }; WalnutDefs.Error => { IF debugging THEN SIGNAL WalnutDefsError; IF code = $AlreadyStarted THEN GOTO ignore; IF code = $DBInternalError THEN GOTO internalError; WalnutWindow.ReportFormat[wH, wDefsError, [atom[code]], [rope[explanation]], [time[BasicTime.Now[]] ] ]; GOTO failure; }; WalnutDefs.VersionMismatch => { WalnutWindow.ReportFormat[wH, wVersError, [rope[explanation]], [time[BasicTime.Now[]] ] ]; GOTO mismatch; }; FS.Error => { IF debugging THEN SIGNAL FSError; WalnutWindow.Report[wH, FSErr, error.explanation]; GOTO failure; }; IO.Error => { ed: FS.ErrorDesc; < CONTINUE];>> IF debugging THEN SIGNAL IOError; IF ed.explanation # NIL THEN WalnutWindow.Report[wH, IOErr, ed.explanation]; IF ec = Failure THEN { IF ed.code = $quotaExceeded THEN GOTO outOfSpace ELSE { WalnutWindow.ReportFormat[wH, "IO Failure with code %g\n", [atom[ed.code]] ]; GOTO failure; }; } ELSE { IF ed.explanation = NIL THEN WalnutWindow.Report[wH, "Other IO error"]; GOTO failure; -- better this than nothing }; }; END; IF proc1 # NIL THEN proc1[] ELSE proc2[wH.walnutState = running]; RETURN[ok]; EXITS internalError => { wH.walnut.icon ¬ wH.iconSet[broken]; IF wH.walnut.iconic THEN ViewerOps.PaintViewer[wH.walnut, all]; WalnutWindow.Report[wH, "\n DB reported an internal error; you'll have to quit"]; WalnutWindow.Report[wH, "\nThen type WalnutScavenge to the commandTool"]; ViewerOps.BlinkIcon[wH.walnut]; TBQueue.FlushWithCallback[wH.walnutQueue, FlushingQueue]; WalnutWindow.Report[wH, forceQuitRope]; [] ¬ GetUserResponse[wH, wH.forceQuitMenu]; wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; wH.mustQuitMsg ¬ NIL; TBQueue.QueueClientAction[wH.walnutQueue, QuitWalnut, wH]; RETURN[quitting]; }; ignore => RETURN[ok]; mismatch => IF MismatchHandled[wH] THEN RETURN[flushAndContinue] ELSE RETURN[HandleFailure[wH]]; failure => RETURN[HandleFailure[wH]]; outOfSpace => { wH.walnut.icon ¬ wH.iconSet[broken]; IF wH.walnut.iconic THEN ViewerOps.PaintViewer[wH.walnut, all]; WalnutWindow.Report[wH, "\n Out of space"]; ViewerOps.BlinkIcon[wH.walnut]; TBQueue.FlushWithCallback[wH.walnutQueue, FlushingQueue]; WalnutWindow.Report[wH, forceQuitRope]; [] ¬ GetUserResponse[wH, wH.forceQuitMenu]; wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; wH.mustQuitMsg ¬ NIL; TBQueue.QueueClientAction[wH.walnutQueue, QuitWalnut, wH]; RETURN[quitting]; }; }; MismatchHandled: PROC[wH: WalnutHandle] RETURNS[ok: BOOL] = { IF (ok ¬ WalnutOps.MsgSetsInfo[wH.opsH].version = wH.msgSetsVersion) THEN ShowMsgSetButtons[wH]; RETURN[ok]; }; HandleFailure: PROC[wH: WalnutHandle] RETURNS[WhatHappened] = { doQuit: BOOL; wH.walnut.icon ¬ wH.iconSet[broken]; IF wH.walnut.iconic THEN ViewerOps.PaintViewer[wH.walnut, all]; SetWalnutState[wH, waitingForUser]; TBQueue.FlushWithCallback[wH.walnutQueue, FlushingQueue]; WalnutOps.Shutdown[wH.opsH]; IF wH.walnut = NIL THEN { wH.mustQuitMsg ¬ NIL; TBQueue.QueueClientAction[wH.walnutQueue, QuitWalnut, wH]; RETURN[quitting]; }; WalnutWindow.Report[wH, "\nClick Quit or Retry"]; IF (doQuit ¬ GetUserResponse[wH, wH.maybeQuitMenu, TRUE]) THEN { WalnutWindow.Report[wH, " quitting ..."]; wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; wH.mustQuitMsg ¬ NIL; TBQueue.QueueClientAction[wH.walnutQueue, QuitWalnut, wH]; RETURN[quitting]; } ELSE { wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; WalnutWindow.Report[wH, " retrying ..."]; TRUSTED { Process.Detach[FORK WaitForRestart[wH]] }; RETURN[restarted]; } }; <> WalnutQueueEntry: TYPE = REF QueueEntryObject; QueueEntryObject: TYPE = RECORD[ outcome: WaitCallOutcome ¬ ok, condition, waitCallFinished: CONDITION]; FlushingQueue: PROC[action: TBQueue.Action] = { WITH action SELECT FROM e1: TBQueue.Action.tbUser => NULL; e2: TBQueue.Action.mbUser => NULL; e3: TBQueue.Action.client => IF e3.proc # NIL THEN e3.proc[e3.data] ELSE { wqe: WalnutQueueEntry ¬ NARROW[e3.data]; Notify: ENTRY PROC = { NOTIFY wqe.condition }; IF wqe # NIL THEN { wqe.outcome ¬ flushed; Notify[] }; }; ENDCASE => ERROR; }; FlushWQueue: PUBLIC INTERNAL PROC[wH: WalnutHandle] = -- for WalnutWindowInternalImpl to call { TBQueue.FlushWithCallback[wH.walnutQueue, FlushingQueue] }; DoWaitCall: PUBLIC PROC[wH: WalnutHandle, proc: PROC[] ] RETURNS[outcome: WaitCallOutcome] = { <> result: WhatHappened; wqe: WalnutQueueEntry; IF wH = NIL THEN RETURN[flushed]; -- a mistake? wqe ¬ NEW[QueueEntryObject ¬ []]; QueueAndWait[wH, wqe]; IF wqe.outcome = ok THEN -- ~flushed or ~notRunning IF (result ¬ CarefullyApply[wH: wH, proc1: proc]) # ok THEN wqe.outcome ¬ flushed; NotifyWaitCallWaiter[wqe]; RETURN[wqe.outcome] }; QueueAndWait: ENTRY PROC[wH: WalnutHandle, wqe: WalnutQueueEntry] = { ENABLE UNWIND => NULL; TBQueue.QueueClientAction[wH.walnutQueue, NIL, wqe]; WAIT wqe.condition }; NotifyWaitCallWaiter: ENTRY PROC[wqe: WalnutQueueEntry] = { ENABLE UNWIND => NULL; NOTIFY wqe.waitCallFinished }; DoStartupCall: PUBLIC PROC[wH: WalnutHandle, proc: PROC[isRunning: BOOL]] RETURNS[outcome: WaitCallOutcome] = { <> result: WhatHappened; wqe: WalnutQueueEntry = NEW[QueueEntryObject ¬ []]; QueueAndWait[wH, wqe]; IF wqe.outcome # flushed THEN -- ok or notRunning IF (result ¬ CarefullyApply[wH: wH, proc2: proc]) # ok THEN wqe.outcome ¬ flushed; NotifyWaitCallWaiter[wqe]; RETURN[wqe.outcome] }; MsgMenuProc: Menus.MenuProc = { mmcd: MsgMenuClientData ¬ NARROW[clientData]; mmcd.mProc[parent, mmcd.data, mouseButton, shift, control]; IF ~mmcd.doReset THEN RETURN; ShowMsgSetButtons[mmcd.wH]; FixUpWalnutViewers[mmcd.wH]; }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> WaitForRestart: PROC[wH: WalnutHandle, exp: ROPE ¬ NIL] = { Rw: PROC[BOOL] = { IF RestartWalnut[wH, FALSE, FALSE] THEN WalnutWindow.ReportFormat[wH, "Restart finished at %g\n", [time[BasicTime.Now[]]] ]; }; OpenTS[wH, exp]; -- displays exp if ts not already open [] ¬ DoStartupCall[wH, Rw]; }; RestartWalnut: PUBLIC PROC[wH: WalnutHandle, scavengeFirst, firstTime: BOOL] RETURNS[restarted: BOOL] = { DO BEGIN ENABLE BEGIN ABORTED, UNWIND => GOTO retry; WalnutDefs.Error => { IF debugging THEN SIGNAL WalnutDefsError; IF code = $AlreadyStarted THEN GOTO ignore; WalnutWindow.ReportFormat[wH, wDefsError, [atom[code]], [rope[explanation]], [time[BasicTime.Now[]]] ]; SELECT code FROM $BadLog, $SchemaMismatch, $MismatchedExistingSegment, $WrongRootFile, $DBInternalError, $NoSegment => GOTO needScav; ENDCASE => GOTO retry; }; IO.Error => { ed: FS.ErrorDesc; < CONTINUE];>> IF debugging THEN SIGNAL IOError; IF ed.explanation # NIL THEN WalnutWindow.Report[wH, IOErr, ed.explanation]; SELECT ec FROM Failure => { IF ed.code = $quotaExceeded THEN WalnutWindow.Report[wH, "\n Out of space"] ELSE WalnutWindow.ReportFormat[wH, "IO Failure with code %g\n", [atom[ed.code]] ] }; SyntaxError => WalnutWindow.Report[wH, "SyntaxError"]; NotImplementedForThisStream => WalnutWindow.Report[wH, "NotImplementedForThisStream"]; BadIndex => WalnutWindow.Report[wH, "BadIndex"]; ENDCASE => WalnutWindow.Report[wH, "Other IO Error"]; GOTO retry; }; FS.Error => { IF debugging THEN SIGNAL FSError; WalnutWindow.Report[wH, FSErr, error.explanation]; GOTO retry }; END; wasReadOnly: BOOL ¬ wH.readOnlyAccess; notFoundRope: ROPE = " not found and can't be created"; newMailExists: BOOL; wH.readOnlyAccess ¬ wH.userAskedForReadOnly; IF scavengeFirst THEN { WalnutWindow.ReportFormat[wH, "Starting scavenge at %g ...\n", [time[BasicTime.Now[]]] ]; newMailExists ¬ WalnutOps.Scavenge[wH.opsH]; WalnutWindow.ReportFormat[wH, "\n Scavenge finished at %g\n", [time[BasicTime.Now[]]] ]; } ELSE newMailExists ¬ WalnutOps.Startup[wH.opsH]; IF wH.dontDoMail THEN wH.opsH.mailFor ¬ NIL; wH.msgNamePrefix ¬ wH.opsH.dbName.Concat["!Msg!"]; wH.msgSetNamePrefix ¬ wH.opsH.dbName.Concat["!MsgSet!"]; wH.personalMailDB ¬ wH.opsH.mailFor.Equal[SystemNames.UserName[], FALSE]; IF wH.opsH.readOnly THEN { wH.walnutMenu ¬ wH.readOnlyDBMenu; wH.personalMailDB ¬ FALSE } ELSE wH.walnutMenu ¬ IF wH.personalMailDB THEN wH.mailDBMenu ELSE wH.nonMailDBMenu; Labels.Set[wH.mailNotifyLabel, IF wH.personalMailDB THEN WaitForNewMailLabel ELSE IF wH.readOnlyAccess THEN ROAccessLabel ELSE NoMailLabel]; IF wH.personalMailDB THEN EnableNewMail[wH]; ShowMsgSetButtons[wH]; IF ~wH.walnut.iconic THEN ViewerOps.PaintViewer[wH.walnut, client]; <> IF firstTime THEN { dif: INTEGER; height: INTEGER ¬ ViewerSpecs.openTopY/4; <> <> <> <> <> LockedSetHeight: PROC = {ViewerOps.SetOpenHeight[wH.walnut, height - dif]}; IF (dif ¬ (height-wH.walnutRulerAfter.wy) - 100) # 0 THEN { ViewerLocks.CallUnderWriteLock[LockedSetHeight, wH.walnut]; IF ~wH.walnut.iconic THEN ViewerOps.ComputeColumn[wH.walnut.column] }; < total/8 THEN>> <> <<"There are %g deleted msgs (%g total); consider doing an expunge\n", [integer[msgsInDeleted]], [integer[total]] ];>> }; FixUpWalnutViewers[wH]; IF initialActiveOpen AND firstTime THEN { IF wH.personalMailDB OR WalnutOps.SizeOfMsgSet[wH.opsH, WalnutOps.ActiveName].messages # 0 THEN [] ¬ QDisplayMsgSet[wH, wH.activeMsgSetButton]; }; IF wH.personalMailDB THEN { IF newMailExists THEN SetMailState[wH, thereIsMail] ELSE { mbxState: MailRetrieve.MBXState ¬ WalnutNewMail.GetLastMailBoxStatus[wH.opsH].mbxState; IF mbxState = unknown THEN { SetMailState[wH, waiting]; <> <> <> <<};>> }; SELECT mbxState FROM unknown => SetMailState[wH, noState]; allEmpty, someEmpty => SetMailState[wH, noMail]; allDown => SetMailState[wH, noServers]; notEmpty => SetMailState[wH, someMail]; ENDCASE => SetMailState[wH, noMail]; }; }; wH.walnut.inhibitDestroy ¬ FALSE; SetWalnutState[wH, running]; ChangeMenu[wH, wH.walnutMenu, FALSE]; RETURN[TRUE]; EXITS ignore => RETURN[TRUE]; retry => { wH.walnut.icon ¬ wH.iconSet[broken]; IF wH.walnut.iconic THEN ViewerOps.PaintViewer[wH.walnut, all]; WalnutWindow.Report[wH, "Start or Restart failed; click Quit or Retry"]; SetWalnutState[wH, waitingForUser]; IF wH.walnut = NIL OR GetUserResponse[wH, wH.maybeQuitMenu, TRUE] THEN { IF wH.walnut # NIL THEN wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; CloseDownWalnut[wH]; RETURN[FALSE] }; WalnutOps.Shutdown[wH.opsH]; -- before trying to restart wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; scavengeFirst ¬ FALSE; }; needScav => { wH.walnut.icon ¬ wH.iconSet[broken]; IF wH.walnut.iconic THEN ViewerOps.PaintViewer[wH.walnut, all]; WalnutWindow.Report[wH, "A scavenge is necessary\n", scavengeMsg]; IF ~GetUserResponse[wH, wH.scavMenu] THEN { wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; CloseDownWalnut[wH]; SetWalnutState[wH, stopped]; RETURN[FALSE] }; WalnutWindow.Report[wH, "\n Starting scavenge ...\n"]; wH.walnut.icon ¬ wH.iconSet[wH.whichIcon]; scavengeFirst ¬ TRUE; }; END; ENDLOOP; }; FixUpWalnutViewers: PUBLIC PROC[wH: WalnutHandle] = { v: Viewer; FOR vL: LIST OF Viewer ¬ wH.msgSetViewerList, vL.rest UNTIL vL=NIL DO msI: MsgSetInfo; IF ( v ¬ vL.first).destroyed THEN LOOP; msI ¬ NARROW[ViewerOps.FetchProp[v, $MsgSetInfo]]; [] ¬ QDisplayMsgSet[wH, GetButton[msI.button.wH, msI.button.msgSet.name], v]; ENDLOOP; FOR vL: LIST OF Viewer ¬ wH.msgViewerList, vL.rest UNTIL vL=NIL DO maH: MsgAndHandle; IF ( v ¬ vL.first).destroyed THEN LOOP; maH ¬ NARROW[ViewerOps.FetchProp[v, $WalnutMsgName]]; IF ~WalnutOps.MsgExists[maH.wH.opsH, maH.msg] THEN { IF ViewerOps.FetchProp[v, $Frozen] # NIL THEN LOOP; WalnutWindow.ReportFormat[maH.wH, "Msg: %g doesn't exist in %g; destroying viewer", [rope[maH.msg]], [rope[maH.wH.opsH.rootName]] ]; ViewerOps.DestroyViewer[v]; }; ENDLOOP; }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> QuitWalnut: PUBLIC PROC[ra: REF ANY] = { wH: WalnutHandle = NARROW[ra]; msg: ROPE = wH.mustQuitMsg; IF GetWalnutState[wH] = stopped THEN RETURN; -- ignore SetWalnutState[wH, waitingForUser]; IF msg # NIL THEN { wH.walnut.inhibitDestroy ¬ TRUE; OpenTS[wH]; -- if after rollback, may not be open TakeDownWalnutViewers[wH]; -- make the user notice ViewerOps.BlinkIcon[wH.walnut, IF wH.walnut.iconic THEN 0 ELSE 1]; WalnutWindow.Report[wH, "\n **********", msg]; WalnutWindow.Report[wH, "You MUST quit out of Walnut; Click Quit when ready"]; [] ¬ GetUserResponse[wH, wH.forceQuitMenu]; }; CloseDownWalnut[wH]; SetWalnutState[wH, stopped]; wH.walnut ¬ NIL; }; CloseDownWalnut: PUBLIC PROC[wH: WalnutHandle] = { <> v: Viewer ¬ wH.walnut; IF wH.walnut = NIL THEN RETURN; -- not running <> wH.walnut.inhibitDestroy ¬ TRUE; IF wH.personalMailDB THEN DisableNewMail[wH]; ChangeMenu[wH, workingMenu, FALSE]; wH.selectedMsgSetButtons ¬ wH.firstMsgSetButton ¬ NIL; TakeDownWalnutViewers[wH]; <> Wallaby.ShutDownWallaby[wH]; WalnutWindow.ReportRope[wH, "Closing database and saving log ..."]; WalnutOps.Shutdown[wH.opsH]; IF wH.walnutEventReg # NIL THEN ViewerEvents.UnRegisterEventProc[wH.walnutEventReg, destroy]; wH.walnutEventReg ¬ NIL; wH.walnut ¬ NIL; -- don't let others try to use Report wH.mailNotifyLabel ¬ NIL; SendMailOps.UnregisterReporter[wH.wtsOut]; WalnutOps.UnregisterReporter[wH.opsH, wH.wtsOut]; IF wH.wtsOut # NIL THEN wH.wtsOut.Close[]; wH.wtsOut ¬ NIL; wH.mustQuitMsg ¬ NIL; v.inhibitDestroy ¬ FALSE; wH.msgSetsTViewer ¬ NIL; ViewerOps.DestroyViewer[v]; }; SetWalnutState: ENTRY PROC[wH: WalnutHandle, newState: WalnutState] = { wH.walnutState ¬ newState}; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> userChangedMessage: ROPE = "Logged-in user changed"; displayMsgSetInIcon: PUBLIC BOOL ¬ UserProfile.Boolean[key: "Walnut.DisplayMsgSetInIcon", default: FALSE]; <> SetWalnutProfileVars: UserProfile.ProfileChangedProc = { curUser: ROPE = SystemNames.UserName[]; initialActiveIconic ¬ UserProfile.Boolean[key: "Walnut.InitialActiveIconic", default: FALSE]; initialActiveOpen ¬ UserProfile.Boolean[key: "Walnut.InitialActiveOpen", default: FALSE]; msbDefaultLooks ¬ UserProfile.Token[key: "Walnut.MsgSetButtonDefaultLooks", default: ""]; msbSelectedLooks ¬ UserProfile.Token[key: "Walnut.MsgSetButtonSelectedLooks", default: "bi"]; tocDefaultLooks ¬ UserProfile.Token[key: "Walnut.TOCDefaultLooks", default: ""]; tocSelectedLooks ¬ UserProfile.Token[key: "Walnut.TOCSelectedLooks", default: "sb"]; tocUnreadLooks ¬ UserProfile.Token[key: "Walnut.TOCUnreadLooks", default: "i"]; userWantsQMs ¬ UserProfile.Boolean[key: "Walnut.ShowUnreadWithQMs", default: TRUE]; plainTextStyle ¬ UserProfile.Token[key: "Walnut.PlainTextStyle", default: "cedar"]; displayMsgSetInIcon ¬ UserProfile.Boolean[key: "Walnut.DisplayMsgSetInIcon", default: FALSE]; IF Rope.Equal[previousUser, curUser, FALSE] THEN RETURN; FOR whL: LIST OF WalnutHandle ¬ WalnutWindow.GetHandleList[], whL.rest UNTIL whL = NIL DO wH: WalnutHandle; IF GetWalnutState[wH ¬ whL.first] = running THEN { wH.mustQuitMsg ¬ userChangedMessage; TBQueue.FlushWithCallback[wH.walnutQueue, FlushingQueue]; TBQueue.QueueClientAction[wH.walnutQueue, QuitWalnut, wH]; previousUser ¬ curUser; }; ENDLOOP; previousUser ¬ curUser; }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> <<>> <> <> < { CloseTS[whL.first]};>> <> <> <> <> < CONTINUE];>> <> <<};>> <<>> <> <> <> <> <> <<};>> <> <> <> <> <<>> <> <> <> <> <<}>> <> <<{ Process.Detach[FORK WaitForRestart[wH, "Restarting after Rollback"]] };>> <> <<};>> <<>> <> <> <> <<};>> UserProfile.CallWhenProfileChanges[SetWalnutProfileVars]; END.