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];
Should somehow validate entryTime a bit.
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 <root file name> <new log name>\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.'"];
}.