<<>> <> <> <> <> <> <> <> <<>> <> <> <> DIRECTORY Ascii USING [Lower], Atom USING [MakeAtomFromRefText, GetPName], IO, LoganBerry, LoganBerryEntry, RefTab USING [Ref, Val, Create, Delete, Fetch, Store], RefText USING [line, InlineAppendChar], Rope, WalnutDefs USING [dontCareDomainVersion, dontCareMsgSetVersion, Error, MsgSet, VersionMismatch, WalnutOpsHandle], WalnutDB, WalnutRegistry USING [MsgGroup, MsgGroupSize], WalnutRegistryPrivate USING [ CheckForMsgGroupRegistration, NotifyForEvent, NotifyForMove, NotifyForMsgEvent, NotifyForMsgGroup], WalnutRoot USING [CommitAndContinue, RootHandle], WalnutSchema; WalnutDBMsgSetsImpl: CEDAR PROGRAM IMPORTS Ascii, Atom, IO, LoganBerry, LoganBerryEntry, RefTab, RefText, Rope, WalnutDefs, WalnutDB, WalnutRegistryPrivate, WalnutRoot EXPORTS WalnutDB, WalnutDefs = BEGIN OPEN WalnutDB, WalnutSchema; <<>> <> ROPE: TYPE = Rope.ROPE; Relship: TYPE = LoganBerry.Entry; WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle; RootHandle: TYPE = WalnutRoot.RootHandle; SchemaHandle: TYPE = WalnutSchema.SchemaHandle; SchemaHandleRec: PUBLIC TYPE = WalnutSchema.SchemaHandleRec; MsgSet: TYPE = WalnutDefs.MsgSet; dontCareDomainVersion: INT = WalnutDefs.dontCareDomainVersion; dontCareMsgSetVersion: INT = WalnutDefs.dontCareMsgSetVersion; CheckReportProc: TYPE = WalnutDB.CheckReportProc; <<>> <> Value: TYPE = LoganBerry.AttributeValue; DBMinusOneInt: Value ¬ LoganBerryEntry.I2V[-1]; DBZeroInt: Value ¬ LoganBerryEntry.I2V[0]; DBTrueBool: Value ¬ LoganBerryEntry.B2V[TRUE]; DBFalseBool: Value ¬ LoganBerryEntry.B2V[FALSE]; nullValue: Value = NIL; ActiveMsgSetName: ROPE = "Active"; DeletedMsgSetName: ROPE = "Deleted"; timeToDelete: CARD16 ¬ 40; IsEntity: TYPE = RECORD [entity: LoganBerry.Entry, exists: BOOL]; <<>> <> MSInfo: TYPE = REF MSInfoObject; MSInfoObject: TYPE = RECORD [canonicalName: ROPE, entity: ROPE, versionRel: Relship]; nameText: REF TEXT = NEW[TEXT[RefText.line]]; LazyEnumerator: TYPE = REF LazyEnumeratorRec; LazyEnumeratorRec: PUBLIC TYPE = RECORD[ opsH: WalnutOpsHandle, msgSet: MsgSet, pos: INT, set: LoganBerry.Cursor, valid: BOOL, checkShow: BOOL ]; <<>> <> MsgSetExists: PUBLIC PROC[opsH: WalnutOpsHandle, name: ROPE, msDomainVersion: INT] RETURNS[existed: BOOL, msVersion: INT] = { <> msI: MSInfo; CheckDomainVersion[opsH, msDomainVersion]; [msI, msVersion] ¬ GetMsgSetAndVersion[opsH, name]; existed ¬ msI # NIL; }; CreateMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, name: ROPE, msDomainVersion: INT] RETURNS [existed: BOOL, msVersion: INT] = { <> msI: MSInfo; mse: ROPE; cName: ROPE; aName: ATOM; sH: SchemaHandle = opsH.schemaHandle; CheckDomainVersion[opsH, msDomainVersion]; [msI, msVersion] ¬ GetMsgSetAndVersion[opsH, name]; IF existed ¬ (msI # NIL) THEN RETURN; cName ¬ Atom.GetPName[aName ¬ CanonicalName[name]]; mse ¬ cName; msI ¬ NEW[MSInfoObject ¬ [canonicalName: cName, entity: mse, versionRel: NIL] ]; msI.versionRel ¬ LIST [ [$Key, Rope.Concat[sH.msBasicInfo, mse]], [sH.msPrintNameIs, name], [sH.msBICount, "0"], [sH.msBIVersion, "1"] ]; LoganBerry.WriteEntry[db: opsH.db, entry: msI.versionRel]; ChangeGlobalMsgSetInfo[opsH, 1]; [] ¬ RefTab.Store[sH.msgSetsTable, aName, msI]; }; NumInMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, name: ROPE] RETURNS[num: INT, msVersion: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; msI: MSInfo; CountOne: PROC[msg, tocEntry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] RETURNS[continue: BOOL¬TRUE] ~ { num ¬ num+1; }; num ¬ 0; [msI, msVersion] ¬ GetMsgSetAndVersion[opsH, name]; <> <> <> IF msI # NIL THEN []¬EnumerateMsgsInSet[opsH, name, TRUE, CountOne]; <> }; EmptyMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, msgSet: MsgSet, report: CheckReportProc] RETURNS[someInDeleted: BOOL ¬ FALSE] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; numIn: INT; msI: MSInfo; IF WalnutDB.EqMsgSets[msgSet.name, DeletedMsgSetName] THEN { WalnutDB.SetOpInProgressPos[opsH, -1]; ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't empty the Deleted MsgSet"]; }; msI ¬ CheckMsgSetEntity[opsH, msgSet]; IF msI = NIL THEN RETURN; numIn ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[msI.versionRel, sH.msBICount]]; someInDeleted ¬ EmptyThisMsgSet[opsH, msI.entity, msgSet.name, report]; IF numIn # 0 THEN WalnutDB.ChangeCountInMsgSet[opsH, msI.entity, -numIn]; }; DestroyMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, msgSet: MsgSet, msDomainVersion: INT, report: CheckReportProc] RETURNS[someInDeleted: BOOL ¬ FALSE] = { <> msI: MSInfo; sH: SchemaHandle = opsH.schemaHandle; IF WalnutDB.EqMsgSets[msgSet.name, DeletedMsgSetName] THEN { WalnutDB.SetOpInProgressPos[opsH, -1]; ERROR WalnutDefs.Error[$db, $InvalidOperation, "Can't destroy the Deleted MsgSet"]; }; CheckDomainVersion[opsH, msDomainVersion]; msI ¬ CheckMsgSetEntity[opsH, msgSet]; IF msI = NIL THEN RETURN; someInDeleted ¬ EmptyThisMsgSet[opsH, msI.entity, msgSet.name, report]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.msBasicInfo, msI.entity]]; msI.versionRel ¬ NIL; ChangeGlobalMsgSetInfo[opsH, -1]; }; VerifyMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, msgSet: MsgSet] RETURNS[exists: BOOL] = { msI: MSInfo ¬ CheckMsgSetEntity[opsH, msgSet]; exists ¬ msI # NIL; }; VerifyDomainVersion: PUBLIC PROC[opsH: WalnutOpsHandle, msDomainVersion: INT] = { CheckDomainVersion[opsH, msDomainVersion] }; ExpungeMsgs: PUBLIC PROC[opsH: WalnutOpsHandle, deletedVersion: INT, report: CheckReportProc] = { <> OPEN WalnutDB; sH: SchemaHandle = opsH.schemaHandle; msI: MSInfo = CheckMsgSetEntity[opsH, [DeletedMsgSetName, deletedVersion]]; BEGIN deletedCount: INT = LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[msI.versionRel, sH.msBICount]]; rs: LoganBerry.Cursor ¬ LoganBerry.GenerateEntries[db: opsH.db, key: sH.cdMsgSet, start: sH.deletedEntity, end: sH.deletedEntity]; 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[opsH, sH.deletedEntity, -deletedCount]; ChangeCountOfMsgs[opsH, -deletedCount]; <> sinceLastCommit ¬ sinceLastCommit + 1 }; BEGIN ENABLE UNWIND => NULL; rel: Relship; rLogInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gLogInfo].entry; bytesDestroyed: INT ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rLogInfo, sH.gBytesInDestroyedMsgs]]; firstDestroyedPos: INT ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rLogInfo, sH.gFirstDestroyedMsgPos]]; UNTIL (rel ¬ LoganBerry.NextEntry[cursor: rs]) = NIL DO me: ROPE = LoganBerryEntry.GetAttr[rel, sH.cdMsg]; textRel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, me]].entry; startPos: INT ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[textRel, sH.mMIEntryStart]]; thisLen: INT ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[textRel, sH.mMITextOffset]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[textRel, sH.mMITextLen]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[textRel, sH.mMIFormatLen]]; bytesDestroyed ¬ bytesDestroyed + thisLen; IF firstDestroyedPos = 0 OR startPos < firstDestroyedPos THEN firstDestroyedPos ¬ startPos; IF doMsgGroup THEN { delArray[numDel] ¬ me; numDel ¬ numDel + 1; }; <> LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, me]]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, me]]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, me]]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, me]]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.toRelation, me]]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.ccRelation, me]]; LoganBerry.DeleteEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.fromRelation, me]]; IF (sinceLastCommit ¬ sinceLastCommit + 1) >= commitFrequency THEN { LoganBerryEntry.SetAttr[rLogInfo, sH.gBytesInDestroyedMsgs, LoganBerryEntry.I2V[bytesDestroyed]]; LoganBerryEntry.SetAttr[rLogInfo, sH.gFirstDestroyedMsgPos, LoganBerryEntry.I2V[firstDestroyedPos]]; LoganBerry.WriteEntry[db: opsH.db, entry: rLogInfo, replace: TRUE]; WalnutRoot.CommitAndContinue[opsH]; sinceLastCommit ¬ 0; IF doMsgGroup THEN { WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray]; delArray ¬ ALL[NIL]; -- clear out the Array numDel ¬ 0; }; }; IF report # NIL THEN count ¬ CheckCount[opsH, count, report]; ENDLOOP; LoganBerry.EndGenerate[cursor: rs]; IF sinceLastCommit # 0 THEN { LoganBerryEntry.SetAttr[rLogInfo, sH.gBytesInDestroyedMsgs, LoganBerryEntry.I2V[bytesDestroyed]]; LoganBerryEntry.SetAttr[rLogInfo, sH.gFirstDestroyedMsgPos, LoganBerryEntry.I2V[firstDestroyedPos]]; LoganBerry.WriteEntry[db: opsH.db, entry: rLogInfo, replace: TRUE]; }; BEGIN -- change the version number of Deleted delRel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.msBasicInfo, sH.deletedEntity]].entry; LoganBerryEntry.SetAttr[delRel, sH.msBIVersion, LoganBerryEntry.I2V[LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[delRel, sH.msBIVersion]] + 1]]; LoganBerry.WriteEntry[db: opsH.db, entry: delRel, replace: TRUE]; <> WalnutRoot.CommitAndContinue[opsH]; IF doMsgGroup AND (numDel # 0) THEN WalnutRegistryPrivate.NotifyForMsgGroup[destroyed, delArray]; WalnutRegistryPrivate.NotifyForEvent[expungeComplete]; END; DestroyOrphanAddrsAndSubjs[opsH, report]; END; END; }; <<>> MsgSetsInfo: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[version, num: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; version ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetsVersion]]; num ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetCount]]; }; <<>> <> AddMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE, from, to: MsgSet] RETURNS[exists: BOOL] = { <> m: IsEntity; msI: MSInfo; sH: SchemaHandle = opsH.schemaHandle; exists ¬ FALSE; IF WalnutDB.EqMsgSets[to.name, DeletedMsgSetName] THEN RETURN; m ¬ GetMsgEntity[opsH, msg]; IF ~m.exists THEN RETURN; [] ¬ CheckMsgSetEntity[opsH, from]; msI ¬ CheckMsgSetEntity[opsH, to]; IF msI = NIL THEN RETURN; -- can't create msgset here { rel: Relship ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, msg]].entry; wasInDeleted: BOOL ¬ FALSE; FOR msL: LIST OF ROPE ¬ LoganBerryEntry.GetAllAttrs[rel, sH.cdMsgSet], msL.rest WHILE msL # NIL DO msgSet: ROPE ¬ msL.first; IF Rope.Equal[msI.entity, msgSet, FALSE] THEN { exists ¬ TRUE; EXIT }; IF Rope.Equal[msgSet, sH.deletedEntity, FALSE] THEN { rel ¬ LoganBerryEntry.RemoveAttr[rel, sH.cdMsgSet, msgSet]; LoganBerry.WriteEntry[db: opsH.db, entry: rel, replace: TRUE]; WalnutDB.ChangeCountInMsgSet[opsH, sH.deletedEntity, -1] } ENDLOOP; }; IF NOT exists THEN AddMsgTo[opsH, msg, to.name]; }; RemoveMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE, from: MsgSet, deletedVersion: INT] RETURNS[deleted: BOOL] = { <> m: IsEntity; msI: MSInfo; rel, thisCDRel: Relship; sH: SchemaHandle = opsH.schemaHandle; deleted ¬ FALSE; IF NOT (m¬ GetMsgEntity[opsH, msg]).exists THEN RETURN; IF (msI¬ CheckMsgSetEntity[opsH, from]) = NIL THEN RETURN; deleted ¬ TRUE; -- We're committed to deleting the message unless we find it in another message set rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, msg]].entry; FOR msL: LIST OF ROPE ¬ LoganBerryEntry.GetAllAttrs[rel, sH.cdMsgSet], msL.rest WHILE msL # NIL DO IF Rope.Equal[msI.entity, msL.first, FALSE] THEN thisCDRel ¬ rel ELSE deleted ¬ FALSE ENDLOOP; IF thisCDRel = NIL THEN { deleted ¬ FALSE; RETURN }; -- Something strange here WalnutDB.ChangeCountInMsgSet[opsH, msI.entity, -1]; IF NOT deleted THEN { thisCDRel ¬ LoganBerryEntry.RemoveAttr[thisCDRel, sH.cdMsgSet, msI.entity]; LoganBerry.WriteEntry[db: opsH.db, entry: thisCDRel, replace: TRUE]; WalnutDB.ChangeCountInMsgSet[opsH, msI.entity, -1]; } ELSE { LoganBerryEntry.SetAttr[thisCDRel, sH.cdMsgSet, sH.deletedEntity]; LoganBerry.WriteEntry[db: opsH.db, entry: thisCDRel, replace: TRUE]; }; }; MoveMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE, from: MsgSet, to: MsgSet] RETURNS [exists: BOOL ¬ FALSE] = { <> m: IsEntity; fromMsI, toMsI: MSInfo; sH: SchemaHandle = opsH.schemaHandle; rel, fromCDRelship, toCDRelship: Relship; IF WalnutDB.EqMsgSets[from.name, to.name] THEN RETURN[TRUE]; -- don't do anything IF NOT (m ¬ GetMsgEntity[opsH, msg]).exists THEN RETURN; IF (fromMsI ¬ CheckMsgSetEntity[opsH, from]) = NIL THEN RETURN; IF (toMsI ¬ CheckMsgSetEntity[opsH, to]) = NIL THEN RETURN; rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, msg]].entry; FOR msL: LIST OF ROPE ¬ LoganBerryEntry.GetAllAttrs[rel, sH.cdMsgSet], msL.rest WHILE msL # NIL DO IF Rope.Equal[fromMsI.entity, msL.first, FALSE] THEN fromCDRelship ¬ rel ELSE IF Rope.Equal[toMsI.entity, msL.first, FALSE] THEN toCDRelship ¬ rel; ENDLOOP; IF fromCDRelship = NIL THEN {exists ¬ FALSE; RETURN}; exists ¬ toCDRelship # NIL; rel ¬ LoganBerryEntry.RemoveAttr[rel, sH.cdMsgSet, fromMsI.entity]; IF NOT exists THEN { rel ¬ LoganBerryEntry.AddAttr[rel, sH.cdMsgSet, toMsI.entity]; }; LoganBerry.WriteEntry[db: opsH.db, entry: rel, replace: TRUE]; WalnutDB.ChangeCountInMsgSet[opsH, fromMsI.entity, -1]; IF NOT exists THEN WalnutDB.ChangeCountInMsgSet[opsH, toMsI.entity, 1]; }; <<>> <> MsgsEnumeration: PUBLIC PROC [opsH: WalnutOpsHandle, alphaOrder: BOOL] RETURNS [mL: LIST OF ROPE] = { ok: BOOL ¬ FALSE; sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; MEnum: PROC = { last: LIST OF ROPE; enum: LoganBerry.Cursor ¬ IF alphaOrder THEN LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: Rope.Concat[sH.mMsgInfo, "\000"], end: Rope.Concat[sH.mMsgInfo, "\177"]] ELSE LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: sH.mMsgInfo, end: Rope.Concat[sH.mMsgInfo, "\255"]]; mL ¬ NIL; BEGIN ENABLE UNWIND => GOTO end; e: LoganBerry.Entry; msg: ROPE; count: INT ¬ 0; FOR e ¬ LoganBerry.NextEntry[cursor: enum], LoganBerry.NextEntry[cursor: enum] UNTIL e = NIL DO msg ¬ LoganBerryEntry.GetAttr[e, sH.mMIOf]; IF mL = NIL THEN mL ¬ last ¬ CONS[msg, NIL] ELSE { last.rest ¬ CONS[msg, NIL]; last ¬ last.rest}; count ¬ CheckForCommit[opsH, count]; ENDLOOP; ok ¬ TRUE; EXITS end => NULL; END; LoganBerry.EndGenerate[cursor: enum ! LoganBerry.Error => CONTINUE]; }; MEnum[]; IF ok THEN RETURN ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During Msgs enumeration"]; }; MsgsInSetEnumeration: PUBLIC PROC[opsH: WalnutOpsHandle, name: ROPE, fromStart: BOOL] RETURNS [mL: LIST OF ROPE, msVersion: INT ¬ -1] = { <> ok: BOOL ¬ FALSE; sH: SchemaHandle = opsH.schemaHandle; MEnum: PROC = { msI: MSInfo; enum: LoganBerry.Cursor; checkShow: BOOL; count: INT ¬ 0; lastInList: LIST OF ROPE ¬ NIL; mL ¬ NIL; [msI, msVersion] ¬ GetMsgSetAndVersion[opsH, name]; IF msI = NIL THEN {ok ¬ TRUE; RETURN}; checkShow ¬ name.Equal["Active", FALSE]; enum ¬ LoganBerry.GenerateEntries[db: opsH.db, key: sH.cdMsgSet, start: msI.entity, end: msI.entity]; BEGIN ENABLE UNWIND => GOTO end; DO rel: Relship = LoganBerry.NextEntry[cursor: enum]; me: ROPE; IF rel = NIL THEN EXIT; me ¬ LoganBerryEntry.GetAttr[rel, sH.cdMsg]; IF checkShow THEN { sRel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, me]].entry; IF NOT Rope.Equal[LoganBerryEntry.GetAttr[sRel, sH.mShowIs], "NULL"] THEN LOOP; -- mShowIs is unaccepted }; IF fromStart THEN { thisL: LIST OF ROPE = CONS[me, NIL]; IF mL = NIL THEN mL ¬ lastInList ¬ thisL ELSE { lastInList.rest ¬ thisL; lastInList ¬ lastInList.rest; }; } ELSE mL ¬ CONS[me, mL]; count ¬ CheckForCommit[opsH, count]; ENDLOOP; ok ¬ TRUE; EXITS end => NULL; END; LoganBerry.EndGenerate[cursor: enum ! LoganBerry.Error => CONTINUE] }; MEnum[]; IF ok THEN RETURN ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"]; }; MsgSetsNames: PUBLIC PROC[opsH: WalnutOpsHandle, alphaOrder: BOOL] RETURNS[msL: LIST OF ROPE, msDomainVersion: INT ¬ -1] = { ok: BOOL ¬ FALSE; sH: SchemaHandle = opsH.schemaHandle; MSEnum: PROC = { last: LIST OF ROPE; rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; enum: LoganBerry.Cursor ¬ IF alphaOrder THEN LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: Rope.Concat[sH.msBasicInfo, "\000"], end: Rope.Concat[sH.msBasicInfo, "\177"]] ELSE LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: sH.msBasicInfo, end: Rope.Concat[sH.msBasicInfo, "\255"]]; msDomainVersion ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetsVersion]]; msL ¬ NIL; BEGIN ENABLE UNWIND => GOTO end; FOR e: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: enum], LoganBerry.NextEntry[cursor: enum] UNTIL e = NIL DO thisName: ROPE = LoganBerryEntry.GetAttr[e, sH.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; LoganBerry.EndGenerate[cursor: enum ! LoganBerry.Error => CONTINUE] }; MSEnum[]; IF ok THEN RETURN ELSE WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"]; }; EnumerateMsgSets: PUBLIC PROC[opsH: WalnutOpsHandle, alphaOrder: BOOL, proc: PROC[msgSet: MsgSet] RETURNS[continue: BOOL] ] RETURNS[msDomainVersion: INT ¬ -1] = { ok: BOOL ¬ FALSE; sH: SchemaHandle = opsH.schemaHandle; MSEnum: PROC = { enum: LoganBerry.Cursor ¬ IF alphaOrder THEN LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: Rope.Concat[sH.msBasicInfo, "\000"], end: Rope.Concat[sH.msBasicInfo, "\177"]] ELSE LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: sH.msBasicInfo, end: Rope.Concat[sH.msBasicInfo, "\255"]]; rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; msDomainVersion ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetsVersion]]; BEGIN ENABLE UNWIND => GOTO end; FOR e: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: enum], LoganBerry.NextEntry[cursor: enum] UNTIL e = NIL DO msgSet: MsgSet ¬ [LoganBerryEntry.GetAttr[e, sH.msPrintNameIs], LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[e, sH.msBIVersion]]]; IF NOT proc[msgSet] THEN EXIT; ENDLOOP; ok ¬ TRUE; EXITS end => NULL; END; LoganBerry.EndGenerate[cursor: enum ! LoganBerry.Error => CONTINUE] }; MSEnum[]; IF ok THEN RETURN ELSE ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"] }; EnumerateMsgsInSet: PUBLIC PROC [ opsH: WalnutOpsHandle, name: ROPE, fromStart: BOOL ¬ TRUE, proc: PROC[msg, tocEntry: ROPE, hasBeenRead: BOOL, startOfSubject: INT] RETURNS[continue: BOOL] ] RETURNS [msVersion: INT ¬ -1] = { <> ok: BOOL ¬ FALSE; sH: SchemaHandle = opsH.schemaHandle; MEnum: PROC = { msI: MSInfo; enum: LoganBerry.Cursor; checkShow: BOOL; count: INT ¬ 0; [msI, msVersion] ¬ GetMsgSetAndVersion[opsH, name]; IF msI = NIL THEN {ok¬ TRUE; RETURN}; checkShow ¬ name.Equal["Active", FALSE]; enum ¬ LoganBerry.GenerateEntries[db: opsH.db, key: sH.cdMsgSet, start: msI.entity, end: msI.entity]; BEGIN ENABLE UNWIND => GOTO end; DO rel: Relship = LoganBerry.NextEntry[cursor: enum]; msg, tocEntry: ROPE; hasBeenRead: BOOL; startOfSubject: INT; IF rel = NIL THEN EXIT; msg ¬ LoganBerryEntry.GetAttr[rel, sH.cdMsg]; IF checkShow THEN { sRel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, msg]].entry; IF NOT Rope.Equal[LoganBerryEntry.GetAttr[sRel, sH.mShowIs], "NULL"] THEN LOOP; -- mShowIs is unaccepted }; [hasBeenRead, tocEntry, startOfSubject] ¬ WalnutDB.GetMsgDisplayInfo[opsH, msg]; IF NOT proc[msg, tocEntry, hasBeenRead, startOfSubject] THEN EXIT; count ¬ CheckForCommit[opsH, count]; ENDLOOP; ok¬ TRUE; EXITS end => NULL; END; LoganBerry.EndGenerate[cursor: enum ! LoganBerry.Error => CONTINUE] }; MEnum[]; IF ok THEN RETURN ELSE ERROR WalnutDefs.Error[$db, $DatabaseInaccessible, "During MsgSets enumeration"] }; EnumerateMsgsInMsgSet: PUBLIC PROC[opsH: WalnutOpsHandle, msgSet: MsgSet] RETURNS[lazyEnum: WalnutDB.LazyEnumerator] = { sH: SchemaHandle = opsH.schemaHandle; lazyEnum ¬ NEW[LazyEnumeratorRec]; lazyEnum.opsH ¬ opsH; lazyEnum.msgSet ¬ msgSet; lazyEnum.pos ¬ 0; lazyEnum.checkShow ¬ Rope.Equal[msgSet.name, ActiveMsgSetName]; lazyEnum.set ¬ LoganBerry.GenerateEntries[db: opsH.db, key: sH.cdMsgSet, start: msgSet.name, end: msgSet.name]; lazyEnum.valid ¬ TRUE; }; NextMsgInMsgSet: PUBLIC PROC[lazyEnum: WalnutDB.LazyEnumerator] RETURNS[msgID: ROPE, valid: BOOL ¬ FALSE] = { howManyIterations: INT ¬ 0; sH: SchemaHandle = lazyEnum.opsH.schemaHandle; GetNextMsg: PROC = { me: ROPE; IF lazyEnum.valid = FALSE THEN { valid ¬ FALSE; RETURN }; DO rel: Relship = LoganBerry.NextEntry[cursor: lazyEnum.set]; howManyIterations ¬ howManyIterations+1; IF rel = NIL THEN { LoganBerry.EndGenerate[cursor: lazyEnum.set]; lazyEnum.valid ¬ FALSE; EXIT }; me ¬ LoganBerryEntry.GetAttr[rel, sH.cdMsg]; IF lazyEnum.checkShow THEN { sRel: Relship = LoganBerry.ReadEntry[db: lazyEnum.opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, me]].entry; IF NOT Rope.Equal[LoganBerryEntry.GetAttr[sRel, sH.mShowIs], "NULL"] THEN LOOP ELSE EXIT } ELSE EXIT ENDLOOP; IF me # NIL THEN msgID ¬ me; valid ¬ TRUE }; ResetToStart: PROC = { lazyEnum.set ¬ LoganBerry.GenerateEntries[db: lazyEnum.opsH.db, key: sH.cdMsgSet, start: lazyEnum.msgSet.name, end: lazyEnum.msgSet.name]; FOR i: INT IN [0..lazyEnum.pos) DO [] ¬ LoganBerry.NextEntry[cursor: lazyEnum.set] ENDLOOP; howManyIterations ¬ 0 }; IF NOT VerifyMsgSet[lazyEnum.opsH, lazyEnum.msgSet] THEN RETURN[NIL, FALSE]; FOR tryRestart: BOOL ¬ TRUE, FALSE WHILE tryRestart DO failed: BOOL ¬ FALSE; GetNextMsg[ ! LoganBerry.Error => { failed ¬ TRUE; CONTINUE } ]; IF NOT failed THEN { lazyEnum.pos ¬ lazyEnum.pos+howManyIterations; RETURN }; ResetToStart[] ENDLOOP }; EnumerateUnacceptedMsgs: PUBLIC PROC[opsH: WalnutOpsHandle, activeVersion: INT, proc: PROC[msg, tocEntry: ROPE, startOfSubject: INT] ] = { ok: BOOL ¬ FALSE; sH: SchemaHandle = opsH.schemaHandle; Eum: PROC = { enum: LoganBerry.Cursor; count: INT ¬ 0; [] ¬ CheckMsgSetEntity[opsH, [ActiveMsgSetName, activeVersion]]; enum ¬ LoganBerry.GenerateEntries[db: opsH.db, key: sH.mShowIs, start: sH.unacceptedEntity, end: sH.unacceptedEntity]; BEGIN ENABLE UNWIND => GOTO end; showRel: Relship; msg: ROPE; UNTIL (showRel ¬ LoganBerry.NextEntry[cursor: enum]) = NIL DO tocEntry: ROPE; startOfSubject: INT; me: ROPE = LoganBerryEntry.GetAttr[showRel, sH.mInfoOf]; msg ¬ me; [ , tocEntry, startOfSubject] ¬ WalnutDB.GetMsgDisplayInfo[opsH, me]; proc[msg, tocEntry, startOfSubject]; count ¬ CheckForCommit[opsH, count]; ENDLOOP; ok¬ TRUE; EXITS end => NULL; END; LoganBerry.EndGenerate[cursor: enum ! LoganBerry.Error => CONTINUE] }; Eum[]; IF ok THEN RETURN; ERROR WalnutDefs.Error[$db, $DatabaseInaccessable, "During Get New Mail"]; }; AcceptNewMail: PUBLIC PROC[opsH: WalnutOpsHandle, pos, activeVersion: INT] = { <> rs: LoganBerry.Cursor; es: LoganBerry.Cursor; commitFrequency: CARDINAL = WalnutRegistry.MsgGroupSize; accArray: WalnutRegistry.MsgGroup; numAcc: CARDINAL ¬ 0; sinceLastCommit: INT ¬ 0; sH: SchemaHandle = opsH.schemaHandle; rNewMailInfo, rLogInfo: Relship; activeRel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.msBasicInfo, sH.activeEntity]].entry; doMsgGroup: BOOL ¬ WalnutRegistryPrivate.CheckForMsgGroupRegistration[]; [] ¬ CheckMsgSetEntity[opsH, [ActiveMsgSetName, activeVersion]]; IF doMsgGroup THEN accArray ¬ ALL[NIL]; BEGIN ENABLE UNWIND => NULL; showRel: Relship; rs ¬ LoganBerry.GenerateEntries[db: opsH.db, key: sH.mShowIs, start: sH.unacceptedEntity, end: sH.unacceptedEntity]; UNTIL (showRel ¬ LoganBerry.NextEntry[cursor: rs]) = NIL DO me: ROPE = LoganBerryEntry.GetAttr[showRel, sH.mInfoOf]; LoganBerryEntry.SetAttr[showRel, sH.mShowIs, "NULL"]; LoganBerry.WriteEntry[db: opsH.db, entry: showRel, replace: TRUE]; IF doMsgGroup THEN { accArray[numAcc] ¬ me; numAcc ¬ numAcc + 1; }; IF (sinceLastCommit ¬ sinceLastCommit + 1) >= commitFrequency THEN { newCount: INT = LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[activeRel, sH.msBICount]] + sinceLastCommit; LoganBerryEntry.SetAttr[activeRel, sH.msBICount, LoganBerryEntry.I2V[newCount]]; LoganBerry.WriteEntry[db: opsH.db, entry: activeRel, replace: TRUE]; WalnutRoot.CommitAndContinue[opsH]; sinceLastCommit ¬ 0; IF doMsgGroup THEN { WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray]; accArray ¬ ALL[NIL]; numAcc ¬ 0; }; }; ENDLOOP; LoganBerry.EndGenerate[cursor: rs]; END; BEGIN ENABLE UNWIND => NULL; es ¬ LoganBerry.GenerateEntries[db: opsH.db, key: $Key, start: Rope.Concat[sH.sBasicInfo, "\000"], end: Rope.Concat[sH.sBasicInfo, "\177"]]; FOR se: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: es], LoganBerry.NextEntry[cursor: es] UNTIL se = NIL DO rel: Relship = se; LoganBerryEntry.SetAttr[rel, sH.sBINum, DBZeroInt]; LoganBerry.WriteEntry[db: opsH.db, entry: rel, replace: TRUE]; ENDLOOP; LoganBerry.EndGenerate[cursor: es]; END; IF sinceLastCommit # 0 THEN LoganBerryEntry.SetAttr[activeRel, sH.msBICount, LoganBerryEntry.I2V[LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[activeRel, sH.msBICount]] + sinceLastCommit]]; LoganBerryEntry.SetAttr[activeRel, sH.msBIVersion, LoganBerryEntry.I2V[LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[activeRel, sH.msBIVersion]] + 1]]; LoganBerry.WriteEntry[db: opsH.db, entry: activeRel, replace: TRUE]; rNewMailInfo ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gNewMailInfo].entry; LoganBerryEntry.SetAttr[rNewMailInfo, sH.gAcceptNewMailLogPos, LoganBerryEntry.I2V[pos]]; LoganBerry.WriteEntry[db: opsH.db, entry: rNewMailInfo, replace: TRUE]; rLogInfo ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gLogInfo].entry; LoganBerryEntry.SetAttr[rLogInfo, sH.gOpInProgressPos, DBMinusOneInt]; LoganBerry.WriteEntry[db: opsH.db, entry: rLogInfo, replace: TRUE]; WalnutRoot.CommitAndContinue[opsH]; IF doMsgGroup AND (numAcc # 0) THEN WalnutRegistryPrivate.NotifyForMsgGroup[added, accArray]; }; <<>> <> mismatchReport: ROPE = "Msgset: %g: version is %g, version expected is: %g"; GetMsgSetBasicInfoRel: PROC[opsH: WalnutOpsHandle, ms: ROPE] RETURNS[rel: Relship] = { RETURN[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[opsH.schemaHandle.msBasicInfo, ms]].entry] }; GetMsgEntity: PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[e: IsEntity] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; e.entity ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry; e.exists ¬ (e.entity # NIL); }; GetMsgSetAndVersion: PROC[opsH: WalnutOpsHandle, name: ROPE] RETURNS[msI: MSInfo, version: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; msI ¬ GetMsgSetEntity[opsH, name]; IF msI # NIL THEN version ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[msI.versionRel, sH.msBIVersion]] ELSE version¬ -1; }; GetMsgSetEntity: PROC[opsH: WalnutOpsHandle, name: ROPE] RETURNS[msInfo: MSInfo] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; aName: ATOM = CanonicalName[name]; -- all lower case cName: ROPE; found: BOOL; val: RefTab.Val; mse: ROPE; IF opsH.schemaHandle.msgSetsTable = NIL THEN { rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; numMsgSets: INT ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetCount]]; opsH.schemaHandle.msgSetsTable ¬ RefTab.Create[MAX[numMsgSets*2+1, 16]]; }; [found, val] ¬ RefTab.Fetch[opsH.schemaHandle.msgSetsTable, aName]; IF found THEN { msInfo ¬ NARROW[val]; IF msInfo.versionRel # NIL THEN RETURN; msInfo.versionRel ¬ GetMsgSetBasicInfoRel[opsH, msInfo.entity]; IF msInfo.versionRel = NIL THEN { -- no longer exists [] ¬ RefTab.Delete[opsH.schemaHandle.msgSetsTable, aName]; RETURN[NIL] }; RETURN; }; mse ¬ cName ¬ Atom.GetPName[aName]; msInfo ¬ NEW[MSInfoObject ¬ [canonicalName: cName, entity: mse]]; msInfo.versionRel ¬ GetMsgSetBasicInfoRel[opsH, mse]; IF msInfo.versionRel = NIL THEN RETURN[NIL]; -- return NIL IF msgSet doesn't exist [] ¬ RefTab.Store[opsH.schemaHandle.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[opsH: WalnutOpsHandle, version: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; is: INT; rVersionInfo: Relship; IF version = dontCareDomainVersion THEN RETURN; rVersionInfo ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; IF version # (is ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetsVersion]]) THEN { rLogInfo: Relship ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gLogInfo].entry; LoganBerryEntry.SetAttr[rLogInfo, sH.gOpInProgressPos, DBMinusOneInt]; LoganBerry.WriteEntry[db: opsH.db, entry: rLogInfo, replace: TRUE]; ERROR WalnutDefs.VersionMismatch[ IO.PutFR["Domain version is %g, version expected is: %g", [integer[is]], [integer[version]]] ]; }; }; CheckMsgSetVersion: PROC[opsH: WalnutOpsHandle, msgSet: MsgSet] RETURNS[msI: MSInfo, version: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; [msI, version] ¬ GetMsgSetAndVersion[opsH, msgSet.name]; <> <> <> <> <> <> <> <> <> <> <<[rope[msgSet.name]], [integer[version]], [integer[msgSet.version]]] ];>> <> <> }; CheckMsgSetEntity: PROC[opsH: WalnutOpsHandle, msgSet: MsgSet] RETURNS[msI: MSInfo] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; msI ¬ GetMsgSetEntity[opsH, msgSet.name]; <> <> <> <> <> <> <> <> <> <> <> <<[rope[msgSet.name]], [integer[is]], [integer[msgSet.version]]] ];>> <> <> }; commitFreq: INT ¬ 500; CheckForCommit: PROC[opsH: WalnutOpsHandle, count: INT] RETURNS[new: INT] = { IF ( (new ¬ count + 1) MOD commitFreq ) = 0 THEN WalnutRoot.CommitAndContinue[opsH]; }; EmptyThisMsgSet: PROC[opsH: WalnutOpsHandle, mse: ROPE, name: ROPE, report: CheckReportProc] RETURNS[someInDeleted: BOOL] = { sH: SchemaHandle = opsH.schemaHandle; rs: LoganBerry.Cursor = LoganBerry.GenerateEntries[db: opsH.db, key: sH.cdMsgSet, start: mse, end: mse]; BEGIN ENABLE UNWIND => NULL; rel: Relship; de: ROPE = sH.deletedEntity; 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 (rel ¬ LoganBerry.NextEntry[cursor: rs]) = NIL DO me: ROPE = LoganBerryEntry.GetAttr[rel, sH.cdMsg]; deleted: BOOL; rel ¬ LoganBerryEntry.RemoveAttr[rel, sH.cdMsgSet, mse]; deleted ¬ LoganBerryEntry.GetAttr[rel, sH.cdMsgSet] = NIL; IF deleted THEN { rel ¬ LoganBerryEntry.AddAttr[rel, sH.cdMsgSet, sH.deletedEntity]; delArray[numDel] ¬ me; numDel ¬ numDel + 1; } ELSE { remArray[numRem] ¬ me; numRem ¬ numRem + 1; }; LoganBerry.WriteEntry[db: opsH.db, entry: rel, replace: TRUE]; someInDeleted¬ someInDeleted OR deleted; IF (sinceLastCommit ¬ sinceLastCommit + 1) >= commitFrequency THEN { WalnutRoot.CommitAndContinue[opsH]; 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[opsH, count, report]; ENDLOOP; LoganBerry.EndGenerate[cursor: rs]; IF sinceLastCommit # 0 THEN { WalnutRoot.CommitAndContinue[opsH]; 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[opsH: WalnutOpsHandle, delta: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; numNow: INT ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetCount]]; newV: INT = LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetsVersion]] + 1; LoganBerryEntry.SetAttr[rVersionInfo, sH.gMsgSetsVersion, LoganBerryEntry.I2V[newV]]; LoganBerryEntry.SetAttr[rVersionInfo, sH.gMsgSetCount, LoganBerryEntry.I2V[numNow + delta]]; LoganBerry.WriteEntry[db: opsH.db, entry: rVersionInfo, replace: TRUE]; }; AddMsgTo: PROC[opsH: WalnutOpsHandle, me: ROPE, mse: ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rel: Relship ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, me]].entry; rel ¬ LoganBerryEntry.AddAttr[rel, sH.cdMsgSet, mse]; LoganBerry.WriteEntry[db: opsH.db, entry: rel, replace: TRUE]; WalnutDB.ChangeCountInMsgSet[opsH, mse, 1] }; CheckCount: PROC[opsH: WalnutOpsHandle, count: INT, report: CheckReportProc] RETURNS[c: INT] = { IF count = 0 THEN report[opsH, " "]; -- put out spaces first IF (c ¬ count + 1) MOD 10 # 0 THEN RETURN; IF c MOD 100 = 0 THEN report[opsH, "! "] ELSE report[opsH, "&"]; }; DestroyOrphanAddrsAndSubjs: PROC[opsH: WalnutOpsHandle, report: CheckReportProc] = { <> }; END. <<>>