<> <> <> <> <<>> <> <<>> <> <> <> 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; <<>> <> <> <> <<$NotScanning>> <<$InvalidOperation>> <<$UnknownLogFileType>> <<$AccessFailed>> <<$LogInaccessible>> <<$TransactionAbort>> <<>> <<>> <> <<>> <<-- the read and write (and temp if open) streams are all under the same transaction; WalnutLogImpl must get them from WalnutRoot>> << >> 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]; }; <<-- 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]; }; <> 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.