-- 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 May 18, 1983 3:19 pm DIRECTORY IO, GVBasics USING[ItemHeader, ItemType], GVRetrieve USING [Failed, GetBlock, Handle, NextItem], Rope, RopeIO USING [GetRope, PutRope], Runtime, WalnutDB, WalnutLog, WalnutRetrieve, WalnutStream, WalnutWindow; WalnutStreamImpl: CEDAR PROGRAM IMPORTS IO, GVRetrieve, Rope, RopeIO, Runtime, WalnutLog, WalnutRetrieve, WalnutWindow EXPORTS WalnutStream = BEGIN OPEN WalnutDB, WalnutLog, WalnutWindow; 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_ GetLine[strm]; pos: INT_ line.Find[":"]; IF pos < 0 THEN RETURN; tag_ line.Substr[0, pos]; value_ line.Substr[pos+inc, Rope.MaxLen ! Runtime.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; prefixLength_ entryLength_ 0; entryChar_ 'X; -- not a valid entryType char IF strm.EndOf[] THEN RETURN; startPos_ strm.GetIndex[]; line_ GetLine[strm]; IF NOT line.Equal["*start*"] THEN BEGIN foo: INT_ startPos; IF doReport THEN Report[ IO.PutFR["**start** not found at pos %g. Skipping to next entry.", IO.int[startPos]]]; UNTIL line.Equal["*start*"] DO IF strm.EndOf[] THEN RETURN; startPos_ strm.GetIndex[]; line_ GetLine[strm] ENDLOOP; END; -- Read entry info line from log, e.g.: 00101 00029 US+ entryLength_ strm.GetInt[]; prefixLength_ strm.GetInt[]; line_ GetLine[strm]; entryChar_ line.Fetch[line.Length[]-1]; EXITS eoS => {entryLength_ -1; RETURN}; 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; GetLine: PROC[h: IO.STREAM] RETURNS[r: ROPE] = BEGIN r_ h.GetSequence[]; []_ h.GetChar[ ! IO.EndOfStream => CONTINUE]; -- read the CR END; -- ******************************************************** MakeLogEntry: PUBLIC PROC [ strm: IO.STREAM, entryType: LogEntryType, entryText: ROPE, msgID: ROPE_ NIL] RETURNS [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; length: INT_ entryText.Length[]; prefixLen: INT_ minPrefixLength + msgID.Length[]; -- magic number isMessage: BOOL_ (entryType=message) OR (entryType=newMessage); 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[prefixLen+length], int[prefixLen], char[typeChar]]; IF msgID.Length[]#0 THEN strm.PutRope[msgID]; RopeIO.PutRope[strm, entryText]; strm.Flush[]; -- flush after every write RETURN[strm.GetIndex[]] 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 DO bytes: INT; IF (bytes_ GVRetrieve.GetBlock[gvH, 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; Recipients, Capability, Audio, updateItem, reMail => NULL; Text => -- text needs to end in CR (for parsing & making log file readable) { 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 => {Report["\nUnexpected EOF, ignoring msg entry"]; GOTO badMessage}; prefixPos: INT_ strm.GetIndex[]; headersPos: INT_ prefixPos + prefixLength - minPrefixLength; 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: Handle_ CreateInputStreamFromRope[categories]; UNTIL h.EndOf[] DO categoriesList_ CONS[h.GetToken[WhiteSpace], 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; [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 { 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]; AddMessageToLog[entryText: msgText, prefix: prefix]; ReportRope["."]; ENDLOOP; RETURN[TRUE]; EXITS eos => RETURN[FALSE] END; TRUSTED { TiogaCTRL_ LOOPHOLE[1013B] }; END.