WalnutMiscLogImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Willie-Sue, November 5, 1985 10:39:09 am PST
Walnut NewMailLog and ReadArchiveLog Operations Implementation
NOTE: Currently this ONLY parses old-style Archive logs & old style dump files
DIRECTORY
AlpFile USING [AccessFailed],
BasicTime USING [nullGMT, Now, ToPupTime],
FS USING [Error, ErrorFromStream, PagesForBytes],
GVBasics USING [Timestamp],
IO,
RefText USING [line, Equal, Length, ObtainScratch, ReleaseScratch, TrustTextAsRope],
Rope,
ViewerTools USING [TiogaContents, TiogaContentsRec],
WalnutKernelDefs USING [MsgLogEntry],
WalnutMiscLog,
WalnutRoot USING [CommitAndContinue, GetNewMailStream, GetReadArchiveStream, ReturnNewMailStream, ReturnReadArchiveStream],
WalnutSendOps USING [simpleUserName, userRName, TiogaTextFromStrm, RopeFromStream],
WalnutStream USING [addMsg, createMsg, createMsgSet, hasbeenRead, moveMsg,
Aborted, AbortStream, ConstructMsgID, FlushStream, MsgEntryInfoFromStream, SetHighWaterMark, WriteEntry,
WriteMsgBody];
WalnutMiscLogImpl: CEDAR PROGRAM
IMPORTS
AlpFile, BasicTime, FS, IO, RefText, Rope,
WalnutRoot, WalnutSendOps, WalnutStream
EXPORTS
WalnutMiscLog
= BEGIN
Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Variables
newMailStream: STREAMNIL;
readArchiveStream: STREAMNIL;
msgIDRope: ROPE = "gvMsgID";
categoriesRope: ROPE = "Categories";
activeMsgSet: REF TEXT = "Active";
newMsgSetList: LIST OF REF TEXT;
generalBuffer: REF TEXT;
msgIDBuffer: REF TEXT;
Procedures
Writing and Reading NewMail/ReadArchive files
GetNewMailLog: PUBLIC PROC[lengthRequired: INT, pagesWanted: INT]
RETURNS[STREAM] = {
opens the NewMailLog (aborting any current stream that might be open); truncates it to the required length; returns NIL if the log is shorter than required, or if the file could not be opened
BEGIN ENABLE IO.Error, FS.Error, AlpFile.AccessFailed =>
IF newMailStream # NIL THEN {
AbortMailLog[];
GOTO exit;
};
IF newMailStream # NIL THEN AbortMailLog[];
IF (newMailStream ←
WalnutRoot.GetNewMailStream[lengthRequired, pagesWanted].strm) = NIL THEN RETURN[NIL];
EXITS
exit => IF newMailStream # NIL THEN AbortMailLog[];
END;
RETURN[newMailStream];
};
CloseNewMailLog: PUBLIC PROC = {
strm: STREAM ← newMailStream;
IF strm = NIL THEN RETURN;
newMailStream ← NIL;
WalnutRoot.ReturnNewMailStream[strm ! IO.Error, FS.Error => CONTINUE];
};
AbortMailLog: PROC = {
IF newMailStream = NIL THEN RETURN;
WalnutStream.AbortStream[newMailStream ! IO.Error, FS.Error => CONTINUE];
CloseNewMailLog[];
};
CreateReadArchiveLog: PUBLIC PROC[
fileToRead: STREAM, msgSet: ROPE, reportProc: PROC[msg1, msg2, msg3: ROPENIL]]
RETURNS[ok: BOOL, reason: ROPE] = {
reads file and writes the ReadArchiveLogFile; returns ok if all went well
lastCommitPosInFileToRead: INT;
fileToReadPages: INTFS.PagesForBytes[fileToRead.GetLength[]];
possibleArchiveLength: INTMAX[20, fileToReadPages + (fileToReadPages/5)]; -- 120%
ok ← FALSE;
reason ← NIL;
BEGIN ENABLE BEGIN
IO.Error => {
reason ← FS.ErrorFromStream[fileToRead].explanation;
IF reason = NIL THEN reason ← FS.ErrorFromStream[readArchiveStream].explanation;
IF reason = NIL THEN reason ← "IO error during createReadArchiveLog";
GOTO exit;
};
FS.Error => { reason ← error.explanation; GOTO exit };
AlpFile.AccessFailed => { reason ← "AlpFile.AccessFailed"; GOTO exit};
UNWIND => GOTO exit;
END;
IF (readArchiveStream ← WalnutRoot.GetReadArchiveStream[possibleArchiveLength]) = NIL THEN
RETURN[FALSE, "Can't get readArchiveStream"];
readArchiveStream.SetIndex[0];
WalnutStream.SetHighWaterMark[strm: readArchiveStream, hwmBytes: 0, numPages: -1];
WalnutRoot.CommitAndContinue[];
[ok, lastCommitPosInFileToRead] ← ArchiveReader[
archiveStream: readArchiveStream, fileToRead: fileToRead,
msgSet: msgSet, reportProc: reportProc, posToStartInFileToRead: 0];
IF ~ok THEN CloseReadArchiveLog[];
EXITS
exit =>
IF readArchiveStream # NIL THEN {
WalnutRoot.ReturnReadArchiveStream[readArchiveStream];
readArchiveStream ← NIL;
};
END;
};
CloseReadArchiveLog: PUBLIC PROC = {
WalnutRoot.ReturnReadArchiveStream[readArchiveStream];
readArchiveStream ← NIL;
};
Shutdown: PUBLIC PROC =
{ newMailStream ← readArchiveStream ← NIL };
********************************************************
ArchiveFileType: TYPE = {unknown, old, new};
ArchiveReader: PUBLIC PROC[archiveStream, fileToRead: STREAM, msgSet: ROPE,
reportProc: PROC[msg1, msg2, msg3: Rope.ROPENIL],
posToStartInFileToRead: INT]
RETURNS [ok: BOOL, lastCommitPosInFileToRead: INT] = {
num: INT ← 0;
BadFormat: PROC[s: ROPE] = {
IF reportProc = NIL THEN RETURN;
reportProc[
IO.PutFR["Bad log file format at %g: %g\n", IO.int[fileToRead.GetIndex[]], IO.rope[s]]];
};
BEGIN ENABLE BEGIN
ABORTED => { ok ← FALSE; GOTO GiveUp };
FS.Error => {
IF reportProc # NIL THEN reportProc[error.explanation];
ok ← FALSE; GOTO GiveUp
};
IO.Error => {
IF reportProc # NIL THEN {
reason: ROPEFS.ErrorFromStream[fileToRead].explanation;
IF reason # NIL THEN
reason ← IO.PutFR["IOError on file being read, reported as %g, at pos %g",
IO.rope[reason], IO.int[fileToRead.GetIndex[]] ]
ELSE reason ← FS.ErrorFromStream[archiveStream].explanation;
IF reason # NIL THEN
reason ←
IO.PutFR["IOError on tempLog being written, reported as %g, at pos %g",
IO.rope[reason], IO.int[archiveStream.GetIndex[]] ]
ELSE reason ←
IO.PutFR["IOError at writePos %g",IO.int[archiveStream.GetIndex[]] ];
reportProc["\n", reason, "\n\n"];
};
ok ← FALSE; GOTO GiveUp
};
IO.EndOfStream => {BadFormat["Unexpected EOF"]; ok ← FALSE; GOTO GiveUp};
END;
archiveFileType: ArchiveFileType ← unknown;
entryLength, prefixLength, beginMsgPos: INT;
entryChar: CHAR;
msgSetRT: REF TEXT ← msgSet.ToRefText[];
bytesWritten: INT ← 0;
bytesToWriteBeforeCommit: INT = 100000;  -- try this
lengthOfFileToRead: INT ← fileToRead.GetLength[];
startPosOnArchive: INT;
newMsgSetList ← NIL;  -- global for each file read
ok ← TRUE;
generalBuffer ← RefText.ObtainScratch[RefText.line];
msgIDBuffer ← RefText.ObtainScratch[RefText.line];
lastCommitPosInFileToRead ← posToStartInFileToRead;
fileToRead.SetIndex[posToStartInFileToRead];
DO
Read *start* from log
[beginMsgPos, prefixLength, entryLength, entryChar] ← FindStartOfEntry[fileToRead];
IF entryLength = -1 OR (entryLength=0) OR (entryLength=prefixLength) AND NOT (entryChar = '←) THEN {
RefText.ReleaseScratch[generalBuffer];
RefText.ReleaseScratch[msgIDBuffer];
reportProc[IO.PutFR["\n %g messages read from archive file", IO.int[num]] ];
IF bytesWritten = 0 THEN RETURN[TRUE, lengthOfFileToRead];
IF Flush[archiveStream] THEN RETURN[TRUE, lengthOfFileToRead];
RETURN[FALSE, lastCommitPosInFileToRead];
};
SELECT entryChar FROM
'-, '+, '← => {
BadFormat[IO.PutFR["Non-message entry (entrychar = %g)", IO.char[entryChar]]];
fileToRead.SetIndex[beginMsgPos+entryLength]
};
ENDCASE => {   -- regular message entry
DO   -- fake DO so can use exit
msgID: REF TEXT;
categories: ROPE;
catList: LIST OF REF TEXT;
outOfSynch, notInActive: BOOL;
headersPos: INT ← beginMsgPos+prefixLength;
body: ViewerTools.TiogaContents;
IF archiveFileType = unknown THEN
archiveFileType ← DetermineFileType[fileToRead];
IF archiveFileType = old THEN {
[msgID, categories, outOfSynch] ← ReadPrefixInfo[fileToRead, headersPos];
IF outOfSynch THEN {
BadFormat[IO.PutFR["\nPrefix Info not found at %g", IO.int[headersPos]]];
EXIT};
body ← WalnutSendOps.TiogaTextFromStrm[  -- careful here
fileToRead, headersPos, entryLength-prefixLength];
IF body.formatting.Length[] <= 2 THEN body.formatting ← NIL; --glitch
IF RefText.Length[msgID] = 0 THEN { -- need to construct an ID
back up and parse from the strm
ts: GVBasics.Timestamp;
id, from: ROPE;
mle: WalnutKernelDefs.MsgLogEntry = WalnutStream.createMsg;
mle.textLen ← entryLength - prefixLength;
fileToRead.SetIndex[headersPos];
WalnutStream.MsgEntryInfoFromStream[fileToRead, mle];
from ← IF mle.sender.Equal[WalnutSendOps.userRName, FALSE] OR
mle.sender.Equal[WalnutSendOps.simpleUserName, FALSE]
THEN Rope.Concat["To: ", mle.to]
ELSE mle.sender;
IF from.Length[] = 0 THEN from ← "NoKnownGVid";
IF mle.date = BasicTime.nullGMT THEN mle.date ← BasicTime.Now[];
ts ← [net: 3, host: 14, time: BasicTime.ToPupTime[mle.date]];
id ← WalnutStream.ConstructMsgID[ts, from];
msgID ← id.ToRefText[];
fileToRead.SetIndex[beginMsgPos+entryLength];
};
IF body.formatting.Length[] = 0 THEN {
pos: INT ← 0;
DO  -- replace \240's with spaces (used by laurel)
pos ← body.contents.Find["\240", pos];
IF pos = -1 THEN EXIT;
body.contents ← body.contents.Replace[start: pos, len: 1, with: " "];
ENDLOOP;
};
}
ELSE {
this: BOOL;
[msgID, categories, body, this] ←
ReadNewStyleArchiveFile[
fileToRead, headersPos, entryLength - (headersPos-beginMsgPos) - 1];
IF ~this THEN {
BadFormat[
IO.PutFR["\nCouldn't read entry at %g", IO.int[beginMsgPos]] ];
EXIT;
};
};
BEGIN ENABLE BEGIN
FS.Error =>
IF error.code = $transAborted THEN GOTO aborted ELSE REJECT;
IO.Error =>
IF WalnutStream.Aborted[archiveStream] THEN GOTO aborted ELSE REJECT;
END;
WalnutStream.createMsg.msg ← msgID;
WalnutStream.createMsg.textLen ← body.contents.Length[];
WalnutStream.createMsg.formatLen ← body.formatting.Length[];
startPosOnArchive ←
WalnutStream.WriteEntry[archiveStream, WalnutStream.createMsg];
WalnutStream.WriteMsgBody[archiveStream, body];
IF RefText.Length[msgSetRT] # 0 THEN categories ← NIL;
[catList, notInActive] ← CheckCategories[archiveStream, msgSetRT, categories];
IF entryChar # '? THEN {
WalnutStream.hasbeenRead.msg ← msgID;
[] ← WalnutStream.WriteEntry[archiveStream, WalnutStream.hasbeenRead];
};
-- ProcessCategories
BEGIN
first: BOOLTRUE;
FOR mL: LIST OF REF TEXT ← catList, mL.rest UNTIL mL=NIL DO
IF first AND notInActive THEN {
WalnutStream.moveMsg.msg ← msgID;
WalnutStream.moveMsg.from ← activeMsgSet;
WalnutStream.moveMsg.to ← mL.first;
[] ← WalnutStream.WriteEntry[archiveStream, WalnutStream.moveMsg]
}
ELSE {
WalnutStream.addMsg.msg ← msgID;
WalnutStream.addMsg.to ← mL.first;
[] ← WalnutStream.WriteEntry[archiveStream, WalnutStream.addMsg];
};
first ← FALSE;
ENDLOOP;
END;
IF (bytesWritten ← bytesWritten + archiveStream.GetLength[]-startPosOnArchive) >= bytesToWriteBeforeCommit THEN {
WalnutStream.FlushStream[archiveStream];
WalnutRoot.CommitAndContinue[];
lastCommitPosInFileToRead ← beginMsgPos+entryLength;
bytesWritten ← 0;
};
EXITS
aborted => {
RefText.ReleaseScratch[generalBuffer];
RefText.ReleaseScratch[msgIDBuffer];
RETURN[FALSE, lastCommitPosInFileToRead];
};
END;  -- of Enable FS.Error, IO.Error above - writing on archiveStream
IF (num ← num + 1) MOD 10 = 0 THEN {
IF num MOD 100 = 0 THEN reportProc[IO.PutFR["(%g)", IO.int[num]]]
ELSE reportProc["~"];
};
EXIT;
ENDLOOP;
fileToRead.SetIndex[beginMsgPos+entryLength];
};
ENDLOOP;
EXITS GiveUp => NULL;
END;
RefText.ReleaseScratch[generalBuffer];
RefText.ReleaseScratch[msgIDBuffer];
reportProc[IO.PutFR["\n %g messages read from archive file", IO.int[num]] ];
RETURN[ok, lastCommitPosInFileToRead];
};
Flush: PROC[strm: STREAM] RETURNS[ok: BOOL] = {
ENABLE FS.Error => IF error.code = $transAborted THEN GOTO aborted ELSE REJECT;
WalnutStream.FlushStream[strm];
WalnutRoot.CommitAndContinue[];
RETURN[TRUE];
EXITS
aborted => RETURN[FALSE]
};
CheckCategories: PROC[archiveStream: STREAM, msgSet: REF TEXT, categories: ROPE]
RETURNS[catList: LIST OF REF TEXT, notInActive: BOOL] = {
h: STREAM;
CheckMsgSet: PROC[ms: REF TEXT] RETURNS[notActive: BOOL] = {
IF RefText.Equal[ms, activeMsgSet, FALSE]
THEN RETURN[FALSE];
FOR mL: LIST OF REF TEXT ← newMsgSetList, mL.rest UNTIL mL = NIL DO
IF RefText.Equal[mL.first, ms, FALSE] THEN RETURN[TRUE];
ENDLOOP;
WalnutStream.createMsgSet.msgSet ← ms;
[] ← WalnutStream.WriteEntry[archiveStream, WalnutStream.createMsgSet];
newMsgSetList ← CONS[ms, newMsgSetList];  -- to be able to check next time
RETURN[TRUE];
};
catList ← NIL;
notInActive ← TRUE;
IF categories = NIL THEN
IF RefText.Length[msgSet] = 0 THEN RETURN[NIL, FALSE]
ELSE {
notInActive ← CheckMsgSet[msgSet];
IF notInActive THEN RETURN[LIST[msgSet], notInActive]
ELSE RETURN[NIL, FALSE];
};
IF RefText.Length[msgSet] # 0 THEN {
notInActive ← CheckMsgSet[msgSet];
IF notInActive THEN catList ← LIST[msgSet];
};
h ← IO.RIS[categories];
UNTIL h.EndOf[] DO
token: ROPE ← h.GetTokenRope[breakProc: IO.IDProc].token;
IF token.Length[] # 0 THEN {
tokenRT: REF TEXT ← token.ToRefText[];
notActive: BOOL ← CheckMsgSet[tokenRT ← token.ToRefText[]];
notInActive ← notInActive AND notActive;
IF notActive THEN catList ← CONS[tokenRT, catList];
};
ENDLOOP;
h.Close[];
};
DetermineFileType: PROC[strm: STREAM] RETURNS[aft: ArchiveFileType] = {
curPos: INT ← strm.GetIndex[];
aft ← old;
IF strm.GetChar[] = '@ THEN {  -- might be new or Hardy file
itemType: INT;
[] ← strm.GetInt[];
IF (itemType ← strm.GetInt[]) = WalnutMiscLog.TiogaControlItemType THEN
aft ← new;
};
strm.SetIndex[curPos];
};
ReadPrefixInfo: PROC[fileToRead: STREAM, headersPos: INT]
RETURNS[msgID: REF TEXT, categories: ROPE, outOfSynch: BOOL] = {
curPos: INT;
outOfSynch ← FALSE;
UNTIL (curPos ← fileToRead.GetIndex[]) = headersPos DO
tag: REF TEXT;
IF curPos > headersPos THEN {outOfSynch ← TRUE; RETURN};
tag ← fileToRead.GetToken[buffer: generalBuffer].token;
IF Rope.Equal[RefText.TrustTextAsRope[tag], msgIDRope, FALSE] THEN {
[] ← fileToRead.GetChar[]; -- the :
[] ← fileToRead.SkipWhitespace[];
msgID ← fileToRead.GetLine[msgIDBuffer];
LOOP;
};
IF Rope.Equal[RefText.TrustTextAsRope[tag], categoriesRope, FALSE] THEN {
[] ← fileToRead.GetChar[]; -- the :
[] ← fileToRead.SkipWhitespace[];
categories ← fileToRead.GetLineRope[];
LOOP;
};
ENDLOOP;
};
ReadNewStyleArchiveFile: PROC[
fileToRead: STREAM, headersPos, bodyLen: INT]
RETURNS[msgID: REF TEXT, categories: ROPE, body: TiogaContents, ok: BOOL] = {
len, type, pos, formatLen: INT;
ok ← FALSE;
body ← NEW[ViewerTools.TiogaContentsRec];
IF fileToRead.GetChar[] # '@ THEN RETURN;
len ← fileToRead.GetInt[];
type ← fileToRead.GetInt[];
formatLen ← fileToRead.GetInt[];
[] ← fileToRead.GetChar[];  -- the \n
IF type = WalnutMiscLog.TiogaControlItemType THEN {
msgID ← fileToRead.GetLine[msgIDBuffer];
categories ← fileToRead.GetLineRope[];
IF formatLen # 0 THEN {
body.formatting ←
WalnutSendOps.RopeFromStream[fileToRead, fileToRead.GetIndex[], formatLen];
[] ← fileToRead.GetChar[]; -- cr
};
};
[] ← fileToRead.GetChar[];  -- the other @
pos ← fileToRead.GetIndex[];
body.contents ← WalnutSendOps.RopeFromStream[fileToRead, pos, bodyLen];
[] ← fileToRead.GetChar[ ! IO.EndOfStream => CONTINUE];  -- the trailing \n
ok ← TRUE;
};
FindStartOfEntry: PROC[fileToRead: STREAM]
RETURNS[startPos, prefixLength, entryLength: INT, entryChar: CHAR] = {
ENABLE IO.EndOfStream => GOTO eoS;
line: REF TEXT;
relPos: INT ← 0;
startFound: BOOL;
prefixLength ← entryLength ← 0;
entryChar ← '\000;  -- not a valid entryType char
IF fileToRead.EndOf[] THEN RETURN;
startPos ← fileToRead.GetIndex[];
line ← fileToRead.GetLine[generalBuffer];
[startFound, relPos] ← IsStart[line];
IF NOT startFound THEN
UNTIL startFound DO
IF fileToRead.EndOf[] THEN RETURN;
startPos ← fileToRead.GetIndex[];
line ← fileToRead.GetLine[generalBuffer];
[startFound, relPos] ← IsStart[line];
ENDLOOP;
-- Read entry info line from log, e.g.: 00101 00029 US+
startPos ← startPos + relPos;
entryLength ← fileToRead.GetInt[];
prefixLength ← fileToRead.GetInt[];
line ← fileToRead.GetLine[generalBuffer];
entryChar ← RefText.Fetch[line, RefText.Length[line]-1];
[] ← fileToRead.GetChar[];  -- space
[] ← fileToRead.GetChar[];  -- flag
[] ← fileToRead.GetChar[];  -- flag
entryChar ← fileToRead.GetChar[];  -- Laurel's march char
IF fileToRead.GetChar[] # '\n THEN entryLength ← -1;
EXITS
eoS => {entryLength ← -1; RETURN};
};
starStartStar: ROPE = "*start*";
IsStart: PROC[line: REF TEXT] RETURNS[startFound: BOOL, relPos: INT] = {
relPos ← 0;
IF RefText.Length[line] = 0 THEN RETURN[FALSE, relPos];
IF Rope.Equal[starStartStar, RefText.TrustTextAsRope[line]] THEN
RETURN[TRUE, relPos];
IF (relPos ← RefText.Length[line] - starStartStar.Length[]) <=0 THEN RETURN[FALSE, relPos];
IF Rope.Find[s1: RefText.TrustTextAsRope[line], s2: starStartStar, pos1: relPos] = relPos THEN
RETURN[TRUE, relPos];
RETURN[FALSE, relPos];
};
END.