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];
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:
BOOL←
TRUE, TOCentry:
ROPE←
NIL, 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 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];
};
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]
};