<<>> <> <> <> <> <> <> <> <> <<>> <> DIRECTORY BasicTime USING [GMT, nullGMT, Now], FS USING [BytesForPages, Error, ErrorDesc, PagesForBytes], IO, MailUtils USING [GeneratePostmark], RefText USING [line, Length, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope, RuntimeError USING [BoundsFault], SendMailOps USING [IsThisTheCurrentUser, TiogaTextFromStrm, RopeFromStream], ViewerTools USING [TiogaContentsRec, TiogaContents], WalnutDefs USING [CheckReportProc, Error, LogInfo, WalnutOpsHandle], WalnutKernelDefs USING [LogEntry, LogEntryObject, MsgLogEntry, WhichTempLog], WalnutLog, WalnutMiscLog USING [TiogaControlItemType], WalnutRoot -- using lots -- , WalnutStream -- using lots -- ; WalnutLogImpl: CEDAR PROGRAM IMPORTS BasicTime, FS, IO, MailUtils, RefText, Rope, RuntimeError, SendMailOps, WalnutDefs, WalnutRoot, WalnutStream EXPORTS WalnutDefs, WalnutLog, 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; LogInfo: TYPE = WalnutDefs.LogInfo; WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle; LogHandle: TYPE = WalnutLog.LogHandle; LogHandleRec: PUBLIC TYPE = WalnutLog.LogHandleRec; RootHandle: TYPE = WalnutRoot.RootHandle; RootHandleRec: PUBLIC TYPE = WalnutRoot.RootHandleRec; InternalLogInfo: TYPE = WalnutRoot.InternalLogInfo; <<>> <> <> <<>> msgIDRope: ROPE = "gvMsgID"; categoriesRope: ROPE = "Categories"; activeMsgSet: ROPE = "Active"; newMsgSetList: LIST OF ROPE; generalBuffer: REF TEXT; <<>> <> <<>> <> ExpungeMsgs: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at, next: INT] = { [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.expungeMsgs] }; <<>> WriteExpungeLog: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at, next: INT] = { [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.writeExpungeLog]; }; CreateMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, name: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.createMsgSet.msgSet ¬ name; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.createMsgSet] }; EmptyMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, msgSet: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.emptyMsgSet.msgSet ¬ msgSet; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.emptyMsgSet] }; DestroyMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, msgSet: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.destroyMsgSet.msgSet ¬ msgSet; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.destroyMsgSet] }; AddMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg, to: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.addMsg.msg ¬ msg; WalnutStream.logInfoRef.addMsg.to ¬ to; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.addMsg] }; RemoveMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg, from: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.removeMsg.msg ¬ msg; WalnutStream.logInfoRef.removeMsg.from ¬ from; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.removeMsg] }; MoveMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg, from, to: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.moveMsg.msg ¬ msg; WalnutStream.logInfoRef.moveMsg.from ¬ from; WalnutStream.logInfoRef.moveMsg.to ¬ to; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.moveMsg] }; HasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.hasbeenRead.msg ¬ msg; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.hasbeenRead] }; RecordNewMailInfo: PUBLIC PROC[opsH: WalnutOpsHandle, logLen: INT, when: GMT, server: ROPE, num: INT] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.recordNewMailInfo.logLen ¬ logLen; WalnutStream.logInfoRef.recordNewMailInfo.when ¬ when; WalnutStream.logInfoRef.recordNewMailInfo.server ¬ server; WalnutStream.logInfoRef.recordNewMailInfo.num ¬ num; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.recordNewMailInfo] }; StartCopyNewMail: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at, next: INT] = { [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.startCopyNewMail] }; AcceptNewMail: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at, next: INT] = { [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.acceptNewMail] }; StartReadArchiveFile: PUBLIC PROC[opsH: WalnutOpsHandle, file: ROPE, msgSet: ROPE] RETURNS[at, next: INT] = { WalnutStream.logInfoRef.startReadArchiveFile.file ¬ file; WalnutStream.logInfoRef.startReadArchiveFile.msgSet ¬ msgSet; [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.startReadArchiveFile] }; EndReadArchiveFile: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at, next: INT] = { [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.endReadArchiveFile] }; StartCopyReadArchive: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at, next: INT] = { [at, next] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.startCopyReadArchive] }; <<>> <> InitLogStream: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[BOOL] = { opsH.logHandle ¬ NEW[LogHandleRec ¬ [] ]; OpenLogStreams[opsH]; RETURN[TRUE]; }; LogLength: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[length: INT] = { li: InternalLogInfo = opsH.rootHandle.currentLog; RETURN[ IF li.writeStream # NIL THEN li.writeStream.GetLength[] ELSE li.readStream.GetLength[] ]; }; OpenLogStreams: PUBLIC PROC[opsH: WalnutOpsHandle] = { IF opsH.rootHandle.currentLog = NIL OR opsH.rootHandle.currentLog.readStream = NIL THEN { WalnutRoot.OpenLogStreams[opsH]; IF opsH.logHandle = NIL THEN opsH.logHandle ¬ NEW[LogHandleRec ¬ [] ]; IF opsH.logHandle.scanning THEN SetLogIndex[opsH, opsH.logHandle.entryPos, TRUE]; }; }; ReleaseWriteLock: PUBLIC PROC[opsH: WalnutOpsHandle] = { logH: LogHandle = opsH.logHandle; IF opsH.rootHandle.currentLog.writeStream = NIL THEN RETURN; WalnutRoot.ReleaseWriteLock[opsH]; IF logH.scanning THEN SetLogIndex[opsH, logH.entryPos, TRUE]; }; ResetLog: PUBLIC PROC[opsH: WalnutOpsHandle, newLength: INT] = { strm: STREAM; IF opsH.logHandle = NIL THEN RETURN; SetLogIndex[opsH, newLength, TRUE]; WalnutStream.SetHighWaterMark[strm ¬ opsH.rootHandle.currentLog.writeStream, newLength, -1]; strm.SetLength[newLength]; strm.Flush[]; <> strm.SetIndex[newLength]; opsH.logHandle.scanning ¬ FALSE; }; ForgetLogStreams: PUBLIC PROC[opsH: WalnutOpsHandle] = { logH: LogHandle = opsH.logHandle; IF logH = NIL THEN RETURN; logH.scanning ¬ FALSE; logH.strm ¬ NIL; logH.scanningLog ¬ NIL; }; ShutdownLog: PUBLIC PROC[opsH: WalnutOpsHandle] = { logH: LogHandle = opsH.logHandle; IF logH = NIL THEN RETURN; logH.scanning ¬ FALSE; logH.strm ¬ NIL; logH.scanningLog ¬ NIL; WalnutRoot.CloseTransaction[opsH]; }; <<>> <> WriteMessage: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE, body: TiogaContents] RETURNS[at: INT]= { wStrm: STREAM = opsH.rootHandle.currentLog.writeStream; wStrm.SetIndex[wStrm.GetLength[]]; WalnutStream.logInfoRef.createMsg.msg ¬ msg; IF ( body.formatting.Length[] # 0 ) AND ( body.contents.Fetch[0] = '\r OR body.contents.Fetch[0] = '\l ) THEN { last: INT ¬ body.contents.Length[] - 1; IF body.contents.Fetch[last] = '\000 THEN -- NUL for padding { body.contents ¬ Rope.Substr[body.contents, 1, last-1]; body.formatting ¬ Rope.Concat["\000", body.formatting] } ELSE IF body.contents.Fetch[0] = '\r OR body.contents.Fetch[0] = '\l THEN body.contents ¬ Rope.Substr[body.contents, 1] }; WalnutStream.logInfoRef.createMsg.textLen ¬ body.contents.Length[]; WalnutStream.logInfoRef.createMsg.formatLen ¬ body.formatting.Length[]; at ¬ WalnutStream.WriteEntry[wStrm, WalnutStream.logInfoRef.createMsg]; WalnutStream.WriteMsgBody[wStrm, body]; WalnutStream.FlushStream[strm: wStrm, setCreateDate: TRUE]; }; <<>> <> SetPosition: PUBLIC PROC[opsH: WalnutOpsHandle, startPos: INT] RETURNS[charsSkipped: INT] = { next: INT; logH: LogHandle = opsH.logHandle; SetLogIndex[opsH, startPos, TRUE]; next ¬ WalnutStream.FindNextEntry[logH.strm]; logH.entryLengthHint ¬ -1; IF next # -1 THEN logH.entryPos ¬ next; RETURN[IF next = -1 THEN next ELSE next - startPos]; }; SetIndex: PUBLIC PROC[opsH: WalnutOpsHandle, pos: INT] = { logH: LogHandle = opsH.logHandle; SetLogIndex[opsH, pos, TRUE]; logH.entryLengthHint ¬ -1; logH.entryPos ¬ pos; }; NextAt: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[at: INT] = { RETURN[opsH.logHandle.entryPos] }; NextEntry: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[le: LogEntry, at: INT] = { [le, at] ¬ NextEntryInternal[opsH, FALSE] }; NextEntryInternal: PROC[opsH: WalnutOpsHandle, quick: BOOL] RETURNS[le: LogEntry, at: INT] = { logH: LogHandle = opsH.logHandle; length, startPos: INT; IF ~logH.scanning THEN ERROR WalnutDefs.Error[$log, $NotScanning]; [le, length] ¬ WalnutStream.ReadEntry[logH.strm, quick]; startPos ¬ logH.entryPos; logH.entryLengthHint ¬ -1; IF length # -1 THEN logH.entryPos ¬ startPos + length; -- normal case RETURN[le, startPos]; }; QuickScan: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[le: LogEntry, at: INT] = { [le, at] ¬ NextEntryInternal[opsH: opsH, quick: TRUE] }; ArchiveEntry: PUBLIC PROC[opsH: WalnutOpsHandle, to: STREAM] RETURNS[ok: BOOL]= { <> logH: LogHandle = opsH.logHandle; IF ~logH.scanning THEN ERROR WalnutDefs.Error[$log, $NotScanning]; ok ¬ FALSE; IF logH.entryLengthHint = -1 THEN logH.entryLengthHint ¬ WalnutStream.PeekEntry[logH.strm, TRUE].length; IF logH.entryLengthHint = -1 THEN RETURN; WalnutStream.CopyBytes[ from: logH.strm, to: to, num: logH.entryLengthHint ]; logH.entryPos ¬ logH.strm.GetIndex[]; logH.entryLengthHint ¬ -1; -- unknown ok ¬ TRUE; }; CopyBytesToArchive: PUBLIC PROC[ opsH: WalnutOpsHandle, to: STREAM, startPos, num: INT] = { SetLogIndex[opsH, startPos, FALSE]; WalnutStream.CopyBytes[from: opsH.logHandle.strm, to: to, num: num]; }; <<>> <> PrepareToCopyTempLog: PUBLIC PROC[opsH: WalnutOpsHandle, which: WalnutKernelDefs.WhichTempLog, pagesAlreadyCopied: INT, reportProc: WalnutDefs.CheckReportProc] RETURNS[BOOL] = { <> RETURN[WalnutRoot.PrepareToCopyTempLog[opsH, which, pagesAlreadyCopied, reportProc] ]; }; CopyTempLog: PUBLIC PROC[opsH: WalnutOpsHandle, which: WalnutKernelDefs.WhichTempLog, startCopyPos, fromPos: INT, reportProc: WalnutDefs.CheckReportProc ] = { <> exp: ROPE; endLE: LogEntry; abort: BOOL ¬ TRUE; ok: BOOL ¬ FALSE; copyStrm, wStrm: STREAM; bytesToCopy: INT; bytesPerCopy: INT = FS.BytesForPages[200]; opsH.logHandle.scanning ¬ FALSE; BEGIN ENABLE BEGIN IO.Error => { ed: FS.ErrorDesc; < CONTINUE];>> IF ed.code = $transAborted OR ed.code = $remoteCallFailed THEN { exp ¬ ed.explanation; GOTO exit }; REJECT; }; FS.Error => { IF error.code = $transAborted THEN GOTO exit; IF error.code = $remoteCallFailed THEN { exp ¬ error.explanation; GOTO exit; }; REJECT; }; END; SELECT which FROM newMail => { WalnutStream.logInfoRef.endCopyNewMailInfo.startCopyPos ¬ startCopyPos; endLE ¬ WalnutStream.logInfoRef.endCopyNewMailInfo; }; readArchive => { WalnutStream.logInfoRef.endCopyReadArchiveInfo.startCopyPos ¬ startCopyPos; endLE ¬ WalnutStream.logInfoRef.endCopyReadArchiveInfo }; ENDCASE => ERROR WalnutDefs.Error[$log, $UnknownLogFileType]; copyStrm ¬ WalnutRoot.GetStreamsForCopy[opsH, which]; wStrm ¬ opsH.rootHandle.currentLog.writeStream; <> wStrm.SetIndex[wStrm.GetLength[]]; bytesToCopy ¬ copyStrm.GetLength[] - fromPos; IF bytesToCopy > 0 THEN { first: BOOL ¬ TRUE; copyStrm.SetIndex[fromPos]; <<<> -- Moved outside conditional.>> DO thisCopy: INT ¬ MIN[bytesPerCopy, bytesToCopy]; WalnutStream.CopyBytes[to: wStrm, from: copyStrm, num: thisCopy]; WalnutStream.FlushStream[wStrm, TRUE]; WalnutRoot.CommitAndContinue[opsH]; IF first THEN first ¬ FALSE ELSE reportProc[opsH, "#"]; bytesToCopy ¬ bytesToCopy - thisCopy; IF bytesToCopy = 0 THEN EXIT; ENDLOOP; }; [] ¬ WalnutStream.WriteEntry[wStrm, endLE]; WalnutStream.FlushStream[wStrm, TRUE]; WalnutRoot.CommitAndContinue[opsH]; EXITS exit => { who: ROPE ¬ IF which = newMail THEN "NewMail" ELSE "ReadArchive"; IF ~abort THEN ERROR WalnutDefs.Error[$log, $AccessFailed, who]; WalnutRoot.AbortTempCopy[opsH, which]; WalnutRoot.CloseTransaction[opsH]; ERROR WalnutDefs.Error[ $log, IF exp = NIL THEN $TransactionAbort ELSE $RemoteCallFailed, IO.PutFR1["Copying the %g log", [rope[who]]] ]; }; END; WalnutRoot.FinishCopy[opsH, which]; }; FinishTempLogCopy: PUBLIC PROC[opsH: WalnutOpsHandle, which: WalnutKernelDefs.WhichTempLog] = { WalnutRoot.FinishCopy[opsH, which] }; CreateArchiveLog: PUBLIC PROC[ opsH: WalnutOpsHandle, fileToRead: STREAM, msgSet: ROPE, reportProc: WalnutDefs.CheckReportProc] RETURNS[ok: BOOL] = { reason: ROPE; [ok, reason] ¬ CreateReadArchiveLog[opsH, fileToRead, msgSet, reportProc]; IF ok THEN [] ¬ WriteEntry[opsH, WalnutStream.logInfoRef.endReadArchiveFile]; WalnutRoot.ReturnReadArchiveStream[opsH]; }; <<>> <> GetTiogaContents: PUBLIC PROC[opsH: WalnutOpsHandle, textStart, textLen, formatLen: INT] RETURNS[contents: TiogaContents] = { logH: LogHandle = opsH.logHandle; IF formatLen # 0 THEN -- tioga formatting nonsense { textLen ¬ textLen + 1; textStart ¬ textStart - 1}; SetLogIndex[opsH, textStart, FALSE]; contents ¬ NEW[ViewerTools.TiogaContentsRec]; contents.contents ¬ WalnutStream.ReadRope[logH.strm, textLen]; contents.formatting ¬ WalnutStream.ReadRope[logH.strm, formatLen]; }; GetRefTextFromLog: PUBLIC PROC[opsH: WalnutOpsHandle, startPos, length: INT, text: REF TEXT] = { natLen: NAT; bytesRead: NAT; logH: LogHandle = opsH.logHandle; CheckSize: PROC[len: INT] RETURNS[nat: NAT] = { nat ¬ len }; natLen ¬ CheckSize[length ! RuntimeError.BoundsFault => GOTO noGood]; SetLogIndex[opsH, startPos, FALSE]; bytesRead ¬ logH.strm.GetBlock[block: text, startIndex: 0, count: natLen]; IF bytesRead < natLen THEN text.length ¬ 0; EXITS noGood => ERROR WalnutDefs.Error[$log, $BadLength, IO.PutFR1[" Can't convert %g into a nat", [integer[length]] ]]; }; <<>> <> SetLogIndex: PROC[opsH: WalnutOpsHandle, pos: INT, setScan: BOOL] = { logH: LogHandle = opsH.logHandle; logH.strm ¬ opsH.rootHandle.currentLog.readStream; IF ( logH.scanning ¬ setScan ) THEN logH.scanningLog ¬ opsH.rootHandle.currentLog; logH.strm.SetIndex[pos ! IO.Error => GOTO err]; EXITS err => ERROR WalnutDefs.Error[$log, $BadLogIndex, IO.PutFR1["Pos beyond end of log %g", [integer[pos]]] ]; }; WriteEntry: PROC[opsH: WalnutOpsHandle, le: LogEntry] RETURNS[at, next: INT] = { wStrm: STREAM = opsH.rootHandle.currentLog.writeStream; at ¬ WalnutStream.WriteEntry[wStrm, le]; WalnutStream.FlushStream[wStrm, TRUE]; next ¬ wStrm.GetIndex[]; }; <<>> <> GetNewMailLog: PUBLIC PROC[opsH: WalnutOpsHandle, lengthRequired, pagesWanted: INT] RETURNS[STREAM] = { <> BEGIN ENABLE IO.Error, FS.Error => { AbortMailLog[opsH]; GOTO exit; }; AbortMailLog[opsH]; IF NOT WalnutRoot.GetNewMailStream[opsH, lengthRequired, pagesWanted].ok THEN RETURN[NIL]; EXITS exit => AbortMailLog[opsH]; END; RETURN[opsH.rootHandle.newMailLog.stream]; }; CloseNewMailLog: PUBLIC PROC[opsH: WalnutOpsHandle] = { mStrm: STREAM = opsH.rootHandle.newMailLog.stream; IF mStrm = NIL THEN RETURN; WalnutRoot.ReturnNewMailStream[opsH]; }; AbortMailLog: PROC[opsH: WalnutOpsHandle] = { mStrm: STREAM = opsH.rootHandle.newMailLog.stream; IF mStrm = NIL THEN RETURN; WalnutStream.AbortStream[mStrm ! IO.Error, FS.Error => CONTINUE]; WalnutRoot.ReturnNewMailStream[opsH]; }; CreateReadArchiveLog: PUBLIC PROC[opsH: WalnutOpsHandle, fileToRead: STREAM, msgSet: ROPE, reportProc: WalnutDefs.CheckReportProc] RETURNS[ok: BOOL, reason: ROPE] = { <> readArchiveStrm: STREAM; 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 => { <> <> IF reason = NIL THEN reason ¬ "IO error during createReadArchiveLog"; GOTO exit; }; FS.Error => { reason ¬ error.explanation; GOTO exit }; UNWIND => GOTO exit; END; IF (readArchiveStrm ¬ WalnutRoot.GetReadArchiveStream[opsH, possibleArchiveLength]) = NIL THEN RETURN[FALSE, "Can't get readArchiveStream"]; readArchiveStrm.SetIndex[0]; WalnutStream.SetHighWaterMark[strm: readArchiveStrm, hwmBytes: 0, numPages: -1]; WalnutRoot.CommitAndContinue[opsH]; [ok, lastCommitPosInFileToRead] ¬ ArchiveReader[ opsH: opsH, archiveStream: readArchiveStrm, fileToRead: fileToRead, msgSet: msgSet, reportProc: reportProc, posToStartInFileToRead: 0]; IF ~ok THEN WalnutRoot.ReturnReadArchiveStream[opsH]; EXITS exit => IF readArchiveStrm # NIL THEN { WalnutRoot.ReturnReadArchiveStream[opsH]; }; END; }; CloseReadArchiveLog: PUBLIC PROC[opsH: WalnutOpsHandle] = { WalnutRoot.ReturnReadArchiveStream[opsH]; }; <<********************************************************>> <<>> ArchiveEntryType: TYPE = {old, new}; ArchiveReader: PUBLIC PROC[opsH: WalnutOpsHandle, archiveStream, fileToRead: STREAM, msgSet: ROPE, reportProc: WalnutDefs.CheckReportProc, posToStartInFileToRead: INT] RETURNS [ok: BOOL, lastCommitPosInFileToRead: INT] = { num: INT ¬ 0; BadFormat: PROC[format: ROPE, v: IO.Value ¬ [null[]] ] = { IF reportProc = NIL THEN RETURN; reportProc[opsH, "Bad log file format at %g: ", [integer[fileToRead.GetIndex[]]] ]; reportProc[opsH, format, v]; reportProc[opsH, "\n"]; }; BEGIN ENABLE BEGIN ABORTED => { ok ¬ FALSE; GOTO GiveUp }; FS.Error => { reportProc[opsH, error.explanation]; ok ¬ FALSE; GOTO GiveUp }; IO.Error => { IF reportProc # NIL THEN { <> reason: ROPE ¬ NIL; IF reason # NIL THEN reason ¬ IO.PutFR["IOError on file being read, reported as %g, at pos %g", [rope[reason]], [integer[fileToRead.GetIndex[]]] ] <>; IF reason # NIL THEN reportProc[opsH, "\nIOError on tempLog being written, reported as %g, at pos%g", [rope[reason]], [integer[archiveStream.GetIndex[]]] ] ELSE reportProc[opsH, "\nIOError at writePos %g", [integer[archiveStream.GetIndex[]]] ]; }; ok ¬ FALSE; GOTO GiveUp }; IO.EndOfStream => {BadFormat["Unexpected EOF"]; ok ¬ FALSE; GOTO GiveUp}; END; entryLength, prefixLength, beginMsgPos: INT; entryChar: CHAR; 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]; 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 { reportProc[opsH, "\t%g messages read from archive file\n", [integer[num]] ]; IF bytesWritten = 0 THEN RETURN[TRUE, lengthOfFileToRead]; IF Flush[opsH, archiveStream] THEN RETURN[TRUE, lengthOfFileToRead]; RETURN[FALSE, lastCommitPosInFileToRead]; }; SELECT entryChar FROM '-, '+, '_ => { BadFormat["Non-message entry (entrychar = %g)\n", [character[entryChar]] ]; fileToRead.SetIndex[beginMsgPos+entryLength] }; ENDCASE => { -- regular message entry DO -- fake DO so can use exit msgID, msgEName, categories: ROPE; catList: LIST OF ROPE; outOfSynch, notInActive: BOOL; headersPos: INT ¬ beginMsgPos+prefixLength; body: ViewerTools.TiogaContents; archiveEntryType: ArchiveEntryType = DetermineEntryType[fileToRead]; IF archiveEntryType = old THEN { [msgID, categories, outOfSynch] ¬ ReadPrefixInfo[fileToRead, headersPos]; IF outOfSynch THEN { BadFormat["\nPrefix Info not found at %g\n", [integer[headersPos]] ]; EXIT}; body ¬ SendMailOps.TiogaTextFromStrm[ -- careful here fileToRead, headersPos, entryLength-prefixLength]; IF body.formatting.Length[] <= 2 THEN body.formatting ¬ NIL; --glitch IF msgID.Length[] = 0 THEN { -- need to construct an ID <> from: ROPE; mle: WalnutKernelDefs.MsgLogEntry = WalnutStream.logInfoRef.createMsg; mle.textLen ¬ entryLength - prefixLength; fileToRead.SetIndex[headersPos]; WalnutStream.MsgEntryInfoFromStream[fileToRead, mle]; from ¬ IF SendMailOps.IsThisTheCurrentUser[mle.sender] THEN Rope.Concat["To: ", mle.to] ELSE mle.sender; IF from.Length[] = 0 THEN from ¬ "UnknownFrom"; IF mle.date = BasicTime.nullGMT THEN mle.date ¬ BasicTime.Now[]; msgEName ¬ msgID ¬ MailUtils.GeneratePostmark[mle.date, "SomeMachine" ]; 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, msgEName, categories, body, this] ¬ ReadNewStyleArchiveFile[ fileToRead, headersPos, entryLength - (headersPos-beginMsgPos) - 1]; IF ~this THEN { BadFormat["\nCouldn't read entry at %g\n", [integer[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.logInfoRef.createMsg.msg ¬ msgID; WalnutStream.logInfoRef.createMsg.textLen ¬ body.contents.Length[]; WalnutStream.logInfoRef.createMsg.formatLen ¬ body.formatting.Length[]; startPosOnArchive ¬ WalnutStream.WriteEntry[archiveStream, WalnutStream.logInfoRef.createMsg]; WalnutStream.WriteMsgBody[archiveStream, body]; IF msgSet.Length[] # 0 THEN categories ¬ NIL; [catList, notInActive] ¬ CheckCategories[archiveStream, msgSet, categories]; IF entryChar # '? THEN { WalnutStream.logInfoRef.hasbeenRead.msg ¬ msgEName; [] ¬ WalnutStream.WriteEntry[archiveStream, WalnutStream.logInfoRef.hasbeenRead]; }; <<-- ProcessCategories>> BEGIN first: BOOL ¬ TRUE; FOR mL: LIST OF ROPE ¬ catList, mL.rest UNTIL mL=NIL DO IF first AND notInActive THEN { WalnutStream.logInfoRef.moveMsg.msg ¬ msgEName; WalnutStream.logInfoRef.moveMsg.from ¬ activeMsgSet; WalnutStream.logInfoRef.moveMsg.to ¬ mL.first; [] ¬ WalnutStream.WriteEntry[archiveStream, WalnutStream.logInfoRef.moveMsg] } ELSE { WalnutStream.logInfoRef.addMsg.msg ¬ msgEName; WalnutStream.logInfoRef.addMsg.to ¬ mL.first; [] ¬ WalnutStream.WriteEntry[archiveStream, WalnutStream.logInfoRef.addMsg]; }; first ¬ FALSE; ENDLOOP; END; IF (bytesWritten ¬ bytesWritten + archiveStream.GetLength[]-startPosOnArchive) >= bytesToWriteBeforeCommit THEN { WalnutStream.FlushStream[archiveStream]; WalnutRoot.CommitAndContinue[opsH]; lastCommitPosInFileToRead ¬ beginMsgPos+entryLength; bytesWritten ¬ 0; }; EXITS aborted => { RefText.ReleaseScratch[generalBuffer]; 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[opsH, "(%g) ", [integer[num]] ] ELSE reportProc[opsH, "~"]; }; EXIT; ENDLOOP; fileToRead.SetIndex[beginMsgPos+entryLength]; }; ENDLOOP; EXITS GiveUp => NULL; END; RefText.ReleaseScratch[generalBuffer]; reportProc[opsH, "\t%g messages read from archive file\n", [integer[num]] ]; RETURN[ok, lastCommitPosInFileToRead]; }; Flush: PROC[opsH: WalnutOpsHandle, strm: STREAM] RETURNS[ok: BOOL] = { ENABLE FS.Error => IF error.code = $transAborted THEN GOTO aborted ELSE REJECT; WalnutStream.FlushStream[strm]; WalnutRoot.CommitAndContinue[opsH]; RETURN[TRUE]; EXITS aborted => RETURN[FALSE] }; CheckCategories: PROC[archiveStream: STREAM, msgSet, categories: ROPE] RETURNS[catList: LIST OF ROPE, notInActive: BOOL] = { h: STREAM; CheckMsgSet: PROC[ms: ROPE] RETURNS[notActive: BOOL] = { IF ms.Equal[activeMsgSet, FALSE] THEN RETURN[FALSE]; FOR mL: LIST OF ROPE ¬ newMsgSetList, mL.rest UNTIL mL = NIL DO IF mL.first.Equal[ms, FALSE] THEN RETURN[TRUE]; ENDLOOP; WalnutStream.logInfoRef.createMsgSet.msgSet ¬ ms; [] ¬ WalnutStream.WriteEntry[archiveStream, WalnutStream.logInfoRef.createMsgSet]; newMsgSetList ¬ CONS[ms, newMsgSetList]; -- to be able to check next time RETURN[TRUE]; }; catList ¬ NIL; notInActive ¬ TRUE; IF categories = NIL THEN IF msgSet.Length[] = 0 THEN RETURN[NIL, FALSE] ELSE { notInActive ¬ CheckMsgSet[msgSet]; IF notInActive THEN RETURN[LIST[msgSet], notInActive] ELSE RETURN[NIL, FALSE]; }; IF msgSet.Length[] # 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 { notActive: BOOL ¬ CheckMsgSet[token]; notInActive ¬ notInActive AND notActive; IF notActive THEN catList ¬ CONS[token, catList]; }; ENDLOOP; h.Close[]; }; DetermineEntryType: PROC[strm: STREAM] RETURNS[aft: ArchiveEntryType] = { 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: ROPE, categories: ROPE, outOfSynch: BOOL] = { curPos: INT; outOfSynch ¬ FALSE; UNTIL (curPos ¬ fileToRead.GetIndex[]) = headersPos DO tag: ROPE; IF curPos > headersPos THEN { outOfSynch ¬ TRUE; RETURN }; tag ¬ fileToRead.GetTokenRope[].token; IF tag.Equal[msgIDRope, FALSE] THEN { [] ¬ fileToRead.GetChar[]; -- the : [] ¬ fileToRead.SkipWhitespace[]; msgID ¬ WalnutStream.MsgNameFromIdOnFile[fileToRead]; LOOP; }; IF tag.Equal[categoriesRope, FALSE] THEN { [] ¬ fileToRead.GetChar[]; -- the : [] ¬ fileToRead.SkipWhitespace[]; categories ¬ fileToRead.GetLineRope[]; LOOP; }; ENDLOOP; }; ReadNewStyleArchiveFile: PROC[fileToRead: STREAM, headersPos, bodyLen: INT] RETURNS[msgID, msgName, 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 { msgIDPos: INT ¬ fileToRead.GetIndex[]; msgID ¬ WalnutStream.MsgNameFromIdOnFile[fileToRead]; fileToRead.SetIndex[msgIDPos]; msgName ¬ WalnutStream.MsgNameFromIdOnFile[fileToRead]; categories ¬ fileToRead.GetLineRope[]; IF formatLen # 0 THEN { body.formatting ¬ SendMailOps.RopeFromStream[fileToRead, fileToRead.GetIndex[], formatLen]; [] ¬ fileToRead.GetChar[]; -- cr }; }; [] ¬ fileToRead.GetChar[]; -- the other @ pos ¬ fileToRead.GetIndex[]; body.contents ¬ SendMailOps.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 startPos ¬ fileToRead.GetIndex[]; IF fileToRead.EndOf[] THEN RETURN; 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 SELECT fileToRead.GetChar[] FROM '\l, '\r => NULL; ENDCASE => 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.