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: " "]; 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. WalnutDBMsgImplbind waln.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Willie-Sue, July 11, 1985 3:07:08 pm PDT 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 ΚΗ˜šΟn™Jšœ Οmœ1™—J˜š œŸœŸœ ŸœŸœ˜_JšœŸœŸœ˜JJšœ˜JšŸœ(˜*JšŸœŸœ˜,Jšœ˜JšŸœ*˜,J˜—J˜š œŸœŸœŸœ˜6JšœŸœŸœ˜EJšœ˜JšŸœ ˜"Jšœ˜JšŸœ ˜"Jšœ˜—J˜—JšŸœ˜J˜—…—*φBΗ