-- File: WalnutStreamImpl.mesa -- Contents: -- procedures for reading & writing Walnut log-Style files -- This module is NOT a MONITOR; its assumes that its caller has a lock on the -- stream if that is appropriate -- Created by: Willie-Sue on April 27, 1983 -- Last edited by: -- Willie-Sue on December 5, 1983 11:27 am DIRECTORY AlpineFS USING [OpenFileFromStream], BasicTime USING [Now], FS USING [OpenFile, OpenFileFromStream, SetByteCountAndCreatedTime], IO, GVBasics USING [ItemHeader, ItemType], GVRetrieve USING [Failed, GetItem, Handle, NextItem], Rope, RopeIO USING [GetRope, PutRope], RuntimeError USING [BoundsFault], WalnutLog USING [LogEntryType, MsgRec, MessageRecObject, minPrefixLength, categoriesRope, copyBuffer, msgIDRope, AddMessageToLog, ConstructMsgID, ParseMsgID], WalnutRetrieve USING [ParseMsgIntoFields], WalnutStream, WalnutWindow USING [logIsAlpineFile, Report, ReportRope]; WalnutStreamImpl: CEDAR PROGRAM IMPORTS AlpineFS, BasicTime, FS, IO, GVRetrieve, Rope, RopeIO, RuntimeError, WalnutLog, WalnutRetrieve, WalnutWindow EXPORTS WalnutStream = BEGIN OPEN WalnutLog; ROPE: TYPE = Rope.ROPE; TiogaCTRL: GVBasics.ItemType; -- ******************************************************** ReadPrefixInfo: PUBLIC PROC[strm: IO.STREAM, headersPos: INT] RETURNS[msgID: ROPE, categories: ROPE, outOfSynch: BOOL] = BEGIN curPos: INT; outOfSynch_ FALSE; UNTIL (curPos_ strm.GetIndex[]) = headersPos DO tag, value: ROPE; IF curPos > headersPos THEN {outOfSynch_ TRUE; RETURN}; [tag, value]_ TagAndValue[strm: strm, inc: 2]; IF Rope.Equal[tag, msgIDRope, FALSE] THEN msgID_ value ELSE IF Rope.Equal[tag, categoriesRope, FALSE] THEN categories_ value; ENDLOOP; END; TagAndValue: PUBLIC PROC[strm: IO.STREAM, inc: INT] RETURNS[tag, value: ROPE] = BEGIN line: ROPE_ strm.GetLineRope[]; pos: INT_ line.Find[":"]; IF pos < 0 THEN RETURN; tag_ line.Substr[0, pos]; value_ line.Substr[pos+inc, Rope.MaxLen ! RuntimeError.BoundsFault => {tag_ NIL; CONTINUE}]; END; FindStartOfEntry: PUBLIC PROC[strm: IO.STREAM, doReport: BOOL] RETURNS[startPos, prefixLength, entryLength: INT, entryChar: CHAR] = BEGIN ENABLE IO.EndOfStream => GOTO eoS; line: ROPE; relPos: INT_ 0; startFound: BOOL; prefixLength_ entryLength_ 0; entryChar_ 'X; -- not a valid entryType char IF strm.EndOf[] THEN RETURN; startPos_ strm.GetIndex[]; line_ strm.GetLineRope[]; [startFound, relPos]_ IsStart[line]; IF NOT startFound THEN BEGIN IF doReport THEN WalnutWindow.Report[ IO.PutFR["*start* not found at pos %g. Skipping to next entry.", IO.int[startPos]]]; UNTIL startFound DO IF strm.EndOf[] THEN RETURN; startPos_ strm.GetIndex[]; line_ strm.GetLineRope[]; [startFound, relPos]_ IsStart[line]; ENDLOOP; END; -- Read entry info line from log, e.g.: 00101 00029 US+ startPos_ startPos + relPos; entryLength_ strm.GetInt[]; prefixLength_ strm.GetInt[]; line_ strm.GetLineRope[]; entryChar_ line.Fetch[line.Length[]-1]; EXITS eoS => {entryLength_ -1; RETURN}; END; starStartStar: ROPE = "*start*"; IsStart: PUBLIC PROC[line: ROPE] RETURNS[startFound: BOOL, relPos: INT] = BEGIN relPos_ 0; IF line.Length = 0 THEN RETURN[FALSE, relPos]; IF line.Equal[starStartStar] THEN RETURN[TRUE, relPos]; IF (relPos_ line.Length[] - starStartStar.Length[]) <=0 THEN RETURN[FALSE, relPos]; IF starStartStar.Equal[line.Substr[relPos]] THEN RETURN[TRUE, relPos]; RETURN[FALSE, relPos]; END; RopeFromStream: PUBLIC PROC[strm: IO.STREAM, startPos, len: INT] RETURNS[ROPE] = -- reads arbitrary length ROPE from a stream BEGIN ENABLE IO.EndOfStream => GOTO TooShort; strm.SetIndex[startPos]; RETURN[RopeIO.GetRope[strm, len]]; EXITS TooShort => RETURN[IO.PutFR["[message body lost!!! (%g,%g)]\n", IO.int[startPos], IO.int[len]]]; END; FlushAndSetCreateDate: PUBLIC PROC[strm: IO.STREAM] = -- flushes the stream and sets the create date to BasicTime.Now BEGIN of: FS.OpenFile; strm.Flush[]; of_ IF WalnutWindow.logIsAlpineFile THEN AlpineFS.OpenFileFromStream[strm] ELSE FS.OpenFileFromStream[strm]; FS.SetByteCountAndCreatedTime[of, -1, BasicTime.Now[]]; END; -- ******************************************************** MakeLogEntry: PUBLIC PROC [ strm: IO.STREAM, entryType: LogEntryType, entryText: ROPE, msgID: ROPE_ NIL, doFlush: BOOL_ TRUE] RETURNS [endStrmPos, prefixLength, entryLength: INT] = -- Puts entryText out in Laurel format on the given stream. -- There are four kinds of entries: -- (1) messages: entryText is the header and body of the message -- (2) insertions: entryText is of form (insertion of new relship): -- Relation: relation -- attr1: xxx -- attr2: yyy -- or of the form (for insertion of new entity): -- Domain: domain -- name: xxx -- (3) deletions: entryText same form as above, but represents deletion of that relship or entity -- (4) hasbeenread: entryText is the messageID -- Returns integer position in file of end of log entry. BEGIN OPEN IO; typeChar: CHAR; isMessage: BOOL_ (entryType=message) OR (entryType=newMessage); prefixLength_ minPrefixLength + msgID.Length[]; -- magic number entryLength_ entryText.Length[] + prefixLength; SELECT entryType FROM message => typeChar_ ' ; newMessage => typeChar_ '?; insertion => typeChar_ '+; deletion => typeChar_ '-; hasbeenread => typeChar_ '_; ENDCASE; -- can't fool with msg if it has formatting, so don't ever add CR at end strm.SetIndex[strm.GetLength[]]; strm.PutF["*start*\n%05d %05d US%g\n", int[entryLength], int[prefixLength], char[typeChar]]; IF msgID.Length[]#0 THEN strm.PutRope[msgID]; RopeIO.PutRope[strm, entryText]; IF doFlush THEN FlushAndSetCreateDate[strm]; RETURN[strm.GetIndex[], prefixLength, entryLength] END; GVLogEntry: PUBLIC PROC [strm: IO.STREAM, gvH: GVRetrieve.Handle, prefix: ROPE] RETURNS [lastIndex: INT, ok: BOOL] = BEGIN startPos: INT_ strm.GetLength[]; -- where this message begins strm.SetIndex[startPos]; BEGIN ENABLE GVRetrieve.Failed => GOTO gvFailed; prefixLen: INT_ minPrefixLength + prefix.Length[]; msgLen, lenWritten: INT_ 0; prefixWritten, ctrlInfo: BOOL_ FALSE; WritePrefix: PROC = BEGIN strm.PutF["*start*\n%05d %05d US%g\n", IO.int[prefixLen+msgLen], IO.int[prefixLen], IO.char['?]]; strm.PutRope[prefix]; lenWritten_ msgLen; prefixWritten_ TRUE; END; CopyItemToLog: PROC = BEGIN -- strm.SetIndex[strm.GetLength[]]; -- -- make sure at end is: IO.STREAM_ GVRetrieve.GetItem[gvH]; DO bytes: INT; IF (bytes_ is.GetBlock[copyBuffer, 0, 512]) = 0 THEN EXIT; -- copyBuffer.length_ bytes;-- -- grapevineUser bug strm.PutBlock[copyBuffer]; ENDLOOP; END; DO item: GVBasics.ItemHeader _ GVRetrieve.NextItem[gvH]; SELECT item.type FROM PostMark, Sender, ReturnTo => ERROR; Audio => NULL; -- we may use this soon Recipients, Capability, updateItem, reMail => NULL; Text => { IF ctrlInfo THEN ERROR; -- items in wrong order!!! msgLen_ msgLen + LOOPHOLE[item.length, INT]; IF ~prefixWritten THEN WritePrefix[]; CopyItemToLog[]; }; TiogaCTRL => { ctrlInfo_ TRUE; msgLen_ msgLen + LOOPHOLE[item.length, INT]; CopyItemToLog[]; }; LastItem => EXIT; ENDCASE => LOOP; ENDLOOP; IF ~ctrlInfo THEN { ch: CHAR; strm.SetIndex[strm.GetLength[]-1]; IF (ch_ strm.GetChar[]) # '\n THEN {strm.PutChar['\n]; msgLen_ msgLen + 1}; }; IF msgLen # lenWritten THEN { strm.SetIndex[startPos]; WritePrefix[]}; -- strm.Flush[];-- -- flush after write RETURN[strm.GetLength[], TRUE]; EXITS gvFailed => {strm.SetIndex[startPos]; strm.SetLength[startPos]; RETURN[startPos, FALSE]}; END; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * MsgRecFromStream: PUBLIC PROC [strm: IO.STREAM, prefixLength, msgLength: INT] RETURNS[msgRec: MsgRec] = BEGIN ENABLE IO.EndOfStream => {WalnutWindow.Report["\nUnexpected EOF, ignoring msg entry"]; GOTO badMessage}; prefixPos: INT_ strm.GetIndex[] - minPrefixLength; headersPos: INT_ prefixPos + prefixLength; msgID, categories: ROPE_ NIL; outOfSynch: BOOL; categoriesList: LIST OF ROPE; [msgID, categories, outOfSynch]_ ReadPrefixInfo[strm, headersPos]; IF outOfSynch THEN GOTO badMessage; IF categories # NIL THEN BEGIN OPEN IO; h: STREAM_ RIS[categories]; UNTIL h.EndOf[] DO categoriesList_ CONS[h.GetTokenRope[breakProc: IO.IDProc].token, categoriesList] ENDLOOP; h.Close[]; END; msgRec_ NEW[MessageRecObject]; []_ WalnutRetrieve.ParseMsgIntoFields[msgRec, strm, msgLength]; IF (msgRec.gvID_ msgID) = NIL THEN []_ ConstructMsgID[msgRec] ELSE ParseMsgID[msgRec]; msgRec.prefixPos_ prefixPos; msgRec.headersPos_ headersPos; msgRec.msgLength_ msgLength; msgRec.categoriesList_ categoriesList; EXITS badMessage => RETURN[NIL]; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- works for Laurel & Hardy files, as well as archiveLogs ReadOldMailFile: PUBLIC PROC[strm: IO.STREAM, defaultPrefix: ROPE] RETURNS [ok: BOOL] = BEGIN ENABLE IO.EndOfStream => GOTO eos; DO beginMsgPos, msgLength, entryLength, prefixLength: INT; msgID, msgText, prefix, categories: ROPE; outOfSynch: BOOL_ FALSE; pos: INT_ 0; [beginMsgPos, prefixLength, entryLength, ]_ FindStartOfEntry[strm, TRUE]; IF entryLength = -1 THEN RETURN[FALSE]; IF (entryLength=0) OR (entryLength=prefixLength) THEN EXIT; -- Hardy's null message at end IF IO.PeekChar[strm] = '@ THEN strm.SetIndex[beginMsgPos + prefixLength] -- hardy file ELSE [msgID, categories, outOfSynch]_ ReadPrefixInfo[strm, beginMsgPos+prefixLength]; IF outOfSynch THEN { WalnutWindow.Report["Can't find prefix info; skipping to next entry"]; strm.SetIndex[beginMsgPos + entryLength]; LOOP }; prefix_ IF categories#NIL THEN Rope.Cat[categoriesRope, ": ", categories, "\n"] ELSE defaultPrefix; IF msgID # NIL THEN prefix_ Rope.Cat[msgIDRope, ": ", msgID, "\n", prefix]; msgLength_ entryLength-prefixLength; msgText_ RopeIO.GetRope[strm, msgLength]; DO -- replace \240's with spaces pos_ msgText.SkipTo[pos, "\240\000"]; IF pos = msgLength THEN EXIT; IF msgText.Fetch[pos] = '\000 THEN { IF msgText.Fetch[pos+1] = '\000 THEN EXIT ELSE pos_ pos + 1} ELSE msgText_ msgText.Replace[start: pos, len: 1, with: " "]; ENDLOOP; AddMessageToLog[entryText: msgText, prefix: prefix]; WalnutWindow.ReportRope["."]; ENDLOOP; RETURN[TRUE]; EXITS eos => RETURN[FALSE] END; TRUSTED { TiogaCTRL_ LOOPHOLE[1013B] }; END. ʉ˜Jš¯Ïc,œ;œOœ#œ?œ+œÏk œ žœ$žœ žœžœ@žœ žœ%žœ4žœ#žœžœªžœ4žœ*Ïnœž œžœžœžœWžœžœžœ žœžœžœ!<œŸœžœžœžœžœžœžœžœžœžœžœ žœžœžœ(žœžœžœžœžœžœ8žœžœžœžœžœ!žœžœžœžœŸ œžœžœžœžœžœžœ žœžœ žœžœžœ žœžœpžœžœžœŸœžœžœžœžœ žœžœ&žœ žœžœžœžœžœžœ žœžœ6œžœžœžœhžœžœ žœžœžœ žœ$žœ@žœžœ žœžœžœžœkžœžœ8œ«žœžœžœžœŸœž œžœžœ žœ žœžœžœžœžœžœ žœžœžœžœ žœ6žœžœžœ žœ*žœžœžœ žœžœ žœŸœžœžœžœžœžœžœžœ-œžœžœžœžœ'žœžœžœžœ+žœžœžœŸœžœžœžœžœ@œžœžœžœžœ%žœžœžœ8žœ<œŸ œžœžœ žœžœ&žœ žœžœ žœžœžœ'žœ<œ$œBœEœœœœ6œœœcœ0œ9œžœžœžœžœžœžœPœ6žœ žœŸžœKœžœžœ;žœ žœ žœ/žœŸ œžœžœžœžœ"žœžœ žœžœžœ žœœžœžœ žœž œžœ;žœžœžœŸ œžœžœªžœžœŸ œžœž œ'žœž œžœ žœžœ.žœžœ#œ ž œžœžœ?žœ žœ%žœžœœ5žœœ žœ žœžœ8žœžœ žœžœ\žœžœžœ:žœžœžœžœžœ žœ žœžœ,žœžœ0žœžœ@œžœžœžœDžœ žœžœžœLŸœž œžœžœžœžœžœžœžœPžœžœ2žœ0žœžœžœžœžœžœIžœ žœžœžœžœžœžœžœžœžœžœ žœžœžœ žœžœ žœVžœžœžœžœžœžœžœœžœL;œŸœžœžœžœžœžœžœžœžœžœžœžœžœ8žœ*žœžœžœ žœMžœžœžœžœžœžœžœžœžœœžœžœžœ+œžœSžœ žœž œžœ žœžœ3žœžœ žœžœŒžœœ+žœžœžœžœžœžœžœžœžœžœ;žœZžœžœžœžœžœžœžœžœžœ žœ˜ûT—…—*~1