WalnutDBMsgSetsImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Willie-Sue, July 11, 1985 1:58:41 pm PDT
Donahue, July 22, 1985 5:17:38 pm PDT
Contents: types and procedures dealing with the Walnut message database
Initiated by Willie-Sue, September 24, 1984
Last Edited by: Willie-Sue, January 9, 1985 11:17:16 am PST
Last Edited by: Donahue, December 11, 1984 7:50:08 pm PST
(Changed RemoveMsg to be more efficient)
DIRECTORY
Ascii USING [Lower],
Atom USING [MakeAtomFromRefText],
DB USING [Aborted, Error, Failure,
Attribute, AttributeValueList, Entity, EntitySet, Relship, RelshipSet, Value,
DeclareEntity, DeclareRelship, DestroyEntity, DestroyRelship, DomainSubset,
GetF, NameOf, NextEntity, NextRelship, Null, PrevRelship,
RelationSubset, ReleaseEntitySet, ReleaseRelshipSet, SetF,
Eq, V2E, V2I],
IO,
RefTab USING [Ref, Val, Create, Delete, Fetch, Store],
RefText USING [line, InlineAppendChar],
Rope,
WalnutDefs USING [dontCareDomainVersion, dontCareMsgSetVersion, Error, MsgSet, VersionMismatch],
WalnutDB,
WalnutDBInternal USING [msgSetsTable, activeMessageSet, deletedMessageSet,
CarefullyApply, ChangeCountInMsgSet, ChangeCountOfMsgs, GetMsgDisplayInfo],
WalnutRegistry USING [MsgGroup, MsgGroupSize],
WalnutRegistryPrivate USING [ CheckForMsgGroupRegistration,
NotifyForEvent, NotifyForMove, NotifyForMsgEvent, NotifyForMsgGroup],
WalnutRoot USING [CommitAndContinue],
WalnutSchema;
WalnutDBMsgSetsImpl: CEDAR PROGRAM
IMPORTS
Ascii, Atom, DB, IO, RefTab, RefText, Rope,
WalnutDefs, WalnutDB, WalnutDBInternal,
WalnutRegistryPrivate,
WalnutRoot, WalnutSchema
EXPORTS WalnutDB =
BEGIN OPEN WalnutSchema;
Types
ROPE: TYPE = Rope.ROPE;
Entity: TYPE = DB.Entity;
EntitySet: TYPE = DB.EntitySet;
Relship: TYPE = DB.Relship;
RelshipSet: TYPE = DB.RelshipSet;
MsgSet: TYPE = WalnutDefs.MsgSet;
dontCareDomainVersion: INT = WalnutDefs.dontCareDomainVersion;
dontCareMsgSetVersion: INT = WalnutDefs.dontCareMsgSetVersion;
Internal Types and Variables
DBIntValue: REF INTNEW[INT];
DBMinusOneInt: REF INTNEW[INT ← -1];
DBZeroInt: REF INTNEW[INT ← 0];
DBTrueBool: REF BOOLNEW[BOOLTRUE];
DBFalseBool: REF BOOLNEW[BOOLFALSE];
ActiveMsgSetName: ROPE = "Active";
DeletedMsgSetName: ROPE = "Deleted";
IsEntity: TYPE = RECORD [entity: Entity, exists: BOOL];
MSInfo's are stored in the msgSetsTable
MSInfo: TYPE = REF MSInfoObject;
MSInfoObject: TYPE = RECORD [name: ROPE, entity: Entity, versionRel: Relship];
nameText: REF TEXT = NEW[TEXT[RefText.line]];
Operations on Message Sets
MsgSetExists: PUBLIC PROC[name: ROPE, msDomainVersion: INT]
RETURNS[existed: BOOL, msVersion: INT] = {
Does this message set already exist in the database.
IsMsgSet: PROC = {
msI: MSInfo;
CheckDomainVersion[msDomainVersion];
[msI, msVersion] ← GetMsgSetAndVersion[name: name];
existed ← msI # NIL;
};
WalnutDBInternal.CarefullyApply[IsMsgSet];
};
CreateMsgSet: PUBLIC PROC[name: ROPE, msDomainVersion: INT]
RETURNS [existed: BOOL, msVersion: INT] = {
Create this message set if it doesn't already exist in the database.
CrMsgSet: PROC = {
msI: MSInfo;
mse: Entity;
CheckDomainVersion[msDomainVersion];
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF existed ← (msI # NIL) THEN RETURN;
mse ← DB.DeclareEntity[MsgSetDomain, name, NewOnly];
msI ← NEW[MSInfoObject ←
[name: name, entity: mse, versionRel: GetMsgSetBasicInfoRel[mse]] ];
DBIntValue^ ← msVersion ← 1;
DB.SetF[msI.versionRel, msBIVersion, DBIntValue];
ChangeGlobalMsgSetInfo[1];
[] ← RefTab.Store[WalnutDBInternal.msgSetsTable, CanonicalName[name], msI];
};
WalnutDBInternal.CarefullyApply[CrMsgSet];
};
NumInMsgSet: PUBLIC PROC[name: ROPE] RETURNS[num: INT, msVersion: INT] = {
NumIn: PROC = {
msI: MSInfo;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN { num← 0; RETURN};
num ← DB.V2I[DB.GetF[msI.versionRel, msBICount]];
};
WalnutDBInternal.CarefullyApply[NumIn];
};
EmptyMsgSet: PUBLIC PROC[msgSet: MsgSet] RETURNS[someInDeleted: BOOL] = {
Removes any messages from msgSet - long running op
EmptyIt: PROC = {
msI: MSInfo = CheckMsgSetEntity[msgSet];
IF msI = NIL THEN RETURN;
someInDeleted ← EmptyThisMsgSet[msI.entity, msgSet.name];
};
IF WalnutDB.EqMsgSets[msgSet.name, WalnutDB.deletedMsgSet.name] THEN {
WalnutDB.SetOpInProgressPos[-1];
ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't empty the Deleted MsgSet"];
};
WalnutDBInternal.CarefullyApply[EmptyIt];
};
DestroyMsgSet: PUBLIC PROC[msgSet: MsgSet, msDomainVersion: INT]
RETURNS[someInDeleted: BOOL] = {
Destroys the given msgSet, removing any messages from it first (uses equivalent of RemoveMsgFromMsgSet below). If the message set is "Deleted" then WalnutDefs.IError[$db, $InvalidMsgSet] - long running op
DestroyIt: PROC = {
msI: MSInfo;
CheckDomainVersion[msDomainVersion];
msI ← CheckMsgSetEntity[msgSet];
IF msI = NIL THEN RETURN;
someInDeleted ← EmptyThisMsgSet[msI.entity, msgSet.name];
DB.DestroyEntity[msI.entity];
ChangeGlobalMsgSetInfo[-1];
};
IF WalnutDB.EqMsgSets[msgSet.name, WalnutDB.deletedMsgSet.name] THEN {
WalnutDB.SetOpInProgressPos[-1];
ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't destroy the Deleted MsgSet"];
};
WalnutDBInternal.CarefullyApply[DestroyIt];
};
VerifyMsgSet: PUBLIC PROC[msgSet: MsgSet] RETURNS[exists: BOOL] = {
Vms: PROC = {
msI: MSInfo ← CheckMsgSetEntity[msgSet];
exists ← msI # NIL;
};
WalnutDBInternal.CarefullyApply[Vms];
};
VerifyDomainVersion: PUBLIC PROC[msDomainVersion: INT] = {
Vdv: PROC =
{ CheckDomainVersion[msDomainVersion] };
WalnutDBInternal.CarefullyApply[Vdv];
};
ExpungeMsgs: PUBLIC PROC[deletedVersion: INT] = {
Destroys the Deleted message set - long running op
OPEN WalnutDBInternal;
DestroyDeletedMsgs: PROC = {
msI: MSInfo = CheckMsgSetEntity[[DeletedMsgSetName, deletedVersion]];
BEGIN
deletedCount: INT = DB.V2I[DB.GetF[msI.versionRel, msBICount]];
rs: RelshipSet = DB.RelationSubset[cdRelation, LIST[[cdMsgSet, deletedMessageSet]]];
sinceLastCommit: INT ← 0;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
delArray: WalnutRegistry.MsgGroup;
numDel: CARDINAL ← 0;
doMsgGroup: BOOL ← WalnutRegistryPrivate.CheckForMsgGroupRegistration[];
IF doMsgGroup THEN delArray ← ALL[NIL];
If the counts are wrong, change them. They may already have been changed if you're restarting an Expunge
IF deletedCount # 0 THEN {
ChangeCountInMsgSet[deletedMessageSet, -deletedCount];
ChangeCountOfMsgs[-deletedCount];
This counts as one operation
sinceLastCommit ← sinceLastCommit + 1 };
BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]};
rel: Relship;
bytesDestroyed: INTDB.V2I[DB.GetF[rLogInfo, gBytesInDestroyedMsgs]];
firstDestroyedPos: INTDB.V2I[DB.GetF[rLogInfo, gFirstDestroyedMsgPos]];
UNTIL DB.Null[rel← DB.NextRelship[rs]] DO
me: Entity = DB.V2E[DB.GetF[rel, cdMsg]];
textRel: Relship← DB.DeclareRelship[mTextInfo, LIST[[mTIOf, me]]];
startPos: INTDB.V2I[DB.GetF[textRel, mTIEntryStart]];
thisLen: INTDB.V2I[DB.GetF[textRel, mTITextOffset]] +
DB.V2I[DB.GetF[textRel, mTITextLen]] + DB.V2I[DB.GetF[textRel, mTIFormatLen]];
bytesDestroyed ← bytesDestroyed + thisLen;
IF firstDestroyedPos = 0 OR startPos < firstDestroyedPos THEN
firstDestroyedPos ← startPos;
IF doMsgGroup THEN {
delArray[numDel] ← DB.NameOf[me];
numDel ← numDel + 1;
};
DB.DestroyEntity[me];
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency THEN {
DBIntValue^ ← bytesDestroyed;
DB.SetF[rLogInfo, gBytesInDestroyedMsgs, DBIntValue];
DBIntValue^ ← firstDestroyedPos;
DB.SetF[rLogInfo, gFirstDestroyedMsgPos, DBIntValue];
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
IF doMsgGroup THEN {
WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray];
delArray ← ALL[NIL];  -- clear out the Array
numDel ← 0;
};
};
ENDLOOP;
DB.ReleaseRelshipSet[rs];
IF sinceLastCommit # 0 THEN {
DBIntValue^ ← bytesDestroyed;
DB.SetF[rLogInfo, gBytesInDestroyedMsgs, DBIntValue];
DBIntValue^ ← firstDestroyedPos;
DB.SetF[rLogInfo, gFirstDestroyedMsgPos, DBIntValue];
};
BEGIN  -- change the version number of Deleted
delRel: Relship = DB.DeclareRelship[msBasicInfo, LIST[[msBIOf, deletedMessageSet]]];
DBIntValue^ ← DB.V2I[DB.GetF[delRel, msBIVersion]] + 1;
DB.SetF[delRel, msBIVersion, DBIntValue];
WalnutRoot.CommitAndContinue[];
IF doMsgGroup AND (numDel # 0) THEN
WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray];
WalnutRegistryPrivate.NotifyForEvent[expungeComplete];
END;
END;
END;
};
[]← CarefullyApply[DestroyDeletedMsgs]
};
MsgSetsInfo: PUBLIC PROC RETURNS[version, num: INT] = {
Msv: PROC = {
version ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
num ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
};
WalnutDBInternal.CarefullyApply[Msv];
};
Operations to Move Messages Among Message Sets
AddMsg: PUBLIC PROC[msg: ROPE, from, to: MsgSet]
RETURNS[exists: BOOL] = {
Adds Msg to MsgSet, if it's not already in it. IF msgSet=deletedMsgSet, does nothing and returns exists=FALSE
Amt: PROC = {
m: IsEntity← GetMsgEntity[msg];
msI: MSInfo;
IF ~m.exists THEN RETURN;
[] ← CheckMsgSetEntity[from];
msI ← CheckMsgSetEntity[to];
IF msI = NIL THEN RETURN;  -- can't create msgset here
check if is in Deleted and if so, remove it (need only check first Relship)
{ rs: RelshipSet = DB.RelationSubset[cdRelation, LIST[[cdMsg, m.entity]]];
wasInDeleted: BOOLFALSE;
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs];
rel: Relship ← DB.NextRelship[rs];
wasInDeleted ← DB.Eq[
DB.V2E[DB.GetF[rel, cdMsgSet]], WalnutDBInternal.deletedMessageSet];
DB.ReleaseRelshipSet[rs];
IF wasInDeleted THEN {
DB.DestroyRelship[rel];
WalnutDBInternal.ChangeCountInMsgSet[WalnutDBInternal.deletedMessageSet, -1];
};
END;
};
exists ← AddMsgTo[m.entity,
DB.GetF[DB.DeclareRelship[mInfo, LIST[[mInfoOf, m.entity]]], mDateIs], msI.entity];
};
exists ← FALSE;
IF WalnutDB.EqMsgSets[to.name, WalnutDB.deletedMsgSet.name] THEN RETURN;
WalnutDBInternal.CarefullyApply[Amt];
};
RemoveMsg: PUBLIC PROC[msg: ROPE, from: MsgSet, deletedVersion: INT]
RETURNS[deleted: BOOL] = {
IF removing msg from msgSet would leave it in no MsgSet, then msg gets added to the distinguished MsgSet Deleted, and returns deleted = TRUE
Rmf: PROC = {
m: IsEntity;
msI: MSInfo;
existed: BOOL;
isInAnother: BOOLFALSE;
date: DB.Value;
rs: RelshipSet;
IF NOT (m← GetMsgEntity[msg]).exists THEN RETURN;
IF (msI← CheckMsgSetEntity[from]) = NIL THEN RETURN;
date ← DB.GetF[DB.DeclareRelship[mInfo, LIST[[mInfoOf, m.entity]]], mDateIs];
existed ← RemoveMsgFrom[m.entity, date, msI.entity];
IF ~existed THEN RETURN;
now if in no msgSet, then add msg to deleted
rs ← DB.RelationSubset[cdRelation, LIST[[cdMsg, m.entity]]];
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs];
isInAnother← NOT DB.Null[DB.NextRelship[rs]];
DB.ReleaseRelshipSet[rs];
END;
IF isInAnother THEN RETURN;
deleted ← TRUE;
Add it to the Deleted message set; Since we know the message cannot exist in any other message set, we use NewOnly to declare the appropriate relationship. This avoids doing a RelationSubset before doing the CreateRelship
[] ← DB.DeclareRelship[cdRelation, LIST[[cdMsg, m.entity], [cdMsgSet, WalnutDBInternal.deletedMessageSet], [cdDate, date]], NewOnly];
WalnutDBInternal.ChangeCountInMsgSet[WalnutDBInternal.deletedMessageSet, 1];
};
deleted← FALSE;
WalnutDBInternal.CarefullyApply[Rmf];
};
MoveMsg: PUBLIC PROC[msg: ROPE, from: MsgSet, to: MsgSet]
RETURNS [exists: BOOL] = {
Move the message. Note that the result of a move may be that a message becomes deleted (if to was the Deleted message set) or undeleted (if from is the Deleted message set)
DoMove: PROC = {
m: IsEntity;
fromMsI, toMsI: MSInfo;
date: DB.Value;
IF NOT (m ← GetMsgEntity[msg]).exists THEN RETURN;
IF (fromMsI ← CheckMsgSetEntity[from]) = NIL THEN RETURN;
IF (toMsI ← CheckMsgSetEntity[to]) = NIL THEN RETURN;
date ← DB.GetF[DB.DeclareRelship[mInfo, LIST[[mInfoOf, m.entity]]], mDateIs];
exists ← RemoveMsgFrom[m.entity, date, fromMsI.entity];
IF ~exists THEN RETURN;
exists ← AddMsgTo[m.entity, date, toMsI.entity];
};
IF WalnutDB.EqMsgSets[from.name, to.name] THEN RETURN[TRUE]; -- don't do anything
WalnutDBInternal.CarefullyApply[DoMove];
};
Enumerations
MsgsEnumeration: PUBLIC PROC [alphaOrder: BOOL] RETURNS [mL: LIST OF ROPE] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum: PROC = {
last: LIST OF ROPE;
enum: DB.EntitySet←
IF alphaOrder THEN
DB.DomainSubset[MsgDomain, WalnutDB.walnutSegment, "\000", "\177"] ELSE
DB.DomainSubset[MsgDomain, WalnutDB.walnutSegment];
mL← NIL;
BEGIN ENABLE UNWIND => IF enum#NIL THEN GOTO end;
e: Entity;
msg: ROPE;
FOR e← DB.NextEntity[enum], DB.NextEntity[enum] UNTIL e = NIL DO
msg← DB.NameOf[e];
IF mL = NIL THEN mL← last← CONS[msg, NIL] ELSE
{ last.rest← CONS[msg, NIL]; last← last.rest};
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDBInternal.CarefullyApply[MEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During Msgs enumeration"];
};
MsgsInSetEnumeration: PUBLIC PROC[name: ROPE, fromStart: BOOL]
RETURNS [mL: LIST OF ROPE, msVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum: PROC = {
msI: MSInfo;
enum: DB.RelshipSet;
checkShow: BOOL;
lastInList: LIST OF ROPENIL;
mL ← NIL;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN {ok ← TRUE; RETURN};
checkShow ← name.Equal["Active", FALSE];
enum ← DB.RelationSubset[r: cdRelation, constraint: LIST[[cdMsgSet, msI.entity]] ];
BEGIN ENABLE UNWIND => GOTO end;
DO
rel: Relship = DB.NextRelship[enum];
me: Entity;
IF rel = NIL THEN EXIT;
me ← DB.V2E[DB.GetF[rel, cdMsg]];
IF checkShow THEN {
sRel: Relship = DB.DeclareRelship[mInfo,
LIST[[mInfoOf, me], [mShowIs, DBFalseBool]], OldOnly];
IF sRel # NIL THEN LOOP;    -- mShowIs is FALSE
};
IF fromStart THEN {
IF mL = NIL THEN mL ← lastInList ← CONS[DB.NameOf[me], NIL]
ELSE {
lastInList.rest ← CONS[DB.NameOf[me], NIL];
lastInList ← lastInList.rest;
};
}
ELSE mL ← CONS[DB.NameOf[me], mL];
ENDLOOP;
ok ← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDBInternal.CarefullyApply[MEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
};
MsgSetsNames: PUBLIC PROC[alphaOrder: BOOL]
RETURNS[msL: LIST OF ROPE, msDomainVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MSEnum: PROC = {
last: LIST OF ROPE;
enum: DB.EntitySet ←
IF alphaOrder THEN
DB.DomainSubset[MsgSetDomain, WalnutDB.walnutSegment, "\000", "\177"] ELSE
DB.DomainSubset[MsgSetDomain, WalnutDB.walnutSegment];
msDomainVersion ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
msL ← NIL;
BEGIN ENABLE UNWIND => IF enum#NIL THEN GOTO end;
e: Entity;
FOR e ← DB.NextEntity[enum], DB.NextEntity[enum] UNTIL e = NIL DO
IF msL = NIL THEN msL ← last ← CONS[DB.NameOf[e], NIL] ELSE
{ last.rest ← CONS[DB.NameOf[e], NIL]; last ← last.rest};
ENDLOOP;
ok ← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDBInternal.CarefullyApply[MSEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
};
EnumerateMsgSets: PUBLIC PROC[alphaOrder: BOOL, proc: PROC[msgSet: MsgSet]]
RETURNS[msDomainVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MSEnum: PROC = {
enum: DB.EntitySet ←
IF alphaOrder THEN
DB.DomainSubset[MsgSetDomain, WalnutDB.walnutSegment, "\000", "\177"] ELSE
DB.DomainSubset[MsgSetDomain, WalnutDB.walnutSegment];
msDomainVersion ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
BEGIN ENABLE UNWIND => IF enum#NIL THEN GOTO end;
e: Entity;
FOR e ← DB.NextEntity[enum], DB.NextEntity[enum] UNTIL e = NIL DO
msgSet: MsgSet ←
[DB.NameOf[e], DB.V2I[DB.GetF[GetMsgSetBasicInfoRel[e], msBIVersion]]];
proc[msgSet];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDBInternal.CarefullyApply[MSEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
};
EnumerateMsgsInSet: PUBLIC PROC [
name: ROPE,
fromStart: BOOL TRUE,
proc: PROC[msg, TOCentry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] ]
RETURNS [msVersion: INT] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum: PROC = {
msI: MSInfo;
enum: DB.RelshipSet;
checkShow: BOOL;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN {ok← TRUE; RETURN};
checkShow ← name.Equal["Active", FALSE];
enum← DB.RelationSubset[r: cdRelation,
 constraint: LIST[[cdMsgSet, msI.entity]],
 start: IF fromStart THEN First ELSE Last];
BEGIN ENABLE UNWIND => GOTO end;
DO
e: Entity;
rel: Relship = IF fromStart THEN DB.NextRelship[enum] ELSE DB.PrevRelship[enum];
msg, TOCentry: ROPE;
hasBeenRead: BOOL;
startOfSubject: INT;
IF rel = NIL THEN EXIT;
msg ← DB.NameOf[e ← DB.V2E[DB.GetF[rel, cdMsg]]];
IF checkShow THEN {
showRel: Relship = DB.DeclareRelship[mInfo,
LIST[[mInfoOf, e], [mShowIs, DBFalseBool]], OldOnly];
IF showRel # NIL THEN LOOP;  -- mShowIs is FALSE
};
[hasBeenRead, TOCentry, startOfSubject] ← WalnutDBInternal.GetMsgDisplayInfo[e];
proc[msg, TOCentry, hasBeenRead, startOfSubject];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDBInternal.CarefullyApply[MEnum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSet enumeration"];
};
EnumerateMsgsInMsgSet: PUBLIC PROC[msgSet: MsgSet]
RETURNS[lazyEnum: WalnutDB.LazyEnumerator, valid: BOOL] = {
RETURN[NIL, FALSE]
};
NextMsgInMsgSet: PUBLIC PROC[lazyEnum: WalnutDB.LazyEnumerator, retry: BOOL]
RETURNS[msgID: ROPE, valid: BOOL] = {
RETURN[NIL, FALSE]
};
EnumerateUnacceptedMsgs: PUBLIC PROC [
activeVersion: INT, proc: PROC[msg, TOCentry: ROPE, startOfSubject: INT] ] = {
ok: BOOLFALSE;
BEGIN ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
Eum: PROC = {
enum: DB.RelshipSet;
[] ← CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]];
enum ←
DB.RelationSubset[cdRelation, LIST[[cdMsgSet, WalnutDBInternal.activeMessageSet]]];
BEGIN ENABLE UNWIND => GOTO end;
cdRel: Relship;
msg: ROPE;
UNTIL DB.Null[cdRel← DB.NextRelship[enum]] DO
TOCentry: ROPE;
startOfSubject: INT;
me: Entity = DB.V2E[DB.GetF[cdRel, cdMsg]];
showRelTrue: Relship =
DB.DeclareRelship[mInfo, LIST[[mInfoOf, me], [mShowIs, DBFalseBool]], OldOnly];
IF showRelTrue = NIL THEN LOOP;  -- is already accepted
msg ← DB.NameOf[me];
[ , TOCentry, startOfSubject] ← WalnutDBInternal.GetMsgDisplayInfo[me];
proc[msg, TOCentry, startOfSubject];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDBInternal.CarefullyApply[Eum];
IF ok THEN RETURN;
EXITS
stop => NULL;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessable, "During Get New Mail"];
};
AcceptNewMail: PUBLIC PROC[pos: INT, activeVersion: INT] = { -- long running op
Am: PROC = {
rs: RelshipSet;
es: EntitySet;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
accArray: WalnutRegistry.MsgGroup;
numAcc: CARDINAL ← 0;
sinceLastCommit: INT ← 0;
activeRel: Relship =
DB.DeclareRelship[msBasicInfo, LIST[[msBIOf, WalnutDBInternal.activeMessageSet]]];
doMsgGroup: BOOL ← WalnutRegistryPrivate.CheckForMsgGroupRegistration[];
[] ← CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]];
IF doMsgGroup THEN accArray ← ALL[NIL];
BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]};
cdRel: Relship;
rs ← DB.RelationSubset[
cdRelation, LIST[[cdMsgSet, WalnutDBInternal.activeMessageSet]]];
UNTIL DB.Null[cdRel← DB.NextRelship[rs]] DO
me: Entity = DB.V2E[DB.GetF[cdRel, cdMsg]];
showRel: Relship = DB.DeclareRelship[mInfo, LIST[[mInfoOf, me], [mShowIs, DBFalseBool]], OldOnly];  -- find a FALSE one
IF showRel = NIL THEN LOOP;
DB.SetF[showRel, mShowIs, DBTrueBool];
IF doMsgGroup THEN {
accArray[numAcc] ← DB.NameOf[me];
numAcc ← numAcc + 1;
};
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency THEN {
DBIntValue^ ← DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit;
DB.SetF[activeRel, msBICount, DBIntValue];
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
IF doMsgGroup THEN {
WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray];
accArray ← ALL[NIL];
numAcc ← 0;
};
};
ENDLOOP;
DB.ReleaseRelshipSet[rs];
END;
BEGIN ENABLE UNWIND => {IF es#NIL THEN DB.ReleaseEntitySet[es]};
se: Entity;
es ← DB.DomainSubset[ServerDomain];
FOR se← DB.NextEntity[es], DB.NextEntity[es] UNTIL se = NIL DO
rel: Relship = DB.DeclareRelship[sBasicInfo, LIST[[sBIOf, se]]];
num: INTDB.V2I[DB.GetF[rel, sBINum]];
DB.SetF[rel, sBINum, DBZeroInt];
ENDLOOP;
DB.ReleaseEntitySet[es];
END;
IF sinceLastCommit # 0 THEN {
DBIntValue^ ← DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit;
DB.SetF[activeRel, msBICount, DBIntValue];
};
DBIntValue^ ← DB.V2I[DB.GetF[activeRel, msBIVersion]] + 1;
DB.SetF[activeRel, msBIVersion, DBIntValue];
DBIntValue^ ← pos;
DB.SetF[rNewMailInfo, gAcceptNewMailLogPos, DBIntValue];
DB.SetF[rLogInfo, gOpInProgressPos, DBMinusOneInt];
WalnutRoot.CommitAndContinue[];
IF doMsgGroup AND (numAcc # 0) THEN
WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray];
};
WalnutDBInternal.CarefullyApply[Am];
};
Internal procedures
mismatchReport: ROPE = "Msgset: %g: version is %g, version expected is: %g";
GetMsgSetBasicInfoRel: PROC[ms: Entity] RETURNS[rel: Relship] = INLINE
{ RETURN[DB.DeclareRelship[msBasicInfo, LIST[[msBIOf, ms]]]] };
GetMsgEntity: PROC[msg: ROPE] RETURNS[e: IsEntity] = {
e.entity ← DB.DeclareEntity[MsgDomain, msg, OldOnly];
e.exists ← NOT DB.Null[e.entity];
};
GetMsgSetAndVersion: PROC[name: ROPE] RETURNS[msI: MSInfo, version: INT] = {
msI ← GetMsgSetEntity[name];
IF msI # NIL THEN version ← DB.V2I[DB.GetF[msI.versionRel, msBIVersion]]
ELSE version← -1;
};
GetMsgSetEntity: PROC[name: ROPE] RETURNS[msInfo: MSInfo] = {
aName: ATOM ← CanonicalName[name];  -- all lower case
found: BOOL;
val: RefTab.Val;
mse: DB.Entity;
IF WalnutDBInternal.msgSetsTable = NIL THEN {
numMsgs: INTDB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
WalnutDBInternal.msgSetsTable ← RefTab.Create[numMsgs*2+1];
};
[found, val] ← RefTab.Fetch[WalnutDBInternal.msgSetsTable, aName];
IF found THEN {
msInfo ← NARROW[val];
IF ~DB.Null[msInfo.entity] THEN RETURN;
msInfo.entity ← DB.DeclareEntity[MsgSetDomain, name, OldOnly];
IF DB.Null[msInfo.entity] THEN {  -- no longer exists
[] ← RefTab.Delete[WalnutDBInternal.msgSetsTable, aName];
RETURN[NIL]
};
msInfo.versionRel ← GetMsgSetBasicInfoRel[msInfo.entity];
RETURN;
};
mse ← DB.DeclareEntity[MsgSetDomain, name, OldOnly];
IF DB.Null[mse] THEN RETURN;  -- return NIL IF msgSet doesn't exist
msInfo ← NEW[MSInfoObject ← [name: name, entity: mse]];
msInfo.versionRel ← GetMsgSetBasicInfoRel[mse];
[] ← RefTab.Store[WalnutDBInternal.msgSetsTable, aName, msInfo];
};
changes name to all lower case, to get canonical names
CanonicalName: PROC[name: ROPE] RETURNS[aName: ATOM] = {
nameText.length ← 0;
FOR i: INT IN [0 .. name.Length[]) DO
[] ← RefText.InlineAppendChar[nameText, Ascii.Lower[name.Fetch[i]]];
ENDLOOP;
aName ← Atom.MakeAtomFromRefText[nameText];
};
CheckDomainVersion: PROC[version: INT] = {
is: INT;
IF version = dontCareDomainVersion THEN RETURN;
IF version # (is ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]]) THEN {
DB.SetF[rLogInfo, gOpInProgressPos, DBMinusOneInt];
ERROR WalnutDefs.VersionMismatch[
IO.PutFR["Domain version is %g, version expected is: %g", IO.int[is], IO.int[version]] ];
};
};
CheckMsgSetVersion: PROC[msgSet: MsgSet] RETURNS[msI: MSInfo, version: INT] = {
[msI, version] ← GetMsgSetAndVersion[msgSet.name];
IF msI = NIL THEN RETURN;
IF msgSet.version = dontCareMsgSetVersion THEN RETURN;
IF msgSet.version = version THEN RETURN;
DB.SetF[rLogInfo, gOpInProgressPos, DBMinusOneInt];
ERROR WalnutDefs.VersionMismatch[IO.PutFR[mismatchReport,
IO.rope[msgSet.name], IO.int[version], IO.int[msgSet.version]] ];
};
CheckMsgSetEntity: PROC[msgSet: MsgSet] RETURNS[msI: MSInfo] = {
is: INT;
msI ← GetMsgSetEntity[msgSet.name];
IF msI = NIL THEN RETURN;
IF msgSet.version = dontCareMsgSetVersion THEN RETURN;
IF msgSet.version = (is ← DB.V2I[DB.GetF[msI.versionRel, msBIVersion]]) THEN RETURN;
DB.SetF[rLogInfo, gOpInProgressPos, DBMinusOneInt];
ERROR WalnutDefs.VersionMismatch[IO.PutFR[mismatchReport,
IO.rope[msgSet.name], IO.int[is], IO.int[msgSet.version]] ];
};
EmptyThisMsgSet: PROC[mse: Entity, name: ROPE] RETURNS[someInDeleted: BOOL] = {
rs: RelshipSet = DB.RelationSubset[cdRelation, LIST[[cdMsgSet, mse]]];
BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]};
rel: Relship;
de: Entity = WalnutDBInternal.deletedMessageSet;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
delArray: WalnutRegistry.MsgGroup;
remArray: WalnutRegistry.MsgGroup;
numDel: CARDINAL ← 0;
numRem: CARDINAL ← 0;
sinceLastCommit: INT ← 0;
someInDeleted← FALSE;
UNTIL DB.Null[rel← DB.NextRelship[rs]] DO
me: Entity = DB.V2E[DB.GetF[rel, cdMsg]];
date: DB.Value = DB.GetF[DB.DeclareRelship[mInfo, LIST[[mInfoOf, me]]], mDateIs];
rs2: RelshipSet;
deleted: BOOL;
DB.DestroyRelship[rel];
BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[rs2];
rs2 ← DB.RelationSubset[cdRelation, LIST[[cdMsg, me]]];
deleted ← DB.Null[DB.NextRelship[rs2]];
DB.ReleaseRelshipSet[rs2];
END;
IF deleted THEN {
[] ← AddMsgTo[me, date, WalnutDBInternal.deletedMessageSet];
delArray[numDel] ← DB.NameOf[me];
numDel ← numDel + 1;
}
ELSE {
remArray[numRem] ← DB.NameOf[me];
numRem ← numRem + 1;
};
someInDeleted← someInDeleted OR deleted;
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency THEN {
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
FOR i: CARDINAL IN [0 .. numDel) DO
WalnutRegistryPrivate.NotifyForMsgEvent[deleted, delArray[i]]; ENDLOOP;
FOR i: CARDINAL IN [0 .. numRem) DO
WalnutRegistryPrivate.NotifyForMove[msg: remArray[i], to: NIL, from: name];
ENDLOOP;
numDel ← numRem ← 0;
};
ENDLOOP;
DB.ReleaseRelshipSet[rs];
IF sinceLastCommit # 0 THEN {
WalnutRoot.CommitAndContinue[];
FOR i: CARDINAL IN [0 .. numDel) DO
WalnutRegistryPrivate.NotifyForMsgEvent[deleted, delArray[i]]; ENDLOOP;
FOR i: CARDINAL IN [0 .. numRem) DO
WalnutRegistryPrivate.NotifyForMove[msg: remArray[i], to: NIL, from: name];
ENDLOOP;
};
END;
};
ChangeGlobalMsgSetInfo: PROC[delta: INT] = {
numNow: INTDB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
DBIntValue^ ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]] + 1;
DB.SetF[rVersionInfo, gMsgSetsVersion, DBIntValue];
DBIntValue^ ← numNow + delta;
DB.SetF[rVersionInfo, gMsgSetCount, DBIntValue];
};
RemoveMsgFrom: PROC[me: Entity, date: DB.Value, mse: Entity] RETURNS[existed: BOOL] = {
avl: DB.AttributeValueList = LIST[[cdMsg, me], [cdMsgSet, mse], [cdDate, date]];
cdrel: Relship = DB.DeclareRelship[cdRelation, avl, OldOnly];
IF DB.Null[cdrel] THEN RETURN[FALSE];
DB.DestroyRelship[cdrel];
WalnutDBInternal.ChangeCountInMsgSet[mse, -1];
RETURN[TRUE];
};
AddMsgTo: PROC[me: Entity, date: DB.Value, mse: Entity] RETURNS[exists: BOOL] = {
avl: DB.AttributeValueList = LIST[[cdMsg, me], [cdMsgSet, mse], [cdDate, date]];
cdrel: Relship = DB.DeclareRelship[cdRelation, avl, OldOnly];
IF ~DB.Null[cdrel] THEN RETURN[TRUE];
[] ← DB.DeclareRelship[cdRelation, avl];
WalnutDBInternal.ChangeCountInMsgSet[mse, 1];
RETURN[FALSE];
};
END.