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
= BEGIN
Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Variables
newMailStream: STREAM ← NIL;
readArchiveStream: STREAM ← NIL;
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:
ROPE ←
NIL]]
RETURNS[ok:
BOOL, reason:
ROPE] = {
reads file and writes the ReadArchiveLogFile; returns ok if all went well
lastCommitPosInFileToRead: INT;
fileToReadPages: INT ← FS.PagesForBytes[fileToRead.GetLength[]];
possibleArchiveLength: INT ← MAX[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.ROPE ← NIL],
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: ROPE ← FS.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: BOOL ← TRUE;
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;
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.