WalnutDBMsgImplbind waln.mesa
Copyright © 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
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;
Types
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;
Private Variables
DBIntValue: REF INTNEW[INT];
DBBoolValue: REF BOOLNEW[BOOL];
DBGmtValue: REF DB.GMTNEW[DB.GMT];
lastMsgEntity: Entity ← NIL;
blankWidth: INT← VFonts.CharWidth[' ]; -- in default font
blanks: ROPE← " "; -- lotsa blanks
Internal Types
IsEntity: TYPE = RECORD [entity: Entity, exists: BOOL];
NullEntity: IsEntity = [NIL, FALSE];
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.
MsgExists: PUBLIC PROC[msg: ROPE] RETURNS [exists: BOOL] = {
Does a message with this name exist in the database.
IsMsg: PROC = { exists ← GetMsgEntity[msg: msg].exists };
WalnutDBInternal.CarefullyApply[IsMsg];
};
DestroyMsg is not allowed; this is ONLY done by the Expunge operation
GetHasBeenRead: PUBLIC PROC[msg: ROPE] RETURNS[has: BOOL] = {
returns the mHasBeenReadIs attribute for msg
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] = {
sets the mHasBeenReadIs attribute for msg to TRUE (no check on old value)
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] = {
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
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] = {
returns the log position for the entry for this message (-1 if the message doesn't exist); used by the lazy evaluator
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] = {
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
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] = {
returns the date by which the message is indexed in the database
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] = {
returns information needed to get the tioga text for msg from log
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] = {
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
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: BOOLTRUE, TOCentry: ROPENIL, startOfSubject: INT← 0] = {
Return the display properties of a message (the hasbeenread flag and the table of contents entry.
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] = {
Return the msgSets that the message belongs to.
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 ROPECONS[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];
};
Internal procedures
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] =
Truncates s with "..." or expands it with blanks, so that it is about
colWidth characters wide. Not exact, uses a few heuristics here...
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: " "];
first remove any "< . . .>" 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] };
then do the same for any ( . . . ) 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] };
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.