-- File: WalnutWindowImpl.mesa

-- Contents: Top level Viewer for Walnut; initializes database & displays control window

-- created February, 1981 by Willie-Sue & Rick
-- Last edit by:
-- Willie-Sue on: December 13, 1983 3:31 pm
-- Donahue, July 26, 1983 9:13 am

DIRECTORY
AlpineWalnutCmds USING [CopyForExpunge],
Buttons USING [ButtonProc],
Commander USING [CommandProc, Handle, Register],
CommandTool USING [Run],
Containers,
DB USING [Aborted, Failure, Transaction, AbortTransaction, DeclareSegment,
     Initialize, TransactionOf],
Labels USING [Set],
FS USING [Error, SetKeep, StreamOpen],
GVRetrieve USING [MBXState],
Icons USING [IconFlavor, NewIconFromFile],
IO,
WQueue USING [Create, QueueClientAction],
Menus,
MessageWindow USING [Append],
PrincOpsUtils USING [IsBound],
Process USING [Detach],
Rope,
Rules USING [Create],
UserCredentials USING [Get],
UserProfile USING[ProfileChangedProc, Boolean, CallWhenProfileChanges, Token],
ViewerBLT USING[ChangeNumberOfLines],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps,
ViewerSpecs USING[openLeftWidth, openRightTopY, openRightWidth],
ViewerTools USING [SetSelection],
ViewerIO USING [CreateViewerStreams],
WalnutControlPrivate USING [CloseTransactions, ClosingWalnut, FlushWQueue, QuitWalnut,
     StartOrRestartWalnut, WalnutNotifier],
WalnutControlMonitorImpl,
WalnutDB USING [RegisterUpdatesPendingProc, UnRegisterUpdatesPendingProc],
WalnutDisplayerOps USING [msgName, msgSetName],
WalnutLog USING [ExpungeMsgs, MarkWalnutTransaction],
WalnutExtras USING [ChangeDBProc, DoWaitCall, DumpProc, EnumWalnutViewers,
     EstablishFileNames, ExpungeProc, ReadMailProc, RetrieveNewMailProc,
     ScavengeProc, SetWalnutIcons, TakeDownWalnutViewers],
WalnutRetrieve USING [CloseConnection],
WalnutSendOps USING [RegisterReporter, UnregisterReporter, WalnutSendProc],
WalnutViewer USING [AnotherLabel, CreateMenuEntry, FirstLabel, ImmediateButton,
     MakeRuler, MakeTypescript, NextRightTextViewer],
WalnutVoice USING [CheckForWalnuthatch],
WalnutWindow USING [Queue, firstMsgSetButton, lastMsgSetButton, selectedMsgSetButtons,
     AppendButtonProc, ArchiveButtonProc, CreateMsgSetButtonProc,
     DeleteMsgSetsProc, DestroyAllMsgSetButtons, DoNewMail, PrintMsgSetsProc,
     SizeOfMsgSetsProc];

WalnutWindowImpl: CEDAR MONITOR LOCKS walnutControlLock
IMPORTS
 AlpineWalnutCmds, DB, FS,
  Commander, CommandTool,
  Containers, Icons, Labels, WQueue, Menus, MessageWindow, Rules,
IO, PrincOpsUtils, Process, Rope,
  UserCredentials, UserProfile,
 ViewerBLT, ViewerEvents, ViewerOps, ViewerTools, ViewerIO, ViewerSpecs,
  walnutControlLock: WalnutControlMonitorImpl,
  WalnutControlPrivate, WalnutDB, WalnutDisplayerOps, WalnutExtras,
  WalnutLog, WalnutRetrieve, WalnutSendOps,
  WalnutViewer, WalnutVoice, WalnutWindow
EXPORTS WalnutControlPrivate, WalnutExtras, WalnutWindow
SHARES WalnutControlMonitorImpl, WalnutWindow =

BEGIN OPEN WalnutControlPrivate, WalnutExtras, WalnutWindow;

-- Walnut Viewers types and global data

ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;

InitialNotifyLabel: PUBLIC ROPE
 " ";

-- walnutVersion: ROPE← "WalnutWithVoice 4.4.101";
walnutVersion: ROPE← "Walnut";

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: PUBLIC ROPENIL;
previousUser: PUBLIC ROPENIL;
doingCheckpoint: PUBLIC BOOLFALSE;
rollbackFinished: PUBLIC CONDITION;

initialActiveIconic: PUBLIC BOOLFALSE;
initialActiveRight: PUBLIC BOOLTRUE;
initialActiveOpen: PUBLIC BOOLTRUE;
msgSetBorders: PUBLIC BOOLFALSE;
enableTailRewrite: PUBLIC BOOLFALSE;
excessBytesInLogFile: PUBLIC INT← 300000;
walnutRulerBefore, walnutRulerAfter, walnutTS: PUBLIC Viewer;

createButton: Viewer;
msForCreateButton: Viewer;

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

autoNewMail: BOOLFALSE;
tryForNewMail: BOOLFALSE;

walnutSegmentFile: PUBLIC ROPE;
walnutLogName: PUBLIC ROPE;
readOnlyAccess: PUBLIC BOOL;
logIsAlpineFile: PUBLIC BOOL;
personalMailDB: PUBLIC BOOL;

segmentName: PUBLIC ATOM← $Walnut;

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

responseSynch: CONDITION;
userHasResponded, userConfirmed: BOOLFALSE;

alpineServer: ROPE = "[Luther.alpine]";  -- for now
lastStateReported: PUBLIC GVRetrieve.MBXState← unknown;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildWalnut: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
 h: IO.STREAM;
 dirName, segName, logName, captionName: ROPE;
 alpNeeded, logIsAlp: BOOL;

IF doingCheckpoint THEN WAIT rollbackFinished;
 personalMailDB← TRUE;

IF cmd # NIL THEN
  { h← IO.RIS[cmd.commandLine];
  dirName← h.GetTokenRope[IO.IDProc ! IO.EndOfStream => {dirName← NIL; CONTINUE}].token;
  };

 [segName, logName, captionName, logIsAlp, alpNeeded]← CmdProcCall[dirName, cmd];
IF segName=NIL THEN RETURN;

IF walnut # NIL THEN
IF segName.Equal[walnutSegmentFile, FALSE] THEN
 { Report["\nWalnut is already running"]; RETURN}
ELSE
  { cdb: ChangeDBParams← NEW[ChangeDBObject←
   [segName: segName, logName: logName, logIsAlp: logIsAlp, alpNeeded: alpNeeded]];
  walnut.name← captionName;
  ViewerOps.PaintViewer[walnut, caption];
  TRUSTED { Process.Detach[FORK ChangeDB[cdb]]};
WAIT cdb.cond;
IF walnut # NIL THEN ViewerOps.AddProp[walnut, $IconLabel, dirName];
RETURN
  };

IF Rope.Length[UserCredentials.Get[].name] = 0 THEN
  { IF cmd # NIL THEN cmd.out.PutRope["Please Login"]; RETURN};
 walnutSegmentFile← segName;
 walnutLogName← logName;
 logIsAlpineFile← logIsAlp;
 alpineNeeded← alpNeeded;
 WalnutDisplayerOps.msgName ← Rope.Concat[walnutSegmentFile, "!Msg!"];
 WalnutDisplayerOps.msgSetName ← Rope.Concat[walnutSegmentFile, "!MsgSet!"];
 StartUp[FALSE, captionName];
IF walnut # NIL THEN
  { ViewerOps.AddProp[walnut, $IconLabel, dirName];
  IF personalMailDB AND autoNewMail THEN
   { tryForNewMail← FALSE;
    TRUSTED {Process.Detach[FORK AutoNewMailProc]};
  };
  };
END;

CmdProcCall: INTERNAL PROC[dirName: ROPE, cmd: Commander.Handle]
  RETURNS[segName, logName, captionName: ROPE, logIsAlp, alpNeeded: BOOL] =
BEGIN
 sName, lName: ROPE;
 alpServer: ROPE = "[Luther.alpine]<";
 seg: ROPE = ">Walnut.segment";
 user: ROPE;

 personalMailDB← TRUE;

IF dirName.Length = 0 THEN
  { sName← UserProfile.Token[key: "Walnut.WalnutSegmentFile", default: ""];
  lName← UserProfile.Token[key: "Walnut.WalnutLogFile", default: "Walnut.DBLog"];
IF sName.Length[] = 0 THEN
    { user← UserCredentials.Get[].name;
    IF user # NIL THEN sName← Rope.Cat[alpServer, user, seg];
    };
  captionName← walnutVersion;
  }
ELSE
  { sName← Rope.Cat[alpServer, dirName, seg];
  personalMailDB← FALSE;
  captionName← Rope.Cat[walnutVersion, " <", dirName, ">"];
  };
 [segName, logName, logIsAlp, alpNeeded]← EstablishFileNames[sName, lName];
 walnutNullTrans← NIL;
IF ~segName.Find[".alpine]", 0, FALSE] > 0 THEN
  { msg: ROPE
    Rope.Concat[segName, ": is not a valid Walnut segment file; it MUST be on alpine"];
IF cmd # NIL THEN {cmd.out.PutRope[msg]; cmd.out.PutChar['\n]}
ELSE ReportRope[msg];
  segName← NIL;
  };
END;

StartUp: INTERNAL PROC[scavengeFirst: BOOL, captionName: ROPE] =
BEGIN
IF ~BuildWalnutControlWindow[captionName] THEN RETURN;
IF ~StartOrRestartWalnut[TRUE, scavengeFirst] THEN RETURN;

FlushWQueue[];  -- for good measure
Report[" ...Ready"];
TRUSTED { Process.Detach[FORK WalnutNotifier[]] };

END;

CloseDownWalnut
: PUBLIC INTERNAL PROC[doCommit: BOOL] =
-- clean up after Walnut
BEGIN
 v: Viewer← walnut;
 walnut.inhibitDestroy← TRUE;
 FlushWQueue[];
IF walnutEventReg # NIL THEN
  ViewerEvents.UnRegisterEventProc[walnutEventReg, destroy];
 walnutEventReg← NIL;
 ChangeWalnutMenu[workingMenu];
 WalnutDB.UnRegisterUpdatesPendingProc[SetWalnutUpdatesPending];
 selectedMsgSetButtons← firstMsgSetButton← lastMsgSetButton← NIL;
 TakeDownWalnutViewers[];

 ReportRope["Closing database and saving log ..."];

BEGIN
  CloseTransactions[doCommit !
  DB.Failure, IO.Error => GOTO failure;  -- IO.Error replaces FileIOAlpine.Failure
  UNWIND => CONTINUE ];
EXITS
  failure => IF DB.TransactionOf[segmentName] # NIL THEN
   DB.AbortTransaction[DB.TransactionOf[segmentName ! IO.Error => CONTINUE]];
END;

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


WalnutReadMailFile: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
 h: IO.STREAM;
 fName, messageSet: ROPE;
IF walnut = NIL THEN {NoWalnutReport[cmd, "read old mail files"]; RETURN};
IF doingCheckpoint THEN WAIT rollbackFinished;
 h← IO.RIS[cmd.commandLine];
 fName← h.GetTokenRope[IO.IDProc ! IO.EndOfStream => GOTO noFile].token;
 messageSet← h.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token;
 ReadMailFile[fName, messageSet];
 WalnutLog.MarkWalnutTransaction[];

EXITS
  noFile => Report["No file specified"];
END;

-- make separate proc so callable by program
ReadMailFile: PUBLIC PROC[fName, msgSet: ROPE] =
BEGIN
 mfp: LIST OF ROPELIST[fName, IF msgSet=NIL THEN "Active" ELSE msgSet];
 DoWaitCall[ReadMailProc, mfp];
END;

WalnutExpunge
: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
IF walnut = NIL THEN {NoWalnutReport[cmd, "do Expunge"]; RETURN};
IF doingCheckpoint THEN WAIT rollbackFinished;
 DoWaitCall[ExpungeProc];
 FinishCall[];
END;

WalnutDump
: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
IF walnut = NIL THEN {NoWalnutReport[cmd, "do Dump"]; RETURN};
IF doingCheckpoint THEN WAIT rollbackFinished;
 DoWaitCall[DumpProc];
 FinishCall[];
END;

WalnutScavenge
: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
IF walnut = NIL THEN
 { segName, logName, captionName: ROPE;
  logIsAlp, alpNeeded: BOOL;
  personalMailDB← TRUE;
  [segName, logName, captionName, logIsAlp, alpNeeded]← CmdProcCall[NIL, cmd];
IF segName=NIL THEN RETURN;
  walnutSegmentFile← segName;
  walnutLogName← logName;
  logIsAlpineFile← logIsAlp;
  alpineNeeded← alpNeeded;
  WalnutDisplayerOps.msgName ← Rope.Concat[walnutSegmentFile, "!Msg!"];
  WalnutDisplayerOps.msgSetName ← Rope.Concat[walnutSegmentFile, "!MsgSet!"];
  StartUp[TRUE, walnutVersion];
IF walnut # NIL THEN ViewerOps.AddProp[walnut, $IconLabel, NIL];
RETURN
};

IF doingCheckpoint THEN WAIT rollbackFinished;
 ChangeWalnutMenu[workingMenu];
 DoWaitCall[ScavengeProc];
 FinishCall[];
END;

FinishCall: INTERNAL PROC =
BEGIN
 Report["\n... Restarting"];
 CloseTransactions[ TRUE ! DB.Failure, IO.Error => CONTINUE];
IF ~StartOrRestartWalnut[TRUE] THEN RETURN;
 Report[" ... done"];
END;

GetNewMail: PUBLIC PROC = {[]← WalnutNewMail[NIL]};

WalnutNewMail: PUBLIC ENTRY Commander.CommandProc =
BEGIN ENABLE UNWIND => NULL;
IF walnut = NIL THEN {NoWalnutReport[cmd, "retrieve new mail"]; RETURN};
IF doingCheckpoint THEN WAIT rollbackFinished;
 DoWaitCall[RetrieveNewMailProc, NIL]
END;

NoWalnutReport: INTERNAL PROC[cmd: Commander.Handle, msg: ROPE] =
BEGIN
 r: ROPE← Rope.Concat["Walnut must be running to ", msg];
IF cmd # NIL THEN cmd.out.PutRope[r] ELSE MessageWindow.Append[r]
END;

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

BuildWalnutControlWindow: INTERNAL PROC[captionName: ROPE] RETURNS[ok: BOOL] =
BEGIN
 bt: Viewer;
 walnutWidth: INTEGER;
 transOK: BOOLTRUE;
 tsLogName: ROPE = "Walnut.TypescriptLog";

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

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

 walnut← ViewerOps.CreateViewer[
   flavor: $Container,
   info: [name: captionName, 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: "Append", proc: AppendButtonProc,
  border: TRUE, sib: bt, fork: FALSE];
bt← WalnutViewer.ImmediateButton
  [name: "on File:", proc: FileNameProc, border: FALSE, sib: bt];
fileNameText← WalnutViewer.NextRightTextViewer[sib: bt, w: walnutWidth];
Containers.ChildXBound[walnut, fileNameText];

 FixupCreateLine[];

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

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

 walnutTS← WalnutViewer.MakeTypescript[sib: walnutRulerAfter];
FS.SetKeep[tsLogName, 2 ! FS.Error => CONTINUE];  -- if tsLogName doesn't exist
 wtsOut← ViewerIO.CreateViewerStreams[NIL, walnutTS, tsLogName].out;

TRUSTED
BEGIN
DO
IF ~PrincOpsUtils.IsBound[WalnutSendOps.RegisterReporter] THEN
  {IF ~ContinueThisWalnut["WalnutSend"] THEN RETURN[FALSE] ELSE LOOP};
IF alpineNeeded AND ~PrincOpsUtils.IsBound[AlpineWalnutCmds.CopyForExpunge] THEN
   {IF ~ContinueThisWalnut["AlpineUserImpls"] THEN RETURN[FALSE] ELSE LOOP};
IF ~PrincOpsUtils.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];
 WalnutSendOps.RegisterReporter[wtsOut];

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

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

IF InternalLoadBcd[who] 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;

LoadBcdForWalnut: PUBLIC ENTRY PROC[who: ROPE] RETURNS[ok: BOOL] =
{ ENABLE UNWIND => NULL;
RETURN[InternalLoadBcd[who]]
};

InternalLoadBcd: INTERNAL PROC[who: ROPE] RETURNS[ok: BOOL] =
BEGIN
 errMsg: ROPE;
 error: BOOL;

 [errMsg, error]← CommandTool.Run[who];
 Report[errMsg];

IF ~error THEN RETURN[TRUE];
IF errMsg.Find["unbound imports", 0, FALSE] >= 0 THEN RETURN[TRUE];
RETURN[FALSE]
END;

FixupCreateLine: PUBLIC PROC =
BEGIN
IF createButton # NIL THEN ViewerOps.DestroyViewer[createButton];
IF msForCreateButton # NIL THEN ViewerOps.DestroyViewer[msForCreateButton];
IF msgSetText # NIL THEN ViewerOps.DestroyViewer[msgSetText];

IF readOnlyAccess THEN
  createButton← WalnutViewer.AnotherLabel[name: "", sib: fileNameText, newLine: TRUE]
ELSE
  { walnutWidth: INTEGERIF walnut.column = right THEN ViewerSpecs.openRightWidth
    ELSE ViewerSpecs.openLeftWidth;
  createButton← WalnutViewer.ImmediateButton[name: "Create", proc: CreateMsgSetButtonProc,
   border: TRUE, sib: fileNameText, fork: FALSE, newLine: TRUE];
  msForCreateButton← WalnutViewer.ImmediateButton[
   name: "MessageSet:", proc: MsgSetNameProc, border: FALSE, sib: createButton];
  msgSetText← WalnutViewer.NextRightTextViewer[sib: msForCreateButton, w: walnutWidth];
  Containers.ChildXBound[walnut, msgSetText]
  }

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;
 WQueue.QueueClientAction[walnutQueue, proc, ra];
END;

-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ChangeWalnutMenu: PUBLIC PROC[menu: Menus.Menu] =
BEGIN
oldCount: Menus.MenuLine = Menus.GetNumberOfLines[walnut.menu];
newCount: Menus.MenuLine = Menus.GetNumberOfLines[menu];
 walnut.menu ← menu;
IF oldCount # newCount THEN
  ViewerBLT.ChangeNumberOfLines[walnut, newCount];
 ViewerOps.PaintViewer[walnut, menu];
END;

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

InternalConfirm: PUBLIC 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;
workingMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
confirmMenu: Menus.Menu← Menus.CreateMenu[];
blankMenu: Menus.Menu← Menus.CreateMenu[];
startupConfirmMenu: Menus.Menu← Menus.CreateMenu[];
forceQuitMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
maybeQuitMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
scavMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];

mailDBMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
readOnlyDBMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];
nonMailDBMenu: PUBLIC Menus.Menu← Menus.CreateMenu[];

BuildWalnutMenu: PROC =
{ OPEN Menus;

-- menu for personal mail database
 AppendMenuEntry[mailDBMenu, CreateEntry["NewForm", NewMsgProc]];
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "NewMail", NewMailProc]];
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Commit", MyCommitProc]];
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "CloseAll", CloseAllProc]];
 AppendMenuEntry[mailDBMenu, CreateEntry["MessageSetOps", MsgSetOpsProc]];
-- now for the second line
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Expunge", proc: MenuExpunge,
    guarded: TRUE], 1];
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Delete", DeleteMsgSetsProc], 1];
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "SizeOf", SizeOfMsgSetsProc], 1];
 AppendMenuEntry[mailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Print", PrintMsgSetsProc], 1];
 Menus.ChangeNumberOfLines[mailDBMenu, 1];

-- menu for read only database
 AppendMenuEntry[readOnlyDBMenu, CreateEntry["NewForm", NewMsgProc]];
 AppendMenuEntry[readOnlyDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "CloseAll", CloseAllProc]];
 AppendMenuEntry[readOnlyDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "SizeOf", SizeOfMsgSetsProc]];
 AppendMenuEntry[readOnlyDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Print", PrintMsgSetsProc]];

-- menu for nonMail writeable database
 AppendMenuEntry[nonMailDBMenu, CreateEntry["NewForm", NewMsgProc]];
 AppendMenuEntry[nonMailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Commit", MyCommitProc]];
 AppendMenuEntry[nonMailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "CloseAll", CloseAllProc]];
 AppendMenuEntry[nonMailDBMenu, CreateEntry["MessageSetOps", MsgSetOpsProc]];
-- now for the second line
 AppendMenuEntry[nonMailDBMenu,
  WalnutViewer.CreateMenuEntry[q: walnutQueue, name: "Expunge", proc: MenuExpunge,
    guarded: TRUE], 1];
 AppendMenuEntry[nonMailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Delete", DeleteMsgSetsProc], 1];
 AppendMenuEntry[nonMailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "SizeOf", SizeOfMsgSetsProc], 1];
 AppendMenuEntry[nonMailDBMenu,
  WalnutViewer.CreateMenuEntry[walnutQueue, "Print", PrintMsgSetsProc], 1];
 Menus.ChangeNumberOfLines[nonMailDBMenu, 1];

 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]];

 AppendMenuEntry[maybeQuitMenu, CreateEntry["Quit", WWConfirmProc]];
 AppendMenuEntry[maybeQuitMenu, CreateEntry["Retry", WWDenyProc]];

 AppendMenuEntry[scavMenu, CreateEntry["Scavenge", WWConfirmProc]];
 AppendMenuEntry[scavMenu, CreateEntry["Quit", WWDenyProc]];
};

ChangeDBParams: TYPE = REF ChangeDBObject;
ChangeDBObject: TYPE =
MONITORED RECORD[segName, logName: ROPE, logIsAlp, alpNeeded: BOOL, cond: CONDITION];

ChangeDB: PROC[cdb: ChangeDBParams] = { DoWaitCall[ChangeDBProc, cdb] };

ChangeDatabase: PUBLIC ENTRY PROC[ra: REF ANY] =
BEGIN
 cdb: ChangeDBParams← NARROW[ra];
BEGIN ENABLE UNWIND => { NOTIFY cdb.cond };
 walnut.inhibitDestroy← TRUE;
 ChangeWalnutMenu[workingMenu];
 Report["\nClosing database on ", walnutSegmentFile];
 TakeDownWalnutViewers[];
 DestroyAllMsgSetButtons[];
 FlushWQueue[];
 CloseTransactions[TRUE ! DB.Failure, IO.Error, UNWIND => CONTINUE];
 WalnutRetrieve.CloseConnection[];
 lastStateReported← unknown;
 walnutSegmentFile← cdb.segName;
 walnutLogName← cdb.logName;
 logIsAlpineFile← cdb.logIsAlp;
 alpineNeeded← cdb.alpNeeded;
 WalnutDisplayerOps.msgName ← Rope.Concat[walnutSegmentFile, "!Msg!"];
 WalnutDisplayerOps.msgSetName ← Rope.Concat[walnutSegmentFile, "!MsgSet!"];
 []← StartOrRestartWalnut[TRUE];
 Report["Database is now ", walnutSegmentFile];
NOTIFY cdb.cond;
END;
END;

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

MsgSetOpsProc: Menus.MenuProc =
BEGIN
 v: Viewer = NARROW[parent];
 count: NAT = Menus.GetNumberOfLines[v.menu];
 newCount: NAT = IF count = 2 THEN 1 ELSE 2;
 Menus.ChangeNumberOfLines[v.menu, newCount];
 ViewerBLT.ChangeNumberOfLines[v, newCount];
END;

MenuExpunge: Menus.MenuProc =
 { EntryExpunge: ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
  DoExpunge[doUpdates: TRUE, tailRewrite: mouseButton=red];
  FinishCall[];
END;
  EntryExpunge[];
 };

DoExpunge: PUBLIC PROC[doUpdates, tailRewrite: BOOL] =
BEGIN
ok: BOOLTRUE;
tempLogName: ROPE← "Walnut.TempLog";
tempLog: IO.STREAM;

ChangeWalnutMenu[workingMenu];
TakeDownWalnutViewers[];
DestroyAllMsgSetButtons[];
WalnutLog.MarkWalnutTransaction[];
FlushWQueue[];
tempLog← FS.StreamOpen[tempLogName, $create];
Report["Dumping message database to ", tempLogName, "..."];

BEGIN ENABLE ABORTED => {ok← FALSE; CONTINUE};
ok← WalnutLog.ExpungeMsgs
 [tempLog: tempLog, doUpdates: doUpdates, tailRewrite: (tailRewrite AND enableTailRewrite)];
END;

IF ~ok THEN
{ DB.AbortTransaction[
  DB.TransactionOf[segmentName ! DB.Aborted, DB.Failure => CONTINUE]];
 Report["Expunge failed or aborted; changes to database have been aborted"];
}
ELSE Report[" Expunge complete"];
ChangeWalnutMenu[walnutMenu];
END;

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

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

AutoNewMailProc: PROC =
BEGIN ENABLE UNWIND => tryForNewMail← TRUE;
 DoWaitCall[RetrieveNewMailProc];
 tryForNewMail← TRUE;
END;

RetrieveNewMail: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
IF doingCheckpoint THEN WAIT rollbackFinished;
 InternalNewMail[];
END;

InternalNewMail: PUBLIC PROC =
BEGIN ENABLE UNWIND => tryForNewMail← TRUE;
 thisUser: ROPE← UserCredentials.Get[].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"];
  FlushWQueue[];
  WQueue.QueueClientAction[walnutQueue, QuitWalnut, r];
  }
ELSE
  { tryForNewMail← FALSE;
  DoNewMail[];
  tryForNewMail← TRUE
  };
END;

CloseAllProc: Menus.MenuProc =
BEGIN
 msgSetList, msgList, queryList: LIST OF Viewer;
 [msgSetList, msgList, queryList]← EnumWalnutViewers[TRUE];
FOR vL: LIST OF Viewer← msgList, vL.rest UNTIL vL=NIL DO
  ViewerOps.DestroyViewer[vL.first]; ENDLOOP;
FOR vL: LIST OF Viewer← queryList, vL.rest UNTIL vL=NIL DO
  ViewerOps.CloseViewer[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;

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;

-- **********************************************************************
-- can't be an entry proc
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[IF walnut#NIL THEN walnut.newVersion ELSE FALSE];
};

-- **********************************************************************
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[IO.CR];
};

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

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

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", IO.time[]]] ELSE status];
IF newState = notEmpty AND autoNewMail AND tryForNewMail THEN
  { tryForNewMail← FALSE;
TRUSTED {Process.Detach[FORK AutoNewMailProc]};
  };
END;

SetAutoNewMailVars: ENTRY UserProfile.ProfileChangedProc = CHECKED
BEGIN ENABLE UNWIND => NULL;
 autoNewMail← UserProfile.Boolean[key: "Walnut.AutoNewMail", default: FALSE];
 tryForNewMail← autoNewMail;
END;

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

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

Commander.Register["Walnut", BuildWalnut, "For retrieving and sending mail"];
Commander.Register["WalnutScavenge", WalnutScavenge, "Rebuilds Mail database from log file"];
Commander.Register["WalnutOldMailReader",
 WalnutReadMailFile, "Reads Laurel, Hardy or Archive files into Mail database"];
Commander.Register["WalnutExpunge", WalnutExpunge, "Expunges deleted messages"];

Commander.Register["WalnutDump", WalnutDump, "Writes an expunged log file on Walnut.TempLog; does not change database"];

Commander.Register["WalnutNewMail", WalnutNewMail, "Retrieves new mail"];

UserProfile.CallWhenProfileChanges[SetAutoNewMailVars];

END.