<> <> <> <> <> <<1. Think about replacing some of the notorious global variables with something more reasonable.>> <<2. Next requirement is for the most minimal form of editor. In some sense, the existing Rope commands with some aka's should do it.>> <<3. Find out how to quote things in the command tool.>> <<4. New syntax for commands:>> <> << :=>> << | . | it | them>> <> <> < (from is default if nothing specified)>> <>> <> <>> < .. ]>> <, , ...., ]>> < | unread | read>> <> <> <> < -- lists message sets for the named dbs, default default >> <> <| -- sets default value>> < (parameters may be in any order)>> < (Does not refresh cache first! See below.)>> < >> < (parameters may be in any order)>> < (parameters may be in any order)>> < (databases may (should) differ)>> < (WalnutOps says "Deleted" is not a legal destination for Move or copy)>> <> <> <> <> <> DIRECTORY Commander USING [CommandProc, Handle, Register], CommandTool USING [NextArgument], Convert USING [ IntFromRope ], Idle USING [ IsIdle ], IO, List USING [Length], Process USING [ Detach, SecondsToTicks, SetTimeout ], RuntimeError USING [ UNCAUGHT ], Rope USING [Cat, Concat, Equal, Fetch, Find, Length, ROPE, Substr], SymTab USING [ Create, EachPairAction, Fetch, Ref, Pairs, Store ], UserProfile USING [ Token ], ViewerClasses USING [ Menu, MenuEntry, Viewer ], ViewerOps USING [ EnumerateViewers, EnumProc ], ViewerTools USING [ TiogaContents ], WalnutDefs USING [dontCareMsgSetVersion ], WalnutOps USING [AddMsg, CreateMsg, dontCareDomainVersion, GetHandleForRootfile, GetMsg, MoveMsg, MsgExists, MsgSetExists, MsgSetNames, GetDisplayProps, GetMsgText, MsgSetVersion, RemoveMsg, SetHasBeenRead, SizeOfMsgSet, MsgsInSetEnumeration, WalnutOpsHandle ], WalnutNewMail USING [ CheckMailBoxes ], WalnutWindowPrivate USING [ WalnutHandle ] ; WalnutKeyboardImpl: CEDAR MONITOR IMPORTS Commander, CommandTool, Convert, Idle, IO, List, Process, Rope, RuntimeError, SymTab, UserProfile, ViewerOps, WalnutOps, WalnutNewMail SHARES ViewerClasses ~ { OPEN IO; ROPE: TYPE = Rope.ROPE; Handle: TYPE = WalnutOps.WalnutOpsHandle; DB: TYPE = REF DBBody; DBBody: TYPE = RECORD [ dbName: ROPE, dbFileName: ROPE, handle: Handle _ NIL, retryCount: INT _ 0 ]; Entries: TYPE ~ REF EntrySequence; EntrySequence: TYPE ~ RECORD[e: SEQUENCE len: NAT OF EntryRecord]; EntryRecord: TYPE ~ RECORD [ msgID: ROPE, toc: ROPE, tocValid: BOOL, hasBeenRead: BOOL, deleted: BOOL_FALSE]; MsgSet: TYPE = REF MsgSetBody; MsgSetBody: TYPE = RECORD [ db: DB, -- belongs to this database. msgSetName: ROPE, msgSetVersion: WalnutOps.MsgSetVersion, entries: Entries ]; NewFilter: TYPE = {none, read, unread}; dbs: SymTab.Ref; -- Indexed by db short name (e.g., "default", "voice", "old") msgSets: SymTab.Ref; -- Cached indices, indexed by dbname.msgSetName defaultSource: MsgSet; defaultDest: MsgSet; thisSource: MsgSet; thisDest: MsgSet; firstMessageNumber: INT_1; lastMessageNumber: INT_1; thisSourceFilter: ROPE_NIL; thisSourceNewFilter: NewFilter_$none; disableIdleReqt: BOOL_FALSE; checkMinutes: INT _ 10; ListDBsCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; ListOneDB: SymTab.EachPairAction = { <<[key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE]>> db: DB _ NARROW[val]; cmd.out.PutF[" %g: %g\n", rope[db.dbName], rope[db.dbFileName]]; }; []_SymTab.Pairs[dbs, ListOneDB]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; ListMsgSetsCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; msgSetNames: LIST OF ROPE; [result, msg] _ ParseArguments[cmd: cmd, dbOnly: TRUE]; IF result = $Failed THEN RETURN; msgSetNames _ WalnutOps.MsgSetNames[thisSource.db.handle].mL; cmd.out.PutF["Message sets from database %g\n", rope[thisSource.db.dbName]]; FOR mN: LIST OF ROPE _ msgSetNames, mN.rest WHILE mN#NIL DO cmd.out.PutF[" %g\n", rope[mN.first]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; SelectMsgSetCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; [result, msg] _ ParseArguments[cmd]; IF result = $Failed THEN RETURN; defaultSource _ thisSource; defaultDest _ thisDest; cmd.out.PutF["Selected message sets: Source = %g, Dest = %g\n", rope[FullName[defaultSource]], rope[FullName[defaultDest]]]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; ListMsgsInMsgSetCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; handle: Handle; refresh: BOOL _ cmd.procData.clientData#$Again; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: refresh, fullRange: TRUE]; cmd.out.PutF["Messages in message set %g:\n", rope[FullName[thisSource]]]; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO hasBeenRead, deleted: BOOL; tocEntry: ROPE; [hasBeenRead, deleted, tocEntry] _ GetTocEntry[thisSource, i]; IF FilterOut[i] THEN LOOP; cmd.out.PutF["%g %3d %g%g\n", char[IF hasBeenRead THEN '\040 ELSE '?], int[i], rope[tocEntry], rope[IF deleted THEN " (deleted)" ELSE ""]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; PrintMsgInMsgSetCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; contents: REF TEXT; handle: Handle; cr: ROPE; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: TRUE]; IF result = $Failed THEN RETURN; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO hasBeenRead, deleted: BOOL; tocEntry, msgID: ROPE; [hasBeenRead, deleted, tocEntry] _ GetTocEntry[thisSource, i]; IF FilterOut[i] THEN LOOP; msgID _ thisSource.entries[i-1].msgID; contents _ WalnutOps.GetMsgText[handle, msgID, contents]; TRUSTED { cr _ LOOPHOLE[contents]; }; cmd.out.PutF["-- Message %g:%g\n%g\n\n", int[i], rope[IF deleted THEN " (deleted)" ELSE ""], rope[cr]]; IF NOT thisSource.entries[i-1].hasBeenRead THEN { WalnutOps.SetHasBeenRead[handle, msgID]; thisSource.entries[i-1].hasBeenRead _ TRUE; }; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; MoveMsgCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; handle: Handle; op: ROPE _ "Moved "; destExists: BOOL; move: BOOL _ cmd.procData.clientData = $Move; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: FALSE]; IF result = $Failed THEN RETURN; destExists _ WalnutOps.MsgSetExists[opsHandle: handle, name: thisDest.msgSetName, msDomainVersion: WalnutOps.dontCareDomainVersion].exists; IF ~destExists THEN RETURN[$Failed, "No destination message set"]; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO IF FilterOut[i] THEN LOOP; IF thisSource.entries[i-1].deleted THEN { cmd.out.PutF["** Can't move message %g from set %g (deleted)\n", int[i], rope[FullName[thisSource]]]; LOOP; }; IF move THEN { thisSource.entries[i-1].deleted _ TRUE; []_WalnutOps.MoveMsg[handle, thisSource.entries[i-1].msgID, [thisSource.msgSetName], [thisDest.msgSetName]]; } ELSE { op _ "Copied "; []_WalnutOps.AddMsg[handle, thisSource.entries[i-1].msgID, [thisSource.msgSetName], [thisDest.msgSetName]]; }; cmd.out.PutF["%gmessage %g from set %g to %g\n", rope[op], int [i], rope[FullName[thisSource]], rope[FullName[thisDest]]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; XferMsgCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; sourceHandle, destHandle: Handle; destExists: BOOL; [result, msg, sourceHandle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: FALSE]; IF result = $Failed THEN RETURN; destHandle _ thisDest.db.handle; destExists _ WalnutOps.MsgSetExists[opsHandle: destHandle, name: thisDest.msgSetName, msDomainVersion: WalnutOps.dontCareDomainVersion].exists; IF ~destExists THEN RETURN[$Failed, "No destination message set"]; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO pos: INT; msgID, msgName: ROPE; tc: ViewerTools.TiogaContents; IF FilterOut[i] THEN LOOP; IF thisSource.entries[i-1].deleted THEN { cmd.out.PutF["** Can't transfer message %g from set %g (deleted)\n", int[i], rope[FullName[thisSource]]]; LOOP; }; msgID _ msgName _ thisSource.entries[i-1].msgID; pos _ Rope.Find[msgID, "$"]; IF pos # 0 THEN msgName _ msgID.Substr[pos]; tc _ WalnutOps.GetMsg[sourceHandle, msgID].contents; IF NOT WalnutOps.MsgExists[destHandle, msgName] THEN { WalnutOps.CreateMsg[destHandle, msgID, tc]; WalnutOps.SetHasBeenRead[destHandle, msgName]; }; IF NOT thisDest.msgSetName.Equal["active", FALSE] THEN []_WalnutOps.MoveMsg[destHandle, msgID, ["active"], [thisDest.msgSetName]]; cmd.out.PutF["Transferred message %g from set %g to %g\n", int [i], rope[FullName[thisSource]], rope[FullName[thisDest]]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; DeleteMsgCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; handle: Handle; [result, msg, handle] _ ParseRange[cmd: cmd, refresh: FALSE, fullRange: FALSE]; IF result = $Failed THEN RETURN; FOR i: INT IN [firstMessageNumber..MIN[lastMessageNumber, thisSource.entries.len]] DO IF FilterOut[i] THEN LOOP; IF thisSource.entries[i-1].deleted THEN { cmd.out.PutF["** Can't delete message %g from set %g (already deleted)\n", int[i], rope[FullName[thisSource]]]; LOOP; }; thisSource.entries[i-1].deleted _ TRUE; []_WalnutOps.RemoveMsg[handle, thisSource.entries[i-1].msgID, [thisSource.msgSetName], WalnutDefs.dontCareMsgSetVersion]; cmd.out.PutF["Deleted message %g from set %g\n", int [i], rope[FullName[thisSource]]]; ENDLOOP; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; GetNewMailCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> SELECT Check[] FROM $running => NULL; $retried => RETURN[$Failed, "** Walnut had to be restarted; wait a few seconds and try again **"]; $failed => RETURN[$Failed, "** Something broke during attempt to restart or retrieve mail. **"]; $quit => RETURN[$Failed, "** Walnut was demanding to be stopped, so it was. **"]; $timedout => RETURN[$Failed, "** Walnut cannot be restarted, apparently. **"]; $noNewMail => RETURN[$Failed, "** No Walnut instance can read mail. **"]; ENDCASE => ERROR; }; Checker: PROC = TRUSTED { DO IF disableIdleReqt OR Idle.IsIdle[] THEN [] _ Check[]; Wait[]; ENDLOOP; }; Wait: ENTRY PROC = TRUSTED { ENABLE UNWIND => NULL; checker: CONDITION; Process.SetTimeout[@checker, Process.SecondsToTicks[60*checkMinutes]]; WAIT checker; }; Check: ENTRY PROC RETURNS[whatHappened: WhatHappened_$failed] = TRUSTED { ENABLE { RuntimeError.UNCAUGHT => GOTO Failed; UNWIND => NULL; }; db: DB; [whatHappened, db] _ CheckAndRetry[]; IF whatHappened = $running THEN WalnutNewMail.CheckMailBoxes[db.handle]; EXITS Failed => NULL; }; WhatHappened: TYPE = { running, retried, timedout, failed, quit, noNewMail }; <> <> <> <> <> maxRetryCount: INT _ 72; -- 12 hours without success and we quit; should we? CheckAndRetry: INTERNAL PROC RETURNS [whatHappened: WhatHappened_$noNewMail, db: DB_NIL] = { CheckOne: ViewerOps.EnumProc ~ { menu: ViewerClasses.Menu _ v.menu; menuLine: ViewerClasses.MenuEntry; quitEntry: ViewerClasses.MenuEntry; retryEntry: ViewerClasses.MenuEntry; db1: DB; waHoppen: WhatHappened _ $running; IF menu = NIL THEN RETURN; menuLine _ menu.lines[0]; IF menuLine = NIL THEN RETURN; db1 _ GetDBFromWalnutHandle[menuLine.clientData]; IF db1=NIL THEN RETURN; quitEntry _ FindEntry[menuLine, "Quit"]; retryEntry _ FindEntry[menuLine, "Retry"]; IF quitEntry=NIL THEN db1.retryCount _ 0 ELSE IF retryEntry#NIL THEN waHoppen _ $retried; IF db1.retryCount > maxRetryCount THEN { retryEntry _ quitEntry; waHoppen _ $timedout; }; IF retryEntry#NIL THEN { db1.retryCount _ db1.retryCount + 1; retryEntry.proc[parent: v, clientData: retryEntry.clientData]; }; IF FindEntry[menuLine, "NewMail"]#NIL THEN { db _ db1; whatHappened _ waHoppen; }; }; ViewerOps.EnumerateViewers[CheckOne]; }; FindEntry: PROC[menuLine: ViewerClasses.MenuEntry, menuLabel: ROPE] RETURNS [entry: ViewerClasses.MenuEntry] ~ { FOR entry _ menuLine, entry.link WHILE entry#NIL DO IF entry.name.Equal[menuLabel, FALSE] THEN RETURN; ENDLOOP; }; GetDBFromWalnutHandle: PROC[wH: REF] RETURNS[ db: DB_NIL ] ~ { handle: Handle; IsItTheOne: SymTab.EachPairAction = { <<[key: ROPE, val: SymTab.Val] RETURNS [quit: BOOL _ FALSE]>> oneDB: DB _ NARROW[val]; IF handle.rootName.Equal[oneDB.dbFileName, FALSE] THEN { []_SetHandle[oneDB]; db _ oneDB; RETURN[quit:TRUE]; }; }; WITH wH SELECT FROM wwh: WalnutWindowPrivate.WalnutHandle => handle _ wwh.opsH; ENDCASE => RETURN; []_SymTab.Pairs[dbs, IsItTheOne]; }; GetDBFromName: PROC[dbName: ROPE] RETURNS[ db: DB_NIL] ~ { db _ NARROW[dbs.Fetch[dbName].val]; }; CacheMsgSet: PROC[cmd: Commander.Handle, msgSet: MsgSet, refresh: BOOL_FALSE] RETURNS[handle: Handle] = { ListLength: PROC [l: LIST OF ROPE] RETURNS [i: INT] ~ TRUSTED { RETURN [List.Length[LOOPHOLE[l]]]; }; numMsgs: INT; i: INT; version1, version2: WalnutOps.MsgSetVersion; msgs: LIST OF ROPE; msgSetName: ROPE _ msgSet.msgSetName; entries: Entries _ msgSet.entries; handle _ SetHandle[msgSet.db]; IF (NOT refresh) AND entries#NIL THEN RETURN; -- have it already [numMsgs, version1] _ WalnutOps.SizeOfMsgSet[handle, msgSetName]; IF entries#NIL AND version1 = msgSet.msgSetVersion THEN RETURN; DO [numMsgs, version1] _ WalnutOps.SizeOfMsgSet[handle, msgSetName]; [msgs, version2] _ WalnutOps.MsgsInSetEnumeration[handle, msgSetName]; IF version2=version1 THEN EXIT; ENDLOOP; IF numMsgs # (i _ ListLength[msgs]) THEN { cmd.out.PutF["*** Curious that Walnut says %g messages but provided %g TOC entries\n", int[numMsgs], int[i]]; numMsgs _ i; }; msgSet.msgSetVersion _ version2; entries _ msgSet.entries _ NEW[EntrySequence[numMsgs]]; i_-1; FOR ml: LIST OF ROPE _ msgs, ml.rest WHILE ml#NIL DO entries[i_i+1].msgID _ ml.first; ENDLOOP; cmd.out.PutF["Message set %g contains %g messages\n", rope[FullName[msgSet]], int[i+1]]; }; FilterOut: PROC[msgNo: INT] RETURNS[BOOL] = {-- TRUE IFF entry does NOT match filter tocEntry: ROPE; read: BOOL; [read,,tocEntry] _ GetTocEntry[thisSource, msgNo]; RETURN[ (SELECT thisSourceNewFilter FROM $read => NOT read, $unread => read, ENDCASE => FALSE) OR (thisSourceFilter#NIL AND Rope.Find[s1: tocEntry, s2: thisSourceFilter, case: FALSE]<0)]; }; ParseRange: PROC [ cmd: Commander.Handle, refresh: BOOL_FALSE, fullRange: BOOL_FALSE] RETURNS [result: REF ANY_NIL, msg: ROPE_NIL, handle: Handle_NIL] ~ { [result, msg] _ ParseArguments[cmd: cmd]; IF result = $Failed THEN RETURN; handle _ CacheMsgSet[cmd, thisSource, refresh]; IF thisSourceFilter#NIL OR thisSourceNewFilter # $none THEN fullRange _ TRUE; IF fullRange THEN [result, msg] _ ParseArguments[cmd: cmd, rangeDefaults: $all]; IF result = $Failed THEN RETURN; IF firstMessageNumber = lastMessageNumber AND lastMessageNumber = -1 THEN RETURN[$Failed, "No message range was specified"]; IF firstMessageNumber > thisSource.entries.len THEN RETURN[$Failed, "There aren't that many"]; IF firstMessageNumber <= 0 THEN RETURN[$Failed, "There aren't that few"]; }; ParseArguments: PROC[ cmd: Commander.Handle, rangeDefaults: {none, all}_$none, dbOnly: BOOL_FALSE, sameDB: BOOL _ FALSE] <> <> RETURNS[result: REF ANY _ NIL, msg: ROPE _ NIL] = { <> <> parsing: BOOL _ FALSE; { ENABLE IO.EndOfStream => IF parsing THEN GOTO Failed ELSE CONTINUE; s: IO.STREAM _ IO.RIS[cmd.commandLine]; arg: ROPE_NIL; quoted: BOOL; GetToken: PROC RETURNS[token: ROPE, quoted: BOOL_FALSE] = { ln: INT; token _ IO.GetCedarTokenRope[s].token; ln _ token.Length[]; IF ln<2 OR token.Fetch[0]#'" THEN RETURN; quoted _ TRUE; token _ token.Substr[start: 1, len: ln-2]; }; ParseMsgSet: PROC[msgSetName: ROPE_NIL] RETURNS[msgSet: MsgSet] ~ { dbName: ROPE _ "default"; parsing _ TRUE; IF msgSetName=NIL THEN msgSetName _ GetToken[].token; IF s.PeekChar[]='. OR dbOnly THEN { dbName _ msgSetName; msgSetName _ NIL; IF dbOnly THEN msgSetName _ "Active" ELSE DO [] _ s.GetChar[]; msgSetName _ msgSetName.Concat[GetToken[].token]; IF s.PeekChar[]#'. THEN EXIT; msgSetName _ msgSetName.Concat["."]; ENDLOOP; }; [result, msg, msgSet] _ GetMsgSet[dbName, msgSetName]; parsing _ FALSE; }; <> lastSource, lastDest: MsgSet; lastSourceFilter: ROPE; lastSourceNewFilter: NewFilter; lastFirstMessageNumber, lastLastMessageNumber: INT; lastSource _ thisSource; thisSource _ defaultSource; lastDest _ thisDest; thisDest _ defaultDest; lastSourceFilter _ thisSourceFilter; thisSourceFilter _ NIL; lastSourceNewFilter _ thisSourceNewFilter; thisSourceNewFilter _ $none; lastFirstMessageNumber_firstMessageNumber; lastLastMessageNumber_lastMessageNumber; firstMessageNumber _ lastMessageNumber _ -1; WHILE result=NIL AND ([arg, quoted]_GetToken[]).token # NIL DO c: CHAR _ IF arg.Length[]>=1 THEN arg.Fetch[0] ELSE '\000; SELECT TRUE FROM NOT quoted AND arg.Equal["unread", FALSE] => thisSourceNewFilter _ $unread; NOT quoted AND arg.Equal["read", FALSE] => thisSourceNewFilter _ $read; NOT quoted AND arg.Equal["from", FALSE] => thisSource _ ParseMsgSet[]; NOT quoted AND arg.Equal["to", FALSE] => thisDest _ ParseMsgSet[]; NOT quoted AND arg.Equal["with", FALSE] => { parsing _ TRUE; thisSourceFilter _ IO.GetRope[s]; IF thisSourceFilter.Length[]>1 THEN thisSourceFilter _ thisSourceFilter.Substr[start: 1, len: thisSourceFilter.Length[]-2]; parsing _ FALSE; }; NOT quoted AND (arg.Equal["it", FALSE] OR arg.Equal["them", FALSE]) => { thisSource _ lastSource; thisDest _ lastDest; thisSourceFilter _ lastSourceFilter; thisSourceNewFilter _ lastSourceNewFilter; firstMessageNumber _ lastFirstMessageNumber; -- [sic] lastMessageNumber _ lastLastMessageNumber; -- [mea culpa] }; c = '[ => { parsing _ TRUE; arg _ GetToken[].token; IF ~ (arg.Fetch[0] IN ['0..'9]) THEN RETURN[$Failed, "Non-number in message range"]; firstMessageNumber _ Convert.IntFromRope[arg]; arg _ GetToken[].token; IF ~arg.Equal[".."] THEN RETURN[$Failed, "Missing ellipsis in message range"]; arg _ GetToken[].token; IF ~ (arg.Fetch[0] IN ['0..'9]) THEN RETURN[$Failed, "Non-number in message range"]; lastMessageNumber _ Convert.IntFromRope[arg]; arg _ GetToken[].token; IF ~arg.Equal["]"] THEN RETURN[$Failed, "Message range not properly terminated"]; parsing _ FALSE; }; c IN ['0..'9] => { firstMessageNumber _ lastMessageNumber _ Convert.IntFromRope[arg]; }; c IN ['a..'z], c IN ['A..'Z] => thisSource _ ParseMsgSet[arg]; ENDCASE => RETURN[$Failed, "Unrecognized input"]; ENDLOOP; EXITS Failed => RETURN[$Failed, "Error while reading command"]; }; IF rangeDefaults = $all THEN { IF firstMessageNumber = -1 THEN firstMessageNumber _ 1; IF lastMessageNumber = -1 THEN lastMessageNumber _ thisSource.entries.len; }; IF sameDB AND thisSource.db#thisDest.db THEN RETURN[$Failed, "Source and destination message sets must be from same Walnut data base."]; }; GetMsgSet: PROC[dbName, msgSetName: ROPE] RETURNS [result: REF ANY, msg: ROPE, msgSet: MsgSet_NIL] ~ { key: ROPE _ Rope.Cat[dbName, ".", msgSetName]; db: DB; msgSet _ NARROW[msgSets.Fetch[key].val]; IF msgSet#NIL THEN { []_SetHandle[msgSet.db]; RETURN; }; db _ GetDBFromName[dbName]; IF db=NIL THEN { result _ $Failed; msg _ "DB Name not registered; use WalnutKeyboard command to register."; RETURN; }; [] _ SetHandle[db]; msgSet _ NEW[MsgSetBody _ [db, msgSetName]]; [] _ msgSets.Store[key, msgSet]; }; SetHandle: PROC[db: DB] RETURNS[Handle] = { rootFileName: ROPE _ db.dbFileName; RETURN[db.handle _ WalnutOps.GetHandleForRootfile[rootFileName]]; }; GetTocEntry: PROC[msgSet: MsgSet, i: INT] RETURNS[hasBeenRead: BOOL_FALSE, deleted: BOOL_FALSE, toc: ROPE_NIL]~{ entries: Entries _ msgSet.entries; i _ i-1; IF NOT entries[i].tocValid THEN { [hasBeenRead, toc] _ WalnutOps.GetDisplayProps[msgSet.db.handle, entries[i].msgID]; entries[i].tocValid _ TRUE; entries[i].toc _ toc; entries[i].hasBeenRead _ hasBeenRead; } ELSE { toc _ entries[i].toc; hasBeenRead _ entries[i].hasBeenRead; deleted _ entries[i].deleted; }; }; FullName: PROC[msgSet: MsgSet] RETURNS [fullName: ROPE] ~ { RETURN[ IO.PutFR["%g.%g", rope[msgSet.db.dbName], rope[msgSet.msgSetName]]]; }; FlushCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; msgSets _ SymTab.Create[case: FALSE]; [result, msg, defaultSource] _ GetMsgSet["default", "Active"]; IF result=$Failed THEN RETURN; defaultDest _ defaultSource; cmd.out.PutF["Flushed message sets, default source and dest are default.Active\n"]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; WalnutKbdCmd: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> dbName: ROPE; rootFile: ROPE; dbName _ CommandTool.NextArgument[cmd]; rootFile _ CommandTool.NextArgument[cmd]; [result, msg, dbName, rootFile] _ WalnutKbdInit[dbName, rootFile]; IF result=$Failed THEN RETURN; cmd.out.PutF["Registered %g: %g\n", rope[dbName], rope[rootFile]]; }; WalnutKbdInit: PROC[dbNameIn, rootFileIn: ROPE_NIL] RETURNS[result: REF ANY_NIL, msg: ROPE_NIL, dbName: ROPE_NIL, rootFile: ROPE_NIL] = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; db: DB; dbName _ dbNameIn; rootFile _ rootFileIn; IF dbName = NIL THEN { dbName _ "default"; rootFile _ UserProfile.Token["Walnut.WalnutRootFile"]; } ELSE IF rootFile=NIL THEN RETURN[$Failed, "No root file name provided"]; db _ GetDBFromName[dbName]; IF db=NIL THEN { db _ NEW[DBBody_[dbName, NIL]]; []_dbs.Store[dbName, db]; }; db.dbFileName _ rootFile; db.handle _ NIL; IF defaultSource=NIL THEN defaultSource _ defaultDest _ GetMsgSet[dbName, "Active"].msgSet; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; Commander.Register["WKDBs", ListDBsCmd, "List available Walnut data bases"]; Commander.Register["WKSets", ListMsgSetsCmd, "List Walnut message sets"]; Commander.Register["WKSelect", SelectMsgSetCmd, "Select working Walnut message set\n Usage: WKSelect |"]; Commander.Register["WKListTOC", ListMsgsInMsgSetCmd, "List messages in message set, as specified by filters and message range.\n Usage: WKList ", $List]; Commander.Register["WKSameTOC", ListMsgsInMsgSetCmd, "List messages in message set, without consulting Walnut again.\n Usage: WKSame ", $Again]; Commander.Register["WKDisplay", PrintMsgInMsgSetCmd, "Display a message designated by number from a selected message set\n Usage: WKDisplay "]; Commander.Register["WKPoll", GetNewMailCmd, "Poll Mail services for new mail"]; Commander.Register["WKMove", MoveMsgCmd, "Move messages from source message set to destination message set.\n Usage: WKMove ", $Move]; Commander.Register["WKCopy", MoveMsgCmd, "Copy messages from source message set to destination message set.\n Usage: WKCopy ", $Copy]; Commander.Register["WKTransfer", XferMsgCmd, "Transfer messages from source message set in one database to destination message set in another.\n Usage: WKTransfer "]; Commander.Register["WKDelete", DeleteMsgCmd, "Delete messages from source message set.\n Usage: WKDelete "]; Commander.Register["WKFlush", FlushCmd, "Flush all cached message sets. Restore default to default.Active."]; Commander.Register["WalnutKeyboard", WalnutKbdCmd, "Usage: Walnutkeyboard \nAdds mapping from to to list of available Walnut databases\nDefault ='default'; default from user profile"]; dbs _ SymTab.Create[case: FALSE]; msgSets _ SymTab.Create[case: FALSE]; TRUSTED { Process.Detach[FORK Checker[]]; }; []_WalnutKbdInit[NIL, NIL]; }.