<> <> <> <> <<(Changed treatment of new messages)>> <<>> <> <> <> <> 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: " "]; <" in the name>> 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.