-- File: WalnutFileImpl.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 October 15, 1982 -- Last edited by: -- Rick Cattell on XXX -- Willie-Sue on March 24, 1983 3:35 pm -- Last Edited by: Woosh, March 28, 1983 10:26 am DIRECTORY DB, IO, GVBasics USING[ItemHeader, ItemType], GVRetrieve USING [Failed, GetBlock, Handle, NextItem], Rope, Runtime, WalnutDB, WalnutDBLog, WalnutWindow; WalnutFileImpl: CEDAR PROGRAM IMPORTS DB, IO, GVRetrieve, Rope, Runtime, WalnutDB, WalnutDBLog, WalnutWindow EXPORTS WalnutDBLog = BEGIN OPEN WalnutDB, WalnutDBLog, DB; 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[h: 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[h: IO.Handle, inc: INT] RETURNS[tag, value: ROPE] = BEGIN line: ROPE_ GetLine[h]; 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; ReadStartOfMsg: 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 WalnutWindow.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, len: INT] RETURNS[ROPE] = -- reads arbitrary length ROPE from a stream BEGIN Get1: SAFE PROC RETURNS[CHAR] = CHECKED {RETURN[strm.GetChar[]]}; RETURN[Rope.FromProc[len, Get1]]; 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 [ entryType: LogEntryType, entryText: ROPE, strm: IO.STREAM, 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); Put1: SAFE PROC[c: CHAR] RETURNS[stop: BOOL] = CHECKED { strm.PutChar[c]; RETURN[FALSE]}; 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 -- IF isMessage THEN -- { IF entryText.Fetch[length-1] # CR THEN -- { entryText_ Rope.Concat[entryText, "\n"]; length_ length + 1}; -- }; strm.SetIndex[strm.GetLength[]]; strm.PutF["**start**\n%05d %05d US%g\n", int[prefixLen+length], int[prefixLen], char[typeChar]]; -- strm.Put[ rope[entryText] ] doesn't work if entryText.Length > 77777B IF msgID.Length[]#0 THEN strm.PutRope[msgID]; IF entryText.Length[] < 77777B THEN strm.PutRope[entryText] ELSE []_ Rope.Map[base: entryText, action: Put1]; strm.Flush[]; -- flush after every write RETURN[strm.GetIndex[]] END; GVLogEntry: PUBLIC PROC [gvH: GVRetrieve.Handle, strm: IO.STREAM, 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; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- this used to be in WalnutDBLogImpl, until that file got too big OldMessageFile: PUBLIC PROC[strm: IO.STREAM, defaultPrefix: ROPE] RETURNS [ok: BOOL] = BEGIN ENABLE UNWIND => NULL; DO beginMsgPos, msgLength, entryLength, prefixLength: INT; msgID, msgText, prefix, categories: ROPE; outOfSynch: BOOL_ FALSE; [beginMsgPos, prefixLength, entryLength, ]_ ReadStartOfMsg[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_ RopeFromStream[strm, msgLength]; WalnutDB.AddMessageToLog[entryText: msgText, prefix: prefix]; WalnutWindow.ReportRope["."]; ENDLOOP; RETURN[TRUE]; END; AddLogEntries: PUBLIC PROC [logStream: IO.STREAM, startPos: INT] RETURNS[success: BOOL] = BEGIN doReport: BOOL_ (startPos = 0); -- report if scavenging from beginning BadFormat: PROC[s: ROPE] = { IF doReport THEN WalnutWindow.Report [IO.PutFR["Bad log file format at %g: %g", IO.int[logStream.GetIndex[]], IO.rope[s]]] }; BEGIN ENABLE IO.EndOfStream => {BadFormat["Unexpected EOF"]; GOTO GiveUp}; existed: BOOL; entryChar: CHAR; entryLength, prefixLength, beginMsgPos: INT; msgSetList: LIST OF Value_ LIST[WalnutWindow.activeMsgSet]; logStream.SetIndex[startPos]; DO -- Read *start* from log [beginMsgPos, prefixLength, entryLength, entryChar]_ ReadStartOfMsg[logStream, doReport]; IF entryLength = -1 THEN GOTO GiveUp; IF (entryLength=0) OR (entryLength=prefixLength) AND NOT (entryChar = '_) THEN EXIT; -- Do delete, create, or message update to database SELECT entryChar FROM '_ => -- mark message as read [ , ]_ ProcessHasBeenReadFromLog[logStream, prefixLength]; '+, '- => -- add or remove relship BEGIN ENABLE DB.Error => IF code=NotFound THEN {BadFormat["Illegal tag"]; CONTINUE}; foo, domainOrRelation: ROPE; logStream.SetIndex[beginMsgPos+prefixLength]; -- ignore prefix info [domainOrRelation, foo] _ TagAndValue[logStream, 2]; IF domainOrRelation.Equal["Domain"] THEN -- Add or remove entity from domain foo BEGIN domain: Domain; entityName, tag: ROPE; [tag, entityName]_TagAndValue[logStream, 2]; WalnutWindow.ReportRope[Rope.FromChar[entryChar]]; -- IfReporting[Rope.FromChar[entryChar], foo, ": ", entityName]; IF NOT tag.Equal["name"] THEN {BadFormat["expected name"]; RETURN[FALSE]}; domain_ DeclareDomain[name: foo, segment: $Walnut, version: OldOnly]; IF entryChar = '- THEN {e: Entity_ DeclareEntity[domain, entityName, OldOnly]; IF e = NIL THEN {BadFormat[Rope.Cat[entityName, " doesn\'t exist!"]]; LOOP}; IF WalnutWindow.walnut # NIL THEN WalnutWindow.DeleteMsgSetButton[e, entityName]; DestroyEntity[e] } ELSE IF entryChar = '+ THEN IF foo.Equal["MsgSet"] THEN { new: BOOL_ TRUE; ms: MsgSet; ms_ DB.DeclareEntity[domain, entityName, NewOnly ! DB.Error => IF code=AlreadyExists THEN {new_ FALSE; CONTINUE}]; IF new AND (WalnutWindow.walnut # NIL) THEN WalnutWindow.AddToMsgSetButtons[ms, entityName]; } ELSE {BadFormat["Only MsgSets should be added!"]; RETURN[FALSE]}; END ELSE IF domainOrRelation.Equal["Relation"] THEN -- Add or remove relship from relation foo. -- We assume foo=mCategory BEGIN ENABLE DB.Error => {BadFormat["Illegal tag"]; CONTINUE}; msgName, categoryName, tag: ROPE; msg: Msg; msgSet: MsgSet; IF NOT foo.Equal["mCategory"] THEN {BadFormat["expected mCategory"]; RETURN[FALSE]}; [tag, msgName]_ TagAndValue[logStream, 2]; IF NOT tag.Equal["of"] THEN {BadFormat["of?"]; RETURN[FALSE]}; msg_ DeclareEntity[MsgDomain, msgName, OldOnly]; IF msg=NIL THEN {BadFormat[Rope.Cat[msgName, " doesn\'t exist!"]]; GOTO Fail}; [tag, categoryName]_ TagAndValue[logStream, 2]; WalnutWindow.ReportRope[Rope.FromChar[entryChar]]; -- *** -- IfReporting["Categorization: ", msgName, " in ", categoryName]; IF NOT tag.Equal["is"] THEN {BadFormat["is?"]; RETURN[FALSE]}; IF entryChar = '- THEN -- check if msgSet exists { msgSet_ DeclareEntity[MsgSetDomain, categoryName, OldOnly]; IF msgSet # NIL THEN []_ RemoveFrom[msg, msgSet, NIL, FALSE, FALSE]} ELSE IF entryChar = '+ THEN -- create msgSet if it doesn't exist { msgSet_ DeclareEntity[MsgSetDomain, categoryName]; []_ AddTo[msg, msgSet, FALSE] }; EXITS Fail => logStream.SetIndex[beginMsgPos+entryLength-1]; -- next entry END ELSE -- domainOrRelation not Domain or Relation {BadFormat["not domain or relation"]; RETURN[FALSE]}; -- Skip over the trailing CR IF logStream.GetChar[]#'\n THEN BadFormat["Missing CR"]; END; ENDCASE => -- regular message entry BEGIN OPEN WalnutDB; msgRec: MsgRec; msgSet: MsgSet; mL: LIST OF Value_ NIL; categoriesList: LIST OF ROPE_ NIL; [msgRec, existed, categoriesList]_ GetMsgFromStream[logStream, prefixLength, entryLength-prefixLength, entryChar]; IF existed OR (msgRec.msg=NIL) THEN LOOP; IF doReport THEN WalnutWindow.ReportRope["."]; -- adding message IF categoriesList = NIL THEN mL_ msgSetList ELSE FOR cL: LIST OF ROPE_ categoriesList, cL.rest UNTIL cL = NIL DO msgSet_ DeclareEntity[MsgSetDomain, cL.first, OldOnly]; IF msgSet = NIL THEN { msgSet_ DeclareEntity[MsgSetDomain, cL.first]; IF WalnutWindow.walnut # NIL THEN WalnutWindow.AddToMsgSetButtons[msgSet, cL.first]; }; []_ AddTo[msgRec.msg, msgSet, FALSE]; -- increments count as well ENDLOOP; END; ENDLOOP; RETURN[TRUE] EXITS GiveUp => -- try to press on with what we have { RETURN[TRUE]} END; END; ProcessHasBeenReadFromLog: PROC[logStream: IO.STREAM, prefixLength: INT] RETURNS[msg: Msg, existed: BOOL] = BEGIN name: ROPE_ RopeFromStream[logStream, prefixLength-minPrefixLength-1 ! IO.EndOfStream => {WalnutWindow.Report["\nUnexpected EOF, ignoring HasBeenRead entry"]; GOTO eof}]; rel: Relship; []_ logStream.GetChar[]; msg_ DeclareEntity[d: MsgDomain, name: name, version: OldOnly]; IF msg = NIL THEN RETURN[msg, FALSE]; -- ignore rel_ DB.SetP[msg, mHasBeenReadIs, B2V[TRUE]]; RETURN[msg, TRUE]; EXITS eof => RETURN; END; TRUSTED { TiogaCTRL_ LOOPHOLE[1013B] }; END.