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. τWalnutDBMsgSetsImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Willie-Sue, September 17, 1986 11:30:26 am PDT Donahue, May 12, 1986 2:13:42 pm PDT Contents: types and procedures dealing with the Walnut message database Initiated by Willie-Sue, September 24, 1984 Types Internal Types and Variables MSInfo's are stored in the msgSetsTable Operations on Message Sets Does this message set already exist in the database. Create this message set if it doesn't already exist in the database. Removes any messages from msgSet - long running op 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 Destroys the Deleted message set - long running op If the counts are wrong, change them. They may already have been changed if you're restarting an Expunge This counts as one operation Operations to Move Messages Among Message Sets Adds Msg to MsgSet, if it's not already in it. IF msgSet=deletedMsgSet, does nothing and returns exists=FALSE IF removing msg from msgSet would leave it in no MsgSet, then msg gets added to the distinguished MsgSet Deleted, and returns deleted = TRUE 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) Enumerations Internal procedures changes name to all lower case, to get canonical names Κ'+˜šΟn™Jšœ Οmœ1™JšœŸœ$˜>JšœŸœ˜1—™JšœŸœŸœ˜JšœŸœ ˜"JšœŸœ˜JšœŸœŸœ˜!JšœŸœŸœ˜#Jšœ˜Jšœ,˜,JšœŸœ ˜"JšœŸœ ˜$J˜Jšœ ŸœŸœŸœ˜7J™Jšœ'™'JšœŸœŸœ˜ JšœŸœŸœŸœ'˜WJš œ ŸœŸœŸœŸœ˜-J˜JšœŸœŸœ˜-Jš œŸœŸœŸœŸœŸœŸœ˜h—š ™š œŸœŸœŸœŸœŸœ Ÿœ Ÿœ˜fJšœ4™4šœŸœ˜J˜ Jšœ$˜$Jšœ3˜3JšœŸœ˜Jšœ˜—Jšœ"˜"J˜—J˜š œŸœŸœŸœŸœŸœ Ÿœ Ÿœ˜gJšœD™DšœŸœ˜Jšœ ˜ J˜ JšœŸœ˜ JšœŸœ˜ Jšœ$˜$Jšœ-˜-JšŸœŸœŸœŸœ˜%J˜3JšœŸœ$ŸœΟc ˜=JšœŸœ@Ÿœ˜PšœŸœ˜.JšŸœŸœŸœ9˜J—J˜Jšœ5˜5Jšœ˜—Jšœ"˜"J˜—J˜š œŸœŸœŸœŸœŸœ Ÿœ˜JšœŸœ˜Jšœ ˜ Jšœ-˜-šŸœŸœŸœ˜JšŸœŸœŸœ"˜6—Jšœ˜—Jšœ˜J˜—J˜š œŸœŸœ)˜AJšœŸœŸœ˜!Jšœ2™2šœŸœ˜JšœŸœ˜ Jšœ(˜(JšŸœŸœŸœŸœ˜JšœŸœŸœ"˜3JšœA˜AJšŸœ Ÿœ2˜CJšœ˜—šŸœ>Ÿœ˜FJ˜ JšŸœL˜QJ˜—Jšœ!˜!J˜—J˜š œŸœŸœ"Ÿœ˜YJšœŸœŸœ˜!JšœΝ™Νš œŸœ˜Jšœ ˜ Jšœ$˜$Jšœ ˜ JšŸœŸœŸœŸœ˜JšœA˜AJšŸœ˜J˜Jšœ˜—šŸœ>Ÿœ˜FJ˜ JšŸœN˜SJ˜—Jšœ#˜#J˜—J˜š  œŸœŸœŸœ Ÿœ˜CšœŸœ˜ Jšœ(˜(JšœŸœ˜J˜—Jšœ˜Jšœ˜—J˜šœŸœŸœŸœ˜:JšœŸœ+˜4Jšœ˜J˜—J˜š œŸœŸœŸœ˜JJšœ2™2JšŸœ ˜šœŸœ˜JšœE˜EJšŸ˜JšœŸœŸœŸœ"˜?JšœŸœB˜UJšœŸœ˜JšœŸœ˜8Jšœ"˜"JšœŸœ˜JšœŸœ˜Jšœ Ÿœ8˜HJšŸœ Ÿœ ŸœŸœ˜'J™Jšœi™išŸœŸœ˜Jšœ6˜6Jšœ!˜!Jšœ™Jšœ%˜%Jšœ˜—J˜šŸœŸœŸœŸœŸœŸœŸœ˜AJ˜ JšœŸœ˜.JšœŸœŸœŸœ(˜GJšœŸœŸœŸœ(˜JšŸœŸœŸœŸ˜(Jšœ ŸœŸœ˜)JšœŸœ˜4Jšœ ŸœŸœŸœ˜8šœ ŸœŸœŸœ ˜8JšŸœŸœŸœŸœ˜N—Jšœ*˜*šŸœŸœŸ˜=Jšœ˜—šŸœ Ÿœ˜JšœŸœ˜*Jšœ˜J˜—JšŸœ˜šŸœ<Ÿœ˜DJšŸœ'Ÿœ˜AJšŸœ'Ÿœ˜DJ˜Jšœ˜šŸœ Ÿœ˜Jšœ=˜=Jšœ ŸœŸœ‘˜,Jšœ ˜ J˜—J˜—JšŸœ ŸœŸœ#˜7JšŸœ˜—JšŸœ˜šŸœŸœŸ˜JšŸœ'Ÿœ˜AJšŸœ'Ÿœ˜DJ˜—šŸœ‘'˜.JšœŸœ0˜DJšŸœ"ŸœŸœ"˜OJ˜šŸœ ŸœŸ˜#Jšœ=˜=—Jšœ6˜6JšŸœ˜——JšŸœ˜JšŸœ˜Jšœ˜—Jšœ&˜&Jšœ˜—J™š œŸ œŸœŸœ˜7šœŸœ˜ JšœŸœ˜6Jšœ ŸœŸœ&˜9JšœŸœŸœ#˜2Jšœ˜—Jšœ˜J˜——š .™.š œŸœŸœŸœŸœ Ÿœ˜JJšœn™nšœŸœ˜ J˜ J˜ JšŸœ ŸœŸœ˜Jšœ˜Jšœ˜Jš ŸœŸœŸœŸœ‘˜6šœŸœ6˜KJšœŸœŸœ˜šŸœŸœŸœŸœ˜0šŸ˜JšœŸœ˜"JšŸœŸœŸœŸœ˜!šŸ˜JšœŸœ ŸœŸœ˜3Jš ŸœŸœŸœ ŸœŸœ˜@šŸœŸœ.Ÿœ˜9JšŸœ˜Jšœ>˜>—JšŸ˜—JšŸœ˜—JšŸœ˜JšŸœ˜—Jšœ˜—JšŸœŸœŸœ ˜2J˜—Jšœ Ÿœ˜JšŸœ:ŸœŸœ˜HJšœ˜J˜—J˜š œŸœŸœŸœ ŸœŸœ Ÿœ˜_JšœŒ™ŒšœŸœ˜ J˜ J˜ Jšœ˜Jšœ˜Jšœ˜J˜JšŸœŸœŸœŸœ˜1JšŸœ"ŸœŸœŸœ˜4J˜JšŸœ ŸœŸœ-˜GJšœ Ÿœ‘S˜dJšœŸœ6˜=šŸœŸœŸœŸœ˜0šŸ˜JšœŸœ˜"JšŸœŸœŸœŸœ˜!Jš ŸœŸœŸœŸœŸœ˜OJšŸœ Ÿ˜JšŸœ˜—JšŸœ˜—JšŸœ˜Jš Ÿœ ŸœŸœ ŸœŸœ‘˜OJšœ-˜-JšŸœŸœ ŸœŸœ˜0šŸœ˜JšŸœŸœ"˜AJšœ=˜=J˜——J˜Jšœ Ÿœ˜Jšœ˜J˜—J˜J˜š œŸœŸœŸœŸœ Ÿœ˜TJ™­šœŸœ˜J˜ J˜Jšœ˜Jšœ˜Jšœ$˜$J˜JšŸœŸœ ŸœŸœ˜2JšŸœ'ŸœŸœŸœ˜9JšŸœ#ŸœŸœŸœ˜5JšŸœ ŸœŸœ-˜GJšœŸœ6˜=šŸœŸœŸœŸœ˜0šŸ˜JšœŸœ˜"JšŸœŸœŸœŸœ˜!š ŸœŸœŸœŸœŸ˜CJšœ˜—š ŸœŸœŸœŸœŸœŸ˜FJšœ˜—JšŸœ˜—JšŸœ˜—JšŸœ˜Jš ŸœŸœŸœ ŸœŸœ˜5JšœŸœ˜JšŸœŸœŸœ˜/JšŸœŸœŸœ˜˜—J˜š œŸœŸœ$ŸœŸœ Ÿœ˜eJšœŸœ˜š œŸœ˜J˜ Jš ŸœŸœŸœ ŸœŸœ˜5šŸ˜JšœŸœ˜,Jšœ(˜(Jš ŸœŸœŸœŸœ1ŸœŸœ˜SJšœŸœŸœ˜!šŸœŸœ˜JšœŸœ˜-JšŸœŸœŸœ ŸœŸœŸœŸœŸœŸœ˜J—JšŸœŸ˜ JšŸœ˜—JšŸœŸœŸœ Ÿœ˜0JšœŸœ˜—š œŸœ˜JšœŸœ%ŸœŸœD˜…Jš ŸœŸœŸœŸœŸœŸœ˜MJšœ˜—J˜Jš ŸœŸœŸœŸœŸœŸœ˜=š Ÿœ ŸœŸœŸœŸœ Ÿ˜6JšœŸœŸœ˜šŸ˜šŸœ˜Jš ŸœŸœ ŸœŸœŸœŸœ˜C—Jšœ#˜#JšŸœ˜—JšŸœŸœŸœ2Ÿœ˜MJšœ%˜%JšŸœ˜ ——J˜šœŸœŸœŸœŸœŸœŸœ˜sJ˜JšœŸœŸœ˜šŸœŸœ˜ Jš ŸœŸœŸœŸœŸœ˜3—J˜šœŸœ˜ Jšœ˜Jšœ:˜:JšœŸœD˜MJ˜šŸœŸœŸœŸœ˜ Jšœ˜JšœŸœ˜ šŸœŸœŸœŸ˜7Jšœ Ÿœ˜JšœŸœ˜Jšœ ŸœŸœ˜/JšœŸœ˜Jšœ?˜?Jšœ$˜$JšŸœ˜—JšœŸœ˜ JšŸœŸœ˜JšŸœ˜—Jš ŸœŸœŸœ Ÿœ Ÿœ˜IJ˜—J˜J˜JšŸœŸœŸœ˜šŸ˜JšœŸœ˜ —šŸœ˜J˜JšŸœE˜J—J˜—J˜š  œŸœŸœŸœŸœ‘˜OšœŸœ˜ Jšœ˜J˜JšœŸœ˜8Jšœ"˜"JšœŸœ˜JšœŸœ˜JšœŸœ8˜OJšœ Ÿœ8˜HJ˜Jšœ:˜:JšŸœ Ÿœ ŸœŸœ˜'J˜šŸœŸœŸœŸœŸœŸœŸœ˜AJ˜šœŸœ˜ Jšœ+˜+—šŸœŸœŸœŸ˜5Jšœ ŸœŸœ˜/JšŸœ#˜%šŸœ Ÿœ˜JšœŸœ˜*Jšœ˜J˜—šŸœ<Ÿœ˜DJšœ ŸœŸœŸœ/˜HJšŸœ.˜0J˜Jšœ˜šŸœ Ÿœ˜Jšœ9˜9Jšœ ŸœŸœ˜Jšœ ˜ J˜—J˜—JšŸœ˜—JšŸœ˜JšŸœ˜—šŸœŸœŸœŸœŸœŸœŸœ˜@J˜ JšœŸœŸœŸœ ˜4š ŸœŸœŸœŸœŸœŸ˜?JšœŸœ ˜1JšŸœ˜ JšŸœ˜—JšŸœ˜—JšŸœ˜J˜šŸœŸ˜JšŸœŸœŸœŸœ1˜_—J˜JšŸœŸœŸœŸœ%˜UJšŸœŸœ3Ÿœ ˜JJšŸœŸœ:˜DJ˜šŸœ ŸœŸ˜#Jšœ9˜9—J˜—Jšœ˜J˜——™JšœŸœ8˜LJ˜šœŸœ ŸœŸ˜FJšœŸœŸœ$˜/—J˜š œŸœŸœŸœ˜6Jšœ Ÿœ˜+JšœŸœ˜J˜—J˜š œŸœŸœŸœŸœ˜LJšœ˜š ŸœŸœŸœ ŸœŸœ#˜HJšŸœ ˜—J˜—J˜šœŸœŸœŸœ˜=JšœŸœ‘˜5JšœŸœ˜ JšœŸœ˜ J˜Jšœ ˜ šŸœŸœŸœ˜%JšœŸœ˜6Jšœ ŸœŸœŸœ#˜>Jšœ&Ÿœ˜?J˜—J˜Jšœ:˜:šŸœŸœ˜Jšœ Ÿœ˜JšŸœŸœŸœŸœ˜-JšœŸœ2˜DšŸœŸœŸœ‘˜2Jšœ1˜1JšŸœŸœ˜ Jšœ˜—Jšœ9˜9JšŸœ˜J˜—JšœŸœ:˜BJš ŸœŸœŸœŸœ‘%˜@Jšœ Ÿœ5˜AJšœ/˜/Jšœ8˜8J˜—J˜Jšœ6™6š  œŸœŸœŸœŸœŸœ˜?J˜šŸœŸœŸœŸ˜%JšœD˜DJšŸœ˜—Jšœ+˜+J˜—J˜šœŸœ Ÿœ˜*JšœŸœ˜Jšœ˜JšŸœ!ŸœŸœ˜/JšœŸœ˜-šŸœŸœŸœ'Ÿœ˜IJšŸœŸœ:˜DšŸœ˜!JšŸœ8Ÿœ Ÿœ˜Y—J˜—J˜—J˜šœŸœŸœŸœ˜OJšœ2˜2JšŸœŸœŸœŸœ˜JšŸœ(ŸœŸœ˜6JšŸœŸœŸœ˜(JšŸœŸœ:˜DšŸœŸœ˜9JšŸœŸœŸœ˜A—J˜—J˜šœŸœŸœ˜@JšœŸœ˜Jšœ#˜#JšŸœŸœŸœŸœ˜JšŸœ(ŸœŸœ˜6Jš ŸœŸœŸœ$ŸœŸœ˜TJšŸœB˜DšŸœŸœ˜9JšŸœŸœ Ÿœ˜<—J˜—J˜šœŸœŸœ˜GJšœŸœŸœ˜!JšœŸœ4˜GšŸœŸœŸœŸœŸœŸœŸœ˜AJ˜ Jšœ(˜(JšœŸœ˜8Jšœ"˜"Jšœ"˜"JšœŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜šŸœŸœŸœŸ˜0Jšœ ŸœŸœ˜)Jšœ˜Jšœ Ÿœ˜šŸœ˜J˜—šŸœŸœŸœŸœ˜1JšœŸœ0˜8Jšœ Ÿœ Ÿœ˜.JšŸœ˜JšŸœ˜—šŸœ Ÿœ˜Jšœ.˜.JšœŸœ˜*Jšœ˜J˜šŸœ˜JšœŸœ˜*Jšœ˜J˜——J˜JšœŸœ ˜(šŸœ<Ÿœ˜DJ˜Jšœ˜šŸœŸœŸœŸ˜#Jšœ?Ÿœ˜G—šŸœŸœŸœŸ˜#šœ:Ÿœ˜KJšŸœ˜——Jšœ˜J˜—JšŸœ ŸœŸœ#˜7JšŸœ˜—JšŸœ˜šŸœŸœ˜J˜šŸœŸœŸœŸ˜#Jšœ?Ÿœ˜G—šŸœŸœŸœŸ˜#Jšœ:Ÿœ˜KJšŸœ˜—J˜——JšŸœ˜J˜—J˜šœŸœŸœ˜,JšœŸœ˜6JšœŸœŸœŸœ#˜:JšœŸœŸœŸœ*˜?JšŸœ3˜5JšŸœ:˜