-- File: WalnutWindowImpl.mesa

-- Contents: Top level Viewer for Walnut; initializes database & displays control window
-- Last Edited by: Willie-Sue, December 13, 1982 9:16 am


-- created February, 1981 by Willie-Sue & Rick
-- Last edit by:
-- Willie-Sue on: June 8, 1983 3:25 pm


DIRECTORY
Atom USING [GetPName],
Buttons USING [ButtonProc],
CedarSnapshot USING [Register, CheckpointProc, RollbackProc],
CIFS USING [Error],
Commander USING [CommandProc, Register],
CommandTool USING [DoCommand],
Containers,
DB,
DBEnvironment USING [PilotTransRecord],
DBFileAlpine USING [CreateTransaction],
Labels,
FileIO,
GVRetrieve USING [MBXState],
Icons USING [IconFlavor, NewIconFromFile],
IO,
MBQueue USING [Action, Create, DequeueAction, Flush, QueueClientAction],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
MessageWindow USING [Append],
Process USING [Detach],
Rope,
Rules USING [Create],
Runtime USING [IsBound],
Transaction USING [nullHandle],
UserCredentials USING [GetUserCredentials],
UserProfile USING[Boolean, CallWhenProfileChanges, ProfileChangedProc, Token],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps,
ViewerSpecs USING[openLeftWidth, openRightTopY, openRightWidth],
ViewerTools USING [SetSelection],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
WalnutDB,
WalnutDBLog,
WalnutDisplayerOps,
WalnutLog,
WalnutRetrieve,
WalnutSendMail,
WalnutStream,
WalnutViewer,
WalnutWindow;

WalnutWindowImpl: CEDAR MONITOR
IMPORTS
DB, DBFileAlpine,
Atom, Commander, CommandTool,
Containers, Icons, Labels, MBQueue, Menus, MessageWindow, Rules,
CedarSnapshot, CIFS, IO, Process, Rope, Runtime, Transaction,
UserCredentials, UserProfile,
ViewerEvents, ViewerOps, ViewerTools, ViewerIO, ViewerSpecs,
WalnutDB, WalnutDBLog, WalnutDisplayerOps, WalnutLog, WalnutRetrieve,
WalnutSendMail, WalnutViewer, WalnutWindow
EXPORTS WalnutWindow
SHARES WalnutWindow =

BEGIN OPEN IO, WalnutDB, WalnutWindow;

-- Walnut Viewers types and global data

ROPE: TYPE = Rope.ROPE;
InitialNotifyLabel: ROPE = "Waiting for Grapevine response... ";
walnutVersion: ROPE← "Walnut 4.2";

walnut: PUBLIC Viewer← NIL;
wtsOut: IO.STREAMNIL;
walnutIcon: PUBLIC Icons.IconFlavor← tool;
newMailIcon: PUBLIC Icons.IconFlavor← tool;
msgSetIcon: PUBLIC Icons.IconFlavor← tool;
msgIcon: PUBLIC Icons.IconFlavor← tool;
iconFilePathName: PUBLIC ROPENIL;

alpineNeeded: BOOLFALSE;
walnutEventReg: ViewerEvents.EventRegistration← NIL;
mustQuitWalnut: ROPENIL;
previousUser: ROPENIL;
doingCheckpoint: BOOLFALSE;
rollbackFinished: CONDITION;

initialActiveIconic: PUBLIC BOOLFALSE;
initialActiveRight: PUBLIC BOOLTRUE;
initialActiveOpen: PUBLIC BOOLTRUE;
msgSetBorders: PUBLIC BOOLFALSE;
enableTailRewrite: PUBLIC BOOLFALSE;
walnutRulerBefore, walnutRulerAfter, walnutTS: PUBLIC Viewer;

msgSetText: PUBLIC Viewer; -- the text argument for Create/Delete MsgSet
fileNameText: PUBLIC Viewer; -- fileName for Archiving
mailNotifyLabel: PUBLIC Viewer; -- is there new mail or not

walnutSegmentFile: PUBLIC ROPE;
walnutLogName: PUBLIC ROPE;
logFileIsPilotFile: PUBLIC BOOLTRUE;

walnutQueue: PUBLIC Queue;
walnutNullTrans: PUBLIC DB.Transaction;

responseSynch: CONDITION;
userHasResponded, userConfirmed: BOOLFALSE;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildWalnut: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
IF walnut # NIL THEN
 { IF doingCheckpoint THEN WAIT rollbackFinished;
  Report["\nWalnut already running"]; RETURN
};

IF Rope.Length[UserCredentials.GetUserCredentials[].name] = 0 THEN
  { IF cmd # NIL THEN cmd.out.PutRope["Please Login"]; RETURN};

IF ~BuildWalnutControlWindow[] THEN RETURN;
 IF ~StartOrRestartWalnut[TRUE] THEN RETURN;

Report["\n", walnutVersion];
MBQueue.Flush[walnutQueue];  -- for good measure
TRUSTED { Process.Detach[FORK WalnutNotifier[]] };

END;

BuildWalnutControlWindow: INTERNAL PROC RETURNS[ok: BOOL] =
BEGIN
 bt: Viewer;
 walnutWidth: INTEGER;
 transOK: BOOLTRUE;

IF iconFilePathName=NIL THEN iconFilePathName← "Walnut.icons"; -- try local one first

IF walnutIcon = tool THEN
BEGIN ENABLE CIFS.Error => GOTO notFound;
  walnutIcon← Icons.NewIconFromFile[iconFilePathName, 0];
EXITS
notFound =>
BEGIN ENABLE CIFS.Error => { iconFilePathName← NIL; CONTINUE};
iconFilePathName← "/Indigo/Cedar/Walnut/Walnut.icons";
walnutIcon ← Icons.NewIconFromFile[iconFilePathName, 0];
END;
END;

IF iconFilePathName#NIL THEN SetWalnutIcons[iconFilePathName];
 newMailIcon← Icons.NewIconFromFile[iconFilePathName, 5];

 walnut← ViewerOps.CreateViewer[
   flavor: $Container,
   info: [name: "Walnut", iconic: FALSE, column: right, scrollable: FALSE,
      icon: walnutIcon, openHeight: ViewerSpecs.openRightTopY/4]];
 walnut.inhibitDestroy← TRUE;
 walnutWidth← IF walnut.column = right THEN ViewerSpecs.openRightWidth ELSE
  ViewerSpecs.openLeftWidth;

 ViewerOps.SetMenu[walnut, blankMenu];
 mailNotifyLabel← WalnutViewer.FirstLabel[name: InitialNotifyLabel, parent: walnut];

bt← WalnutViewer.ImmediateButton[name: "Archive", proc: ArchiveButtonProc,
  border: TRUE, sib: mailNotifyLabel, fork: FALSE, newLine: TRUE];
bt← WalnutViewer.ImmediateButton
  [name: "on File:", proc: FileNameProc, border: FALSE, sib: bt];
fileNameText← WalnutViewer.NextRightTextViewer[sib: bt, w: walnutWidth];
Containers.ChildXBound[walnut, fileNameText];

 bt← WalnutViewer.ImmediateButton[name: "Create", proc: CreateMsgSetButtonProc,
   border: TRUE, sib: bt, fork: FALSE, newLine: TRUE];
 bt← WalnutViewer.ImmediateButton[name: "Delete", proc: DeleteMsgSetButtonProc,
   border: TRUE, sib: bt, fork: FALSE];
 bt← WalnutViewer.ImmediateButton[name: "SizeOf", proc: SizeOfMsgSetButtonProc,
   border: TRUE, sib: bt, fork: FALSE];

 bt← WalnutViewer.ImmediateButton[
   name: "MsgSet:", proc: MsgSetNameProc, border: FALSE, sib: bt];
 msgSetText← WalnutViewer.NextRightTextViewer[sib: bt, w: walnutWidth];
 Containers.ChildXBound[walnut, msgSetText];

 bt← walnutRulerBefore← WalnutViewer.MakeRuler[sib: bt, h: 2];

 walnutRulerAfter← Rules.Create[
  info: [parent: walnut, wy: bt.wy+bt.wh+14+2, ww: walnutWidth, wh: 2]];
 Containers.ChildXBound[walnut, walnutRulerAfter];

 walnutTS← WalnutViewer.MakeTypescript[sib: walnutRulerAfter];
 wtsOut← ViewerIO.CreateViewerStreams[NIL, walnutTS].out;

TRUSTED
BEGIN
DO
IF ~Runtime.IsBound[WalnutSendMail.RegisterReporter] THEN
  {IF ~ContinueThisWalnut["WalnutSend"] THEN RETURN[FALSE] ELSE LOOP};
IF alpineNeeded AND ~Runtime.IsBound[DBFileAlpine.CreateTransaction] THEN
   {IF ~ContinueThisWalnut["AlpineUserImpls"] THEN RETURN[FALSE] ELSE LOOP};
IF ~Runtime.IsBound[DB.DeclareSegment] THEN
  {IF ~ContinueThisWalnut["Cypress"] THEN RETURN[FALSE] ELSE LOOP};
EXIT;
ENDLOOP;
END;

ViewerOps.SetMenu[walnut, workingMenu];
walnutEventReg←
   ViewerEvents.RegisterEventProc[proc: QuitProc, event: destroy, filter: walnut];
 WalnutDB.RegisterUpdatesPendingProc[SetWalnutUpdatesPending];
 WalnutSendMail.RegisterReporter[wtsOut];

DB.Initialize[nCachePages: 256];
RETURN[TRUE];
END;

ContinueThisWalnut: INTERNAL PROC[who: ROPE] RETURNS[reTry: BOOL]=
BEGIN
 v: Viewer;
 err: ROPE;

 Report[IO.PutFR["Loading and starting %g", IO.rope[who]]];
 err← CommandTool.DoCommand["Run", who].err;

IF err.Length[] = 0 THEN RETURN[TRUE];
 Report[err];
IF err.Find["There are unbound imports", 0, FALSE] >= 0 THEN RETURN[TRUE];
 Report["Click Continue to push on anyway, Quit to quit out of Walnut"];
IF InternalConfirm[startupConfirmMenu] THEN RETURN[TRUE];
 msgSetText← mailNotifyLabel← NIL;
 v← walnut;
 walnut← NIL;
 wtsOut.Close[];
 wtsOut← NIL;
 ViewerOps.DestroyViewer[v];
RETURN[FALSE]
END;

RestartWalnut: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
BEGIN
IF DB.TransactionOf[$Walnut]#NIL THEN
  WalnutLog.CloseWalnutTransaction[ ! DB.Aborted => GOTO dbAborted];
EXITS
  dbAborted => DB.AbortTransaction[DB.TransactionOf[$Walnut]];
END;

IF ~StartOrRestartWalnut[FALSE] THEN RETURN;
 Report["Restart finished"];
END;

StartOrRestartWalnut: INTERNAL PROC[firstTime: BOOLFALSE] RETURNS[BOOL] =
BEGIN OPEN WalnutLog, WalnutDBLog;
ENABLE
BEGIN
UNWIND => GOTO mustQuit;
DB.Failure =>
{ Report["Failure: what: ", Atom.GetPName[what], "; info: ", info]; GOTO mustQuit};
END;
 transOK: BOOLTRUE;
 curVersion: GreenwichMeanTime;
 curLength, expectedLength: INT;

DB.DeclareSegment[filePath: walnutSegmentFile, segment: $Walnut];

OpenWalnutTransaction[NIL, FALSE ! -- does InitializeDBVars
DB.InternalError => transOK← FALSE;
WalnutDBLog.SchemaMismatch => {curVersion← schemaVersion; transOK← FALSE}
];

IF ~transOK THEN
  { Report[
   PutFR["Database schema is of %g, but Walnut wants it to be %g\nYou Must Scavenge",
    time[curVersion], time[WalnutDBLog.SchemaVersionTime]]];
  Report["Click Confirm to Scavenge, Deny to quit"];
  IF ~InternalConfirm[] THEN { CloseDownWalnut[]; RETURN[FALSE]};
  DoScavenge[startPos: 0, internal: TRUE];
  };

curLength← InitializeLog[walnutLogName];

IF GetCopyInProgress[] THEN
  { ReportRope[" Recovering from interrupted Expunge ..."];
  WalnutLog.FinishExpunge[];
  Report[" done"];
  };

expectedLength← GetExpectedLogLength[];
IF curLength < expectedLength THEN
{ Report["Log length is less than expected; you Must Scavenge"];
Report["Click Confirm to Scavenge, Deny to quit"];
IF ~InternalConfirm[] THEN { CloseDownWalnut[]; RETURN[FALSE]};
DoScavenge[startPos: 0, internal: TRUE]
};

 WalnutRetrieve.OpenConnection[WalnutSendMail.userRName];
 ChangeWalnutMenu[workingMenu];

IF (expectedLength← GetExpectedDBLogPos[]) # curLength THEN
  { ReportRope["Updating database from log file ..."];
IF expectedLength = 0 THEN DoScavenge[startPos: 0, internal: TRUE]
ELSE
   { []← WalnutLog.UpdateFromLog[expectedLength];
   WalnutLog.MarkWalnutTransaction[];  -- commit what's been read
   };
  };

 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← walnut.ch;
IF (dif← (wH-walnutRulerAfter.cy)) < 64 THEN
  { ViewerOps.SetOpenHeight[walnut, wH + (64-dif)];
IF ~walnut.iconic THEN ViewerOps.ComputeColumn[walnut.column]
  };
  };

 FixUpWalnutViewers[];
IF firstTime AND initialActiveOpen THEN []← DisplayMsgSet[activeMsgSet];
ChangeWalnutMenu[walnutMenu];
doingCheckpoint← FALSE;
BROADCAST rollbackFinished;
 walnut.inhibitDestroy← FALSE;
RETURN[TRUE];

EXITS
  mustQuit =>
  { Report["Aborting start or restart; click quit when ready"];
[]← InternalConfirm[forceQuitMenu];
CloseDownWalnut[];
RETURN[FALSE]
};
END;

FixUpWalnutViewers: PROC =
BEGIN
 msgSetList, msgList: LIST OF Viewer;
 v: Viewer;
 fullName: ROPE;
 [msgSetList, msgList]← EnumWalnutViewers[TRUE];

FOR vL: LIST OF Viewer← msgSetList, vL.rest UNTIL vL=NIL DO
  fullName← NARROW[ViewerOps.FetchProp[v← vL.first, $Entity]];
  WalnutDisplayerOps.FixUpMsgSetViewer[fullName.Substr[14], v];
ENDLOOP;

FOR vL: LIST OF Viewer← msgList, vL.rest UNTIL vL=NIL DO
  fullName← NARROW[ViewerOps.FetchProp[v← vL.first, $Entity]];
  WalnutDisplayerOps.FixUpMsgViewer[fullName.Substr[11], v];
ENDLOOP;
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WalnutNotifier: PROC =
BEGIN OPEN MBQueue;
DO
  BEGIN ENABLE

BEGIN
ABORTED => {Flush[walnutQueue]; LOOP};
DB.Aborted => GOTO dbAborted;
END;

  action: Action← DequeueAction[walnutQueue];
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 = ClosingWalnut THEN {Flush[walnutQueue]; RETURN};
e2.proc[e2.data];
};
ENDCASE => ERROR;
EXITS
dbAborted =>
{ Flush[walnutQueue];
DB
.AbortTransaction[DB.TransactionOf[$Walnut]];
Report["\nDatabase transaction was aborted ... Restarting ..."];
RestartWalnut[];
   };
END;
ENDLOOP;
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
QueueExecCall: PUBLIC ENTRY PROC[proc: PROC[REF ANY], ra: REF ANY] =
BEGIN ENABLE UNWIND => NULL;
IF walnut # NIL THEN IF doingCheckpoint THEN WAIT rollbackFinished;
 MBQueue.QueueClientAction[walnutQueue, proc, ra];
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

SetWalnutIcons: PROC[file: ROPE] =
BEGIN
 newMailIcon← Icons.NewIconFromFile[file, 5];
 msgSetIcon← Icons.NewIconFromFile[file, 1];
 msgIcon← Icons.NewIconFromFile[file, 2];
END;

EstablishFileNames: PROC =
BEGIN
 sName: ROPE← walnutSegmentFile;
 lName: ROPE;

SELECT sName.Fetch[0] FROM
  '< => {sName← Rope.Concat["[Luther.Alpine]", sName]; alpineNeeded← TRUE};
  '[ => IF sName.Find["Local", 0, FALSE] < 0 THEN alpineNeeded← TRUE;
ENDCASE => sNameRope.Concat["[Local]", sName];

-- check for extension
IF Rope.Find[sName, "."] > 0 THEN walnutSegmentFile← sName  -- has extension
ELSE walnutSegmentFile← Rope.Concat[sName, ".Segment"];
IF ~alpineNeeded THEN
  { walnutNullTrans← NEW[DBEnvironment.PilotTransRecord← [trans: Transaction.nullHandle]];
  lName← walnutSegmentFile.Substr[7, walnutSegmentFile.Find[".", 8]-7];
  }
ELSE
  { sPos: INT← walnutSegmentFile.Find[">"]+1;
  lName← walnutSegmentFile.Substr[0, walnutSegmentFile.Find[".", sPos]];
walnutNullTrans← NIL;
  };

IF walnutLogName.Length[]=0 THEN walnutLogName← lName.Concat[".DBLog"]
ELSE IF walnutLogName.Find["."] < 0 THEN
   walnutLogName← walnutLogName.Concat[".DBLog"];

 logFileIsPilotFile← (walnutLogName.Fetch[0] # '[ );
 alpineNeeded← alpineNeeded OR ~logFileIsPilotFile;
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ChangeWalnutMenu: PUBLIC PROC[menu: Menus.Menu] =
BEGIN
 walnut.menu← menu;
 ViewerOps.PaintViewer[walnut, menu];
END;

UserConfirmed: PUBLIC ENTRY PROC RETURNS[BOOL] =
BEGIN ENABLE UNWIND => NULL;
RETURN[InternalConfirm[confirmMenu]];
END;

InternalConfirm: INTERNAL PROC[m: Menus.Menu← NIL] RETURNS[BOOL] =
BEGIN
 ChangeWalnutMenu[IF m = NIL THEN confirmMenu ELSE m];
IF walnut.iconic THEN ViewerOps.BlinkIcon[walnut, 0] ELSE ViewerOps.BlinkIcon[walnutTS];
UNTIL userHasResponded DO WAIT responseSynch; ENDLOOP;
 userHasResponded← FALSE;
 ChangeWalnutMenu[workingMenu];
RETURN[userConfirmed];
END;

---------------------------
walnutMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
workingMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
confirmMenu: Menus.Menu← Menus.CreateMenu[];
blankMenu: Menus.Menu← Menus.CreateMenu[];
startupConfirmMenu: Menus.Menu← Menus.CreateMenu[];
forceQuitMenu: Menus.Menu← Menus.CreateMenu[];

BuildWalnutMenu: PROC =
{ OPEN Menus;

 AppendMenuEntry[walnutMenu, CreateEntry["NewForm", NewMsgProc]];
 AppendMenuEntry[walnutMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "NewMail", NewMailProc]];
 AppendMenuEntry[walnutMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Commit", MyCommitProc]];
 AppendMenuEntry[walnutMenu,
  WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Expunge", proc: MenuExpunge,
    guarded: TRUE]];
 AppendMenuEntry[walnutMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "CloseAll", CloseAllProc]];

 AppendMenuEntry[workingMenu, CreateEntry["NewForm", NewMsgProc]];

 AppendMenuEntry[confirmMenu, CreateEntry["NewForm", NewMsgProc]];
 AppendMenuEntry[confirmMenu, CreateEntry["Confirm", WWConfirmProc]];
 AppendMenuEntry[confirmMenu, CreateEntry["Deny", WWDenyProc]];

 AppendMenuEntry[startupConfirmMenu, CreateEntry["Continue", WWConfirmProc]];
 AppendMenuEntry[startupConfirmMenu, CreateEntry["Quit", WWDenyProc]];

 AppendMenuEntry[forceQuitMenu, CreateEntry["Quit", WWConfirmProc]];
};

NewMsgProc: Menus.MenuProc = { WalnutSendMail.WalnutSendProc[fromExec: FALSE]};
-- Create a new blank form in a viewer for user to send msg with.

MenuExpunge: Menus.MenuProc =
 { DoExpunge[doUpdates: TRUE, tailRewrite: mouseButton=red]};

MyCommitProc: Menus.MenuProc =
{ ChangeWalnutMenu[workingMenu];
WalnutLog.MarkWalnutTransaction[];
ChangeWalnutMenu[walnutMenu]
};

NewMailProc: Menus.MenuProc =
{ ChangeWalnutMenu[workingMenu];
RetrieveNewMail[];
ChangeWalnutMenu[walnutMenu]
};

RetrieveNewMail: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
 thisUser: ROPE;
IF doingCheckpoint THEN WAIT rollbackFinished;

 thisUser← UserCredentials.GetUserCredentials[].name;
IF ~Rope.Equal[thisUser, previousUser, FALSE] THEN
  { r: ROPE← "User has changed";
  Report[IO.PutFR
  ["******%g may not retrieve %g's new mail", IO.rope[thisUser], IO.rope[previousUser]]];
  Report["You will be forced to quit out of Walnut; you should then Login to the exec"];
  MBQueue.Flush[walnutQueue];
  MBQueue.QueueClientAction[walnutQueue, QuitWalnut, r];
  }
ELSE DoNewMail[];
END;

CloseAllProc: Menus.MenuProc =
BEGIN
 msgSetList, msgList: LIST OF Viewer;
 [msgSetList, msgList]← EnumWalnutViewers[TRUE];
FOR vL: LIST OF Viewer← msgList, vL.rest UNTIL vL=NIL DO
  ViewerOps.DestroyViewer[vL.first]; ENDLOOP;
FOR vL: LIST OF Viewer← msgSetList, vL.rest UNTIL vL=NIL DO
  ViewerOps.CloseViewer[vL.first]; ENDLOOP;
 MyCommitProc[parent];
 ViewerOps.CloseViewer[walnut];
END;

QuitProc: ViewerEvents.EventProc =
BEGIN
IF walnutEventReg = NIL THEN RETURN[FALSE];
TRUSTED {Process.Detach[ FORK QuitWalnut[NIL]]};
RETURN[TRUE];
END;

QuitWalnut: ENTRY PROC[ra: REF ANY ] =
BEGIN ENABLE UNWIND => NULL;
IF ra # NIL THEN
  { msg: ROPENARROW[ra];
IF walnut = NIL THEN RETURN;  -- something funny
  walnut.inhibitDestroy← TRUE;
  TakeDownWalnutViewers[];  -- make the user notice
  ViewerOps.BlinkIcon[walnut, IF walnut.iconic THEN 0 ELSE 1];
  Report["**********", msg];
  Report["You MUST quit out of Walnut; Click Quit when ready"];
  []← InternalConfirm[forceQuitMenu];
  };
 CloseDownWalnut[]
END;

WWDenyProc
: ENTRY Menus.MenuProc =
{ ENABLE UNWIND => NULL;
userConfirmed← FALSE;
userHasResponded← TRUE;
BROADCAST responseSynch
};

WWConfirmProc: ENTRY Menus.MenuProc =
{ ENABLE UNWIND => NULL;
userConfirmed← TRUE;
userHasResponded← TRUE;
BROADCAST responseSynch
};

----------------------------

MsgSetNameProc: Buttons.ButtonProc =
BEGIN
 ViewerTools.SetSelection[msgSetText, NIL];
END;

FileNameProc: Buttons.ButtonProc =
BEGIN
 ViewerTools.SetSelection[fileNameText, NIL];
END;

CloseDownWalnut: INTERNAL PROC[doCommit: BOOLTRUE] =
-- clean up after Walnut
BEGIN ENABLE UNWIND => CONTINUE;
 v: Viewer← walnut;
 walnut.inhibitDestroy← TRUE;
 MBQueue.Flush[walnutQueue];
IF walnutEventReg # NIL THEN
  ViewerEvents.UnRegisterEventProc[walnutEventReg, destroy];
 walnutEventReg← NIL;
 ChangeWalnutMenu[workingMenu];
 ReportRope["Saving log ..."];
 []← WalnutLog.LogLength[doFlush: TRUE];
 WalnutDB.UnRegisterUpdatesPendingProc[SetWalnutUpdatesPending];
 selectedMsgSetButtons← firstMsgSetButton← lastMsgSetButton← NIL;
 TakeDownWalnutViewers[];
BEGIN
IF DB.TransactionOf[$Walnut]#NIL THEN
  { IF doCommit THEN WalnutLog.CloseWalnutTransaction[ ! DB.Aborted => GOTO dbAborted]
ELSE DB.AbortTransaction[DB.TransactionOf[$Walnut]]
  };
EXITS
  dbAborted => DB.AbortTransaction[DB.TransactionOf[$Walnut]];
END;

 WalnutLog.CloseLogStream[];
 walnut← NIL;  -- don't let others try to use Report
 WalnutRetrieve.CloseConnection[];  -- NOP if no open connection
 lastStateReported← unknown;
 msgSetText← mailNotifyLabel← NIL;
 WalnutSendMail.UnregisterReporter[wtsOut];
 wtsOut.Close[];
wtsOut← NIL;
mustQuitWalnut← NIL;
 MBQueue.QueueClientAction[walnutQueue, ClosingWalnut, NIL];
 v.inhibitDestroy← FALSE;
 doingCheckpoint← FALSE;
 ViewerOps.DestroyViewer[v];
END;

ClosingWalnut: PROC[ra: REF ANY] = {NULL};

EnumWalnutViewers: PROC[keepSeparate: BOOL]
  RETURNS [msgSetList, msgList: LIST OF Viewer] =
BEGIN
Enum: ViewerOps.EnumProc =
BEGIN
  ra: REF ANY; fullName: ROPE;
  IF (ra← ViewerOps.FetchProp[v, $Entity]) = NIL THEN RETURN[TRUE];
   fullName← NARROW[ra];
   IF fullName.Find["Walnut!"] # 0 THEN RETURN[TRUE];
   IF ~keepSeparate THEN msgSetList← CONS[v, msgSetList]
   ELSE
   { IF fullName.Find["!MsgSet!", 6] = 6 THEN msgSetList← CONS[v, msgSetList]
    ELSE msgList← CONS[v, msgList];
   };
  RETURN[TRUE];
END;
 ViewerOps.EnumerateViewers[Enum]
END;

TakeDownWalnutViewers: PUBLIC PROC =
BEGIN
FOR vL: LIST OF Viewer← EnumWalnutViewers[FALSE].msgSetList, vL.rest UNTIL vL=NIL DO
  ViewerOps.DestroyViewer[vL.first];
ENDLOOP;
END;

-- **********************************************************************
SetWalnutUpdatesPending: PUBLIC PROC[newVersion: BOOL] =
BEGIN
 old: BOOL;
IF walnut = NIL OR walnut.destroyed THEN RETURN;
 old← walnut.newVersion;
IF newVersion#old THEN
  {walnut.newVersion← newVersion; ViewerOps.PaintViewer[walnut, caption]};
END;

UncomittedUpdates: PUBLIC ENTRY PROC RETURNS[updatesPending: BOOL] =
{ ENABLE UNWIND => NULL; RETURN[walnut.newVersion]};

-- **********************************************************************
Report: PUBLIC PROC[msg1, msg2, msg3, msg4: ROPENIL] =
{ IF wtsOut = NIL THEN
{ IF msg1#NIL THEN MessageWindow.Append[msg1];
  IF msg2#NIL THEN MessageWindow.Append[msg2];
  IF msg3#NIL THEN MessageWindow.Append[msg3];
IF msg4#NIL THEN MessageWindow.Append[msg4];
RETURN
  };
IF msg1#NIL THEN wtsOut.PutRope[msg1];
IF msg2#NIL THEN wtsOut.PutRope[msg2];
IF msg3#NIL THEN wtsOut.PutRope[msg3];
IF msg4#NIL THEN wtsOut.PutRope[msg4];
  wtsOut.PutChar[CR];
};

ReportRope: PUBLIC PROC[msg1: ROPE] =
{ IF wtsOut # NIL THEN wtsOut.PutRope[msg1] ELSE MessageWindow.Append[msg1];
};

-- **********************************************************************
-- ***** typescript log file when Walnut Control Window not up

SetUpTSLog: PUBLIC ENTRY PROC[who: ROPE] =
{ ENABLE UNWIND => NULL;
v: Viewer;
  wtsOut← ViewerIO.CreateViewerStreams[
 name: "WalnutTS", backingFile: "Walnut.TypescriptLog"].out;

  v← ViewerIO.GetViewerFromStream[wtsOut];
  v.inhibitDestroy← TRUE;
wtsOut.PutRope["Writing Walnut typescript info to Walnut.TypescriptLog"];
  wtsOut.PutRope[IO.PutFR["\n******Call to %g at %g\n", rope[who], time[]]];
};

CloseTSLog: PUBLIC ENTRY PROC =
{ ENABLE UNWIND => NULL;
v: Viewer← ViewerIO.GetViewerFromStream[wtsOut];
wtsOut.Close[];
wtsOut← NIL;
v.inhibitDestroy← FALSE;
ViewerOps.DestroyViewer[v];
};
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
lastStateReported: GVRetrieve.MBXState← unknown;

WatchMailBox: PUBLIC PROC[newState: GVRetrieve.MBXState] =
-- This is called when the condition of the mailbox changes
BEGIN
showTime: BOOLTRUE;
status: ROPE;
icon: Icons.IconFlavor← walnutIcon;
IF walnut = NIL THEN RETURN;
IF walnut.destroyed THEN RETURN;
IF newState = unknown THEN RETURN;
IF (lastStateReported = notEmpty) AND (newState = someEmpty OR newState = allEmpty) THEN
status← "New mail was read"
ELSE
{ SELECT newState FROM
   badName => {status← "Your user name is invalid, please log in"; showTime← FALSE};
   badPwd => {status← "Your password is invalid"; showTime← FALSE};
   cantAuth => {status← "Can't check your credentials at this time"; showTime← FALSE};
   userOK => {status← "Your credentials are OK"; showTime← FALSE};
   allDown => status← "All of the mail servers are down";
   someEmpty => status← "All of the mail servers checked are empty";
   allEmpty => status← "There is no new mail";
   notEmpty => {status← "You have new mail"; icon← newMailIcon};
ENDCASE => status← "Bad State!";
};
 lastStateReported ← newState;
IF walnut.icon # icon THEN
  { walnut.icon← icon; IF walnut.iconic THEN ViewerOps.PaintViewer[walnut, all]};
 Labels.Set[mailNotifyLabel,
IF showTime THEN Rope.Concat[status, IO.PutFR[" at %g", time[]]] ELSE status];
END;

----------------------------
SetWalnutProfileVars: ENTRY UserProfile.ProfileChangedProc = CHECKED
BEGIN ENABLE UNWIND => NULL;
 prevSegmentFile, prevLogName, curUser: ROPE;

 enableTailRewrite← UserProfile.Boolean[key: "Walnut.EnableTailRewrite", default: FALSE];
 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];
 prevSegmentFile← walnutSegmentFile;
 walnutSegmentFile←
  UserProfile.Token[key: "Walnut.WalnutSegmentFile", default: "Walnut.Segment"];
 prevLogName← walnutLogName;
 walnutLogName←
  UserProfile.Token[key: "Walnut.WalnutLogFile", default: "Walnut.DBLog"];
 EstablishFileNames[];
 curUser← UserCredentials.GetUserCredentials[].name;
IF walnut = NIL THEN {previousUser← curUser; RETURN};
IF ~Rope.Equal[previousUser, curUser, FALSE]
THEN mustQuitWalnut← "Logged-in user changed"
ELSE IF ~Rope.Equal[prevSegmentFile, walnutSegmentFile, FALSE]
THEN mustQuitWalnut← "segment file changed"
ELSE IF ~Rope.Equal[prevLogName, walnutLogName, FALSE]
THEN mustQuitWalnut← "log file changed";
IF mustQuitWalnut # NIL THEN
  { MBQueue.Flush[walnutQueue];
IF reason # rollBack THEN
    MBQueue.QueueClientAction[walnutQueue, QuitWalnut, mustQuitWalnut];
  };
 previousUser← curUser;
END;

----------------------------

WalnutCheckpointProc: ENTRY CedarSnapshot.CheckpointProc =
BEGIN ENABLE UNWIND => NULL;
IF walnut = NIL THEN RETURN;
 MBQueue.Flush[walnutQueue];
 ChangeWalnutMenu[workingMenu];
BEGIN
 WalnutLog.CloseWalnutTransaction[ ! DB.Aborted => GOTO dbAborted];
EXITS
  dbAborted => DB.AbortTransaction[DB.TransactionOf[$Walnut]];
END;

 WalnutLog.CloseLogStream[];
 WalnutRetrieve.CloseConnection[];
 lastStateReported← unknown;
 doingCheckpoint← TRUE;
END;

WalnutRollbackProc: ENTRY CedarSnapshot.RollbackProc =
{ ENABLE UNWIND => NULL;
IF walnut = NIL THEN RETURN;
TRUSTED
  {IF mustQuitWalnut#NIL THEN Process.Detach[FORK QuitWalnut[mustQuitWalnut]]
ELSE Process.Detach[FORK RestartWalnut[]];
  };
};

walnutQueue← MBQueue.Create[pushModel: FALSE];  -- queue for menu/button pushes
BuildWalnutMenu[];

Commander.Register["Walnut", BuildWalnut, "For retrieving and sending mail"];

-- clean up Walnut at checkpoint time
TRUSTED {CedarSnapshot.Register[c: WalnutCheckpointProc, r: WalnutRollbackProc]};

UserProfile.CallWhenProfileChanges[SetWalnutProfileVars];

END
.