<> <> <> <> <<>> <> <> 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; WalnutDBMsgSetsImpl: CEDAR PROGRAM IMPORTS Ascii, Atom, DB, IO, RefTab, RefText, Rope, WalnutDefs, WalnutDB, WalnutRegistryPrivate, WalnutRoot, WalnutSchema EXPORTS WalnutDB = BEGIN OPEN WalnutSchema; <> 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; <> 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: 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]; <> MsgSetExists: PUBLIC PROC[name: ROPE, msDomainVersion: INT] RETURNS[existed: BOOL, msVersion: INT] = { <> 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] = { <> 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] = { <> 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] = { <> 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] = { <> 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 deletedCount # 0 THEN { ChangeCountInMsgSet[deletedMessageSet, -deletedCount]; ChangeCountOfMsgs[-deletedCount]; <> 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]; }; <> AddMsg: PUBLIC PROC[msg: ROPE, from, to: MsgSet] RETURNS[exists: BOOL] = { <> 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] = { <> 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] = { <> 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]; }; <> 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; 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: 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; EXITS stop => NULL; 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; 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.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]; }; <> 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]; }; <> 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; DB.DestroyRelship[rel]; 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["~"]; }; END.