DIRECTORY
Ascii USING [Lower],
Atom USING [MakeAtomFromRefText, GetPName],
BasicTime USING [GMT, nullGMT],
DB
USING [Aborted, Error, Failure,
CreateRelship, DeclareEntity, DestroyEntity, DestroyRelship, DomainSubset,
EntityInfo, EntityEq, FirstRelship, GetF, LookupEntity, LookupProperty, NextEntity,
NextRelship, NullEntity, NullRelship, PrevRelship,
RelationSubset, ReleaseEntitySet, ReleaseRelshipSet, RelshipsWithEntityField, SetF,
L2C, L2VS, B2V, E2V, I2V, V2E, V2I, V2S,
Constraint, Entity, EntitySet, Relship, RelshipSet, Value, ValueSequence],
IO,
RefTab USING [Ref, Val, Create, Delete, Fetch, Store],
RefText USING [line, InlineAppendChar],
Rope,
WalnutDefs USING [dontCareDomainVersion, dontCareMsgSetVersion, Error, MsgSet, VersionMismatch],
WalnutDB,
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;
CheckReportProc: TYPE = WalnutDB.CheckReportProc;
Internal Types and Variables
Value: TYPE = DB.Value;
DBMinusOneInt: Value ← DB.I2V[-1];
DBZeroInt: Value ← DB.I2V[0];
DBTrueBool: Value ← DB.B2V[TRUE];
DBFalseBool: Value ← DB.B2V[FALSE];
nullValue: Value = [null[]];
nullDate: Value = [time[BasicTime.nullGMT]];
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 [canonicalName: ROPE, entity: Entity, versionRel: Relship];
nameText: REF TEXT = NEW[TEXT[RefText.line]];
LazyEnumerator: TYPE = REF LazyEnumeratorRec;
LazyEnumeratorRec: PUBLIC TYPE = RECORD[ msgSet: MsgSet, pos: INT, set: DB.RelshipSet, checkShow: BOOL];
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;
};
WalnutDB.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;
cName: ROPE;
aName: ATOM;
CheckDomainVersion[msDomainVersion];
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF existed ← (msI # NIL) THEN RETURN;
cName ← Atom.GetPName[aName ← CanonicalName[name]];
mse ← DB.DeclareEntity[MsgSetDomain, cName, TRUE]; -- newOnly
msI ← NEW[MSInfoObject ← [canonicalName: cName, entity: mse, versionRel: NIL] ];
msI.versionRel ←
DB.CreateRelship[msBasicInfo,
DB.L2VS[LIST [ DB.E2V[mse], [integer[0]], [integer[1]], [rope[name]]] ] ];
ChangeGlobalMsgSetInfo[1];
[] ← RefTab.Store[WalnutDB.msgSetsTable, aName, msI];
};
WalnutDB.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
ELSE num ← DB.V2I[DB.GetF[msI.versionRel, msBICount]];
};
WalnutDB.CarefullyApply[NumIn];
};
EmptyMsgSet:
PUBLIC
PROC[msgSet: MsgSet, report: CheckReportProc]
RETURNS[someInDeleted: BOOL] = {
Removes any messages from msgSet - long running op
EmptyIt:
PROC = {
numIn: INT;
msI: MSInfo = CheckMsgSetEntity[msgSet];
IF msI = NIL THEN RETURN;
numIn ← DB.V2I[DB.GetF[msI.versionRel, msBICount]];
someInDeleted ← EmptyThisMsgSet[msI.entity, msgSet.name, report];
IF numIn # 0 THEN WalnutDB.ChangeCountInMsgSet[msI.entity, -numIn];
};
IF WalnutDB.EqMsgSets[msgSet.name, WalnutDB.deletedMsgSet.name]
THEN {
WalnutDB.SetOpInProgressPos[-1];
ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't empty the Deleted MsgSet"];
};
WalnutDB.CarefullyApply[EmptyIt];
};
DestroyMsgSet:
PUBLIC
PROC[msgSet: MsgSet, msDomainVersion:
INT, report: CheckReportProc]
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, report];
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"];
};
WalnutDB.CarefullyApply[DestroyIt];
};
VerifyMsgSet:
PUBLIC
PROC[msgSet: MsgSet]
RETURNS[exists:
BOOL] = {
Vms:
PROC = {
msI: MSInfo ← CheckMsgSetEntity[msgSet];
exists ← msI # NIL;
};
WalnutDB.CarefullyApply[Vms];
};
VerifyDomainVersion:
PUBLIC
PROC[msDomainVersion:
INT] = {
Vdv: PROC = { CheckDomainVersion[msDomainVersion] };
WalnutDB.CarefullyApply[Vdv];
};
ExpungeMsgs:
PUBLIC
PROC[deletedVersion:
INT, report: CheckReportProc] = {
Destroys the Deleted message set - long running op
OPEN WalnutDB;
DestroyDeletedMsgs:
PROC = {
msI: MSInfo = CheckMsgSetEntity[[DeletedMsgSetName, deletedVersion]];
BEGIN
deletedCount: INT = DB.V2I[DB.GetF[msI.versionRel, msBICount]];
rs: RelshipSet = DB.RelshipsWithEntityField[cdRelation, cdMsgSet, deletedMessageSet];
sinceLastCommit: INT ← 0;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
delArray: WalnutRegistry.MsgGroup;
numDel: CARDINAL ← 0;
count: INT ← 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;
rLogInfo: Relship = DB.FirstRelship[gLogInfo];
bytesDestroyed: INT ← DB.V2I[DB.GetF[rLogInfo, gBytesInDestroyedMsgs]];
firstDestroyedPos: INT ← DB.V2I[DB.GetF[rLogInfo, gFirstDestroyedMsgPos]];
UNTIL (rel←
DB.NextRelship[rs]) =
NIL
DO
me: Entity = DB.V2E[DB.GetF[rel, cdMsg]];
textRel: Relship = DB.LookupProperty[mTextInfo, 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.EntityInfo[me].name;
numDel ← numDel + 1;
};
DB.DestroyEntity[me];
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency
THEN {
DB.SetF[rLogInfo, gBytesInDestroyedMsgs, DB.I2V[bytesDestroyed]];
DB.SetF[rLogInfo, gFirstDestroyedMsgPos, DB.I2V[firstDestroyedPos]];
WalnutRoot.CommitAndContinue[];
sinceLastCommit ← 0;
IF doMsgGroup
THEN {
WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray];
delArray ← ALL[NIL]; -- clear out the Array
numDel ← 0;
};
};
IF report # NIL THEN count ← CheckCount[count, report];
ENDLOOP;
DB.ReleaseRelshipSet[rs];
IF sinceLastCommit # 0
THEN
{
DB.SetF[rLogInfo, gBytesInDestroyedMsgs, DB.I2V[bytesDestroyed]];
DB.SetF[rLogInfo, gFirstDestroyedMsgPos, DB.I2V[firstDestroyedPos]];
};
BEGIN
-- change the version number of Deleted
delRel: Relship = DB.LookupProperty[msBasicInfo, deletedMessageSet];
DB.SetF[delRel, msBIVersion, DB.I2V[DB.V2I[DB.GetF[delRel, msBIVersion]] + 1]];
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 = {
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
version ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]];
num ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
};
WalnutDB.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
{ rs: RelshipSet =
DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity];
wasInDeleted: BOOL ← FALSE;
BEGIN
ENABLE
UNWIND =>
DB.ReleaseRelshipSet[rs];
DO
rel: Relship = DB.NextRelship[rs];
IF DB.NullRelship[rel] THEN EXIT;
BEGIN
msgSet: DB.Entity = DB.V2E[DB.GetF[rel, cdMsgSet]];
IF DB.EntityEq[msI.entity, msgSet] THEN { exists ← TRUE; EXIT };
IF
DB.EntityEq[msgSet, WalnutDB.deletedMessageSet]
THEN {
DB.DestroyRelship[rel];
WalnutDB.ChangeCountInMsgSet[WalnutDB.deletedMessageSet, -1] }
END
ENDLOOP;
DB.ReleaseRelshipSet[rs]
END;
};
IF NOT exists THEN AddMsgTo[m.entity, msI.entity];
};
exists ← FALSE;
IF WalnutDB.EqMsgSets[to.name, WalnutDB.deletedMsgSet.name] THEN RETURN;
WalnutDB.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;
date: Value ← nullDate;
rs: RelshipSet;
thisCDRel: Relship;
IF NOT (m← GetMsgEntity[msg]).exists THEN RETURN;
IF (msI← CheckMsgSetEntity[from]) = NIL THEN RETURN;
TRUSTED {date ← DB.GetF[DB.LookupProperty[mInfo, m.entity], mDateIs] };
deleted ← TRUE; -- We're committed to deleting the message unless we find it in another message set
rs ← DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity];
BEGIN
ENABLE
UNWIND =>
DB.ReleaseRelshipSet[rs];
DO
rel: Relship = DB.NextRelship[rs];
IF DB.NullRelship[rel] THEN EXIT;
IF DB.EntityEq[msI.entity, DB.V2E[DB.GetF[rel, cdMsgSet]]] THEN thisCDRel ← rel
ELSE deleted ← FALSE
ENDLOOP;
DB.ReleaseRelshipSet[rs];
END;
IF thisCDRel = NIL THEN { deleted ← FALSE; RETURN }; -- Something strange here
WalnutDB.ChangeCountInMsgSet[msI.entity, -1];
IF NOT deleted THEN DB.DestroyRelship[thisCDRel]
ELSE {
DB.SetF[thisCDRel, cdMsgSet, DB.E2V[WalnutDB.deletedMessageSet]];
WalnutDB.ChangeCountInMsgSet[WalnutDB.deletedMessageSet, 1] }
};
deleted← FALSE;
WalnutDB.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: Value ← nullDate;
rs: RelshipSet;
fromCDRelship, toCDRelship: Relship;
IF NOT (m ← GetMsgEntity[msg]).exists THEN RETURN;
IF (fromMsI ← CheckMsgSetEntity[from]) = NIL THEN RETURN;
IF (toMsI ← CheckMsgSetEntity[to]) = NIL THEN RETURN;
TRUSTED {date ← DB.GetF[DB.LookupProperty[mInfo, m.entity], mDateIs] };
rs ← DB.RelshipsWithEntityField[cdRelation, cdMsg, m.entity];
BEGIN
ENABLE
UNWIND =>
DB.ReleaseRelshipSet[rs];
DO
rel: Relship = DB.NextRelship[rs];
IF DB.NullRelship[rel] THEN EXIT;
IF
DB.EntityEq[fromMsI.entity,
DB.V2E[
DB.GetF[rel, cdMsgSet]]]
THEN
fromCDRelship ← rel
ELSE
IF
DB.EntityEq[toMsI.entity,
DB.V2E[
DB.GetF[rel, cdMsgSet]]]
THEN
toCDRelship ← rel;
ENDLOOP;
DB.ReleaseRelshipSet[rs];
END;
IF fromCDRelship = NIL THEN {exists ← FALSE; RETURN};
exists ← toCDRelship # NIL;
IF exists THEN DB.DestroyRelship[fromCDRelship]
ELSE DB.SetF[fromCDRelship, cdMsgSet, DB.E2V[toMsI.entity]];
WalnutDB.ChangeCountInMsgSet[fromMsI.entity, -1];
IF NOT exists THEN WalnutDB.ChangeCountInMsgSet[toMsI.entity, 1];
};
IF WalnutDB.EqMsgSets[from.name, to.name] THEN RETURN[TRUE]; -- don't do anything
WalnutDB.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: EntitySet ←
IF alphaOrder
THEN
DB.DomainSubset[MsgDomain, "\000", "\177", First] ELSE
DB.DomainSubset[MsgDomain, NIL, NIL, First];
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.EntityInfo[e].name;
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];
};
WalnutDB.CarefullyApply[MEnum];
IF ok THEN RETURN
ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During Msgs enumeration"];
EXITS
stop => WalnutDefs.Error[$db, $TransactionAbort, "During Msgs enumeration"];
END };
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: RelshipSet;
checkShow: BOOL;
constraint: DB.Constraint;
lastInList: LIST OF ROPE ← NIL;
mL ← NIL;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN {ok ← TRUE; RETURN};
checkShow ← name.Equal["Active", FALSE];
constraint ← DB.L2C[LIST[[entity[msI.entity]], [time[]]]];
enum ←
DB.RelationSubset[cdRelation, cdIndex, constraint, IF fromStart THEN First ELSE Last];
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.LookupProperty[mInfo, me];
IF NOT DB.NullEntity[DB.V2E[DB.GetF[sRel, mShowIs]]] THEN LOOP; -- mShowIs is unaccepted
};
IF fromStart
THEN {
thisL: LIST OF ROPE = CONS[DB.EntityInfo[me].name, NIL];
IF mL =
NIL
THEN mL ← lastInList ← thisL
ELSE {
lastInList.rest ← thisL;
lastInList ← lastInList.rest;
};
}
ELSE mL ← CONS[DB.EntityInfo[me].name, mL];
ENDLOOP;
ok ← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDB.CarefullyApply[MEnum];
IF ok THEN RETURN
ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
EXITS
stop => WalnutDefs.Error[$db, $TransactionAbort, "During MsgSets enumeration"];
END };
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;
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
enum: EntitySet ←
IF alphaOrder
THEN
DB.DomainSubset[MsgSetDomain, "\000", "\177", First] ELSE
DB.DomainSubset[MsgSetDomain, NIL, NIL, First];
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
thisName:
ROPE =
DB.V2S[DB.GetF[DB.LookupProperty[msBasicInfo, e], msPrintNameIs]];
IF msL =
NIL
THEN msL ← last ←
CONS[thisName,
NIL]
ELSE
{ last.rest ← CONS[thisName, NIL]; last ← last.rest};
ENDLOOP;
ok ← TRUE;
EXITS end => NULL;
END;
DB.ReleaseEntitySet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE];
};
WalnutDB.CarefullyApply[MSEnum];
IF ok THEN RETURN
ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"];
EXITS
stop => WalnutDefs.Error[$db, $TransactionAbort, "During MsgSets enumeration"];
END };
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: EntitySet ←
IF alphaOrder
THEN
DB.DomainSubset[MsgSetDomain, "\000", "\177", First] ELSE
DB.DomainSubset[MsgSetDomain, NIL, NIL, First];
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
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.EntityInfo[e].name, 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];
};
WalnutDB.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: RelshipSet;
checkShow: BOOL;
constraint: DB.Constraint;
[msI, msVersion] ← GetMsgSetAndVersion[name];
IF msI = NIL THEN {ok← TRUE; RETURN};
checkShow ← name.Equal["Active", FALSE];
constraint ← DB.L2C[LIST[[entity[msI.entity]], [time[]]]];
enum ←
DB.RelationSubset[cdRelation, cdIndex, constraint, 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.EntityInfo[e ← DB.V2E[DB.GetF[rel, cdMsg]]].name;
IF checkShow
THEN {
showRel: Relship = DB.LookupProperty[mInfo, e];
v: Value = DB.GetF[showRel, mShowIs];
IF v.type # null THEN LOOP; -- mShowIs is Unaccepted
};
[hasBeenRead, TOCentry, startOfSubject] ← WalnutDB.GetMsgDisplayInfo[e];
proc[msg, TOCentry, hasBeenRead, startOfSubject];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDB.CarefullyApply[MEnum];
IF ok THEN RETURN;
END;
ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSet enumeration"];
};
EnumerateMsgsInMsgSet:
PUBLIC
PROC[msgSet: MsgSet]
RETURNS[lazyEnum: WalnutDB.LazyEnumerator] = {
lazyEnum ← NEW[LazyEnumeratorRec ← [msgSet: msgSet, pos: 0, checkShow: Rope.Equal[msgSet.name, ActiveMsgSetName]]];
lazyEnum.set ← DB.RelationSubset[cdRelation, cdIndex, DB.L2C[LIST[[entity[GetMsgSetEntity[msgSet.name].entity]],[time[]]]]] };
NextMsgInMsgSet:
PUBLIC
PROC[lazyEnum: WalnutDB.LazyEnumerator]
RETURNS[msgID:
ROPE, valid:
BOOL] = {
howManyIterations: INT ← 0;
GetNextMsg:
PROC[] = {
me: Entity;
IF lazyEnum.set = NIL THEN { valid ← FALSE; RETURN };
DO
rel: Relship = DB.NextRelship[lazyEnum.set];
howManyIterations ← howManyIterations+1;
IF rel = NIL THEN { DB.ReleaseRelshipSet[lazyEnum.set]; lazyEnum.set ← NIL; EXIT };
me ← DB.V2E[DB.GetF[rel, cdMsg]];
IF lazyEnum.checkShow
THEN {
sRel: Relship = DB.LookupProperty[mInfo, me];
IF NOT DB.NullEntity[DB.V2E[DB.GetF[sRel, mShowIs]]] THEN LOOP ELSE EXIT }
ELSE EXIT
ENDLOOP;
IF me # NIL THEN msgID ← DB.EntityInfo[me].name;
valid ← TRUE };
ResetToStart:
PROC[] = {
lazyEnum.set ← DB.RelationSubset[cdRelation, cdIndex, DB.L2C[LIST[[entity[GetMsgSetEntity[lazyEnum.msgSet.name].entity]],[time[]]]]];
FOR i: INT IN [0..lazyEnum.pos) DO [] ← DB.NextRelship[lazyEnum.set] ENDLOOP;
howManyIterations ← 0 };
IF NOT VerifyMsgSet[lazyEnum.msgSet] THEN RETURN[NIL, FALSE];
FOR tryRestart:
BOOL ←
TRUE,
FALSE
WHILE tryRestart
DO
failed: BOOL ← FALSE;
BEGIN
ENABLE WalnutDefs.Error =>
IF code = $DBTransAbort THEN {failed ← TRUE; CONTINUE} ELSE REJECT;
WalnutDB.CarefullyApply[GetNextMsg]
END;
IF NOT failed THEN { lazyEnum.pos ← lazyEnum.pos+howManyIterations; RETURN };
WalnutDB.CarefullyApply[ResetToStart]
ENDLOOP };
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: RelshipSet;
[] ← CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]];
enum ← DB.RelshipsWithEntityField[mInfo, mShowIs, WalnutDB.unacceptedEntity];
BEGIN
ENABLE
UNWIND =>
GOTO end;
showRel: Relship;
msg: ROPE;
UNTIL
DB.NullRelship[showRel ←
DB.NextRelship[enum]]
DO
TOCentry: ROPE;
startOfSubject: INT;
me: Entity = DB.V2E[DB.GetF[showRel, mInfoOf]];
msg ← DB.EntityInfo[me].name;
[ , TOCentry, startOfSubject] ← WalnutDB.GetMsgDisplayInfo[me];
proc[msg, TOCentry, startOfSubject];
ENDLOOP;
ok← TRUE;
EXITS end => NULL;
END;
DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE]
};
WalnutDB.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.LookupProperty[msBasicInfo, WalnutDB.activeMessageSet];
doMsgGroup: BOOL ← WalnutRegistryPrivate.CheckForMsgGroupRegistration[];
[] ← CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]];
IF doMsgGroup THEN accArray ← ALL[NIL];
BEGIN
ENABLE
UNWIND => {
IF rs#
NIL
THEN
DB.ReleaseRelshipSet[rs]};
showRel: Relship;
rs ←
DB.RelshipsWithEntityField[
mInfo, mShowIs, WalnutDB.unacceptedEntity];
UNTIL
DB.NullRelship[showRel ←
DB.NextRelship[rs]]
DO
me: Entity = DB.V2E[DB.GetF[showRel, mInfoOf]];
DB.SetF[showRel, mShowIs, nullValue];
IF doMsgGroup
THEN {
accArray[numAcc] ← DB.EntityInfo[me].name;
numAcc ← numAcc + 1;
};
IF (sinceLastCommit ← sinceLastCommit + 1) >= commitFrequency
THEN {
newCount: INT = DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit;
DB.SetF[activeRel, msBICount, DB.I2V[newCount]];
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, NIL, NIL, First];
FOR se ←
DB.NextEntity[es],
DB.NextEntity[es]
UNTIL se =
NIL
DO
rel: Relship = DB.LookupProperty[sBasicInfo, se];
DB.SetF[rel, sBINum, DBZeroInt];
ENDLOOP;
DB.ReleaseEntitySet[es];
END;
IF sinceLastCommit # 0
THEN
DB.SetF[activeRel, msBICount, DB.I2V[DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit]];
DB.SetF[activeRel, msBIVersion, DB.I2V[DB.V2I[DB.GetF[activeRel, msBIVersion]] + 1]];
DB.SetF[DB.FirstRelship[gNewMailInfo], gAcceptNewMailLogPos, DB.I2V[pos]];
DB.SetF[DB.FirstRelship[gLogInfo], gOpInProgressPos, DBMinusOneInt];
WalnutRoot.CommitAndContinue[];
IF doMsgGroup
AND (numAcc # 0)
THEN
WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray];
};
WalnutDB.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.LookupProperty[msBasicInfo, ms]] };
GetMsgEntity:
PROC[msg:
ROPE]
RETURNS[e: IsEntity] = {
e.entity ← DB.LookupEntity[MsgDomain, msg];
e.exists ← (e.entity # NIL);
};
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
cName: ROPE;
found: BOOL;
val: RefTab.Val;
mse: Entity;
IF WalnutDB.msgSetsTable =
NIL
THEN {
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
numMsgSets: INT ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
WalnutDB.msgSetsTable ← RefTab.Create[MAX[numMsgSets*2+1, 16]];
};
[found, val] ← RefTab.Fetch[WalnutDB.msgSetsTable, aName];
IF found
THEN {
msInfo ← NARROW[val];
IF ~DB.NullEntity[msInfo.entity] THEN RETURN;
msInfo.entity ← DB.LookupEntity[MsgSetDomain, msInfo.canonicalName];
IF msInfo.entity =
NIL
THEN {
-- no longer exists
[] ← RefTab.Delete[WalnutDB.msgSetsTable, aName];
RETURN[NIL]
};
msInfo.versionRel ← GetMsgSetBasicInfoRel[msInfo.entity];
RETURN;
};
mse ← DB.LookupEntity[MsgSetDomain, cName ← Atom.GetPName[aName]];
IF mse = NIL THEN RETURN; -- return NIL IF msgSet doesn't exist
msInfo ← NEW[MSInfoObject ← [canonicalName: cName, entity: mse]];
msInfo.versionRel ← GetMsgSetBasicInfoRel[mse];
[] ← RefTab.Store[WalnutDB.msgSetsTable, aName, msInfo];
};
changes name to all lower case, to get canonical names
CanonicalName:
PUBLIC
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;
rVersionInfo: Relship;
IF version = dontCareDomainVersion THEN RETURN;
rVersionInfo ← DB.FirstRelship[gVersionInfo];
IF version # (is ←
DB.V2I[
DB.GetF[rVersionInfo, gMsgSetsVersion]])
THEN {
DB.SetF[DB.FirstRelship[gLogInfo], 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[DB.FirstRelship[gLogInfo], 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[DB.FirstRelship[gLogInfo], 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, report: CheckReportProc]
RETURNS[someInDeleted: BOOL] = {
rs: RelshipSet = DB.RelshipsWithEntityField[cdRelation, cdMsgSet, mse];
BEGIN
ENABLE
UNWIND => {
IF rs#
NIL
THEN
DB.ReleaseRelshipSet[rs]};
rel: Relship;
de: Entity = WalnutDB.deletedMessageSet;
commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize;
delArray: WalnutRegistry.MsgGroup;
remArray: WalnutRegistry.MsgGroup;
numDel: CARDINAL ← 0;
numRem: CARDINAL ← 0;
sinceLastCommit: INT ← 0;
count: INT ← 0;
someInDeleted ← FALSE;
UNTIL
DB.NullRelship[rel←
DB.NextRelship[rs]]
DO
me: Entity = DB.V2E[DB.GetF[rel, cdMsg]];
rs2: RelshipSet;
deleted: BOOL;
BEGIN
ENABLE
UNWIND =>
DB.ReleaseRelshipSet[rs2];
rs2 ← DB.RelshipsWithEntityField[cdRelation, cdMsg, me];
deleted ← DB.NullRelship[DB.NextRelship[rs2]];
DB.ReleaseRelshipSet[rs2];
END;
IF deleted
THEN {
[] ← AddMsgTo[me, WalnutDB.deletedMessageSet];
delArray[numDel] ← DB.EntityInfo[me].name;
numDel ← numDel + 1;
}
ELSE {
remArray[numRem] ← DB.EntityInfo[me].name;
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;
};
IF report # NIL THEN count ← CheckCount[count, report];
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] = {
rVersionInfo: Relship = DB.FirstRelship[gVersionInfo];
numNow: INT ← DB.V2I[DB.GetF[rVersionInfo, gMsgSetCount]];
newV: INT = DB.V2I[DB.GetF[rVersionInfo, gMsgSetsVersion]] + 1;
DB.SetF[rVersionInfo, gMsgSetsVersion, DB.I2V[newV]];
DB.SetF[rVersionInfo, gMsgSetCount, DB.I2V[numNow + delta]];
};
AddMsgTo:
PROC[me: Entity, mse: Entity] = {
date: Value = DB.GetF[DB.LookupProperty[mInfo, me], mDateIs];
val: DB.ValueSequence = DB.L2VS[LIST[DB.E2V[me], DB.E2V[mse], date]];
[] ← DB.CreateRelship[cdRelation, val];
WalnutDB.ChangeCountInMsgSet[mse, 1]
};
CheckCount:
PROC[count:
INT, report: CheckReportProc]
RETURNS[c:
INT] = {
IF count = 0 THEN report["\n"]; -- put out CR first
IF (c ← count + 1) MOD 10 # 0 THEN RETURN;
IF c MOD 100 = 0 THEN report["!"] ELSE report["~"];
};