<> <> <> <> <> DIRECTORY BasicTime USING [ earliestGMT, GMT, Period, nullGMT ], Commander USING [CommandProc, Handle, Register], CommanderOps USING [NextArgument], Convert USING [ Error, TimeFromRope ], FS USING [StreamOpen], IO, LoganBerry USING [ Cursor, Entry, GenerateEntries, NextEntry ], LoganBerryEntry USING [ GetAttr ], RuntimeError USING [ UNCAUGHT ], Rope USING [Concat, Equal, Fetch, Find, Length, ROPE, Substr], ViewerTools USING [ TiogaContents ], WalnutKernelDefs USING [ LogEntry, LogEntryObject ], WalnutDB USING [ GetCategories ], WalnutOps USING [EnumerateMsgSets, GetHandleForRootfile, GetMsg, GetHasBeenRead, MsgSet, WalnutOpsHandle ], WalnutStream USING [ WriteEntry, WriteMsgBody ] ; WalnutCompactImpl: CEDAR PROGRAM IMPORTS BasicTime, Commander, CommanderOps, Convert, FS, IO, LoganBerry, LoganBerryEntry, Rope, RuntimeError, WalnutDB, WalnutOps, WalnutStream ~ { OPEN IO; ROPE: TYPE = Rope.ROPE; Handle: TYPE ~ WalnutOps.WalnutOpsHandle; LogEntry: TYPE ~ WalnutKernelDefs.LogEntry; LogEntryObject: TYPE ~ WalnutKernelDefs.LogEntryObject; rootFileName: ROPE; newLogFileName: ROPE; doReportID: BOOL ¬ FALSE; earliest: BasicTime.GMT ¬ BasicTime.nullGMT; latest: BasicTime.GMT ¬ BasicTime.nullGMT; WalnutCompactCommand: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> ENABLE RuntimeError.UNCAUGHT => GOTO Failed; rootFileName ¬ CommanderOps.NextArgument[cmd]; newLogFileName ¬ CommanderOps.NextArgument[cmd]; earliest ¬ GetTimeRange[cmd]; latest ¬ GetTimeRange[cmd]; [result, msg] ¬ DoCompact[cmd]; EXITS Failed => RETURN[$Failed, "*** Uncaught signal ***"] }; DoCompact: PROC[cmd: Commander.Handle] RETURNS[result: REF ANY ¬ NIL, msg: ROPE ¬ NIL]~ { logStream: IO.STREAM ¬ FS.StreamOpen[newLogFileName, $create]; handle: WalnutOps.WalnutOpsHandle ¬ WalnutOps.GetHandleForRootfile[rootFileName]; numRead, numWritten: INT¬0; IF handle=NIL THEN RETURN[$Failed, "No such Walnut database"]; []¬WalnutStream.WriteEntry[logStream, NEW[LogFileInfo LogEntryObject ¬ [LogFileInfo[key: "Swinehart's mail database", internalFileID: 100001, logSeqNo: 1]]]]; { msgSetEntry: REF CreateMsgSet LogEntryObject ¬ NEW[CreateMsgSet LogEntryObject ¬ [CreateMsgSet[NIL]]]; WriteMsgSets: PROC[msgSet: WalnutOps.MsgSet] RETURNS [continue: BOOL¬TRUE] ~ { msgSetEntry.msgSet ¬ msgSet.name; []¬WalnutStream.WriteEntry[logStream, msgSetEntry]; }; [] ¬ WalnutOps.EnumerateMsgSets[handle, TRUE, WriteMsgSets]; }; { createEntry: REF CreateMsg LogEntryObject ¬ NEW[CreateMsg LogEntryObject ¬ [CreateMsg[show: FALSE]]]; addEntry: REF AddMsg LogEntryObject ¬ NEW[AddMsg LogEntryObject ¬ [AddMsg[]]]; moveEntry: REF MoveMsg LogEntryObject ¬ NEW[MoveMsg LogEntryObject ¬ [MoveMsg[]]]; beenReadEntry: REF HasBeenRead LogEntryObject ¬ NEW[HasBeenRead LogEntryObject ¬ [HasBeenRead[]]]; enum: LoganBerry.Cursor ¬ LoganBerry.GenerateEntries[db: handle.db, key: $Key, start: "Msg-", end: "Msg."]; DO deleted: BOOL¬FALSE; outOfRange: BOOL¬FALSE; entryTime: BasicTime.GMT; msgID: ROPE; { msList: LIST OF ROPE; contents: ViewerTools.TiogaContents; last: INT; char0: CHAR; isActive: BOOL ¬ FALSE; -- Will be TRUE if message is in Active message set entry: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: enum]; msgID ¬ LoganBerryEntry.GetAttr[entry, $MsgID]; IF msgID=NIL THEN EXIT; msList ¬ WalnutDB.GetCategories[handle, msgID]; numRead ¬ numRead+1; IF msList#NIL AND msList.rest=NIL AND msList.first.Equal["deleted", FALSE] THEN { deleted ¬ TRUE; GOTO Report; }; IF earliest # BasicTime.nullGMT THEN { entryTime ¬ TimeFromMsgID[msgID]; <> IF entryTime=BasicTime.nullGMT THEN ERROR; IF BasicTime.Period[earliest, entryTime]<0 OR (latest#BasicTime.nullGMT AND BasicTime.Period[entryTime, latest]<=0) THEN { outOfRange ¬ TRUE; GOTO Report; }; }; contents ¬ WalnutOps.GetMsg[handle, msgID].contents; createEntry.msg ¬ msgID; last ¬ contents.contents.Length[] - 1; char0 ¬ IF last>=0 THEN contents.contents.Fetch[0] ELSE '~; IF ( contents.formatting.Length[] # 0 ) AND ( char0 = '\r OR char0 = '\l ) THEN { IF contents.contents.Fetch[last] = '\000 THEN -- NUL for padding { contents.contents ¬ Rope.Substr[contents.contents, 1, last-1]; contents.formatting ¬ Rope.Concat["\000", contents.formatting] } ELSE IF char0 = '\r OR char0 = '\l THEN contents.contents ¬ Rope.Substr[contents.contents, 1]; }; createEntry.textLen ¬ contents.contents.Length[]; createEntry.formatLen ¬ contents.formatting.Length[]; []¬WalnutStream.WriteEntry[logStream, createEntry]; []¬WalnutStream.WriteMsgBody[logStream, contents]; IF WalnutOps.GetHasBeenRead[handle, msgID] THEN { beenReadEntry.msg ¬ msgID; []¬WalnutStream.WriteEntry[logStream, beenReadEntry]; }; FOR mL: LIST OF ROPE ¬ msList, mL.rest WHILE mL#NIL DO SELECT TRUE FROM mL.first.Equal["active", FALSE] => { isActive ¬ TRUE; LOOP; }; mL.first.Equal["deleted", FALSE] => LOOP; -- should not happen, but harmless mL.rest=NIL AND ~isActive => { moveEntry.from ¬ "Active"; moveEntry.to ¬ mL.first; moveEntry.msg ¬ msgID; []¬WalnutStream.WriteEntry[logStream, moveEntry]; }; ENDCASE => { addEntry.to ¬ mL.first; addEntry.msg ¬ msgID; []¬WalnutStream.WriteEntry[logStream, addEntry]; }; ENDLOOP; numWritten ¬ numWritten+1; GO TO Report; EXITS Report => { IF doReportID THEN cmd.out.PutF["%g%g\n", rope[msgID], rope[IF deleted THEN " (XXX)" ELSE IF outOfRange THEN " (<<>>)" ELSE ""]] ELSE IF numRead MOD 10=0 THEN cmd.out.PutRope["! "] ELSE cmd.out.PutChar[IF deleted THEN 'x ELSE IF outOfRange THEN '~ ELSE '.]; }; }; ENDLOOP; }; logStream.Close[]; cmd.err.PutF["%g messages read, %g written.\n", int[numRead], int[numWritten]]; }; WCDetailsCommand: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> arg: Rope.ROPE ¬ CommanderOps.NextArgument[cmd]; SELECT TRUE FROM arg.Equal["on", FALSE] => doReportID ¬ TRUE; arg.Equal["off", FALSE] => doReportID ¬ FALSE; ENDCASE; RETURN[NIL, IF doReportID THEN "(verbose)" ELSE "(terse)"]; }; GetTimeRange: PROC[cmd: Commander.Handle] RETURNS [gmt: BasicTime.GMT¬BasicTime.nullGMT] ~ { timeRope: ROPE ¬ CommanderOps.NextArgument[cmd]; IF timeRope=NIL OR timeRope.Equal["."] THEN RETURN; gmt ¬ Convert.TimeFromRope[timeRope]; }; TimeFromMsgID: PROC[msgID: ROPE] RETURNS [time: BasicTime.GMT¬BasicTime.nullGMT] ~ { index: INT ¬ Rope.Find[msgID, "@", 0]; IF index=-1 THEN RETURN[BasicTime.earliestGMT]; time ¬ Convert.TimeFromRope[Rope.Substr[msgID, index+1]! Convert.Error => { time ¬ BasicTime.earliestGMT; CONTINUE;}]; }; Commander.Register["WalnutCompact", WalnutCompactCommand, "WalnutCompact \nCreate most compact Walnut log possible from current, consistent database."]; Commander.Register["WCDetails", WCDetailsCommand, "WCDetails on|off\n (Default is off) Determines whether MsgIDs are logged during the WalnutCompact operation.\nWith no parameter, indicates the current state as 'terse' or 'verbose.'"]; }.