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;
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 INT ← NEW[INT];
DBMinusOneInt: REF INT ← NEW[INT ← -1];
DBZeroInt: REF INT ← NEW[INT ← 0];
DBTrueBool: REF BOOL ← NEW[BOOL ← TRUE];
DBFalseBool: REF BOOL ← NEW[BOOL ← FALSE];
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: INT ← DB.V2I[DB.GetF[rLogInfo, gBytesInDestroyedMsgs]];
firstDestroyedPos: INT ← DB.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: INT ← DB.V2I[DB.GetF[textRel, mTIEntryStart]];
thisLen:
INT ←
DB.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: BOOL ← FALSE;
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: BOOL← FALSE;
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: BOOL ← FALSE;
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;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During Msgs enumeration"];
};
MsgsInSetEnumeration:
PUBLIC
PROC[name:
ROPE, fromStart:
BOOL]
RETURNS [mL:
LIST
OF
ROPE, msVersion:
INT] = {
ok: BOOL ← FALSE;
BEGIN
ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN GOTO stop ELSE REJECT;
MEnum:
PROC = {
msI: MSInfo;
enum: DB.RelshipSet;
checkShow: BOOL;
lastInList: LIST OF ROPE ← NIL;
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;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
};
MsgSetsNames:
PUBLIC
PROC[alphaOrder:
BOOL]
RETURNS[msL:
LIST
OF
ROPE, msDomainVersion:
INT] = {
ok: BOOL ← FALSE;
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;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
};
EnumerateMsgSets:
PUBLIC
PROC[alphaOrder:
BOOL, proc:
PROC[msgSet: MsgSet]]
RETURNS[msDomainVersion:
INT] = {
ok: BOOL ← FALSE;
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;
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: BOOL ← FALSE;
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;
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: BOOL ← FALSE;
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;
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: INT ← DB.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: INT ← DB.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: INT ← DB.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];
};