DIRECTORY Ascii USING [Lower], Atom USING [MakeAtomFromRefText], DB USING [Aborted, Error, Failure, Attribute, AttributeValueList, Entity, EntitySet, Relship, RelshipSet, Value, DeclareEntity, DeclareRelship, DestroyEntity, DestroyRelship, DomainSubset, GetF, NameOf, NextEntity, NextRelship, Null, PrevRelship, RelationSubset, ReleaseEntitySet, ReleaseRelshipSet, SetF, Eq, V2E, V2I], IO, RefTab USING [Ref, Val, Create, Delete, Fetch, Store], RefText USING [line, InlineAppendChar], Rope, WalnutDefs USING [dontCareDomainVersion, dontCareMsgSetVersion, Error, MsgSet, VersionMismatch], WalnutDB, WalnutDBInternal USING [msgSetsTable, activeMessageSet, deletedMessageSet, CarefullyApply, ChangeCountInMsgSet, ChangeCountOfMsgs, GetMsgDisplayInfo], WalnutRegistry USING [MsgGroup, MsgGroupSize], WalnutRegistryPrivate USING [ CheckForMsgGroupRegistration, NotifyForEvent, NotifyForMove, NotifyForMsgEvent, NotifyForMsgGroup], WalnutRoot USING [CommitAndContinue], WalnutSchema; WalnutDBMsgSetsImpl: CEDAR PROGRAM IMPORTS Ascii, Atom, DB, IO, RefTab, RefText, Rope, WalnutDefs, WalnutDB, WalnutDBInternal, WalnutRegistryPrivate, WalnutRoot, WalnutSchema EXPORTS WalnutDB = BEGIN OPEN WalnutSchema; 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; 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: TYPE = REF MSInfoObject; MSInfoObject: TYPE = RECORD [name: ROPE, entity: Entity, versionRel: Relship]; nameText: REF TEXT = NEW[TEXT[RefText.line]]; 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; }; WalnutDBInternal.CarefullyApply[IsMsgSet]; }; CreateMsgSet: PUBLIC PROC[name: ROPE, msDomainVersion: INT] RETURNS [existed: BOOL, msVersion: INT] = { 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] = { 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] = { 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] = { 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 deletedCount # 0 THEN { ChangeCountInMsgSet[deletedMessageSet, -deletedCount]; ChangeCountOfMsgs[-deletedCount]; 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]; }; 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.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] = { 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; 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; [] _ 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] = { 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]; }; 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; EXITS stop => NULL; END; ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During Msgs enumeration"]; }; MsgsInSetEnumeration: PUBLIC PROC[name: ROPE, fromStart: BOOL] RETURNS [mL: LIST OF ROPE, msVersion: INT] = { ok: 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; EXITS stop => NULL; END; ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"]; }; MsgSetsNames: PUBLIC PROC[alphaOrder: BOOL] RETURNS[msL: LIST OF ROPE, msDomainVersion: INT] = { ok: 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; EXITS stop => NULL; END; ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"]; }; EnumerateMsgSets: PUBLIC PROC[alphaOrder: BOOL, proc: PROC[msgSet: MsgSet]] RETURNS[msDomainVersion: INT] = { ok: 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; 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: DB.RelshipSet; checkShow: BOOL; [msI, msVersion] _ GetMsgSetAndVersion[name]; IF msI = NIL THEN {ok_ TRUE; RETURN}; checkShow _ name.Equal["Active", FALSE]; enum_ DB.RelationSubset[r: cdRelation, constraint: LIST[[cdMsgSet, msI.entity]], start: IF fromStart THEN First ELSE Last]; BEGIN ENABLE UNWIND => GOTO end; DO e: Entity; rel: Relship = IF fromStart THEN DB.NextRelship[enum] ELSE DB.PrevRelship[enum]; msg, TOCentry: ROPE; hasBeenRead: BOOL; startOfSubject: INT; IF rel = NIL THEN EXIT; msg _ DB.NameOf[e _ DB.V2E[DB.GetF[rel, cdMsg]]]; IF checkShow THEN { showRel: Relship = DB.DeclareRelship[mInfo, LIST[[mInfoOf, e], [mShowIs, DBFalseBool]], OldOnly]; IF showRel # NIL THEN LOOP; -- mShowIs is FALSE }; [hasBeenRead, TOCentry, startOfSubject] _ WalnutDBInternal.GetMsgDisplayInfo[e]; proc[msg, TOCentry, hasBeenRead, startOfSubject]; ENDLOOP; ok_ TRUE; EXITS end => NULL; END; DB.ReleaseRelshipSet[enum ! DB.Error, DB.Aborted, DB.Failure => CONTINUE] }; WalnutDBInternal.CarefullyApply[MEnum]; IF ok THEN RETURN; EXITS stop => NULL; END; ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSet enumeration"]; }; EnumerateMsgsInMsgSet: PUBLIC PROC[msgSet: MsgSet] RETURNS[lazyEnum: WalnutDB.LazyEnumerator, valid: BOOL] = { RETURN[NIL, FALSE] }; NextMsgInMsgSet: PUBLIC PROC[lazyEnum: WalnutDB.LazyEnumerator, retry: BOOL] RETURNS[msgID: ROPE, valid: BOOL] = { RETURN[NIL, FALSE] }; EnumerateUnacceptedMsgs: PUBLIC PROC [ activeVersion: INT, proc: PROC[msg, TOCentry: ROPE, startOfSubject: INT] ] = { ok: 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; EXITS stop => NULL; END; ERROR WalnutDefs.Error[$db, $DatabaseInaccessable, "During Get New Mail"]; }; AcceptNewMail: PUBLIC PROC[pos: INT, activeVersion: INT] = { -- long running op Am: PROC = { rs: RelshipSet; es: EntitySet; commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize; accArray: WalnutRegistry.MsgGroup; numAcc: CARDINAL _ 0; sinceLastCommit: INT _ 0; activeRel: Relship = DB.DeclareRelship[msBasicInfo, LIST[[msBIOf, WalnutDBInternal.activeMessageSet]]]; doMsgGroup: BOOL _ WalnutRegistryPrivate.CheckForMsgGroupRegistration[]; [] _ CheckMsgSetEntity[[ActiveMsgSetName, activeVersion]]; IF doMsgGroup THEN accArray _ ALL[NIL]; BEGIN ENABLE UNWIND => {IF rs#NIL THEN DB.ReleaseRelshipSet[rs]}; cdRel: Relship; rs _ DB.RelationSubset[ cdRelation, LIST[[cdMsgSet, WalnutDBInternal.activeMessageSet]]]; UNTIL DB.Null[cdRel_ DB.NextRelship[rs]] DO me: Entity = DB.V2E[DB.GetF[cdRel, cdMsg]]; showRel: Relship = DB.DeclareRelship[mInfo, LIST[[mInfoOf, me], [mShowIs, DBFalseBool]], OldOnly]; -- find a FALSE one IF showRel = NIL THEN LOOP; DB.SetF[showRel, mShowIs, DBTrueBool]; IF doMsgGroup THEN { accArray[numAcc] _ DB.NameOf[me]; numAcc _ numAcc + 1; }; IF (sinceLastCommit _ sinceLastCommit + 1) >= commitFrequency THEN { DBIntValue^ _ DB.V2I[DB.GetF[activeRel, msBICount]] + sinceLastCommit; DB.SetF[activeRel, msBICount, DBIntValue]; WalnutRoot.CommitAndContinue[]; sinceLastCommit _ 0; IF doMsgGroup THEN { WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray]; accArray _ ALL[NIL]; numAcc _ 0; }; }; ENDLOOP; DB.ReleaseRelshipSet[rs]; END; BEGIN ENABLE UNWIND => {IF es#NIL THEN DB.ReleaseEntitySet[es]}; se: Entity; es _ DB.DomainSubset[ServerDomain]; FOR se_ DB.NextEntity[es], DB.NextEntity[es] UNTIL se = NIL DO rel: Relship = DB.DeclareRelship[sBasicInfo, LIST[[sBIOf, se]]]; num: 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]; }; 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]; }; 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]; }; END. ζWalnutDBMsgSetsImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Willie-Sue, July 11, 1985 1:58:41 pm PDT Donahue, July 22, 1985 5:17:38 pm PDT Contents: types and procedures dealing with the Walnut message database Initiated by Willie-Sue, September 24, 1984 Last Edited by: Willie-Sue, January 9, 1985 11:17:16 am PST Last Edited by: Donahue, December 11, 1984 7:50:08 pm PST (Changed RemoveMsg to be more efficient) 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 check if is in Deleted and if so, remove it (need only check first Relship) IF removing msg from msgSet would leave it in no MsgSet, then msg gets added to the distinguished MsgSet Deleted, and returns deleted = TRUE now if in no msgSet, then add msg to deleted 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 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˜Jš œ ŸœŸœŸœŸœ˜Jš œŸœŸœŸœŸœ˜'Jš œ ŸœŸœŸœŸœ˜"Jš œ ŸœŸœŸœŸœŸœ˜(Jš œ ŸœŸœŸœŸœŸœ˜*JšœŸœ ˜"JšœŸœ ˜$J˜Jšœ ŸœŸœŸœ˜7J™Jšœ'™'JšœŸœŸœ˜ JšœŸœŸœŸœ'˜NJš œ ŸœŸœŸœŸœ˜-—š ™š œŸœŸœŸœŸœŸœ Ÿœ Ÿœ˜fJšœ4™4šœŸœ˜J˜ Jšœ$˜$Jšœ3˜3JšœŸœ˜Jšœ˜—Jšœ*˜*J˜—J˜š œŸœŸœŸœŸœŸœ Ÿœ Ÿœ˜gJšœD™DšœŸœ˜Jšœ ˜ J˜ Jšœ$˜$Jšœ-˜-JšŸœŸœŸœŸœ˜%JšœŸœ,˜4šœŸœ˜JšœD˜D—Jšœ˜JšŸœ/˜1J˜JšœK˜KJšœ˜—Jšœ*˜*J˜—J˜š œŸœŸœŸœŸœŸœ Ÿœ˜JšœŸœ˜Jšœ ˜ Jšœ-˜-JšŸœŸœŸœ Ÿœ˜$JšœŸœŸœ"˜1Jšœ˜—Jšœ'˜'J˜—J˜š œŸ œŸœŸœ˜IJšœ2™2šœŸœ˜Jšœ(˜(JšŸœŸœŸœŸœ˜Jšœ9˜9Jšœ˜—šŸœ>Ÿœ˜FJ˜ JšŸœL˜QJ˜—Jšœ)˜)J˜—J˜š  œŸœŸœ"ŸœŸœŸœ˜aJšœΝ™Νš œŸœ˜Jšœ ˜ Jšœ$˜$Jšœ ˜ JšŸœŸœŸœŸœ˜Jšœ9˜9JšŸœ˜J˜Jšœ˜—šŸœ>Ÿœ˜FJ˜ JšŸœN˜SJ˜—Jšœ+˜+J˜—J˜š  œŸœŸœŸœ Ÿœ˜CšœŸœ˜ Jšœ(˜(JšœŸœ˜J˜—Jšœ%˜%Jšœ˜—J˜šœŸœŸœŸœ˜:šœŸœ˜ Jšœ(˜(—Jšœ%˜%J˜—J˜š œŸœŸœŸœ˜1Jšœ2™2JšŸœ˜šœŸœ˜JšœE˜EJšŸ˜JšœŸœŸœŸœ"˜?JšœŸœŸœ!˜TJšœŸœ˜JšœŸœ˜8Jšœ"˜"JšœŸœ˜Jšœ Ÿœ8˜HJšŸœ Ÿœ ŸœŸœ˜'J™Jšœi™išŸœŸœ˜Jšœ6˜6Jšœ!˜!Jšœ™Jšœ(˜(—J˜šŸœŸœŸœŸœŸœŸœŸœ˜AJ˜ JšœŸœŸœŸœ(˜GJšœŸœŸœŸœ(˜JšŸœŸœ ŸœŸ˜)Jšœ ŸœŸœ˜)JšœŸœŸœ˜BJšœ ŸœŸœŸœ˜8šœ ŸœŸœŸœ ˜8JšŸœŸœŸœŸœ˜N—Jšœ*˜*šŸœŸœŸ˜=Jšœ˜—šŸœ Ÿœ˜JšœŸœ ˜!Jšœ˜J˜—JšŸœ˜šŸœ<Ÿœ˜DJšœ˜JšŸœ3˜5Jšœ ˜ JšŸœ3˜5J˜Jšœ˜šŸœ Ÿœ˜Jšœ=˜=Jšœ ŸœŸœΟc˜,Jšœ ˜ J˜—J˜—JšŸœ˜—JšŸœ˜šŸœŸœŸ˜Jšœ˜JšŸœ3˜5Jšœ ˜ JšŸœ3˜5J˜—šŸœ‘'˜.JšœŸœŸœ˜TJšœŸœŸœ ˜7JšŸœ'˜)J˜šŸœ ŸœŸ˜#Jšœ=˜=—Jšœ6˜6JšŸœ˜——JšŸœ˜JšŸœ˜Jšœ˜—Jšœ&˜&Jšœ˜—J™š œŸ œŸœŸœ˜7šœŸœ˜ Jšœ ŸœŸœ&˜9JšœŸœŸœ#˜2Jšœ˜—Jšœ%˜%J˜——š .™.š œŸœŸœŸœŸœ Ÿœ˜JJšœn™nšœŸœ˜ J˜J˜ JšŸœ ŸœŸœ˜Jšœ˜Jšœ˜Jš ŸœŸœŸœŸœ‘˜6šœK™KšœŸœŸœ˜JJšœŸœŸœ˜šŸœŸœŸœŸœ˜0JšœŸœ˜"šœŸœ˜JšŸœŸœ;˜D—JšŸœ˜šŸœŸœ˜JšŸœ˜JšœM˜MJ˜—JšŸœ˜—Jšœ˜——šœ˜JšŸœŸœŸœ.˜S—J˜—Jšœ Ÿœ˜JšŸœ:ŸœŸœ˜HJšœ%˜%J˜—J˜š œŸœŸœŸœ ŸœŸœ Ÿœ˜_JšœŒ™ŒšœŸœ˜ J˜ J˜ Jšœ Ÿœ˜Jšœ ŸœŸœ˜JšœŸœ˜Jšœ˜J˜JšŸœŸœŸœŸœ˜1JšŸœ"ŸœŸœŸœ˜4J˜JšœŸœŸœŸœ!˜MJšœ4˜4JšŸœ ŸœŸœ˜Jšœ,™,JšœŸœŸœ˜<šŸœŸœŸœŸœ˜0Jšœ ŸœŸœŸœ˜.JšŸœ˜—JšŸœ˜JšŸœ ŸœŸœ˜Jšœ Ÿœ˜Jšœή™ήJšœŸœŸœ^˜…JšœL˜LJ˜—Jšœ Ÿœ˜Jšœ%˜%J˜—J˜J˜š œŸœŸœŸœŸœ Ÿœ˜TJ™­šœŸœ˜J˜ J˜JšœŸœ˜J˜JšŸœŸœ ŸœŸœ˜2JšŸœ'ŸœŸœŸœ˜9JšŸœ#ŸœŸœŸœ˜5JšœŸœŸœŸœ!˜MJšœ7˜7JšŸœ ŸœŸœ˜Jšœ0˜0J˜—Jš Ÿœ(ŸœŸœŸœ‘˜QJšœ(˜(J˜——™ šœŸœŸœŸœŸœŸœŸœŸœ˜NJšœŸœŸœ˜šŸœŸœ˜ Jš ŸœŸœŸœŸœŸœ˜3—J˜šœŸœ˜JšœŸœŸœŸœ˜šœŸœ ˜šŸœ Ÿ˜JšŸœ@Ÿ˜GJšŸœ1˜3——JšœŸœ˜šŸœŸœŸœŸœŸœŸœŸœ˜1Jšœ ˜ JšœŸœ˜ š ŸœŸœŸœŸœŸœŸ˜@JšœŸœ ˜š ŸœŸœŸœ ŸœŸœŸ˜.Jšœ ŸœŸœ˜.—JšŸœ˜—JšœŸœ˜ JšŸœŸœ˜—JšŸœ˜Jš ŸœŸœŸœ Ÿœ Ÿœ˜IJ˜—Jšœ'˜'JšŸœŸœŸœ˜šŸ˜JšœŸœ˜ —šŸœ˜J˜JšŸœI˜N—J˜—J˜šœŸœŸœŸœ ŸœŸœŸœŸœŸœ Ÿœ˜mJšœŸœŸœ˜šŸœŸœ˜ Jš ŸœŸœŸœŸœŸœ˜3—J˜šœŸœ˜Jšœ ˜ JšœŸœ ˜Jšœ Ÿœ˜Jš œ ŸœŸœŸœŸœ˜JšœŸœ˜ J˜Jšœ-˜-Jš ŸœŸœŸœŸœŸœ˜&Jšœ"Ÿœ˜)JšœŸœ+Ÿœ˜SšŸœŸœŸœŸœ˜ šŸ˜JšœŸœ˜$J˜ JšŸœŸœŸœŸœ˜JšœŸœŸœ˜!šŸœ Ÿœ˜šœŸœ˜(JšŸœ2˜6—Jš ŸœŸœŸœŸœ‘˜/Jšœ˜—šŸœ Ÿœ˜š ŸœŸœŸœŸœŸœ Ÿœ˜;šŸœ˜JšœŸœŸœ Ÿœ˜+Jšœ˜J˜—J˜—JšŸœŸœŸœ˜"—JšŸœ˜—JšœŸœ˜ JšŸœŸœ˜JšŸœ˜—Jš ŸœŸœŸœ Ÿœ Ÿœ˜IJ˜—J˜'JšŸœŸœŸœ˜šŸ˜JšœŸœ˜ —šŸœ˜J˜JšŸœL˜Q—J˜—J˜š œŸœŸœ ŸœŸœŸœŸœŸœŸœ˜`JšœŸœŸœ˜šŸœŸœ˜ Jš ŸœŸœŸœŸœŸœ˜3—J˜šœŸœ˜JšœŸœŸœŸœ˜šœŸœ ˜šŸœ Ÿ˜JšŸœDŸ˜JJšŸœ4˜6——JšœŸœŸœ&˜AJšœŸœ˜ šŸœŸœŸœŸœŸœŸœŸœ˜1Jšœ ˜ š ŸœŸœŸœŸœŸœŸ˜Aš ŸœŸœŸœŸœŸœ ŸœŸ˜;JšœŸœŸœ Ÿœ˜9—JšŸœ˜—JšœŸœ˜ JšŸœŸœ˜—JšŸœ˜Jš ŸœŸœŸœ Ÿœ Ÿœ˜IJ˜—J˜(JšŸœŸœŸœ˜šŸ˜JšœŸœ˜ —šŸœ˜J˜JšŸœL˜Q—J˜—J˜šœŸœŸœ ŸœŸœŸœŸœ˜mJšœŸœŸœ˜šŸœŸœ˜ Jš ŸœŸœŸœŸœŸœ˜3—J˜šœŸœ˜šœŸœ ˜šŸœ Ÿ˜JšŸœDŸ˜JJšŸœ4˜6——JšœŸœŸœ&˜AšŸœŸœŸœŸœŸœŸœŸœ˜1Jšœ ˜ š ŸœŸœŸœŸœŸœŸ˜Ašœ˜JšœŸœ ŸœŸœ/˜G—Jšœ ˜ JšŸœ˜—JšœŸœ˜ JšŸœŸœ˜—JšŸœ˜Jš ŸœŸœŸœ Ÿœ Ÿœ˜IJ˜—J˜(JšŸœŸœŸœ˜šŸ˜JšœŸœ˜ —šŸœ˜J˜JšŸœL˜Q—J˜—J˜šœŸœŸœ Ÿœ ŸœŸœŸœŸœŸœŸœŸœ Ÿœ˜¬JšœŸœŸœ˜šŸœŸœ˜ Jš ŸœŸœŸœŸœŸœ˜3—šœŸœ˜Jšœ ˜ JšœŸœ ˜Jšœ Ÿœ˜J˜Jšœ-˜-Jš ŸœŸœŸœŸœŸœ˜%Jšœ!Ÿœ˜(šœŸœ˜&Jšœ Ÿœ˜*JšœŸœ ŸœŸœ˜+—šŸœŸœŸœŸœ˜ šŸ˜Jšœ ˜ Jš œŸœ ŸœŸœŸœŸœ˜PJšœŸœ˜Jšœ Ÿœ˜JšœŸœ˜JšŸœŸœŸœŸœ˜JšœŸœ ŸœŸœ˜1šŸœ Ÿœ˜šœŸœ˜+JšŸœ1˜5—Jš Ÿœ ŸœŸœŸœ‘˜0Jšœ˜—JšœP˜PJšœ1˜1JšŸœ˜—JšœŸœ˜ JšŸœŸœ˜JšŸœ˜—Jš ŸœŸœŸœ Ÿœ Ÿœ˜IJ˜—J˜'JšŸœŸœŸœ˜šŸ˜JšœŸœ˜ —šŸœ˜JšŸœK˜P—J˜—J˜šœŸ œ˜2JšœŸœ+Ÿœ˜JšœŸœŸœ˜@JšœŸœŸœŸœ˜(JšŸœ˜ JšŸœ˜—JšŸœ˜—JšŸœ˜J˜šŸœŸœ˜JšœŸœŸœ/˜FJšŸœ(˜*Jšœ˜—J˜JšœŸœŸœ#˜:JšŸœ*˜,Jšœ˜JšŸœ6˜8JšŸœ1˜3J˜šŸœ ŸœŸ˜#Jšœ9˜9—J˜—Jšœ$˜$J˜——™JšœŸœ8˜LJ˜šœŸœ ŸœŸ˜FJšœŸœŸœŸœ˜?—J˜š œŸœŸœŸœ˜6Jšœ Ÿœ(˜5Jšœ ŸœŸœ˜!J˜—J˜š œŸœŸœŸœŸœ˜LJšœ˜š ŸœŸœŸœ ŸœŸœ#˜HJšŸœ ˜—J˜—J˜šœŸœŸœŸœ˜=JšœŸœ‘˜5JšœŸœ˜ J˜JšœŸœ˜šŸœ!ŸœŸœ˜-Jšœ ŸœŸœŸœ#˜;Jšœ;˜;J˜—J˜JšœB˜BšŸœŸœ˜Jšœ Ÿœ˜JšŸœŸœŸœŸœ˜'JšœŸœ,˜>šŸœŸœŸœ‘˜5Jšœ9˜9JšŸœŸœ˜ Jšœ˜—Jšœ9˜9JšŸœ˜J˜—JšœŸœ,˜4Jš ŸœŸœ ŸœŸœ‘%˜CJšœ Ÿœ+˜7Jšœ/˜/Jšœ@˜@J˜—J˜Jšœ6™6š  œŸœŸœŸœŸœ˜8J˜šŸœŸœŸœŸ˜%JšœD˜DJšŸœ˜—Jšœ+˜+J˜—J˜šœŸœ Ÿœ˜*JšœŸœ˜JšŸœ!ŸœŸœ˜/J˜šŸœŸœŸœ'Ÿœ˜IJšŸœ1˜3šŸœ˜!JšŸœ8Ÿœ Ÿœ˜Y—J˜—J˜—J˜šœŸœŸœŸœ˜OJšœ2˜2JšŸœŸœŸœŸœ˜JšŸœ(ŸœŸœ˜6JšŸœŸœŸœ˜(JšŸœ1˜3šŸœŸœ˜9JšŸœŸœŸœ˜A—J˜—J˜šœŸœŸœ˜@JšœŸœ˜Jšœ#˜#JšŸœŸœŸœŸœ˜JšŸœ(ŸœŸœ˜6Jš ŸœŸœŸœ$ŸœŸœ˜TJšŸœ1˜3šŸœŸœ˜9JšŸœŸœ Ÿœ˜<—J˜—J˜š œŸœŸœŸœŸœ˜OJšœŸœŸœ˜FšŸœŸœŸœŸœŸœŸœŸœ˜AJ˜ Jšœ0˜0JšœŸœ˜8Jšœ"˜"Jšœ"˜"JšœŸœ˜JšœŸœ˜JšœŸœ˜Jšœ Ÿœ˜šŸœŸœ ŸœŸ˜)Jšœ ŸœŸœ˜)Jš œŸœ ŸœŸœŸœ˜QJšœ˜Jšœ Ÿœ˜JšŸœ˜šŸœŸœŸœŸœ˜1JšœŸœŸœ˜7Jšœ ŸœŸœ˜'JšŸœ˜JšŸœ˜—šŸœ Ÿœ˜Jšœ<˜