DIRECTORY AlpFile USING [AccessFailed], AlpineFS USING [ErrorFromStream, OpenFile, OpenFileFromStream], BasicTime USING [GMT, nullGMT, Now, ToPupTime], FS USING [Error, ErrorDesc, OpenFile, BytesForPages, ErrorFromStream, GetInfo, PagesForBytes, SetByteCountAndCreatedTime, SetPageCount], GVBasics USING [Timestamp], IO, RefText USING [line, AppendRope, Equal, Length, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope, RuntimeError USING [BoundsFault], ViewerTools USING [TiogaContentsRec, TiogaContents], WalnutDefs USING [Error], WalnutKernelDefs USING [LogEntry, MsgLogEntry, WhichTempLog], WalnutLog USING [], WalnutLogExpunge USING [EntryStatus], WalnutMiscLog USING [TiogaControlItemType], WalnutRoot -- using lots -- , WalnutSendOps USING [simpleUserName, userRName, TiogaTextFromStrm, RopeFromStream], WalnutStream -- using lots -- ; WalnutLogImpl: CEDAR PROGRAM IMPORTS AlpFile, AlpineFS, BasicTime, FS, IO, RefText, Rope, RuntimeError, WalnutDefs, WalnutRoot, WalnutSendOps, WalnutStream EXPORTS WalnutLog, WalnutLogExpunge, WalnutMiscLog = BEGIN GMT: TYPE = BasicTime.GMT; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; LogEntry: TYPE = WalnutKernelDefs.LogEntry; MsgLogEntry: TYPE = WalnutKernelDefs.MsgLogEntry; TiogaContents: TYPE = ViewerTools.TiogaContents; writeStream: STREAM _ NIL; tempStream: STREAM _ NIL; -- when copying a tempFile readStream: STREAM _ NIL; -- only one log for now scanning: BOOL _ FALSE; currentEntryLengthHint: INT _ -1; currentEntryPos: INT _ -1; field1: REF TEXT = NEW[TEXT[RefText.line]]; field2: REF TEXT = NEW[TEXT[RefText.line]]; field3: REF TEXT = NEW[TEXT[RefText.line]]; 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; currentStream: STREAM; expCurrentEntryLengthHint: INT; expCurrentEntryPos: INT; expungeStream: STREAM; keyIs: ROPE; expungeInternalFileID: INT; logSeqNo: INT; ExpungeMsgs: PUBLIC PROC RETURNS[at, next: INT] = { [at, next] _ WriteEntry[WalnutStream.expungeMsgs] }; WriteExpungeLog: PUBLIC PROC RETURNS[at, next: INT] = { WalnutStream.writeExpungeLog.internalFileID _ WalnutRoot.GetExpungeID[]; [at, next] _ WriteEntry[WalnutStream.writeExpungeLog]; }; CreateMsgSet: PUBLIC PROC[name: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.createMsgSet.msgSet _ RefText.AppendRope[field1, name]; [at, next] _ WriteEntry[WalnutStream.createMsgSet] }; EmptyMsgSet: PUBLIC PROC[msgSet: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.emptyMsgSet.msgSet _ RefText.AppendRope[field1, msgSet]; [at, next] _ WriteEntry[WalnutStream.emptyMsgSet] }; DestroyMsgSet: PUBLIC PROC[msgSet: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.destroyMsgSet.msgSet _ RefText.AppendRope[field1, msgSet]; [at, next] _ WriteEntry[WalnutStream.destroyMsgSet] }; AddMsg: PUBLIC PROC[msg: ROPE, to: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.addMsg.msg _ RefText.AppendRope[field1, msg]; field2.length _ 0; WalnutStream.addMsg.to _ RefText.AppendRope[field2, to]; [at, next] _ WriteEntry[WalnutStream.addMsg] }; RemoveMsg: PUBLIC PROC[msg: ROPE, from: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.removeMsg.msg _ RefText.AppendRope[field1, msg]; field2.length _ 0; WalnutStream.removeMsg.from _ RefText.AppendRope[field2, from]; [at, next] _ WriteEntry[WalnutStream.removeMsg] }; MoveMsg: PUBLIC PROC[msg: ROPE, from, to: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.moveMsg.msg _ RefText.AppendRope[field1, msg]; field2.length _ 0; WalnutStream.moveMsg.from _ RefText.AppendRope[field2, from]; field3.length _ 0; WalnutStream.moveMsg.to _ RefText.AppendRope[field3, to]; [at, next] _ WriteEntry[WalnutStream.moveMsg] }; HasBeenRead: PUBLIC PROC[msg: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.hasbeenRead.msg _ RefText.AppendRope[field1, msg]; [at, next] _ WriteEntry[WalnutStream.hasbeenRead] }; RecordNewMailInfo: PUBLIC PROC[logLen: INT, when: GMT, server: ROPE, num: INT] RETURNS[at, next: INT] = { WalnutStream.recordNewMailInfo.logLen _ logLen; WalnutStream.recordNewMailInfo.when _ when; field1.length _ 0; WalnutStream.recordNewMailInfo.server _ RefText.AppendRope[field1, server]; WalnutStream.recordNewMailInfo.num _ num; [at, next] _ WriteEntry[WalnutStream.recordNewMailInfo] }; StartCopyNewMail: PUBLIC PROC RETURNS[at, next: INT] = { [at, next] _ WriteEntry[WalnutStream.startCopyNewMail] }; AcceptNewMail: PUBLIC PROC RETURNS[at, next: INT] = { [at, next] _ WriteEntry[WalnutStream.acceptNewMail] }; StartReadArchiveFile: PUBLIC PROC[file: ROPE, msgSet: ROPE] RETURNS[at, next: INT] = { field1.length _ 0; WalnutStream.startReadArchiveFile.file _ RefText.AppendRope[field1, file]; field2.length _ 0; WalnutStream.startReadArchiveFile.msgSet _ RefText.AppendRope[field2, msgSet]; [at, next] _ WriteEntry[WalnutStream.startReadArchiveFile] }; EndReadArchiveFile: PUBLIC PROC RETURNS[at, next: INT] = { [at, next] _ WriteEntry[WalnutStream.endReadArchiveFile] }; StartCopyReadArchive: PUBLIC PROC[] RETURNS[at, next: INT] = { [at, next] _ WriteEntry[WalnutStream.startCopyReadArchive] }; AcquireWriteLock: PUBLIC PROC = { Awl: PROC = { WalnutRoot.AcquireWriteLock[] }; CarefullyApply[Awl, "AcquireWriteLock"]; }; ForgetLogStreams: PUBLIC PROC = { readStream _ writeStream _ NIL }; CloseLogStreams: PUBLIC PROC = { WalnutRoot.CloseTransaction[]; readStream _ writeStream _ NIL; }; LogLength: PUBLIC PROC RETURNS[length: INT] = { Llen: PROC = { length _ writeStream.GetLength[] }; CarefullyApply[Llen, "LogLength"]; }; OpenLogStreams: PUBLIC PROC = { IF writeStream = NIL THEN { [readStream, writeStream] _ WalnutRoot.GetCurrentLogStreams[]; IF scanning THEN readStream.SetIndex[currentEntryPos]; }; }; ReleaseWriteLock: PUBLIC PROC = { IF writeStream = NIL THEN RETURN; [readStream, writeStream] _ WalnutRoot.ReleaseWriteLock[]; IF scanning THEN readStream.SetIndex[currentEntryPos]; }; ResetLog: PUBLIC PROC[newLength: INT] = { Rl: PROC = { writeStream.SetIndex[newLength]; WalnutStream.SetHighWaterMark[writeStream, newLength, -1]; writeStream.SetIndex[newLength]; writeStream.Flush[]; }; CarefullyApply[Rl, "ResetLog"]; }; ReturnCurrentLogStreams: PUBLIC PROC = { readStream _ writeStream _ NIL; scanning _ FALSE; }; ShutdownLog: PUBLIC PROC = { WalnutRoot.CloseTransaction[]; readStream _ writeStream _ NIL; scanning _ FALSE; }; WriteMessage: PUBLIC PROC[msg: ROPE, body: TiogaContents] RETURNS[at: INT]= { WriteMsg: PROC[] = { writeStream.SetIndex[writeStream.GetLength[]]; field1.length _ 0; WalnutStream.createMsg.msg _ RefText.AppendRope[field1, msg]; IF body.formatting.Length[] # 0 THEN { last: INT _ body.contents.Length[] - 1; IF body.contents.Fetch[last] = '\000 THEN -- NULL for padding { body.contents _ Rope.Substr[body.contents, 1, last-1]; body.formatting _ Rope.Concat["\000", body.formatting] } ELSE body.contents _ Rope.Substr[body.contents, 1]; }; WalnutStream.createMsg.textLen _ body.contents.Length[]; WalnutStream.createMsg.formatLen _ body.formatting.Length[]; at _ WalnutStream.WriteEntry[writeStream, WalnutStream.createMsg]; WalnutStream.WriteMsgBody[writeStream, body]; WalnutStream.FlushStream[strm: writeStream, setCreateDate: TRUE]; }; CarefullyApply[WriteMsg, "WriteMessage"]; }; SetPosition: PUBLIC PROC[startPos: INT] RETURNS[charsSkipped: INT] = { next: INT; SetPs: PROC[] = { readStream.SetIndex[startPos]; next _ WalnutStream.FindNextEntry[readStream]; currentEntryLengthHint _ -1; IF next # -1 THEN currentEntryPos _ next; scanning _ TRUE; }; CarefullyApply[SetPs, "SetPosition"]; RETURN[IF next = -1 THEN next ELSE next - startPos]; }; SetIndex: PUBLIC PROC[pos: INT] = { Si: PROC[] = { readStream.SetIndex[pos]; currentEntryLengthHint _ -1; currentEntryPos _ pos; scanning _ TRUE; }; CarefullyApply[Si, "SetIndex"]; }; NextAt: PUBLIC PROC RETURNS[at: INT] = { at _ currentEntryPos }; NextEntry: PUBLIC PROC RETURNS[le: LogEntry, at: INT] = { [le, at] _ NextEntryInternal[FALSE, "NextEntry"] }; NextEntryInternal: PROC[quick: BOOL, who: ROPE] RETURNS[le: LogEntry, at: INT] = { Ne: PROC[] = { length: INT; [le, length] _ WalnutStream.ReadEntry[readStream, quick]; at _ currentEntryPos; currentEntryLengthHint _ -1; IF length = -1 THEN { IF at = readStream.GetLength[] THEN RETURN; at _ -1; RETURN }; currentEntryPos _ at + length; }; IF ~scanning THEN ERROR WalnutDefs.Error[$log, $NotScanning]; CarefullyApply[Ne, who]; }; QuickScan: PUBLIC PROC RETURNS[le: LogEntry, at: INT] = { [le, at] _ NextEntryInternal[quick: TRUE, who: "QuickScan"] }; ArchiveEntry: PUBLIC PROC[to: IO.STREAM] RETURNS[ok: BOOL]= { Ane: PROC[] = { ok _ FALSE; IF currentEntryLengthHint = -1 THEN currentEntryLengthHint _ WalnutStream.PeekEntry[readStream].length; IF currentEntryLengthHint = -1 THEN RETURN; WalnutStream.CopyBytes[ from: readStream, to: to, num: currentEntryLengthHint]; currentEntryPos _ readStream.GetIndex[]; currentEntryLengthHint _ -1; -- unknown ok _ TRUE; }; IF ~scanning THEN ERROR WalnutDefs.Error[$log, $NotScanning]; CarefullyApply[Ane, "ArchiveEntry"]; }; CopyBytesToArchive: PUBLIC PROC[to: IO.STREAM, startPos, num: INT] = { Cba: PROC = { readStream.SetIndex[startPos]; WalnutStream.CopyBytes[from: readStream, to: to, num: num]; scanning _ FALSE; }; CarefullyApply[Cba, "CopyBytesToArchive"]; }; PrepareToCopyTempLog: PUBLIC PROC[which: WalnutKernelDefs.WhichTempLog, pagesAlreadyCopied: INT, reportProc: PROC[msg1, msg2, msg3: ROPE _ NIL]] RETURNS[BOOL] = { [writeStream, tempStream] _ WalnutRoot.PrepareToCopyTempLog[which, pagesAlreadyCopied, reportProc]; RETURN[tempStream # NIL]; }; CopyTempLog: PUBLIC PROC[which: WalnutKernelDefs.WhichTempLog, startCopyPos, fromPos: INT, reportProc: PROC[msg1, msg2, msg3: ROPE _ NIL] ] = { exp: ROPE; endLE: LogEntry; abort: BOOL _ TRUE; ok: BOOL _ FALSE; bytesToCopy: INT; bytesPerCopy: INT = FS.BytesForPages[200]; BEGIN ENABLE BEGIN IO.Error => { why: ATOM = AlpineFS.ErrorFromStream[stream].code; IF why = $transAborted THEN GOTO exit; IF why = $remoteCallFailed THEN { exp _ AlpineFS.ErrorFromStream[stream].explanation; GOTO exit; }; REJECT; }; FS.Error => { IF error.code = $transAborted THEN GOTO exit; IF error.code = $remoteCallFailed THEN { exp _ error.explanation; GOTO exit; }; REJECT; }; AlpFile.AccessFailed => { abort _ FALSE; GOTO exit }; END; SELECT which FROM newMail => { WalnutStream.endCopyNewMailInfo.startCopyPos _ startCopyPos; endLE _ WalnutStream.endCopyNewMailInfo; }; readArchive => { WalnutStream.endCopyReadArchiveInfo.startCopyPos _ startCopyPos; endLE _ WalnutStream.endCopyReadArchiveInfo }; ENDCASE => ERROR WalnutDefs.Error[$log, $UnknownLogFileType]; IF tempStream = NIL THEN [writeStream, tempStream] _ WalnutRoot.GetStreamsForCopy[which]; bytesToCopy _ tempStream.GetLength[] - fromPos; IF bytesToCopy > 0 THEN { first: BOOL _ TRUE; tempStream.SetIndex[fromPos]; writeStream.SetIndex[writeStream.GetLength[]]; DO thisCopy: INT _ MIN[bytesPerCopy, bytesToCopy]; WalnutStream.CopyBytes[to: writeStream, from: tempStream, num: thisCopy]; WalnutStream.FlushStream[writeStream, TRUE]; WalnutRoot.CommitAndContinue[]; IF first THEN first _ FALSE ELSE reportProc["#"]; bytesToCopy _ bytesToCopy - thisCopy; IF bytesToCopy = 0 THEN EXIT; ENDLOOP; }; [] _ WalnutStream.WriteEntry[writeStream, endLE]; WalnutStream.FlushStream[writeStream, TRUE]; EXITS exit => { who: ROPE _ IF which = newMail THEN "NewMail" ELSE "ReadArchive"; IF ~abort THEN ERROR WalnutDefs.Error[$log, $AccessFailed, who]; WalnutRoot.AbortTempCopy[which, tempStream]; WalnutRoot.CloseTransaction[]; writeStream _ readStream _ tempStream _ NIL; ERROR WalnutDefs.Error[ $log, IF exp = NIL THEN $TransactionAbort ELSE $RemoteCallFailed, IO.PutFR["Copying the %g log", IO.rope[who]] ]; }; END; tempStream _ NIL; WalnutRoot.FinishCopy[which]; }; FinishTempLogCopy: PUBLIC PROC[which: WalnutKernelDefs.WhichTempLog] = { WalnutRoot.FinishCopy[which] }; CreateArchiveLog: PUBLIC PROC[ fileToRead: STREAM, msgSet: ROPE, reportProc: PROC[msg1, msg2, msg3: ROPE _ NIL]] RETURNS[ok: BOOL] = { reason: ROPE; [ok, reason] _ CreateReadArchiveLog[fileToRead, msgSet, reportProc]; IF ok THEN [] _ WriteEntry[WalnutStream.endReadArchiveFile]; CloseReadArchiveLog[]; }; GetTiogaContents: PUBLIC PROC[textStart, textLen, formatLen: INT] RETURNS[contents: TiogaContents] = { TContents: PROC[] = { readStream.SetIndex[textStart]; scanning _ FALSE; contents _ NEW[ViewerTools.TiogaContentsRec]; contents.contents _ WalnutStream.ReadRope[readStream, textLen]; contents.formatting _ WalnutStream.ReadRope[readStream, formatLen]; }; IF formatLen # 0 THEN -- tioga formatting nonsense { textLen _ textLen + 1; textStart _ textStart - 1}; CarefullyApply[TContents, "GetTiogaContents"]; }; GetRefTextFromLog: PUBLIC PROC[startPos, length: INT, text: REF TEXT] = { natLen: NAT; Grf: PROC = { bytesRead: NAT; readStream.SetIndex[startPos]; scanning _ FALSE; bytesRead _ readStream.GetBlock[block: text, startIndex: 0, count: natLen]; IF bytesRead < natLen THEN text.length _ 0; }; CheckSize: PROC[len: INT] RETURNS[nat: NAT] = { nat _ len }; natLen _ CheckSize[length ! RuntimeError.BoundsFault => GOTO noGood]; CarefullyApply[Grf, "GetRefTextFromLog"]; EXITS noGood => ERROR WalnutDefs.Error[$log, $BadLength, IO.PutFR[" Can't convert %g into a nat", IO.int[length] ]]; }; WriteEntry: PROC[le: LogEntry] RETURNS[at, next: INT] = { We: PROC = { at _ WalnutStream.WriteEntry[writeStream, le]; WalnutStream.FlushStream[writeStream, TRUE]; next _ writeStream.GetIndex[]; }; CarefullyApply[We, "WriteEntry"]; }; curLog: ROPE = "On currentLog, during "; CarefullyApply: PROC[ proc: PROC[], who: ROPE ] = { exp: ROPE; BEGIN ENABLE BEGIN IO.Error => { why: ATOM = AlpineFS.ErrorFromStream[stream].code; IF why = $transAborted THEN GOTO aborted; IF why = $remoteCallFailed THEN { exp _ AlpineFS.ErrorFromStream[stream].explanation; GOTO failed; }; REJECT; }; FS.Error => { IF error.code = $transAborted THEN GOTO aborted; IF error.code = $remoteCallFailed THEN { exp _ error.explanation; GOTO failed; }; REJECT; }; AlpFile.AccessFailed => IF missingAccess = spaceQuota THEN GOTO outOfSpace ELSE GOTO exit; END; IF readStream = NIL THEN -- test read, not write stream, in case readOnly ERROR WalnutDefs.Error[$log, $LogNotOpen, curLog.Concat[who]]; proc[]; EXITS aborted => { WalnutRoot.StatsReport[Rope.Concat["\n**** trans abort on currentLog doing", who]]; ERROR WalnutDefs.Error[$log, $TransactionAbort, curLog.Concat[who]]; }; failed => { WalnutRoot.StatsReport[Rope.Concat["\n**** remote call failed on currentLog doing", who]]; ERROR WalnutDefs.Error[$log, $RemoteCallFailed, exp]; }; outOfSpace => ERROR WalnutDefs.Error[$log, $OutOfSpace, curLog.Concat[who]]; exit => ERROR WalnutDefs.Error[$log, $OtherAlpineMissingAccess, curLog.Concat[who]]; END; }; 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; }; MiscShutdown: 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]; }; 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]; }; StartExpunge: PUBLIC PROC[pagesNeeded: INT] RETURNS[expungeFileID: INT] = { reason: FS.ErrorDesc; actualPages: INT; of: FS.OpenFile; BEGIN ENABLE BEGIN FS.Error => { reason _ error; GOTO err }; IO.Error => { reason _ AlpineFS.ErrorFromStream[stream]; GOTO err }; UNWIND => { currentStream _ expungeStream _ NIL }; END; [currentStream, expungeStream, keyIs, expungeInternalFileID, logSeqNo] _ WalnutRoot.GetStreamsForExpunge[]; actualPages _ FS.GetInfo[of _ AlpineFS.OpenFileFromStream[expungeStream]].pages; IF actualPages < pagesNeeded THEN FS.SetPageCount[of, pagesNeeded ! FS.Error => IF error.code = $quotaExceeded THEN GOTO quota ELSE REJECT]; IF expungeStream.GetLength[] # 0 THEN { WalnutStream.SetHighWaterMark[expungeStream, 0, -1]; expungeStream.SetIndex[0]; expungeStream.SetLength[0]; }; FS.SetByteCountAndCreatedTime[of, -1, BasicTime.Now[]]; WalnutStream.logFileInfo. key _ keyIs.ToRefText[]; WalnutStream.logFileInfo.internalFileID _ expungeInternalFileID; WalnutStream.logFileInfo.logSeqNo _ logSeqNo; [] _ WalnutStream.WriteEntry[expungeStream, WalnutStream.logFileInfo]; WalnutStream.FlushStream[expungeStream, TRUE]; RETURN[expungeInternalFileID]; EXITS quota => ERROR WalnutDefs.Error[$expungeLog, $quotaExceeded, IO.PutFR[" An ExpungeLog of %g pages exceeds your quota; file has %g pages", IO.int[pagesNeeded], IO.int[actualPages]]]; err => { WalnutRoot.StatsReport["\n *** During start expunge "]; WalnutRoot.StatsReport[reason.explanation]; ERROR WalnutDefs.Error[$expungeLog, reason.code, "During Start Expunge"]; }; END; }; RestartExpunge: PUBLIC PROC[currentLogPos, expungeLogLength: INT] RETURNS[ok: BOOL] = { DoRE: PROC = { expungePosToUse: INT _ expungeLogLength; le: LogEntry; keyFromExpLog: REF TEXT; internalFileIDFromExpLog, logSeqNoFromExpLog: INT; reason: FS.ErrorDesc; BEGIN ENABLE BEGIN FS.Error => { reason _ error; GOTO error }; IO.Error => { reason _ AlpineFS.ErrorFromStream[stream]; GOTO error }; UNWIND => { currentStream _ expungeStream _ NIL }; END; ok _ FALSE; [currentStream, expungeStream, keyIs, expungeInternalFileID, logSeqNo] _ WalnutRoot.GetStreamsForExpunge[]; IF expungeStream = NIL THEN RETURN; IF expungeStream.GetLength[] < expungeLogLength THEN RETURN; -- too short expungeStream.SetIndex[0]; le _ WalnutStream.ReadEntry[expungeStream].le; IF le = NIL THEN RETURN; TRUSTED { WITH le: le SELECT FROM LogFileInfo => { keyFromExpLog _ le.key; internalFileIDFromExpLog _ le.internalFileID; logSeqNoFromExpLog _ le.logSeqNo; }; ENDCASE => RETURN; }; IF expungeLogLength = 0 THEN expungePosToUse _ expungeStream.GetIndex[]; IF ~keyIs.Equal[RefText.TrustTextAsRope[keyFromExpLog]] THEN RETURN; IF internalFileIDFromExpLog # expungeInternalFileID THEN RETURN; IF logSeqNoFromExpLog # logSeqNo THEN RETURN; WalnutStream.SetHighWaterMark[expungeStream, expungePosToUse, -1]; expungeStream.SetIndex[expungePosToUse]; currentStream.SetIndex[currentLogPos]; expCurrentEntryLengthHint _ -1; currentEntryPos _ currentLogPos; ok _ TRUE; EXITS error => { WalnutRoot.StatsReport["\n *** During Start Expunge "]; WalnutRoot.StatsReport[reason.explanation]; ok _ FALSE; }; END; }; DoRE[]; IF ~ok THEN { WalnutRoot.ReturnExpungeStreams[]; currentStream _ expungeStream _ NIL; }; }; CopyBytesToExpungeLog: PUBLIC PROC[bytesToCopy: INT] = { Cbel: PROC = { expungeStream.SetIndex[expungeStream.GetLength[]]; -- to be sure WalnutStream.CopyBytes[ from: currentStream, to: expungeStream, num: bytesToCopy]; currentEntryPos _ currentStream.GetIndex[]; expCurrentEntryLengthHint _ -1; -- unknown }; ExpCarefullyApply[Cbel, "CopyBytesToExpungeLog"]; }; GetExpungeProgress: PUBLIC PROC RETURNS[currentLogPos, expungeLogLength: INT] = { Gep: PROC = { WalnutStream.FlushStream[expungeStream, TRUE]; WalnutRoot.CommitAndContinue[]; currentLogPos _ currentEntryPos; expungeLogLength _ expungeStream.GetLength[]; }; ExpCarefullyApply[Gep, "GetExpungeProgress"]; }; EndExpunge: PUBLIC PROC = { expungeStream _ currentStream _ NIL }; ExpShutdown: PUBLIC PROC = { expungeStream _ currentStream _ NIL }; ExpSetPosition: PUBLIC PROC[startPos: INT] RETURNS[charsSkipped: INT] = { next: INT; SetPs: PROC = { currentStream.SetIndex[startPos]; next _ WalnutStream.FindNextEntry[currentStream]; expCurrentEntryLengthHint _ -1; IF next # -1 THEN expCurrentEntryPos _ next; }; ExpCarefullyApply[SetPs, "SetPosition"]; RETURN[IF next = -1 THEN next ELSE next - startPos]; }; ExpSetIndex: PUBLIC PROC[pos: INT] = { Si: PROC = { currentStream.SetIndex[pos]; expCurrentEntryLengthHint _ -1; expCurrentEntryPos _ pos; }; ExpCarefullyApply[Si, "SetIndex"]; }; PeekEntry: PUBLIC PROC RETURNS[ident: ATOM, msgID: REF TEXT, at: INT] = { Pne: PROC = { length: INT; [ident, msgID, length] _ WalnutStream.PeekEntry[currentStream]; expCurrentEntryLengthHint _ length; IF length = -1 THEN { IF expCurrentEntryPos = currentStream.GetLength[] THEN at _ expCurrentEntryPos ELSE at _ -1; RETURN; }; at _ expCurrentEntryPos; }; ExpCarefullyApply[Pne, "PeekEntry"]; }; SkipEntry: PUBLIC PROC RETURNS[ok: BOOL] = { Sne: PROC = { length: INT; ok _ FALSE; IF expCurrentEntryLengthHint = -1 THEN length _ WalnutStream.PeekEntry[currentStream].length ELSE { length _ expCurrentEntryLengthHint; expCurrentEntryLengthHint _ -1; }; IF length = -1 THEN RETURN; currentStream.SetIndex[expCurrentEntryPos _ currentStream.GetIndex[] + length]; ok _ TRUE; }; ExpCarefullyApply[Sne, "SkipEntry"]; }; CopyEntry: PUBLIC PROC RETURNS[newPosition, bytesCopied: INT] = { Ce: PROC = { bytesCopied _ 0; newPosition _ expungeStream.GetIndex[]; IF expCurrentEntryLengthHint = -1 THEN expCurrentEntryLengthHint _ WalnutStream.PeekEntry[currentStream].length; IF expCurrentEntryLengthHint = -1 THEN {newPosition _ -1; RETURN}; WalnutStream.CopyBytes[ from: currentStream, to: expungeStream, num: expCurrentEntryLengthHint]; expCurrentEntryPos _ currentStream.GetIndex[]; bytesCopied _ expCurrentEntryLengthHint; expCurrentEntryLengthHint _ -1; -- unknown }; ExpCarefullyApply[Ce, "CopyEntry"]; }; EndCopyEntry: PUBLIC PROC RETURNS[startCopyPos: INT] = { Ne: PROC[] = { wle: LogEntry _ WalnutStream.ReadEntry[currentStream].le; currentStream.SetIndex[expCurrentEntryPos]; IF wle = NIL THEN ERROR WalnutDefs.Error[$log, $NoEntryFound, "During Expunge"]; TRUSTED { WITH le: wle SELECT FROM EndCopyNewMailInfo => startCopyPos _ le.startCopyPos; EndCopyReadArchiveInfo => startCopyPos _ le.startCopyPos; ENDCASE => ERROR WalnutDefs.Error[$log, $WrongEntryFound, "During Expunge"]; }; }; startCopyPos _ 0; ExpCarefullyApply[Ne, "EndCopyNewMailEntry"]; }; ExpLogLength: PUBLIC PROC RETURNS[length: INT] = { IF currentStream = NIL THEN RETURN[-1]; RETURN[currentStream.GetLength[]]; }; GetIndex: PUBLIC PROC RETURNS[length: INT] = { IF currentStream = NIL THEN RETURN[-1]; RETURN[currentStream.GetIndex[]]; }; CopyBytes: PUBLIC PROC[strm: STREAM, num: INT] = { Cb: PROC = { strm.SetIndex[strm.GetLength[]]; WalnutStream.CopyBytes[from: currentStream, to: strm, num: num]; expCurrentEntryPos _ currentStream.GetIndex[]; expCurrentEntryLengthHint _ -1; -- unknown }; ExpCarefullyApply[Cb, "CopyBytes"]; }; ExamineThisEntry: PUBLIC PROC RETURNS[status: WalnutLogExpunge.EntryStatus] = { Ete: PROC = { length: INT; curPos: INT; status _ noValidEntry; IF expCurrentEntryLengthHint = -1 THEN length _ WalnutStream.PeekEntry[currentStream].length ELSE { length _ expCurrentEntryLengthHint; expCurrentEntryLengthHint _ -1; }; IF length = -1 THEN { status _ noValidEntry; RETURN}; status _ validEntry; currentStream.SetIndex[ expCurrentEntryPos _ (curPos _ currentStream.GetIndex[]) + length ! IO.EndOfStream => { status _ EndOfStream; CONTINUE }]; IF status = EndOfStream THEN currentStream.SetIndex[expCurrentEntryPos _ curPos]; }; ExpCarefullyApply[Ete, "ExamineThisEntry"]; }; ExpCarefullyApply: PROC[proc: PROC[], who: ROPE ] = { ENABLE BEGIN IO.Error => IF WalnutStream.Aborted[stream] THEN GOTO aborted ELSE { ed: FS.ErrorDesc; ed _ FS.ErrorFromStream[stream ! FS.Error, IO.Error => CONTINUE]; IF ed.code = $quotaExceeded THEN GOTO quota; WalnutRoot.ReturnExpungeStreams[]; currentStream _ expungeStream _ NIL; REJECT }; FS.Error => { IF error.code = $transAborted THEN GOTO aborted; IF error.code = $quotaExceeded THEN GOTO quota; WalnutRoot.ReturnExpungeStreams[]; currentStream _ expungeStream _ NIL; REJECT }; UNWIND => { WalnutRoot.ReturnExpungeStreams[]; currentStream _ expungeStream _ NIL; }; END; proc[]; EXITS aborted => { WalnutRoot.StatsReport["\n **** Trans abort during expunge doing "]; WalnutRoot.StatsReport[who]; WalnutRoot.ReturnExpungeStreams[]; currentStream _ expungeStream _ NIL; ERROR WalnutDefs.Error[$log, $TransactionAbort, "During expunge"]; }; quota => { WalnutRoot.StatsReport["\n **** Quota exceeded during expunge doing "]; WalnutRoot.StatsReport[who]; WalnutRoot.ReturnExpungeStreams[]; currentStream _ expungeStream _ NIL; ERROR WalnutDefs.Error[$log, $QuotaExceeded, "During expunge"]; }; }; END. άWalnutLogImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Willie-Sue, June 18, 1986 5:10:01 pm PDT Donahue, January 22, 1986 8:04:31 am PST Walnut Log Operations Implementation Last Edited by: Willie-Sue, January 10, 1985 12:27:10 pm PST Last Edited by: Wert, August 31, 1984 9:49:21 pm PDT Last Edited by: Donahue, December 11, 1984 10:37:45 am PST Plan The log streams provide an abstraction for WalnutOps use in handling the log. WalnutOps procedures deal only with the notion of writing an entry on the current log and reading the entry from a specified position. The log position for a write is always at the end of the current log. Types Signals WalnutDefs.Error: ERROR[who, code: ATOM, explanation: ROPE _ NIL] = CODE; Raised for a variety of reasons: the current set of codes used by WalnutLogImpl include $NotScanning $InvalidOperation $UnknownLogFileType $AccessFailed $LogInaccessible $TransactionAbort Variables for WalnutLog -- the read and write (and temp if open) streams are all under the same transaction; WalnutLogImpl must get them from WalnutRoot Variables for WalnutMiscLog Variables for WalnutLogExpunge Procedures Logging of operations that perform actions on the Walnut database Managing the current log Write a message on the current log Parsing a log (used by Archive and Replay/Scavenge Copies the next entry of the current log to an archive stream. Copying NewMail/ReadArchive logs makes sure the writeStream is long enought to copy the which log onto copies the which templog, starting at fromPos, to the end of the writeStream; writes an EndCopy log entry, and sets the length of which to 0; if the operation fails, WalnutDefs.Error is raised; NOT done inside carefullyApply, so that it can catch transaction aborts Utilities Local Utilities Writing and Reading NewMail/ReadArchive files 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 reads file and writes the ReadArchiveLogFile; returns ok if all went well ******************************************************** Read *start* from log back up and parse from the strm -- ProcessCategories line _ fileToRead.GetLine[generalBuffer]; entryChar _ RefText.Fetch[line, RefText.Length[line]-1]; Managing the ExpungeLog Writes the first log record on the expunge log, from information from the root; returns the internalFileID of the expunge log being written on Set the length of the expunge log and the read position of the current log to be the given values. If it returns NOT ok, then you must StartExpunge -- StartExpunge is always guaranteed to succeed (if space exists!) Copies bytesToCopy from the currentLog at its current position onto the end of the expungeLog; does not flush the expungeLog (must use GetExpungeProgress) Get the read position of the current log and the length of the expunge log (this information can later be used to restart an expunge that was in progress); forces a flush (commit) of the expunge log - raises WalnutDefs.Error with code = $LogTransAbort if the flush fails Parsing a log for Expunge returns at = -1 if there is not a valid entry at the current position. The entry is not "consumed" so that performing a NextEntry or a CopyEntry after reading the ident & msg ID information will copy or retrieve the entry returned. Performing successive PeekEntry's will NOT advance the log position; use SkipEntry to advance the log Does not check if ident is for a legal log entry type. returns NOT ok if there is not a valid entry at the current position, and does not advance the log position. If there is a valid entry, it is "consumed", and the log is advanced. Copies the next entry from the current log to the expunge log; it returns the new position that the entry now has in the expunge log; does NOT flush (commit) the expungeLog, raises WalnutDefs.Error with code = $ExpungeTransAbort if the expunge transaction aborts returns noValidEntry if there is not a valid entry at the current position, and does not advance the log position. If there is a valid entry, it is "consumed", and the log is advanced. IF IO.EndOfStream is raised, EndOfStream is returned and log is not advanced. Local utilities Κ1e˜šΟb™Jšœ Οmœ1™Jšœ˜Jšœ?˜?Jšœ1˜1J˜—J˜š‘œŸœŸœ ŸœŸœŸœŸœ˜NJšŸœ Ÿœ˜Jšœ/˜/Jšœ+˜+Jšœ˜JšœK˜KJšœ)˜)Jšœ7˜7Jšœ˜—J˜š ‘œŸœŸœŸœ Ÿœ˜6Jšœ;˜;—J˜š ‘ œŸœŸœŸœ Ÿœ˜3Jšœ8˜8—J˜š‘œŸœŸœŸœ ŸœŸœ Ÿœ˜VJšœ˜JšœJ˜JJšœ˜JšœN˜NJšœ:˜:J˜—J˜š‘œŸœŸ œ Ÿœ˜8Jšœ=˜=—J˜š ‘œŸœŸœŸœ Ÿœ˜˜>JšŸœ Ÿœ&˜6Jšœ˜—J˜—J˜š‘œŸœŸœ˜!JšŸœŸœŸœŸœ˜!Jšœ:˜:JšŸœ Ÿœ&˜6J˜—J˜š‘œŸœŸœ Ÿœ˜)š‘œŸœ˜ Jšœ ˜ Jšœ:˜:Jšœ ˜ Jšœ˜J˜—J˜J˜—J˜š‘œŸœŸœ˜(JšœŸœ˜Jšœ Ÿœ˜Jšœ˜—J˜š‘ œŸœŸœ˜Jšœ˜JšœŸœ˜Jšœ Ÿœ˜Jšœ˜——š"™"š ‘ œŸœŸœŸœŸœŸœ˜Mš‘œŸœ˜Jšœ.˜.Jšœ˜Jšœ=˜=šŸœŸœ˜&JšœŸœ˜'šŸœ#Ÿœ ˜>šœ8˜8J˜6—J˜—JšŸœ/˜3J˜—Jšœ8˜8Jšœ<˜š‘œŸœ˜JšœŸœ˜ šŸœŸ˜#šœ˜Jšœ*˜*——JšŸœŸœŸœ˜+šœ˜Jšœ7˜7—Jšœ(˜(Jšœ  ˜(JšœŸœ˜ Jšœ˜—JšŸœ Ÿ œ&˜=Jšœ$˜$J˜—J˜š ‘œŸœŸœŸœŸœŸœ˜Fš‘œŸœ˜ Jšœ˜Jšœ;˜;Jšœ Ÿœ˜J˜—Jšœ*˜*J˜——š ™ J˜š‘œŸœŸœ;ŸœŸœŸœŸœŸœŸœ˜’JšœE™EJšœc˜cJšŸœŸœ˜J˜—J˜š‘ œŸœŸœ>Ÿœ ŸœŸœŸœ˜Jšœ‰™‰JšœŸœ˜ J˜JšœŸœŸœ˜JšœŸœŸœ˜Jšœ Ÿœ˜JšœŸœŸœ˜*šŸœŸœŸ˜šŸœ Ÿ˜ JšœŸœ)˜2JšŸœŸœŸœ˜&šŸœŸœ˜!Jšœ3˜3JšŸœ˜ Jšœ˜—JšŸœ˜J˜—šŸœ Ÿœ˜ JšŸœŸœŸœ˜-šŸœ Ÿœ˜(Jšœ˜JšŸœ˜ Jšœ˜—JšŸœ˜J˜—Jšœ"ŸœŸœ˜5JšŸœ˜J˜šŸœŸ˜˜ J˜—J˜J˜J˜šŸ˜šœ ˜ JšœS˜SJšŸœ?˜DJ˜—šœ ˜ JšœZ˜ZJšŸœ0˜5J˜—šœ ˜ JšŸœ9˜>—šœ˜JšŸœG˜L——JšŸœ˜J˜——š-™-š ‘ œŸœŸœŸœŸœ˜AJšŸœŸœ˜J™ΑšŸœŸœŸœŸœ˜8šŸœŸœŸœ˜Jšœ˜JšŸœ˜ J˜—JšŸœŸœŸœ˜+J˜šŸœ˜Jš œAŸœŸœŸœŸœ˜V—J˜šŸ˜JšœŸœŸœŸœ˜3—JšŸœ˜—JšŸœ˜J˜—J˜š‘œŸœŸœ˜ JšœŸœ˜JšŸœŸœŸœŸœ˜JšœŸœ˜Jšœ&ŸœŸœ Ÿœ˜FJšœ˜—J˜š‘ œŸœ˜JšŸœŸœŸœŸœ˜#Jšœ)ŸœŸœ Ÿœ˜IJšœ˜J˜—J˜š‘œŸœŸœŸœ ŸœŸœŸœŸœŸœŸœ Ÿœ˜˜J™JJšœŸœ˜JšœŸœŸœ'˜@JšœŸœŸœ- ˜TJšœŸœ˜ Jšœ Ÿœ˜ šŸœŸœŸ˜šŸœ ˜ Jšœ Ÿœ)˜4JšŸœ ŸœŸœ Ÿœ0˜PJšŸœ ŸœŸœ1˜EJšŸœ˜ J˜—JšŸœ(Ÿœ˜6Jšœ;Ÿœ˜FJšŸœŸœ˜JšŸœ˜J˜šŸœPŸœŸ˜ZJšŸœŸœ!˜-—J˜Jšœ˜JšœR˜RJ˜J˜J˜šœ0˜0Jšœ9˜9JšœC˜C—J˜JšŸœŸœ˜"J˜šŸ˜šœ˜šŸœŸœŸœ˜!Jšœ6˜6JšœŸœ˜J˜———JšŸœ˜—J˜—J˜š‘œŸœŸœ˜$Jšœ6˜6JšœŸœ˜J˜—J˜š‘ œŸœŸœ˜Jšœ&Ÿœ˜,———˜Jšœ8™8J™JšœŸœ˜,˜Jš‘ œŸ œŸœ ŸœŸœŸœŸœŸœŸœŸœŸœ˜ΤJ˜JšœŸœ˜ š‘ œŸœŸœ˜JšŸœŸœŸœŸœ˜ šœ ˜ JšŸœ*ŸœŸœ ˜X—J˜J˜šŸœŸœŸ˜JšŸœ ŸœŸœ ˜'šŸœ ˜ JšŸœŸœŸœ˜7JšœŸœŸœ˜J˜—šŸœ ˜ šŸœŸœŸœ˜JšœŸœŸœ)˜:šŸœ ŸœŸ˜šœ Ÿœ?˜JJšŸœŸœ˜0—JšŸœ Ÿœ,˜<—šŸœ ŸœŸ˜šœ˜šŸœE˜GJšŸœŸœ ˜3——šŸœ ˜ JšŸœ Ÿœ!˜E——Jšœ!˜!J˜—JšœŸœŸœ˜Jšœ˜—JšŸœ3ŸœŸœ ˜IJšŸœ˜—J˜J˜+Jšœ(Ÿœ˜,Jšœ Ÿœ˜Jšœ ŸœŸœ˜(JšœŸœ˜JšœŸœ   ˜4JšœŸœ˜1JšœŸœ˜J˜JšœŸœ ˜2JšœŸœ˜ Jšœ4˜4Jšœ2˜2Jšœ3˜3Jšœ,˜,J˜šŸ˜Jšœ™JšœS˜Sš ŸœŸœŸœŸœŸœŸœ˜eJ˜&J˜$Jšœ Ÿœ1Ÿœ ˜MJšŸœŸœŸœŸœ˜:JšŸœŸœŸœŸœ˜>JšŸœŸœ˜)J˜—J˜šŸœ Ÿ˜šœ˜Jšœ Ÿœ-Ÿœ˜NJšœ,˜,Jšœ˜J˜—šŸœ ˜'JšŸœ ˜JšœŸœ˜Jšœ Ÿœ˜Jšœ ŸœŸœŸœ˜JšœŸœ˜Jšœ Ÿœ˜+Jšœ ˜ J˜šŸœŸ˜!Jšœ0˜0—J˜šŸœŸœ˜JšœI˜IšŸœ Ÿœ˜Jšœ Ÿœ(Ÿœ˜IJšŸœ˜—šœ) ˜8Jšœ2˜2—JšŸœŸœŸœ ˜E—˜šŸœŸœ ˜>Jšœ™J˜Jšœ Ÿœ˜Jšœ;˜;Jšœ)˜)Jšœ ˜ Jšœ5˜5šœŸœ+ŸœŸ˜=šœ/Ÿœ˜5JšŸœ˜ JšŸœ ˜——JšŸœŸœ˜/JšŸœŸœ˜@J˜Jšœ=˜=Jšœ+˜+J˜Jšœ-˜-J˜—J˜šŸœŸœ˜&JšœŸœ˜ šŸœ .˜2Jšœ&˜&JšŸœ ŸœŸœ˜JšœE˜EJšŸœ˜—J˜—J˜J˜šŸœ˜JšœŸœ˜ šœ!˜!šœ˜JšœD˜D——šŸœŸœ˜šœ ˜ JšŸœ&Ÿœ˜?—JšŸœ˜J˜—J˜—J˜——šŸœŸœŸ˜šŸœ ˜ Jš ŸœŸœŸœ ŸœŸœ˜<—šŸœ ˜ Jš Ÿœ%ŸœŸœ ŸœŸœ˜E—JšŸœ˜J˜Jšœ#˜#Jšœ8˜8Jšœ<˜<šœ˜Jšœ?˜?—Jšœ/˜/J˜JšŸœŸœŸœ˜6JšœN˜NJ˜šŸœŸœ˜Jšœ%˜%JšœF˜FJ˜—J˜Jšœ™šŸ˜JšœŸœŸœ˜šŸœŸœŸœŸœŸœŸœŸœŸ˜;šŸœŸœ Ÿœ˜Jšœ!˜!Jšœ)˜)Jšœ#˜#JšœA˜AJ˜—šŸœ˜Jšœ ˜ Jšœ"˜"JšœA˜AJ˜—JšœŸœ˜JšŸœ˜——JšŸœ˜J˜šŸœiŸœ˜qJ˜(J˜Jšœ4˜4Jšœ˜J˜—J˜šŸ˜šœ Ÿ˜ J˜&J˜$JšŸœŸœ˜)J˜——JšŸœ @˜FJ˜šŸœŸœŸœ˜$Jš ŸœŸœ Ÿœ ŸœŸœ ˜AJšœŸœ˜J˜—JšŸœ˜JšŸœ˜Jšœ-˜-Jšœ˜——JšŸœ˜˜JšŸœ Ÿœ˜—JšŸœ˜—J˜J˜&J˜$Jšœ Ÿœ1Ÿœ ˜MJšŸœ ˜&Jšœ˜—J˜š ‘œŸœŸœŸœŸœ˜/JšŸœŸœ ŸœŸœŸœ ŸœŸœ˜PJ˜J˜J˜JšŸœŸœ˜ šŸ˜Jšœ ŸœŸœ˜—Jšœ˜J˜—š‘œŸœŸœ ŸœŸœŸœŸœ ŸœŸœŸœŸœŸœ˜ŠJšœŸœ˜ š ‘ œŸœŸœŸœ Ÿœ˜<šŸœ!Ÿœ˜)JšŸœŸœŸœ˜—J˜šŸœŸœŸœŸœŸœŸœŸœŸ˜CJš ŸœŸœŸœŸœŸœ˜8JšŸœ˜—J˜J˜&JšœG˜GJšœŸœ  ˜JJšŸœŸœ˜ J˜—J˜Jšœ Ÿœ˜JšœŸœ˜šŸœŸœŸ˜š ŸœŸœŸœŸœŸœ˜5šŸœ˜Jšœ"˜"šŸœ ŸœŸœŸœ˜5JšŸœŸœŸœŸœ˜—J˜———J˜šŸœŸœ˜$Jšœ"˜"JšŸœ Ÿœ Ÿœ ˜+J˜J˜—JšœŸœŸœ ˜šŸœ Ÿ˜JšœŸœŸœ˜9šŸœŸœ˜Jšœ ŸœŸœ˜&Jšœ Ÿœ,˜;JšœŸœ ˜(JšŸœ Ÿœ Ÿœ˜3J˜—JšŸœ˜Jšœ ˜ —J˜—J˜š‘œŸœŸœŸœ˜GJšœŸœ˜Jšœ ˜ šŸœŸœ ˜ŸœŸœŸœ ˜[šŸœXŸ˜^JšŸœŸœ ˜—J˜JšŸœŸœ ˜J˜——J˜—š™š ‘ œŸ œŸœŸœŸœ˜KJšœŽ™ŽJšœŸœ ˜Jšœ Ÿœ˜JšœŸœ ˜J˜šŸ œŸ˜JšŸœŸœ˜)JšŸœ7Ÿœ˜DJšŸœ&Ÿœ˜2JšŸœ˜J˜šœH˜HJšœ"˜"—J˜JšœŸœ@˜PšŸœŸ˜!šŸœ Ÿœ ˜-Jš ŸœŸœŸœŸœŸœ˜<——J˜šŸœŸœ˜'Jšœ4˜4Jšœ˜Jšœ˜J˜—J˜JšŸœ5˜7Jšœ2˜2Jšœ@˜@Jšœ-˜-JšœF˜FJšœ(Ÿœ˜.J˜JšŸœ˜J˜šŸ˜šœ ˜ šŸœ.˜3JšŸœKŸœŸœ˜x——šœ˜J˜7J˜+JšŸœD˜IJ˜——JšŸœ˜—Jšœ˜—J˜š ‘œŸ œ"ŸœŸœŸœ˜WJ™Χš‘œŸœ˜JšœŸœ˜(Jšœ ˜ JšœŸœŸœ˜Jšœ.Ÿœ˜2JšœŸœ ˜J˜šŸœŸœŸ˜JšŸœŸœ ˜+JšŸœ7Ÿœ ˜FJšŸœ&Ÿœ˜2JšŸœ˜—JšœŸœ˜ J˜šœH˜HJšœ"˜"—J˜JšŸœŸœŸœŸœ˜#JšŸœ.Ÿ œ  ˜JJ˜Jšœ˜Jšœ.˜.JšŸœŸœŸœŸœ˜šŸœ˜ šŸœŸœŸ˜šœ˜Jšœ˜J˜-J˜!Jšœ˜—JšŸœŸœ˜—J˜—JšŸœŸœ,˜HJšŸœ6ŸœŸœ˜DJšŸœ2ŸœŸœ˜@JšŸœŸœŸœ˜-J˜JšœB˜BJ˜Jšœ(˜(Jšœ&˜&Jšœ˜Jšœ ˜ šœŸœ˜ šŸœ ˜J˜7J˜+JšœŸœ˜ J˜——JšŸœ˜J˜—J˜šŸœŸœ˜ J˜"Jšœ Ÿœ˜$J˜—J˜—J˜š‘œŸœŸœŸœ˜8Jšœ›™›š‘œŸœ˜Jšœ3  ˜@šœ˜Jšœ:˜:—Jšœ+˜+Jšœ!  ˜+J˜—J˜Jšœ1˜1J˜—J˜š ‘œŸœŸœŸœ"Ÿœ˜QJšœ™š‘œŸœ˜ Jšœ(Ÿœ˜.J˜Jšœ ˜ Jšœ-˜-J˜—Jšœ-˜-J˜—J™š‘ œŸ œ˜Jšœ"Ÿœ˜(—J˜š‘ œŸ œ˜Jšœ"Ÿœ˜(——š™š ‘œŸœŸœ ŸœŸœŸœ˜IJšœŸœ˜ š‘œŸœ˜Jšœ!˜!Jšœ1˜1Jšœ˜JšŸœ Ÿœ˜,J˜—Jšœ(˜(JšŸœŸœ ŸœŸœ˜4J˜—J˜š‘ œŸ œŸœ˜&š‘œŸœ˜ Jšœ˜Jšœ˜Jšœ˜Jšœ˜—Jšœ"˜"J˜—J˜š‘ œŸ œŸœŸœ ŸœŸœŸœ˜IJšœ†™†š‘œŸœ˜ JšœŸœ˜ Jšœ?˜?Jšœ#˜#šŸœ Ÿœ˜šŸœ/˜1JšŸœŸœ ˜*JšŸœ˜J˜——Jšœ˜J˜—Jšœ$˜$J˜—J™š‘ œŸ œŸœŸœ˜,Jšœ³™³š‘œŸœ˜ JšœŸœ˜ JšœŸœ˜ šŸœ Ÿ˜&Jšœ5˜5šŸœ˜Jšœ#˜#Jšœ˜Jšœ˜——JšŸœ ŸœŸœ˜JšœO˜OJšœŸœ˜ J˜—Jšœ$˜$J˜—J˜š‘ œŸ œŸœŸœ˜AJšœ‡™‡š‘œŸœ˜ Jšœ˜Jšœ'˜'šŸœ Ÿ˜&JšœI˜I—JšŸœ ŸœŸœ˜Bšœ˜JšœH˜H—J˜Jšœ.˜.Jšœ(˜(Jšœ!  ˜+J˜—Jšœ#˜#Jšœ˜—J˜š ‘ œŸœŸœŸœŸœ˜8š‘œŸœ˜Jšœ9˜9Jšœ+˜+šŸœŸœŸ˜JšŸœ9˜>—J˜šŸœŸœ ŸœŸ˜"Jšœ5˜5Jšœ9˜9šŸœ˜ JšŸœ<˜A—J˜—J˜—Jšœ˜Jšœ-˜-J˜—J˜š ‘ œŸœŸœŸœ Ÿœ˜2JšŸœŸœŸ œ˜(JšŸœ˜"J˜—J˜š ‘œŸœŸœŸœ Ÿœ˜.JšŸœŸœŸ œ˜(JšŸœ˜!J˜—J˜š ‘ œŸœŸœŸœŸœ˜2š‘œŸœ˜ J˜ Jšœ@˜@Jšœ.˜.Jšœ!  ˜+J˜—Jšœ#˜#Jšœ˜—J˜š‘œŸœ*˜OJšœΉ™ΉJšœM™Mš‘œŸœ˜ JšœŸœ˜ JšœŸœ˜ Jšœ˜šŸœ Ÿ˜&Jšœ5˜5šŸœ˜Jšœ#˜#Jšœ˜Jšœ˜——JšŸœ ŸœŸœ˜5Jšœ˜šœ˜JšœC˜CJšœŸœ(Ÿœ˜8—JšŸœŸœ5˜QJ˜—Jšœ+˜+J˜—J˜—™š‘œŸœŸœ Ÿœ˜5šŸœŸ˜ š Ÿœ ŸœŸœŸœ Ÿœ˜DJšœŸœ ˜Jš œŸœŸœŸœ Ÿœ˜AJšœŸœŸœ˜,J˜"Jšœ Ÿœ˜$JšŸ˜Jšœ˜—šŸœ Ÿ˜ JšŸœŸœŸœ ˜0JšŸœŸœŸœ˜/J˜"Jšœ Ÿœ˜$JšŸ˜Jšœ˜—šŸœ˜ J˜"Jšœ Ÿœ˜$J˜—JšŸœ˜J˜J˜šŸ˜šœ ˜ J˜DJ˜J˜"Jšœ Ÿœ˜$JšŸœ=˜BJ˜—šœ ˜ J˜GJ˜J˜"Jšœ Ÿœ˜$JšŸœ:˜?J˜———J˜——˜J˜JšŸœ˜——…—”~ΦΏ