WalnutOpsArchiveFileImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Willie-Sue, April 2, 1986 6:00:16 pm PST
Donahue, August 5, 1985 2:47:44 pm PDT
Implementation of Reading and Writing Archive files
DIRECTORY
FS USING [Error, ErrorFromStream],
IO,
Rope,
WalnutDB -- using lots -- ,
WalnutDefs USING [Error, VersionMismatch, MsgSet],
WalnutLog -- using lots -- ,
WalnutMiscLog USING [walnutItemFixedLength, CreateReadArchiveLog],
WalnutOps USING [],
WalnutOpsInternal USING [CarefullyApply, CheckInProgress, CheckReport, LongRunningApply, ParseLog],
WalnutOpsMonitorImpl,
WalnutStream USING [Open],
WalnutRoot USING [CommitAndContinue];
WalnutOpsArchiveFileImpl: CEDAR MONITOR LOCKS walnutOpsMonitorImpl
IMPORTS
FS, IO, Rope,
walnutOpsMonitorImpl: WalnutOpsMonitorImpl,
WalnutDB, WalnutDefs, WalnutLog, WalnutMiscLog,
WalnutOpsInternal, WalnutStream, WalnutRoot
EXPORTS WalnutOps
SHARES WalnutOpsMonitorImpl
= BEGIN OPEN WalnutOpsInternal;
Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Procedures
These procedures are some of the primitive atomic actions out of which Walnut is built. More complex operations will be found in WalnutClientOps (someday).
Producing and reading archive files
Reading an archive file is a long operation; it is resilient, so that it will complete when restarted. The file is first copied to the current log (if the archive file exists) and then the tail of the log is "replayed", which may involve creating new messages and shuffling them around in the database.
ReadArchiveFile: PUBLIC ENTRY PROC[file: ROPE, msgSet: WalnutDefs.MsgSet ← [NIL, -1]]
  RETURNS[numNew: INT] = {
Write a "readArchiveFile" log entry; if the archiveFile exists, parses it and writes the appropriate entries (new msgs and moves if msgSet is not "Active" (NIL defaults to categories specified in the file). Then replays the log. If the file couldn't be read, numNew = -1;
ENABLE UNWIND => NULL;
ok: BOOLFALSE;
fStream: STREAM;
reason: ROPE;
Raf: PROC = {
at: INT;
IF msgSet.name # NIL THEN [] ← WalnutDB.VerifyMsgSet[msgSet];
at ← WalnutLog.StartReadArchiveFile[file, msgSet.name].at;
WalnutDB.SetReadArchivePos[at];
};
Raf2: PROC = {
[] ← WalnutLog.EndReadArchiveFile[];
WalnutDB.SetReadArchivePos[0];
};
Caf: PROC[inProgress: BOOL] = {
fromPos: INT ← 0;
IF ~inProgress THEN {
at: INT;
ok ← WalnutLog.PrepareToCopyTempLog[ which: readArchive, pagesAlreadyCopied: 0, reportProc: WalnutOpsInternal.CheckReport];
IF ~ok THEN RETURN;
at ← WalnutLog.StartCopyReadArchive[].at;
WalnutDB.SetCopyReadArchivePos[at];
WalnutDB.SetOpInProgressPos[at];
WalnutRoot.CommitAndContinue[];
}
ELSE {  -- calculate fromPos
logLen: INT ← WalnutLog.LogLength[];
startedCopyAt, startCopyPos: INT;
startCopyPos ← WalnutDB.GetCopyReadArchivePos[];
IF WalnutLog.SetPosition[startCopyPos] # 0 THEN
ERROR WalnutDefs.Error[$log, $BadLog, IO.PutFR["no entry at %g",
IO.int[startCopyPos]]];
[] ← WalnutLog.NextEntry[];   -- skip the copy entry
startedCopyAt ← WalnutLog.NextAt[];
fromPos ← logLen - startedCopyAt;
};
CheckReport[
IO.PutFR["\nCopying the ReadArchiveTempLog, starting at bytePos %g\n",
IO.int[fromPos]]];
WalnutLog.CopyTempLog[
readArchive, WalnutDB.GetCopyReadArchivePos[], fromPos, CheckReport];
CheckReport["\n"];
WalnutDB.SetParseLogInProgress[TRUE];
WalnutDB.SetParseLogPos[WalnutDB.GetOpInProgressPos[]];
WalnutDB.SetOpInProgressPos[-1];
};
WalnutOpsInternal.CheckInProgress[];
fStream ← WalnutStream.Open[name: file, readOnly: TRUE ! FS.Error =>
{ CheckReport[error.explanation]; fStream ← NIL; CONTINUE} ].strm;
IF fStream = NIL THEN RETURN[-1];
WalnutOpsInternal.CarefullyApply[proc: Raf, didUpdate: TRUE];
BEGIN ENABLE BEGIN
FS.Error => { reason ← error.explanation; GOTO exit };
IO.Error => {
reason ← FS.ErrorFromStream[stream].explanation;
IF reason = NIL THEN reason ← "IO Error creating readArchiveLog";
GOTO exit
};
END;
[ok, reason] ←
WalnutMiscLog.CreateReadArchiveLog[fStream, msgSet.name, CheckReport];
EXITS
exit => ok ← FALSE;
END;
fStream.Close[ ! IO.Error, FS.Error => CONTINUE];
IF ~ok THEN {
CheckReport[IO.PutFR[" Archive Read of %g failed", IO.rope[file]]];
IF reason # NIL THEN CheckReport[" Error reported as: ", reason];
RETURN[-1];
}
ELSE Raf2[];
WalnutOpsInternal.LongRunningApply[Caf];
IF ~ok THEN {
CheckReport[IO.PutFR[" Out of space trying to copy readArchiveLog for file %g",
IO.rope[file]]];
RETURN[-1];
};
CheckReport["\nAdding messages to database\n"];
numNew ← WalnutOpsInternal.ParseLog[TRUE];
};
WriteArchiveFile: PUBLIC ENTRY PROC[
file: ROPE, msgSetList: LIST OF WalnutDefs.MsgSet, append: BOOL] RETURNS[ok: BOOL]= {
Write an archive file that contains the messages from the given message sets. No log entry is written and no updates are made to the database (we just hold the monitor to guarantee that no changes to the message sets occur).
ENABLE UNWIND => NULL;
wStream: STREAM;
someMsgWasTooBig: BOOLFALSE;
walnutItemForm: ROPE = "@%05d 00525 %05d\n";  -- 20 chars, tioga formatting
startHeaderForm: ROPE = "*start*\n%05d %05d US \n";
thisMsgSet, exp: ROPE;
startHeaderFixedLen: INT = 24;
first: BOOLTRUE;
WriteProc: PROC[msg, TOCentry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] = {
textStart, textLen, formatLen, prefixLen: INT;
length, walnutItemLen: INT ← 0;
walnutItem: ROPE;
[textStart, textLen, formatLen, , ] ← WalnutDB.GetMsgText[msg];
walnutItem ← Rope.Cat[msg, "\n", thisMsgSet, "\n"];
walnutItemLen ←
WalnutMiscLog.walnutItemFixedLength + walnutItem.Length[] + formatLen;
IF formatLen # 0 THEN walnutItemLen ← walnutItemLen + 1; -- for extra CR after formatting
prefixLen ← startHeaderFixedLen + walnutItemLen;
length ← prefixLen + textLen + 1;  -- extra CR after text
IF length > 99999 THEN {
CheckReport[IO.PutFR["\nLength of msg %g is too big (%g bytes) - skipping",
IO.rope[msg], IO.int[length]] ];
someMsgWasTooBig ← TRUE;
RETURN
};
-- the -2 below are because the bytecount within the prefix item does not include the surrounding @'s
wStream.PutRope[
IO.PutFR[startHeaderForm, IO.int[length], IO.int[prefixLen]] ];
wStream.PutRope[
IO.PutFR[walnutItemForm, IO.int[walnutItemLen-2], IO.int[formatLen] ]];
wStream.PutRope[walnutItem];
IF formatLen # 0 THEN {
WalnutLog.CopyBytesToArchive[wStream, textStart+textLen, formatLen];
wStream.PutChar['\n];
};
wStream.PutChar['@];
WalnutLog.CopyBytesToArchive[wStream, textStart, textLen];
wStream.PutChar['\n];
};
ok ← FALSE;
WalnutOpsInternal.CheckInProgress[];
BEGIN ENABLE BEGIN
WalnutDefs.Error => {
IF wStream # NIL THEN wStream.Close[ ! IO.Error, FS.Error => CONTINUE];
REJECT;
};
WalnutDefs.VersionMismatch => {
IF wStream # NIL THEN wStream.Close[ ! IO.Error, FS.Error => CONTINUE];
REJECT;
};
IO.Error => { CheckReport[exp ← FS.ErrorFromStream[stream].explanation]; GOTO err };
FS.Error => { CheckReport[exp ← error.explanation]; GOTO err };
END;
BEGIN
wStream ← WalnutStream.Open[
name: file, useOldIfFound: append, exclusive: TRUE ! FS.Error =>
{ CheckReport[error.explanation]; GOTO none }].strm;
EXITS
none => wStream ← NIL;
END;
IF wStream = NIL THEN {
CheckReport[IO.PutFR["\nCould not open %g", IO.rope[file]]];
RETURN
};
IF append THEN wStream.SetIndex[wStream.GetLength[]]
ELSE {
wStream.SetIndex[0];
wStream.SetLength[0];
};
CheckReport["\n Archiving: "];
FOR mL: LIST OF WalnutDefs.MsgSet ← msgSetList, mL.rest UNTIL mL=NIL DO
thisMsgSet ← mL.first.name;
IF ~WalnutDB.VerifyMsgSet[mL.first] THEN {
CheckReport["\n MsgSet ", thisMsgSet, " doesn't exist - continuing"];
LOOP;
};
IF first THEN first ← FALSE ELSE CheckReport[", "];
CheckReport[thisMsgSet];
[] ← WalnutDB.EnumerateMsgsInSet[name: thisMsgSet, proc: WriteProc];
ENDLOOP;
EXITS
err => {
wStream.Close[];
ERROR WalnutDefs.Error[$log, $ErrorDuringWriteArchive, exp];
};
END;
wStream.Close[];
ok ← TRUE;
CheckReport["\n Finished writing archive file\n"];
};
END.