WalnutCompactImpl.mesa
Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved.
Swinehart, October 25, 1991 4:22 pm PDT
Notes:
Writes a new Walnut log file as Walnut.LogC, from an open, allegedly-consistent WallTapestry database. The new log contains only message set creation entries for current message sets, and bodies of messages that are still in the database and not deleted. The user is expected to follow a procedure that has yet to be designed to reassign logs and scavenge a new index set into existence.
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 ANYNIL, msg: ROPENIL]
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 ANYNIL, msg: ROPENIL]
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.'"];
}.