<> <> <> <<>> <> <<>> <> <<>> 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 <<>> <<>> <> ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; TiogaContents: TYPE = ViewerTools.TiogaContents; <<>> <> <<>> 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; <> <> GetNewMailLog: PUBLIC PROC[lengthRequired: INT, pagesWanted: INT] RETURNS[STREAM] = { <> 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] = { <> 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 <> [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 <> 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; 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[]; <> <> [] _ 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.