WalnutDBMsgImpl.mesa
Copyright Ó 1984, 1988, 1989, 1992 by Xerox Corporation. All rights reserved.
Willie-Sue, September 11, 1989 11:13:48 am PDT
Donahue, May 17, 1985 4:03:40 pm PDT
(Changed treatment of new messages)
Jack Kent, November 24, 1986 2:14:39 pm PST
Doug Terry, December 3, 1990 4:25 pm PST
Contents: procedures dealing with Msgs in the Walnut message database
Initiated by Donahue, 19 April 1983
Willie-s, April 27, 1992 1:44 pm PDT
DIRECTORY
BasicTime USING [nullGMT, GMT, Now],
Convert,
GvNsMapExtras USING [GvSyntax, SyntaxOf],
IO,
MailParse USING [endOfInput],
LoganBerry,
LoganBerryEntry,
Rope,
RuntimeError USING [BoundsFault],
SendMailOps USING [IsThisTheCurrentUser],
UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc],
VFonts USING [CharWidth, StringWidth],
ViewerTools USING [TiogaContents],
WalnutDefs USING [Error, MsgSet, SeFromToCcSuDaMid, WalnutOpsHandle],
WalnutDB USING [ ChangeCountOfMsgs, ChangeCountInMsgSet, EntryObject, EntryRef, GeneralEnumerator, GeneralEnumeratorRec, GetMsgDisplayInfo],
WalnutKernelDefs USING [MsgLogEntry],
WalnutSchema;
WalnutDBMsgImpl: CEDAR PROGRAM
IMPORTS
BasicTime, Convert, IO, LoganBerry, LoganBerryEntry, Rope, RuntimeError,
SendMailOps, UserProfile, VFonts,
WalnutDB, WalnutDefs
EXPORTS WalnutDB, WalnutDefs =
BEGIN OPEN WalnutDB, WalnutSchema;
Types
ROPE: TYPE = Rope.ROPE;
GMT: TYPE = BasicTime.GMT;
TiogaContents: TYPE = ViewerTools.TiogaContents;
Relship: TYPE = LoganBerry.Entry;
Address: TYPE = ROPE;
Subject: TYPE = ROPE;
Keyword: TYPE = ROPE;
WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle;
SchemaHandle: TYPE = WalnutSchema.SchemaHandle;
SchemaHandleRec: PUBLIC TYPE = WalnutSchema.SchemaHandleRec;
MsgSet: TYPE = WalnutDefs.MsgSet;
MsgLogEntry: TYPE = WalnutKernelDefs.MsgLogEntry;
Private Variables
useFromFieldInTOC: BOOL ¬
UserProfile.Boolean[key: "Walnut.UseFromFieldInTOC", default: FALSE];
blankWidth: INT ¬ VFonts.CharWidth[' ]; -- in default font
blanks: ROPE ¬ " "; -- lotsa blanks
Internal Types
IsEntity: TYPE = RECORD [entity: LoganBerry.Entry, exists: BOOL];
NilEntity: IsEntity = [NIL, FALSE];
GeneralEnumerator: TYPE = WalnutDB.GeneralEnumerator;
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[opsH: WalnutOpsHandle, msg: ROPE] RETURNS [exists: BOOL] =
Does a message with this name exist in the database.
{ RETURN[GetMsgEntity[opsH, msg].exists] };
DestroyMsg is not allowed; this is ONLY done by the Expunge operation
GetIsInReplyTo: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE]
RETURNS[isInReplyTo: BOOL] = {
returns TRUE if the subject field of this message started with "Re: "
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
IF ~m.exists THEN RETURN[FALSE];
isInReplyTo ¬ LoganBerryEntry.V2B[LoganBerryEntry.GetAttr[m.entity, sH.mMIIsInReplyTo]];
};
GetHasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE]
RETURNS[has: BOOL] = {
returns the mHasBeenReadIs attribute for msg
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
IF ~m.exists THEN { has ¬ FALSE; RETURN };
has ¬ LoganBerryEntry.V2B[LoganBerryEntry.GetAttr[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, msg]].entry, sH.mDIHasBeenRead]];
};
SetHasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] = {
sets the mHasBeenReadIs attribute for msg to TRUE (no check on old value)
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
md: LoganBerry.Entry;
IF ~m.exists THEN RETURN;
md ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, msg]].entry;
LoganBerryEntry.SetAttr[md, sH.mDIHasBeenRead, LoganBerryEntry.B2V[TRUE]];
LoganBerry.WriteEntry[db: opsH.db, entry: md, replace: TRUE];
};
AddNewMsg: PUBLIC PROC[opsH: WalnutOpsHandle, 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
msgEName: ROPE = msg.msg;
sH: SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msgEName];
IF mExisted ¬ m.exists THEN RETURN;  -- id's assumed unique
BEGIN
me: ROPE = msgEName;
herald, tocHead: ROPE;
shortNameLen: INT;
date: BasicTime.GMT = msg.date;
sender: Address;
[herald, tocHead, shortNameLen] ¬ ComputeHeraldAndTOC[msg];
sender ¬ SetAddresses[opsH, me, msg];
SetAllMsgInfo[opsH, me, msg, herald, shortNameLen, date, sender];
SetMsgDisplayInfo[opsH, me, FALSE, tocHead];
SetMsgInfo[opsH, me, msg.date, msg.show];
WalnutDB.ChangeCountOfMsgs[opsH, 1];
BEGIN   -- put it in active - no checking necessary
init: LoganBerry.Entry =
LIST[
[$Key, Rope.Concat[sH.cdRelation, me]],
[sH.cdMsg, me],
[sH.cdMsgSet, opsH.schemaHandle.activeEntity],
[sH.cdDate, LoganBerryEntry.T2V[msg.date]]
];
LoganBerry.WriteEntry[db: opsH.db, entry: init];
IF msg.show THEN
WalnutDB.ChangeCountInMsgSet[opsH, opsH.schemaHandle.activeEntity, 1];
END;
END;
};
GetMsgEntryPosition: PUBLIC PROC[opsH: WalnutOpsHandle, 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
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
IF ~m.exists THEN {pos ¬ -1; RETURN};
pos ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry, sH.mMIEntryStart] ];
};
SetMsgEntryPosition: PUBLIC PROC[opsH: WalnutOpsHandle, 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
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
IF sH.lastMsgEntity = NIL THEN RETURN;
LoganBerryEntry.SetAttr[sH.lastMsgEntity, sH.mMIEntryStart, LoganBerryEntry.I2V[to]];
LoganBerry.WriteEntry[db: opsH.db, entry: sH.lastMsgEntity, replace: TRUE];
};
GetMsgDate: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[date: GMT] = {
returns the date by which the message is indexed in the database
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
IF ~m.exists THEN { date ¬ BasicTime.nullGMT; RETURN}
ELSE {
rel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, msg]].entry;
date ¬ LoganBerryEntry.V2T[LoganBerryEntry.GetAttr[rel, sH.mDateIs]];
};
};
GetMsgTextInfo: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE]
RETURNS[textStart, textLen, formatLen: INT] = {
returns information needed to get the tioga text for msg from log
m: IsEntity = GetMsgEntity[opsH, msg];
IF ~m.exists THEN {
textStart ¬ textLen ¬ formatLen ¬ 0;
RETURN;
};
[textStart, textLen, formatLen] ¬ GetTextInfo[opsH.schemaHandle, m.entity];
};
GetMsgText: PUBLIC PROC[opsH: WalnutOpsHandle, 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
m: IsEntity = GetMsgEntity[opsH, msg];
IF ~m.exists THEN {
textStart ¬ textLen ¬ formatLen ¬ shortNameLen ¬ 0;
RETURN;
};
[textStart, textLen, formatLen, herald, shortNameLen] ¬
 GetAllMsgTextInfo[opsH.schemaHandle, m.entity];
};
GetDisplayProps: PUBLIC PROC[opsH: WalnutOpsHandle, 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.
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
IF m.exists THEN
[hasBeenRead, tocEntry, startOfSubject] ¬ WalnutDB.GetMsgDisplayInfo[opsH, LoganBerryEntry.GetAttr[m.entity, sH.mMIOf]];
};
GetCategories: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE]
RETURNS[msList: LIST OF ROPE] = {
Return the msgSets that the message belongs to.
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
m: IsEntity = GetMsgEntity[opsH, msg];
rel: Relship;
IF ~m.exists THEN RETURN;
rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, msg]].entry;
msList ¬ LoganBerryEntry.GetAllAttrs[rel, sH.cdMsgSet];
};
SizeOfDatabase: PUBLIC PROC[opsH: WalnutOpsHandle]
RETURNS[messages, msgSets: INT] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry;
messages ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgCount]];
msgSets ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetCount]];
};
GenerateEntriesPlusDate: PUBLIC PROC [opsH: WalnutOpsHandle, attr: ATOM, start: ROPE ¬ NIL, end: ROPE ¬ NIL, dateStart: ROPE ¬ NIL, dateEnd: ROPE ¬ NIL]
RETURNS [genEnum: GeneralEnumerator ] = {
This only works well on attributes that are indexed in the database (typically the Sender, Date, or Subject attribute). All other attributes result in a full scan. dateStart and dateEnd are completely ignored.
IndexedField: PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType] RETURNS [BOOLEAN ¬ FALSE] ~ {
schema: LoganBerry.SchemaInfo ¬ LoganBerry.Describe[NIL, db];
FOR i: LIST OF LoganBerry.IndexInfo ¬ schema.indices, i.rest WHILE i # NIL DO
IF i.first.key = key THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
GenerateEntriesOrFullScan: PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue, end: LoganBerry.AttributeValue] RETURNS [cursor: LoganBerry.Cursor] ~ {
IF key#NIL AND IndexedField[db, key]
THEN cursor ¬ LoganBerry.GenerateEntries[db: db, key: key, start: start, end: end]
ELSE cursor ¬ LoganBerry.GenerateEntries[db: db, key: $Key, start: sH.mMsgInfo, end: Rope.Concat[sH.mMsgInfo, "\255"]]; -- full scan
};
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
genEnum ¬ NEW[WalnutDB.GeneralEnumeratorRec];
genEnum.opsH ¬ opsH;
SELECT attr FROM
$Key => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end];
};
$MsgSetName => {
This always does a full scan. We could get a cursor on the message set, but would need to run an additional query to get the message info for each message in the set. This does not fit well into the current model.
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end];
};
$MessageText => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end];
};
$From => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end];
};
$To => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end];
};
$Cc => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end];
};
$Date => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMIDate, start: start, end: end];
};
$Sender => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMISender, start: start, end: end];
};
$Subject => {
genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMISubject, start: start, end: end];
};
ENDCASE => ERROR WalnutDefs.Error[$db, $invalidQueryAttr,
IO.PutFR1["Attribute is %g", [atom[attr]]] ];
};
NextEntry: PUBLIC PROC[genEnum: GeneralEnumerator] RETURNS[entry: EntryRef ¬ NIL] = {
sH: WalnutSchema.SchemaHandle = genEnum.opsH.schemaHandle;
msgID: ROPE;
e: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: genEnum.cursor];
IF e = NIL THEN RETURN[NIL];
msgID ¬ LoganBerryEntry.GetAttr[e, sH.mMIOf];
entry ¬ NEW[WalnutDB.EntryObject];
entry.seFromToCcSuDaMid.sender ¬ LoganBerryEntry.GetAttr[e, sH.mMISender];
entry.seFromToCcSuDaMid.from ¬ GetFromList[genEnum.opsH, msgID];
entry.seFromToCcSuDaMid.to ¬ GetToList[genEnum.opsH, msgID];
entry.seFromToCcSuDaMid.cc ¬ GetCcList[genEnum.opsH, msgID];
entry.seFromToCcSuDaMid.keyword ¬ NIL;
entry.seFromToCcSuDaMid.subject ¬ LoganBerryEntry.GetAttr[e, sH.mMISubject];
entry.seFromToCcSuDaMid.fullSubjectText ¬ LoganBerryEntry.GetAttr[e, sH.mMISubjectText];
entry.seFromToCcSuDaMid.date ¬ LoganBerryEntry.GetAttr[e, sH.mMIDate];
entry.seFromToCcSuDaMid.msgID ¬ msgID;
entry.msgSetName ¬ GetCategories[genEnum.opsH, msgID].first; -- this probably isn't right
};
Internal procedures
ComputeHeraldAndTOC: PROC[mle: MsgLogEntry]
RETURNS[herald, tocHead: ROPE, shortNameLen: INT] = {
date, tocx: ROPE;
starter: ROPE = IF useFromFieldInTOC THEN mle.from ELSE mle.sender;
from: ROPE = IF SendMailOps.IsThisTheCurrentUser[starter] THEN Rope.Concat["To: ", mle.to] ELSE starter;
IF mle.date = BasicTime.nullGMT THEN mle.date ¬ BasicTime.Now[];
date ¬ Rope.Substr[Convert.RopeFromTimeRFC822[mle.date], 0, 9];
tocx ¬ date.Cat[" ", from];
tocHead ¬ SquashRopeIntoWidth[tocx, 165];
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, 55], " ..."];
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.Concat[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.Concat[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[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[e: IsEntity] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
IF msg.Length[] = 0 THEN {
sH.lastMsgEntity ¬ NIL;
RETURN[NilEntity];
};
sH.lastMsgEntity ¬ e.entity ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry;
e.exists ¬ sH.lastMsgEntity # NIL;
};
GetAllMsgTextInfo: PROC[sH: SchemaHandle, m: LoganBerry.Entry]
RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = {
rel: Relship = m;
textStart ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIEntryStart]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextOffset]];
textLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextLen]];
formatLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIFormatLen]];
herald ¬ LoganBerryEntry.GetAttr[rel, sH.mMIHerald];
shortNameLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIShortNameLen]];
};
GetTextInfo: PROC[sH: SchemaHandle, m: LoganBerry.Entry]
RETURNS[textStart, textLen, formatLen: INT] = {
rel: Relship = m;
textStart ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIEntryStart]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextOffset]];
textLen¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextLen]];
formatLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIFormatLen]];
};
SetAllMsgInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, mle: MsgLogEntry, herald: ROPE, shortNameLen: INT, date: BasicTime.GMT, sender: Address] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
isInReplyTo: BOOL ¬ FALSE;
sub: ROPE = mle.subject;
subject: Subject;
subName: ROPE;
afterRe: INT ¬ 0;
DO
IF sub.Find["Re: ", afterRe, FALSE] # afterRe THEN EXIT;
afterRe ¬ afterRe + 4;
isInReplyTo ¬ TRUE;
ENDLOOP;
subName ¬ sub.Substr[start: afterRe, len: 20];
IF subName.Length[] = 0 THEN subName ¬ "No Subject Field";
subject ¬ subName;
BEGIN
init: LoganBerry.Entry = LIST[
[$Key, Rope.Concat[sH.mMsgInfo, m]],
[sH.mMIOf, m],
[sH.mMIHerald, herald],
[sH.mMIShortNameLen, LoganBerryEntry.I2V[shortNameLen]],
[sH.mMIEntryStart, LoganBerryEntry.I2V[mle.entryStart]],
[sH.mMITextOffset, LoganBerryEntry.I2V[mle.textOffset]],
[sH.mMITextLen, LoganBerryEntry.I2V[mle.textLen]],
[sH.mMIFormatLen, LoganBerryEntry.I2V[mle.formatLen]],
[sH.mMIDate, LoganBerryEntry.T2V[date]],
[sH.mMISubject, subject],
[sH.mMISubjectText, Rope.Substr[base: mle.subject, len: 99]],
[sH.mMIIsInReplyTo, LoganBerryEntry.B2V[isInReplyTo]],
[sH.mMISender, sender]
];
LoganBerry.WriteEntry[db: opsH.db, entry: init];
END;
};
SetMsgDisplayInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, hasBeenRead: BOOL, tocHeadEntry: ROPE] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
init: LoganBerry.Entry = LIST[
[$Key, Rope.Concat[sH.mDisplayInfo, m]],
[sH.mDIOf, m],
[sH.mDITOCHeadEntry, tocHeadEntry],
[sH.mDIHasBeenRead, LoganBerryEntry.B2V[hasBeenRead]]
];
LoganBerry.WriteEntry[db: opsH.db, entry: init];
};
SetMsgInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, date: GMT, show: BOOL] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
init: LoganBerry.Entry = LIST[
[$Key, Rope.Concat[sH.mInfo, m]],
[sH.mInfoOf, m],
[sH.mDateIs, LoganBerryEntry.T2V[date]],
[sH.mShowIs, IF show THEN "NULL" ELSE opsH.schemaHandle.unacceptedEntity]
];
LoganBerry.WriteEntry[db: opsH.db, entry: init];
};
ParseForSender: PROC[sH: SchemaHandle, adr: ROPE]
RETURNS[e: Address, defaultReg: ROPE] = {
thisName: ROPE;
thisName ¬ NameInWalnutFormat[adr];
defaultReg ¬ BreakName[thisName].reg;
e ¬ thisName;
};
BreakName: PROC[name: Rope.ROPE] RETURNS[sn, reg: Rope.ROPE] = {
length: INT = name.Length[];
FOR i: INT DECREASING IN [0..length) DO
IF name.Fetch[i] = '. THEN RETURN[
sn: name.Substr[start: 0, len: i],
reg: name.Substr[start: i+1, len: length-(i+1)] ];
ENDLOOP;
RETURN[sn: name, reg: NIL];
};
NameInWalnutFormat: PROC[nameIn: ROPE] RETURNS[ROPE] = {
this: ROPE ¬ JustTheName[nameIn];
use ← GvNsMapExtras.GvSyntax[name: this, quoteIfNeeded: FALSE];
IF use = NIL THEN {
append: ROPE;
SELECT GvNsMapExtras.SyntaxOf[this] FROM
gv => append ← ".gv";
ns => append ← ".ns";
ENDCASE => append ← ".unknown";
use ← this.Concat[append];
};
IF this.Fetch[0] = '" THEN { -- strip "'s
other: INT = this.Find["\"", 1];
IF other = -1 THEN this ¬ this.Substr[1]
ELSE {
firstPart: ROPE = this.Substr[1, other-1];
IF other # this.Length[] - 1 THEN {
rest: ROPE = this.Substr[other+1];
this ¬ Rope.Concat[firstPart, rest];
}
ELSE this ¬ firstPart;
};
};
RETURN[this];
};
JustTheName: PROC[nameIn: ROPE]
RETURNS[name: ROPE] = {
len: INT ¬ Rope.Length[nameIn];
lastChar: CHAR ¬ nameIn.Fetch[len-1];
comment: BOOL ¬ Rope.Match[pattern: "*(*)*", object: nameIn, case: FALSE];
route: BOOL ¬ Rope.Match[pattern: "*<*>*", object: nameIn, case: FALSE];
SELECT TRUE FROM
comment => {
len: INT ¬ Rope.Length[nameIn];
posLeft: INT ¬ Rope.Find[nameIn, "("];
posRight: INT ¬ Rope.Find[nameIn, ")", posLeft];
IF posLeft > len-posRight
THEN name ¬ Rope.Substr[base: nameIn, start: 0, len: posLeft-1]
ELSE name ¬ Rope.Substr[base: nameIn, start: posRight+1, len: len-posRight-1];
};
route => {
posLeft: INT ¬ Rope.Find[nameIn, "<"];
posRight: INT ¬ Rope.Find[nameIn, ">", posLeft];
name ¬ Rope.Substr[base: nameIn, start: posLeft+1, len: posRight-posLeft-1];
};
ENDCASE => RETURN[nameIn];
name ¬ StripOffWhiteSpace[name];
IF name.Length[] = 0 THEN RETURN[nameIn];
};
StripOffTrailingWhiteSpace: PROC [r: ROPE] RETURNS [ROPE] = {
len: INT ¬ Rope.Length[r];
skip: ROPE ¬ " \t";
WHILE len > 0 DO
len ¬ len-1;
IF Rope.Find[Rope.FromChar[Rope.Fetch[r, len]], skip] = -1 THEN EXIT;
r ¬ Rope.Substr[r, 0, len];
ENDLOOP;
RETURN[r];
};
StripOffLeadingWhiteSpace: PROC[rope: ROPE] RETURNS [clean: ROPE] = {
skip: ROPE ¬ " \t";
len: INT ¬ Rope.Length[rope];
first: INT ¬ Rope.SkipOver[rope, 0, skip];
clean ¬ Rope.Substr[rope, first, len];
};
StripOffWhiteSpace: PROC[rope: ROPE, stripLeft: BOOL ¬ TRUE, stripRight: BOOL ¬ TRUE] RETURNS [clean: ROPE] = {
clean ¬ rope;
IF stripLeft THEN clean ¬ StripOffLeadingWhiteSpace[clean];
IF stripRight THEN clean ¬ StripOffTrailingWhiteSpace[clean];
};
ParseForAddressList: PROC[sH: SchemaHandle, adr: ROPE, defaultReg: ROPE]
RETURNS[aList: LIST OF Address] = {
nameList: LIST OF ROPE ¬ NIL;
startOfAdr, charIndex: INT ¬ 0; -- nowAt reset at each call to OneName
aLen: INT = adr.Length[];
NextChar: PROC RETURNS [char: CHAR] = {
IF charIndex >= aLen THEN RETURN[MailParse.endOfInput];
char ¬ adr.Fetch[charIndex];
charIndex ¬ charIndex + 1;
};
IF adr.Length[] = 0 THEN RETURN[NIL]
ELSE DO
ch: CHAR;
this: ROPE;
UNTIL (ch ¬ NextChar[]) = ', OR ch = MailParse.endOfInput DO ENDLOOP;
SELECT ch FROM
', => this ¬ adr.Substr[startOfAdr, charIndex - startOfAdr -1];
ENDCASE => this ¬ adr.Substr[startOfAdr, charIndex - startOfAdr];
startOfAdr ¬ charIndex;
IF this.Length[] # 0 THEN {
this ¬ NameInWalnutFormat[this];
nameList ¬ CONS[FixReg[this, defaultReg], nameList];
};
IF ch = MailParse.endOfInput THEN EXIT;
ENDLOOP;
FOR nL: LIST OF ROPE ¬ nameList, nL.rest UNTIL nL = NIL DO
ad: Address;
IF nL.first.Length[] = 0 THEN LOOP;  -- one more check
ad ¬ nL.first;
aList ¬ CONS[ad, aList];
ENDLOOP;
};
FixReg: PROC[this, defaultReg: ROPE] RETURNS[ROPE] = {
dotRope: ROPE = ".";
pos: INT = Rope.FindBackward[this, dotRope];
IF pos = -1 THEN RETURN[ Rope.Cat[this, dotRope, defaultReg] ];
RETURN[this];
};
TrySomethingForThisName: PROC[bad: ROPE] RETURNS[something, reg: ROPE] = {
match, prev: INT;
matchRope: ROPE ¬ "\"";
IF (match ¬ bad.Find["<"]) # -1 THEN matchRope ¬ ">"
ELSE match ¬ bad.Find["\""];
IF match # -1 THEN {
end: INT ¬ bad.Find[matchRope, match+1];
IF end = -1 THEN something ¬ bad
ELSE something ¬ bad.Substr[match+1, end-match-1];
}
ELSE something ¬ bad;  -- use it all
match ¬ prev ¬ -1;
UNTIL (match ¬ something.Find[".", prev+1]) = -1 DO prev ¬ match; ENDLOOP;
IF prev # -1 THEN reg ¬ something.Substr[prev+1];
};
SetAddresses: PROC[opsH: WalnutOpsHandle, m: ROPE, mle: MsgLogEntry]
RETURNS[sender: Address]= {
sH: SchemaHandle = opsH.schemaHandle;
defaultReg: ROPE;
from: LIST OF Address;
to: LIST OF Address;
cc: LIST OF Address;
[sender, defaultReg] ¬ ParseForSender[sH, mle.sender];
IF opsH.completeSchema THEN {
init: LoganBerry.Entry;
from ¬ ParseForAddressList[sH, mle.from, defaultReg];
to ¬ ParseForAddressList[sH, mle.to, defaultReg];
cc ¬ ParseForAddressList[sH, mle.cc, defaultReg];
init ¬ LIST[
[$Key, Rope.Concat[sH.toRelation, m]],
[sH.toMsg, m],
[sH.toDate, LoganBerryEntry.T2V[mle.date]]
];
FOR tL: LIST OF Address ¬ to, tL.rest UNTIL tL = NIL DO
init ¬ LoganBerryEntry.AddAttr[init, sH.toAddress, tL.first];
ENDLOOP;
LoganBerry.WriteEntry[db: opsH.db, entry: init];
init ¬ LIST[
[$Key, Rope.Concat[sH.ccRelation, m]],
[sH.ccMsg, m],
[sH.ccDate, LoganBerryEntry.T2V[mle.date]]
];
FOR ccL: LIST OF Address ¬ cc, ccL.rest UNTIL ccL = NIL DO
init ¬ LoganBerryEntry.AddAttr[init, sH.ccAddress, ccL.first];
ENDLOOP;
LoganBerry.WriteEntry[db: opsH.db, entry: init];
init ¬ LIST[
[$Key, Rope.Concat[sH.fromRelation, m]],
[sH.fromMsg, m],
[sH.fromDate, LoganBerryEntry.T2V[mle.date]]
];
FOR fL: LIST OF Address ¬ from, fL.rest UNTIL fL = NIL DO
init ¬ LoganBerryEntry.AddAttr[init, sH.fromAddress, fL.first];
ENDLOOP;
LoganBerry.WriteEntry[db: opsH.db, entry: init];
};
};
GetToList: PROC[opsH: WalnutOpsHandle, me: ROPE] RETURNS[tL: LIST OF ROPE] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
rel: Relship;
rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.toRelation, me]].entry;
tL ¬ LoganBerryEntry.GetAllAttrs[rel, sH.toAddress];
};
GetFromList: PROC[opsH: WalnutOpsHandle, me: ROPE] RETURNS[fL: LIST OF ROPE] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
rel: Relship;
rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.fromRelation, me]].entry;
fL ¬ LoganBerryEntry.GetAllAttrs[rel, sH.fromAddress];
};
GetCcList: PROC[opsH: WalnutOpsHandle, me: ROPE] RETURNS[ccL: LIST OF ROPE] = {
sH: WalnutSchema.SchemaHandle = opsH.schemaHandle;
rel: Relship;
rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.ccRelation, me]].entry;
ccL ¬ LoganBerryEntry.GetAllAttrs[rel, sH.ccAddress];
};
SetVar: UserProfile.ProfileChangedProc = {
useFromFieldInTOC ¬
UserProfile.Boolean[key: "Walnut.UseFromFieldInTOC", default: FALSE];
};
UserProfile.CallWhenProfileChanges[SetVar];
END.