<> <> <> <> <<(Changed treatment of new messages)>> <<>> <> <> <> <> DIRECTORY BasicTime USING [nullGMT, GMT, Now], DB USING [ Attribute, AttributeValueList, Entity, EntitySet, GMT, Relship, RelshipSet, Value, DeclareEntity, DeclareRelship, GetF, NameOf, NextRelship, Null, RelationSubset, ReleaseRelshipSet, SetF, S2V, V2B, V2E, V2I, V2S, V2T], Rope, RuntimeError USING [BoundsFault], VFonts USING [CharWidth, StringWidth], ViewerTools USING [TiogaContents], WalnutDefs USING [MsgSet], WalnutDB USING [], WalnutDBInternal USING [activeMessageSet, CarefullyApply, ChangeCountOfMsgs, ChangeCountInMsgSet, GetMsgDisplayInfo], WalnutKernelDefs USING [MsgLogEntry], WalnutSchema, WalnutSendOps USING [simpleUserName, userRName, RFC822Date]; WalnutDBMsgImpl: CEDAR PROGRAM IMPORTS BasicTime, DB, Rope, RuntimeError, VFonts, WalnutDBInternal, 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; <> DBIntValue: REF INT _ NEW[INT]; DBBoolValue: REF BOOL _ NEW[BOOL]; DBGmtValue: REF DB.GMT _ NEW[DB.GMT]; lastMsgEntity: Entity _ NIL; blankWidth: INT_ VFonts.CharWidth[' ]; -- in default font blanks: ROPE_ " "; -- lotsa blanks <> IsEntity: TYPE = RECORD [entity: Entity, exists: BOOL]; NullEntity: IsEntity = [NIL, FALSE]; <> <> <<>> <> <> <> <<>> MsgExists: PUBLIC PROC[msg: ROPE] RETURNS [exists: BOOL] = { <> IsMsg: PROC = { exists _ GetMsgEntity[msg: msg].exists }; WalnutDBInternal.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[GetMsgDisplayInfoRel[m.entity], mDIHasBeenRead]]; }; WalnutDBInternal.CarefullyApply[Ghbr]; }; SetHasBeenRead: PUBLIC PROC[msg: ROPE] = { <> DoSet: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; IF ~m.exists THEN RETURN; DBBoolValue^ _ TRUE; DB.SetF[GetMsgDisplayInfoRel[m.entity], mDIHasBeenRead, DBBoolValue]; }; WalnutDBInternal.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, NewOnly]; 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]; WalnutDBInternal.ChangeCountOfMsgs[1]; DBGmtValue^ _ [msg.date]; BEGIN -- put it in active - no checking necessary avl: DB.AttributeValueList = LIST[ [cdMsg, me], [cdMsgSet, WalnutDBInternal.activeMessageSet], [cdDate, DBGmtValue] ]; [] _ DB.DeclareRelship[cdRelation, avl]; IF msg.show THEN WalnutDBInternal.ChangeCountInMsgSet[WalnutDBInternal.activeMessageSet, 1]; END; END; }; WalnutDBInternal.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[GetMsgTextInfoRel[lastMsgEntity], mTIEntryStart] ]; }; WalnutDBInternal.CarefullyApply[Gmep]; }; SetMsgEntryPosition: PUBLIC PROC[to: INT] = { <> Smep: PROC = { IF DB.Null[lastMsgEntity] THEN RETURN; DBIntValue^ _ to; DB.SetF[GetMsgTextInfoRel[lastMsgEntity], mTIEntryStart, DBIntValue]; }; WalnutDBInternal.CarefullyApply[Smep]; }; GetMsgDate: PUBLIC PROC[msg: ROPE] RETURNS[date: GMT] = { <> Gmd: PROC = { m: IsEntity = GetMsgEntity[msg: msg]; date _ BasicTime.nullGMT; IF ~m.exists THEN { date _ BasicTime.nullGMT; RETURN} ELSE { rel: Relship = DB.DeclareRelship[mInfo, LIST[[mInfoOf, m.entity]]]; date _ DB.V2T[DB.GetF[rel, mDateIs]]; }; }; WalnutDBInternal.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]; }; WalnutDBInternal.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]; }; WalnutDBInternal.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]_ WalnutDBInternal.GetMsgDisplayInfo[m.entity]; }; WalnutDBInternal.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; lastInList_ msList_ NIL; -- for retries rs _ DB.RelationSubset[cdRelation, LIST[[cdMsg, m.entity]]]; BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]}; UNTIL DB.Null[rel_ DB.NextRelship[rs]] DO new: LIST OF ROPE _ CONS[DB.NameOf[DB.V2E[DB.GetF[rel, cdMsgSet]]], NIL]; IF msList = NIL THEN lastInList_ msList_ new ELSE {lastInList.rest_ new; lastInList_ lastInList.rest}; ENDLOOP; DB.ReleaseRelshipSet[rs]; END; }; WalnutDBInternal.CarefullyApply[DoCats]; }; SizeOfDatabase: PUBLIC PROC RETURNS[messages, msgSets: INT] = { SizeOf: PROC = { messages _ DB.V2I[DB.GetF[rVersionInfo, gMsgCount]]; msgSets _ DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]]; }; WalnutDBInternal.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[NullEntity]; }; e.entity _ DB.DeclareEntity[MsgDomain, msg, OldOnly]; e.exists _ NOT DB.Null[lastMsgEntity _ e.entity]; }; GetMsgTextInfoRel: PROC[m: Entity] RETURNS[rel: Relship] = INLINE { RETURN[DB.DeclareRelship[mTextInfo, LIST[[mTIOf, m]]]] }; GetAllMsgTextInfo: PROC[m: Entity] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { rel: Relship_ GetMsgTextInfoRel[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_ GetMsgTextInfoRel[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] = { rel: Relship = DB.DeclareRelship[mTextInfo, LIST[[mTIOf, m]], NewOnly]; DBIntValue^ _ mle.entryStart; DB.SetF[rel, mTIEntryStart, DBIntValue]; DBIntValue^ _ mle.textOffset; DB.SetF[rel, mTITextOffset, DBIntValue]; DBIntValue^ _ mle.textLen; DB.SetF[rel, mTITextLen, DBIntValue]; DBIntValue^ _ mle.formatLen; DB.SetF[rel, mTIFormatLen, DBIntValue]; DB.SetF[rel, mTIHerald, DB.S2V[herald]]; DBIntValue^ _ shortNameLen; DB.SetF[rel, mTIShortNameLen, DBIntValue]; }; GetMsgDisplayInfoRel: PROC[m: Entity] RETURNS[Relship] = INLINE { RETURN[DB.DeclareRelship[mDisplayInfo, LIST[[mDIOf, m]]]] }; SetMsgDisplayInfo: PROC [m: Entity, hasBeenRead: BOOL, tocEntry: ROPE, startOfSubject: INT] = { rel: Relship = DB.DeclareRelship[mDisplayInfo, LIST[[mDIOf, m]], NewOnly]; DBBoolValue^ _ hasBeenRead; DB.SetF[rel, mDIHasBeenRead, DBBoolValue]; DB.SetF[rel, mDITOCEntry, DB.S2V[tocEntry]]; DBIntValue^ _ startOfSubject; DB.SetF[rel, mDIStartOfSubject, DBIntValue]; }; SetMsgInfo: PROC[m: Entity, date: GMT, show: BOOL] = { rel: Relship = DB.DeclareRelship[mInfo, LIST[[mInfoOf, m]], NewOnly]; DBGmtValue^ _ [date]; DB.SetF[rel, mDateIs, DBGmtValue]; DBBoolValue^ _ show; DB.SetF[rel, mShowIs, DBBoolValue] }; END.