DIRECTORY BasicTime USING [nullGMT, GMT, Now], DB USING [ CreateRelship, DeclareEntity, EntityInfo, FirstRelship, GetF, LookupEntity, LookupProperty, NextRelship, NullEntity, RelshipsWithEntityField, ReleaseRelshipSet, SetF, L2VS, B2V, E2V, I2V, S2V, T2V, V2B, V2E, V2I, V2S, V2T, Entity, EntitySet, Relship, RelshipSet, ValueSequence], Rope, RuntimeError USING [BoundsFault], VFonts USING [CharWidth, StringWidth], ViewerTools USING [TiogaContents], WalnutDefs USING [MsgSet], WalnutDB USING [activeMessageSet, unacceptedEntity, CarefullyApply, ChangeCountOfMsgs, ChangeCountInMsgSet, GetMsgDisplayInfo], WalnutKernelDefs USING [MsgLogEntry], WalnutSchema, WalnutSendOps USING [simpleUserName, userRName, RFC822Date]; WalnutDBMsgImpl: CEDAR PROGRAM IMPORTS BasicTime, DB, Rope, RuntimeError, VFonts, WalnutDB, WalnutSchema, WalnutSendOps EXPORTS WalnutDB = BEGIN OPEN WalnutSchema; ROPE: TYPE = Rope.ROPE; GMT: TYPE = BasicTime.GMT; TiogaContents: TYPE = ViewerTools.TiogaContents; Entity: TYPE = DB.Entity; EntitySet: TYPE = DB.EntitySet; Relship: TYPE = DB.Relship; RelshipSet: TYPE = DB.RelshipSet; MsgSet: TYPE = WalnutDefs.MsgSet; MsgLogEntry: TYPE = WalnutKernelDefs.MsgLogEntry; lastMsgEntity: Entity _ NIL; blankWidth: INT_ VFonts.CharWidth[' ]; -- in default font blanks: ROPE_ " "; -- lotsa blanks IsEntity: TYPE = RECORD [entity: Entity, exists: BOOL]; NilEntity: IsEntity = [NIL, FALSE]; MsgExists: PUBLIC PROC[msg: ROPE] RETURNS [exists: BOOL] = { IsMsg: PROC = { exists _ GetMsgEntity[msg: msg].exists }; WalnutDB.CarefullyApply[IsMsg]; }; GetHasBeenRead: PUBLIC PROC[msg: ROPE] RETURNS[has: BOOL] = { Ghbr: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN {has_ FALSE; RETURN }; has _ DB.V2B[DB.GetF[DB.LookupProperty[mDisplayInfo, m.entity], mDIHasBeenRead]]; }; WalnutDB.CarefullyApply[Ghbr]; }; SetHasBeenRead: PUBLIC PROC[msg: ROPE] = { DoSet: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN RETURN; DB.SetF[DB.LookupProperty[mDisplayInfo, m.entity], mDIHasBeenRead, DB.B2V[TRUE]]; }; WalnutDB.CarefullyApply[DoSet]; }; AddNewMsg: PUBLIC PROC[msg: MsgLogEntry] RETURNS[mExisted: BOOL] = { DoAddNew: PROC = { msgRope: ROPE = Rope.FromRefText[msg.msg]; m: IsEntity = GetMsgEntity[msg: msgRope]; IF mExisted _ m.exists THEN RETURN; -- id's assumed unique BEGIN me: Entity = DB.DeclareEntity[MsgDomain, msgRope, TRUE]; herald, toc: ROPE; startOfSubject, shortNameLen: INT; [herald, toc, startOfSubject, shortNameLen] _ ComputeHeraldAndTOC[msg]; SetMsgTextInfo[me, msg, herald, shortNameLen]; SetMsgDisplayInfo[me, FALSE, toc, startOfSubject]; SetMsgInfo[me, msg.date, msg.show]; WalnutDB.ChangeCountOfMsgs[1]; BEGIN -- put it in active - no checking necessary init: DB.ValueSequence = DB.L2VS[LIST[ DB.E2V[me], DB.E2V[WalnutDB.activeMessageSet], DB.T2V[msg.date] ]]; [] _ DB.CreateRelship[cdRelation, init]; IF msg.show THEN WalnutDB.ChangeCountInMsgSet[WalnutDB.activeMessageSet, 1]; END; END; }; WalnutDB.CarefullyApply[DoAddNew]; }; GetMsgEntryPosition: PUBLIC PROC[msg: ROPE] RETURNS[pos: INT] = { Gmep: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN {pos _ -1; RETURN}; pos _ DB.V2I[DB.GetF[DB.LookupProperty[mTextInfo, lastMsgEntity], mTIEntryStart] ]; }; WalnutDB.CarefullyApply[Gmep]; }; SetMsgEntryPosition: PUBLIC PROC[to: INT] = { Smep: PROC = { IF DB.NullEntity[lastMsgEntity] THEN RETURN; DB.SetF[DB.LookupProperty[mTextInfo, lastMsgEntity], mTIEntryStart, DB.I2V[to]]; }; WalnutDB.CarefullyApply[Smep]; }; GetMsgDate: PUBLIC PROC[msg: ROPE] RETURNS[date: GMT] = { Gmd: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN { date _ BasicTime.nullGMT; RETURN} ELSE { rel: Relship = DB.LookupProperty[mInfo, m.entity]; date _ DB.V2T[DB.GetF[rel, mDateIs]]; }; }; WalnutDB.CarefullyApply[Gmd]; }; GetMsgTextInfo: PUBLIC PROC[msg: ROPE] RETURNS[textStart, textLen, formatLen: INT] = { Gmti: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN { textStart _ textLen _ formatLen _ 0; RETURN; }; [textStart, textLen, formatLen] _ GetTextInfo[m.entity]; }; WalnutDB.CarefullyApply[Gmti]; }; GetMsgText: PUBLIC PROC[msg: ROPE] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { Gmt: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN { textStart _ textLen _ formatLen _ shortNameLen _ 0; RETURN; }; [textStart, textLen, formatLen, herald, shortNameLen] _ GetAllMsgTextInfo[m.entity]; }; WalnutDB.CarefullyApply[Gmt]; }; GetDisplayProps: PUBLIC PROC[msg: ROPE] RETURNS [hasBeenRead: BOOL_ TRUE, TOCentry: ROPE_ NIL, startOfSubject: INT_ 0] = { DoDisplayProps: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF m.exists THEN [hasBeenRead, TOCentry, startOfSubject] _ WalnutDB.GetMsgDisplayInfo[m.entity]; }; WalnutDB.CarefullyApply[DoDisplayProps]; }; GetCategories: PUBLIC PROC[msg: ROPE] RETURNS [msList: LIST OF ROPE] = { DoCats: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; lastInList: LIST OF ROPE; rs: RelshipSet; rel: Relship; IF ~m.exists THEN RETURN; msList _ NIL; -- for retries rs _ DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity]; BEGIN ENABLE UNWIND => { IF rs#NIL THEN DB.ReleaseRelshipSet[rs] }; UNTIL (rel _ DB.NextRelship[rs]) = NIL DO new: LIST OF ROPE _ CONS[DB.EntityInfo[DB.V2E[DB.GetF[rel, cdMsgSet]]].name, NIL]; IF msList = NIL THEN lastInList _ msList _ new ELSE { lastInList.rest_ new; lastInList_ lastInList.rest}; ENDLOOP; DB.ReleaseRelshipSet[rs]; END; }; WalnutDB.CarefullyApply[DoCats]; }; SizeOfDatabase: PUBLIC PROC RETURNS[messages, msgSets: INT] = { SizeOf: PROC = { rVersionInfo: Relship = DB.FirstRelship[gVersionInfo]; messages _ DB.V2I[DB.GetF[rVersionInfo, gMsgCount]]; msgSets _ DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]]; }; WalnutDB.CarefullyApply[SizeOf]; }; ComputeHeraldAndTOC: PROC[mle: MsgLogEntry] RETURNS[herald, toc: ROPE, startOfSubject, shortNameLen: INT] = { date, tocx: ROPE; from: ROPE = IF mle.sender.Equal[WalnutSendOps.userRName, FALSE] OR mle.sender.Equal[WalnutSendOps.simpleUserName, FALSE] THEN Rope.Concat["To: ", mle.to] ELSE mle.sender; IF mle.date = BasicTime.nullGMT THEN mle.date _ BasicTime.Now[]; date _ Rope.Substr[WalnutSendOps.RFC822Date[mle.date], 0, 9]; tocx _ date.Cat[" ", from]; tocx _ SquashRopeIntoWidth[tocx, 165]; startOfSubject _ tocx.Length[]; toc _ Rope.Concat[tocx, mle.subject]; herald _ RemoveComments[from]; herald _ Rope.Cat[herald, " ", date]; shortNameLen _ herald.Length[]; herald _ Rope.Cat[herald, " ", mle.subject]; IF herald.Length[] > 60 THEN herald _ Rope.Concat[herald.Substr[0, 56], " ..."]; IF herald.Length[] < shortNameLen THEN shortNameLen _ herald.Length[]; }; SquashRopeIntoWidth: PROC[s: ROPE, colWidth: INT] RETURNS[ROPE] = BEGIN blankCount: INT; width: INT; BEGIN ENABLE RuntimeError.BoundsFault => GOTO doItTheHardWay; width_ VFonts.StringWidth[s]; DO IF width<= colWidth THEN EXIT; -- truncate BEGIN guessLength: INT_ s.Length[] * colWidth / width; s_ Rope.Cat[s.Substr[0, MAX[0, guessLength-4]], "..."]; width_ VFonts.StringWidth[s]; END; ENDLOOP; EXITS doItTheHardWay => [width, s]_ DoItTheHardWay[s, colWidth]; END; -- of enable -- At this point s is shorter than colWidth and we want to extend it with blanks blankCount_ ((colWidth - width) / blankWidth) + 1; -- force at least one blank s_ Rope.Cat[s, Rope.Substr[blanks, 0, MIN[blankCount, blanks.Length[]]]]; RETURN[s] END; DoItTheHardWay: PROC[s: ROPE, colWidth: INT] RETURNS[width: INT, s1: ROPE] = { thisWidth: INTEGER; dots: ROPE = "..."; nullWidth: INTEGER = VFonts.CharWidth['\000]; width_ VFonts.StringWidth[dots]; FOR i: INT IN [0 .. s.Length[]) DO thisWidth_ VFonts.CharWidth[s.Fetch[i] ! RuntimeError.BoundsFault => thisWidth_ nullWidth ]; width_ width + thisWidth; IF width > colWidth THEN { width_ width - thisWidth; s1_ Rope.Concat[s.Substr[0, MAX[0, i-1]], dots]; RETURN }; ENDLOOP; s1_ s.Concat[dots]; }; RemoveComments: PROC[name: ROPE] RETURNS[shortName: ROPE] = { start, end: INT; name _ Rope.Concat[base: name, rest: " "]; start _ Rope.Find[name, "<"]; IF start > 0 THEN { end _ Rope.Find[s1: name, s2: ">", pos1: start+1]; IF end > 0 THEN name _ Rope.Replace[name, start, end-start+1] }; start _ Rope.Find[name, "("]; IF start > 0 THEN { end _ Rope.Find[s1: name, s2: ")", pos1: start+1]; IF end > 0 THEN name _ Rope.Replace[name, start, end-start+1] }; shortName _ Rope.Substr[name, 0, Rope.Length[name]-1] }; GetMsgEntity: PROC[msg: ROPE] RETURNS[e: IsEntity] = { IF msg.Length[] = 0 THEN { lastMsgEntity _ NIL; RETURN[NilEntity]; }; lastMsgEntity _ e.entity _ DB.LookupEntity[MsgDomain, msg]; e.exists _ lastMsgEntity # NIL; }; GetAllMsgTextInfo: PROC[m: Entity] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { rel: Relship = DB.LookupProperty[mTextInfo, m]; textStart _ DB.V2I[DB.GetF[rel, mTIEntryStart]] + DB.V2I[DB.GetF[rel, mTITextOffset]]; textLen_ DB.V2I[DB.GetF[rel, mTITextLen]]; formatLen_ DB.V2I[DB.GetF[rel, mTIFormatLen]]; herald_ DB.V2S[DB.GetF[rel, mTIHerald]]; shortNameLen_ DB.V2I[DB.GetF[rel, mTIShortNameLen]]; }; GetTextInfo: PROC[m: Entity] RETURNS[textStart, textLen, formatLen: INT] = { rel: Relship = DB.LookupProperty[mTextInfo, m]; textStart _ DB.V2I[DB.GetF[rel, mTIEntryStart]] + DB.V2I[DB.GetF[rel, mTITextOffset]]; textLen_ DB.V2I[DB.GetF[rel, mTITextLen]]; formatLen_ DB.V2I[DB.GetF[rel, mTIFormatLen]]; }; SetMsgTextInfo: PROC[m: Entity, mle: MsgLogEntry, herald: ROPE, shortNameLen: INT] = { init: DB.ValueSequence = DB.L2VS[LIST[ -- order is crucial DB.E2V[m], DB.S2V[herald], DB.I2V[shortNameLen], DB.I2V[mle.entryStart], DB.I2V[mle.textOffset], DB.I2V[mle.textLen], DB.I2V[mle.formatLen] ]]; [] _ DB.CreateRelship[mTextInfo, init]; }; SetMsgDisplayInfo: PROC [m: Entity, hasBeenRead: BOOL, tocEntry: ROPE, startOfSubject: INT] = { init: DB.ValueSequence = DB.L2VS[LIST[ -- order is crucial DB.E2V[m], DB.S2V[tocEntry], DB.I2V[startOfSubject], DB.B2V[hasBeenRead] ]]; [] _ DB.CreateRelship[mDisplayInfo, init]; }; SetMsgInfo: PROC[m: Entity, date: GMT, show: BOOL] = { init: DB.ValueSequence = DB.L2VS[LIST[ DB.E2V[m], DB.T2V[date], IF show THEN [null[]] ELSE DB.E2V[WalnutDB.unacceptedEntity] ]]; [] _ DB.CreateRelship[mInfo, init]; }; END. WalnutDBMsgImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Willie-Sue, April 15, 1986 1:09:49 pm PST Donahue, May 17, 1985 4:03:40 pm PDT (Changed treatment of new messages) Contents: procedures dealing with Msgs in the Walnut message database Initiated by Donahue, 19 April 1983 Willie-Sue, January 18, 1985 1:29:34 pm PST Last Edited by: Willie-Sue, January 4, 1985 10:40:02 am PST Types Private Variables Internal Types Operations on Messages The message invariants are as follows: All messages belong to at least one message set. A message which is a member of the Deleted message set cannot belong to any other message sets. A message is automatically added to the Active message set when it is created. A message cannot be destroyed unless it has been deleted. Does a message with this name exist in the database. DestroyMsg is not allowed; this is ONLY done by the Expunge operation returns the mHasBeenReadIs attribute for msg sets the mHasBeenReadIs attribute for msg to TRUE (no check on old value) takes a parsed message from the log & puts it in the database -- if the message already exists in the database, then return TRUE. Note: this guy takes the LogEntry given and has the responsibility for constructing the TOCEntry and herald for the message returns the log position for the entry for this message (-1 if the message doesn't exist); used by the lazy evaluator Sets the message entry position to refer to the new value. We depend on the fact that SetMsgEntryPosition is ONLY called by the expunge code and that it has just done a MsgExists call, which sets the local cached variable lastMsgEntity returns the date by which the message is indexed in the database returns information needed to get the tioga text for msg from log returns information needed to get the tioga text for msg from log (also produce the herald to be used if displaying it in a viewer Return the display properties of a message (the hasbeenread flag and the table of contents entry. Return the msgSets that the message belongs to. Internal procedures Truncates s with "..." or expands it with blanks, so that it is about colWidth characters wide. Not exact, uses a few heuristics here... first remove any "< . . .>" in the name then do the same for any ( . . . ) in the name Κm˜šΟn™Jšœ Οmœ1™—šŸœ ŸœŸœ˜.JšŸœ6˜:—JšŸœ˜—JšŸœ˜—JšŸœ˜J˜—Jšœ ˜ J˜—J˜šœŸ œŸœŸœ˜?šœŸœ˜JšœŸœ˜6Jšœ ŸœŸœ ˜4Jšœ ŸœŸœ#˜6Jšœ˜—Jšœ ˜ J˜—Lš ™š œŸœŸœŸœ Ÿœ˜mJšœ Ÿœ˜šœŸœŸœ+ŸœŸ˜Cšœ/Ÿœ˜5JšŸœ˜ JšŸœ ˜——JšŸœŸœ˜@Jšœ=˜=Jšœ˜Jšœ&˜&J˜Jšœ%˜%Jšœ˜Jšœ%˜%Jšœ˜Jšœ,˜,JšŸœŸœ4˜PJšŸœ Ÿœ ˜FJ˜—J˜š œŸœŸœ ŸœŸœŸœ˜AJšœE™EJšœC™CšŸ˜Jšœ Ÿœ˜JšœŸœ˜ šŸ œŸœ˜=Jšœ˜šŸ˜JšŸœŸœŸœ˜Jš‘ ˜ šŸ˜Jšœ Ÿœ!˜1JšœŸœ˜7Jšœ˜—JšŸœ˜—JšŸœ˜šŸ˜Jšœ:˜:——JšŸœ‘ ˜Jš‘P˜PJšœ4‘˜OJšœ&Ÿœ ˜IJšŸœ˜ —JšŸœ˜—J˜šœŸœŸœ ŸœŸœŸœŸœ˜NJšœ Ÿœ˜JšœŸœ ˜Jšœ Ÿœ˜-Jšœ ˜ šŸœŸœŸœŸ˜"šœD˜DJšœ˜—Jšœ˜šŸœŸ˜šœ˜JšœŸœ˜0JšŸ˜—J˜——JšŸœ˜Jšœ˜Jšœ˜—J˜š œŸœŸœŸœ Ÿœ˜=Jšœ Ÿœ˜J˜*Jšœ'™'J˜šŸœ Ÿ˜˜4JšŸœ Ÿœ1˜@——Jšœ.™.J˜šŸœ Ÿ˜˜4JšŸœ Ÿœ1˜@——J˜5J˜—J˜š œŸœŸœŸœ˜6šŸœŸœ˜JšœŸœ˜JšŸœ ˜J˜—JšœŸœ˜;JšœŸœ˜J˜—J˜š œŸœ Ÿœ Ÿœ ŸœŸœ˜sJšœŸœ˜/Jš œ ŸœŸœŸœŸœ˜VJšœ ŸœŸœ˜*Jšœ ŸœŸœ˜.JšœŸœŸœ˜(JšœŸœŸœ˜4J˜—J˜š œŸœ Ÿœ Ÿœ˜LJšœŸœ˜/Jš œ ŸœŸœŸœŸœ˜VJšœ ŸœŸœ˜*Jšœ ŸœŸœ˜.J˜—J˜šœŸœ&ŸœŸœ˜VšœŸœŸœŸœ‘˜:JšŸœ˜ JšŸœ ˜JšŸœ˜JšŸœ˜JšŸœ˜JšŸœ˜JšŸœ˜J˜—JšœŸœ ˜(J˜—J˜š œŸœŸœ ŸœŸœ˜_šœŸœŸœŸœ‘˜:JšŸœ˜ JšŸœ˜JšŸœ˜JšŸœ˜J˜—JšœŸœ#˜*J˜—J˜š œŸœŸœŸœ˜6šœŸœŸœŸœ˜&JšŸœ˜ JšŸœ Ÿ˜ JšŸœŸœ ŸœŸœ˜