<> <> <> <> <> <> DIRECTORY Ascii, Atom, BasicTime USING [GMT, Now, nullGMT, Period, Update], Booting, Convert, DB, DBCommon, DBDefs, FingerLog, FingerOps, FingerSimpleOps, FS USING[StreamOpen, Error], IO, LupineRuntime, Process USING [Detach, Ticks, SecondsToTicks, Pause], ThisMachine USING[Name], RefText USING[New, AppendChar, TrustTextAsRope], RefTab, RemoteFingerOps, Rope, RemoteFingerOpsRpcControl, TextFind USING [CreateFromRope, Finder, SearchRope], UserProfile USING [Token]; FingerOpsImpl: CEDAR MONITOR IMPORTS Ascii, Atom, BasicTime, Booting, Convert, DB, FingerSimpleOps, FS, IO, LupineRuntime, Process, ThisMachine, RefTab, RefText, Rope, RemoteFingerOpsRpcControl, TextFind, UserProfile EXPORTS FingerOps, RemoteFingerOps = BEGIN OPEN FingerOps, FingerLog; <> machineName: Rope.ROPE = ThisMachine.Name[$Pup]; <> fingerSegmentRope: Rope.ROPE = UserProfile.Token[key: "Finger.segment", default: "[Luther.Alpine]7.0>Finger.segment"]; fingerSegment: DBDefs.Segment = $Finger; fingerSegmentNumber: NAT = 260B; fingerTransaction: DBCommon.TransactionHandle _ NIL; <> FingerError: PUBLIC ERROR[reason: FingerOps.Reason] ~ CODE; activity: BOOL _ TRUE; ticksToWait: Process.Ticks _ Process.SecondsToTicks[10]; SomethingHappened: CONDITION; <> person, machine: DBDefs.Domain; machineType: DB.TypeSpec = [ indirect[ machineTypeProc ] ]; machineTypeProc: PROC[] RETURNS[type: DB.TypeCode] = { type _ DB.TypeForDomain[machine] }; personType: DB.TypeSpec = [ indirect[ personTypeProc ] ]; personTypeProc: PROC[] RETURNS[type: DB.TypeCode] = { type _ DB.TypeForDomain[person] }; event: DB.Domain; eventType: DB.TypeSpec = [ indirect[ eventTypeProc ] ]; eventTypeProc: PROC[] RETURNS[type: DB.TypeCode] = { type _ DB.TypeForDomain[event] }; login: DB.Entity; logout: DB.Entity; unknown: DB.Entity; <> <> userRelation: DBDefs.Relation; machineIs: CARDINAL = 0; -- a key of the relation userIs: CARDINAL = 1; -- the person performing the last event lastEventIs: CARDINAL = 2; -- whether the last event was login or logout lastEventTimeIs: CARDINAL = 3; -- when the last event occurred userRelationType: DB.FieldSpec = DB.L2FS[LIST[[name: "machine", type: machineType], [name: "user", type: personType], [name: "lastEvent", type: eventType], [name: "lastEventTime", type: DB.Time]]]; timeIndex: DB.Index; -- an index is maintained on time of last event <> lastChangedProp: DB.Relation; lastChangedTime: CARDINAL = 1; <> machinePropType: DB.FieldSpec = DB.L2FS[LIST[[name: "of", type: machineType], [name: "is", type: DB.String]]]; userPropType: DB.FieldSpec = DB.L2FS[LIST[[name: "of", type: personType], [name: "is", type: DB.String]]]; versionType: DB.FieldSpec = DB.L2FS[LIST[[name: "stamp", type: DB.Integer]]]; lastChangedType: DB.FieldSpec = DB.L2FS[LIST[[name: "of", type: personType], [name: "is", type: DB.Time]]]; machinePropVersion, userPropVersion: INT _ -1; <> machinePropRelation: DBDefs.Relation; machinePropAttr: CARDINAL = 0; machinePropVersionRelship: DBDefs.Relship; -- the single relship of this relation <> userPropRelation: DBDefs.Relation; userPropAttr: CARDINAL = 0; userPropVersionRelship: DBDefs.Relship; -- the single relship of this relation <<>> <> userPropNames: DB.Domain; machinePropNames: DB.Domain; <> PropertyTable: TYPE = RECORD[names: DB.Domain _ NIL, table: RefTab.Ref]; <> machineProps: PropertyTable _ [table: RefTab.Create[]]; userProps: PropertyTable _ [table: RefTab.Create[]]; DevoPattern: PROC [value: Rope.ROPE] RETURNS [ItsADevoPattern:BOOL] ~ { <<... returns true iff value is of the form *.>> lastPos: INT _ (Rope.Length[base: value] - 1); <<>> RETURN [Rope.Find[s1: value, s2: "*", pos1: 0, case: FALSE] = lastPos] }; -- DevoPattern RopeBracket: PROC [base: Rope.ROPE] RETURNS [low, high: Rope.ROPE] ~ { <<... if base is of the form * the replace with the first possible match () and the first mismatch (replace the last character of with the next character).>> lastPos: INT _ (Rope.Length[base: base] - 1); starPos: INT _ Rope.Find[s1: base, s2: "*", pos1: 0, case: FALSE]; IF Rope.Equal[s1: base, s2: "*"] THEN { low _ high _ NIL; RETURN }; IF starPos = lastPos THEN { lastLetter: CHAR _ Rope.Fetch[base: base, index: lastPos - 1]; low _ Rope.Substr[base: base, start: 0, len: lastPos]; high _ Rope.Concat[base: Rope.Substr[base: low, start: 0, len: lastPos - 1], rest: Rope.FromChar[c: lastLetter.SUCC]] } ELSE IF starPos = -1 THEN low _ high _ base ELSE low _ high _ NIL; }; -- RopeBracket WatchDBActivity: PROC[] = { WHILE TRUE DO Process.Pause[ticksToWait]; CheckConnection[] ENDLOOP }; CheckConnection: ENTRY PROC[] = { ENABLE UNWIND => NULL; IF NOT activity THEN { CloseTransaction[]; WAIT SomethingHappened }; activity _ FALSE }; CloseTransaction: INTERNAL PROC [] = { caughtAborted: BOOL _ FALSE; IF fingerTransaction = NIL THEN RETURN; DB.CloseTransaction[fingerTransaction ! DB.Aborted => { caughtAborted _ TRUE; CONTINUE }; DB.Error, DB.Failure => CONTINUE ]; IF caughtAborted THEN DB.AbortTransaction[fingerTransaction]; fingerTransaction _ NIL }; OpenTransaction: INTERNAL PROC [] = { schemaInvalid: BOOL; IF fingerTransaction = NIL THEN { [fingerTransaction, schemaInvalid] _ DB.OpenTransaction[$Finger]; IF schemaInvalid THEN ResetSchema[] } }; CarefullyOpenTransaction: INTERNAL PROC [] ~ { ENABLE UNWIND => NULL; BEGIN ENABLE BEGIN DB.Error => GOTO Error; DB.Failure => GOTO Failure; END; aborted: BOOL _ FALSE; IF NOT activity THEN {activity _ TRUE; NOTIFY SomethingHappened}; BEGIN ENABLE DB.Aborted => { aborted _ TRUE; CONTINUE }; OpenTransaction[]; END; IF NOT aborted THEN RETURN; -- no aborted occurred BEGIN ENABLE DB.Aborted => GOTO Aborted; AbortTransaction[]; -- now try again OpenTransaction[]; END EXITS Error => { CloseTransaction[]; ERROR FingerError[Error] }; Failure => ERROR FingerError[Failure]; Aborted => { CloseTransaction[]; ERROR FingerError[Aborted] } END; }; AbortTransaction: INTERNAL PROC [] = { IF fingerTransaction = NIL THEN RETURN; DB.AbortTransaction[fingerTransaction ! DB.Error, DB.Failure, DB.Aborted => CONTINUE ]; fingerTransaction _ NIL }; InitFingerDB: ENTRY PROC = BEGIN ENABLE UNWIND => NULL; DB.Initialize[nCachePages: 256]; DB.DeclareSegment[filePath: fingerSegmentRope, segment: fingerSegment, number: fingerSegmentNumber, nPagesInitial: 256, nPagesPerExtent: 256]; CarefullyOpenTransaction[]; END; ResetSchema: INTERNAL PROC[] ~ { <> person _ DB.DeclareDomain["person", fingerSegment]; machine _ DB.DeclareDomain["machine", fingerSegment]; event _ DB.DeclareDomain["event", fingerSegment]; login _ DB.CopyEntity[DB.DeclareEntity[event, "login"]]; logout _ DB.CopyEntity[DB.DeclareEntity[event, "logout"]]; unknown _ DB.CopyEntity[DB.DeclareEntity[event, "unknown"]]; <<>> <> machinePropRelation _ DB.DeclareRelation[name: "MachineVersion", segment: fingerSegment, fields: versionType]; <> machinePropVersionRelship _ DB.FirstRelship[machinePropRelation]; IF machinePropVersionRelship = NIL THEN machinePropVersionRelship _ DB.CopyRelship[DB.CreateRelship[r: machinePropRelation, init: DB.L2VS[LIST[[integer[0]]]]]] ELSE machinePropVersionRelship _ DB.CopyRelship[machinePropVersionRelship]; userPropRelation _ DB.DeclareRelation[name: "UserVersion", segment: fingerSegment, fields: versionType]; userPropVersionRelship _ DB.FirstRelship[userPropRelation]; IF userPropVersionRelship = NIL THEN userPropVersionRelship _ DB.CopyRelship[DB.CreateRelship[r: userPropRelation, init: DB.L2VS[LIST[[integer[0]]]]]] ELSE userPropVersionRelship _ DB.CopyRelship[userPropVersionRelship]; lastChangedProp _ DB.DeclareProperty[name: "lastChanged", segment: fingerSegment, fields: lastChangedType]; <<>> <> machinePropNames _ DB.DeclareDomain[name: "MachineProps", segment: fingerSegment]; machineProps.names _ machinePropNames; userPropNames _ DB.DeclareDomain[name: "UserProps", segment: fingerSegment]; userProps.names _ userPropNames; <<>> <> userRelation _ DB.DeclareProperty[name: "UserInfo", segment: fingerSegment, fields: userRelationType]; <> timeIndex _ DB.DeclareIndex[userRelation, DB.L2F[LIST[lastEventIs, lastEventTimeIs]]]; <<>> <> BEGIN DBMachineStamp: INT = DB.V2I[DB.GetF[machinePropVersionRelship, machinePropAttr]]; DBUserStamp: INT = DB.V2I[DB.GetF[userPropVersionRelship, userPropAttr]]; IF DBMachineStamp # machinePropVersion THEN { machineProps.table _ RefTab.Create[]; <> EnumerateProperties[machineProps]; machinePropVersion _ DBMachineStamp }; IF DBUserStamp # userPropVersion THEN { userProps.table _ RefTab.Create[]; EnumerateProperties[userProps]; userPropVersion _ DBUserStamp }; END; SetAttributes[machineProps, machine]; SetAttributes[userProps, person] }; EnumerateProperties: INTERNAL PROC[propTable: PropertyTable] = { attrSet: DB.EntitySet = DB.DomainSubset[d: propTable.names]; FOR prop: DB.Entity _ DB.NextEntity[attrSet], DB.NextEntity[attrSet] UNTIL prop = NIL DO propName: Rope.ROPE = DB.EntityInfo[prop].name; [] _ RefTab.Store[x: propTable.table, key: Atom.MakeAtom[propName], val: NIL] ENDLOOP}; SetAttributes: INTERNAL PROC[propTable: PropertyTable, domain: DB.Domain] = { <> type: DB.TypeCode = DB.TypeForDomain[domain]; EachProp: RefTab.EachPairAction = { name: Rope.ROPE = Atom.GetPName[NARROW[key]]; fullName: Rope.ROPE = Rope.Concat[name, DB.DomainInfo[domain].name]; propRelation: DB.Relation = DB.DeclareProperty[name: fullName, segment: fingerSegment, fields: DB.L2FS[LIST[[name: "of", type: [direct[type]]], [name: "is", type: DB.String]]]]; [] _ RefTab.Store[ propTable.table, key, propRelation ]; quit _ FALSE }; [] _ RefTab.Pairs[propTable.table, EachProp] }; ListUserProps: PUBLIC PROC[] RETURNS[propList: LIST OF ATOM] = { <> Do: ENTRY PROC[] ~ { ENABLE UNWIND => NULL; EachProp: RefTab.EachPairAction = { propList _ CONS[NARROW[key], propList]; quit _ FALSE }; [] _ RefTab.Pairs[userProps.table, EachProp]; }; FingerSimpleOps.AttemptToPlayLog[]; Do[]; }; ListMachineProps: PUBLIC PROC[] RETURNS[propList: LIST OF ATOM] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; EachProp: RefTab.EachPairAction = { propList _ CONS[NARROW[key], propList]; quit _ FALSE }; [] _ RefTab.Pairs[machineProps.table, EachProp]; }; FingerSimpleOps.AttemptToPlayLog[]; Do[]; }; AddUserProp: PUBLIC PROC[name: Rope.ROPE] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; FingerSimpleOps.Log[ logEntry: NEW[LogEntryObject _ [AddUserProp [name: name, version: userPropVersion]]] ]; }; Do[]; FingerSimpleOps.AttemptToPlayLog[] }; AddMachineProp: PUBLIC PROC[name: Rope.ROPE] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; FingerSimpleOps.Log[ NEW[LogEntryObject _ [AddMachineProp[name: name, version: machinePropVersion]]] ]; }; Do[]; FingerSimpleOps.AttemptToPlayLog[] }; DeleteUserProp: PUBLIC PROC[name: Rope.ROPE] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; FingerSimpleOps.Log[ NEW[LogEntryObject _ [ DeleteUserProp[name: name, version: userPropVersion]]] ]; }; Do[]; FingerSimpleOps.AttemptToPlayLog[] }; DeleteMachineProp: PUBLIC PROC[name: Rope.ROPE] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; FingerSimpleOps.Log[ NEW[LogEntryObject _ [DeleteMachineProp[name: name, version: machinePropVersion]]] ]; }; Do[]; FingerSimpleOps.AttemptToPlayLog[] }; SetUserProps: PUBLIC PROC [user: Rope.ROPE, props: LIST OF FingerOps.PropPair] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; now: BasicTime.GMT = BasicTime.Now[]; FOR p: LIST OF PropPair _ props, p.rest UNTIL p = NIL DO FingerSimpleOps.Log[NEW[LogEntryObject _ [UserPropChange [user: user, name: Atom.GetPName[p.first.prop], val: Canonicalize[p.first.val], time: now]]] ] ENDLOOP; }; Do[]; FingerSimpleOps.AttemptToPlayLog[] }; SetMachineProps: PUBLIC PROC[machine: Rope.ROPE, props: LIST OF PropPair] = { Do: ENTRY PROC [] ~ { ENABLE UNWIND => NULL; FOR p: LIST OF PropPair _ props, p.rest UNTIL p = NIL DO FingerSimpleOps.Log[NEW[LogEntryObject _ [ MachinePropChange[machine: machine, name: Atom.GetPName[p.first.prop], val: Canonicalize[p.first.val]]]] ] ENDLOOP; }; Do[]; FingerSimpleOps.AttemptToPlayLog[] }; GetUserProps: PUBLIC PROC[user: Rope.ROPE] RETURNS[props: LIST OF PropPair] = { DoGetProps: INTERNAL PROC[] = { entity: DB.Entity = DB.LookupEntity[person, Canonicalize[user]]; EachProp: RefTab.EachPairAction = { relation: DB.Relation = NARROW[val]; property: DB.Relship = DB.LookupProperty[relation, entity]; propertyValue: Rope.ROPE = IF property # NIL THEN DB.V2S[DB.GetF[property, 1]] ELSE NIL; props _ CONS[NEW[PropPairObject _ [prop: NARROW[key], val: propertyValue]], props]; quit _ FALSE }; IF entity = NIL THEN RETURN; [] _ RefTab.Pairs[userProps.table, EachProp] }; FingerSimpleOps.AttemptToPlayLog[]; -- make sure no pending updates exist CarefullyApply[DoGetProps] }; GetMachineProps: PUBLIC PROC[machineName: Rope.ROPE] RETURNS[props: LIST OF PropPair] = { DoGetProps: INTERNAL PROC[] = { entity: DB.Entity = DB.LookupEntity[machine, Canonicalize[machineName]]; EachProp: RefTab.EachPairAction = { relation: DB.Relation = NARROW[val]; property: DB.Relship = DB.LookupProperty[relation, entity]; propertyValue: Rope.ROPE = IF property # NIL THEN DB.V2S[DB.GetF[property, 1]] ELSE NIL; props _ CONS[NEW[PropPairObject _ [prop: NARROW[key], val: propertyValue]], props]; quit _ FALSE }; IF entity = NIL THEN RETURN; [] _ RefTab.Pairs[machineProps.table, EachProp] }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGetProps] }; CarefullyApply: ENTRY PROC [proc: PROC[]] ~ { ENABLE UNWIND => NULL; BEGIN ENABLE BEGIN DB.Error => GOTO Error; DB.Failure => GOTO Failure; END; aborted: BOOL _ FALSE; IF NOT activity THEN {activity _ TRUE; NOTIFY SomethingHappened}; BEGIN ENABLE DB.Aborted => { aborted _ TRUE; CONTINUE }; OpenTransaction[]; proc[] END; IF NOT aborted THEN RETURN; -- no aborted occurred BEGIN ENABLE DB.Aborted => GOTO Aborted; AbortTransaction[]; -- now try again OpenTransaction[]; proc[] END EXITS Error => { CloseTransaction[]; Unexport[]; ERROR FingerError[Error] }; Failure => { Unexport[]; ERROR FingerError[Failure] }; Aborted => { CloseTransaction[]; Unexport[]; ERROR FingerError[Aborted] } END; }; Unexport: PROC [] ~ { RemoteFingerOpsRpcControl.UnexportInterface[ ! LupineRuntime.BindingError => CONTINUE ]; }; PersonExists: PUBLIC PROC [name: Rope.ROPE] RETURNS[result: BOOLEAN] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { result _ DB.LookupEntity[d: person, name: Canonicalize[name]] # NIL }; CarefullyApply[DoGet] }; MachineExists: PUBLIC PROC [name: Rope.ROPE] RETURNS[result: BOOLEAN] = { DoGet: INTERNAL PROC[] = { result _ DB.LookupEntity[machine, Canonicalize[name]] # NIL }; CarefullyApply[DoGet] }; GetMachineData: PUBLIC PROC [name: Rope.ROPE] RETURNS[lastChange: StateChange, time: BasicTime.GMT, user: Rope.ROPE _ NIL] = { DoGet: INTERNAL PROC[] = { theMachine: DB.Entity = DB.DeclareEntity[machine, Canonicalize[name]]; machineRel: DB.Relship _ DB.LookupProperty[userRelation, theMachine]; IF DB.NullRelship[machineRel] THEN machineRel _ DB.CreateRelship[userRelation, DB.L2VS[LIST[DB.E2V[theMachine], DBDefs.NullValue, DB.E2V[unknown], DB.T2V[BasicTime.Now[]]]]]; lastChange _ IF DB.EntityEq[DB.V2E[DB.GetF[machineRel, lastEventIs]], login] THEN FingerLog.StateChange[login] ELSE FingerLog.StateChange[logout]; time _ DB.V2T[DB.GetF[machineRel, lastEventTimeIs]]; IF time # BasicTime.nullGMT THEN user _ DB.EntityInfo[DB.V2E[DB.GetF[machineRel, userIs]]].name }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGet] }; GetUserData: PUBLIC PROC [name: Rope.ROPE] RETURNS[machineList: LIST OF Rope.ROPE] = { DoGet: INTERNAL PROC[] = { user: DB.Entity = DB.DeclareEntity[person, Canonicalize[name]]; usedMachines: DB.RelshipSet = DB.RelshipsWithEntityField[userRelation, userIs, user]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[usedMachines]; FOR machines: DB.Relship _ DB.NextRelship[usedMachines], DB.NextRelship[usedMachines] UNTIL machines = NIL DO machineList _ CONS[DB.EntityInfo[DB.V2E[DB.GetF[machines, machineIs]]].name, machineList] ENDLOOP; END; DB.ReleaseRelshipSet[usedMachines] }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGet] }; MatchUserProperty: PUBLIC PROC[propVal: PropPair] RETURNS[result: LIST OF Rope.ROPE] = { <<... will return a list of user names that match the pattern propVal.val in the property propVal.prop. >> DoGet: INTERNAL PROC[] = { relation: DB.Relation = NARROW[RefTab.Fetch[userProps.table, propVal.prop].val]; indexList: LIST OF DB.Index _ DB.OtherIndices[relation]; relships: DB.RelshipSet; <<>> <<... we know that the relation we are dealing with is a binary relation so if there are any Indices on this relation then this attribute is a index attribute. Of course if it's a complicated pattern we have then we just use the normal method.>> IF (indexList # NIL) AND DevoPattern[propVal.val] THEN { low, high: Rope.ROPE _ NIL; <<>> [low, high] _ RopeBracket[propVal.val]; relships _ DB.RelationSubset[r: relation, index: indexList.first, constraint: DB.L2C[LIST[DBDefs.ValueConstraint[rope[low: low, high: high]]]]]; FOR next: DB.Relship _ DB.NextRelship[relships], DB.NextRelship[relships] UNTIL next = NIL DO result _ CONS[DB.EntityInfo[e: DB.V2E[v: DB.GetF[t: next, field: 0]]].name, result] ENDLOOP; RETURN } ELSE relships _ DB.RelationSubset[r: relation, index: NIL, constraint: NIL]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[relships]; FOR next: DB.Relship _ DB.NextRelship[relships], DB.NextRelship[relships] UNTIL next = NIL DO IF Rope.Match[DB.V2S[DB.GetF[next, 1]], Canonicalize[propVal.val]] THEN result _ CONS[DB.EntityInfo[DB.V2E[DB.GetF[next, 0]]].name, result] ENDLOOP; DB.ReleaseRelshipSet[relships] END }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGet] }; -- MatchUserProperty MatchMachineProperty: PUBLIC PROC[propVal: PropPair] RETURNS[result: LIST OF Rope.ROPE] ~ { <<... will return a list of machine names that match the pattern propVal.val in the property propVal.prop. >> DoGet: INTERNAL PROC[] = { relation: DB.Relation = NARROW[RefTab.Fetch[machineProps.table, propVal.prop].val]; indexList: LIST OF DB.Index _ DB.OtherIndices[relation]; relships: DB.RelshipSet; <<>> <<... we know that the relation we are dealing with is a binary relation so if there are any Indices on this relation then this attribute is a index attribute. Of course if it's a complicated pattern we have then we just use the normal method.>> IF (indexList # NIL) AND DevoPattern[propVal.val] THEN { low, high: Rope.ROPE _ NIL; <<>> [low, high] _ RopeBracket[propVal.val]; relships _ DB.RelationSubset[r: relation, index: indexList.first, constraint: DB.L2C[LIST[DBDefs.ValueConstraint[rope[low: Canonicalize[low], high: Canonicalize[high]]]]]]; FOR next: DB.Relship _ DB.NextRelship[relships], DB.NextRelship[relships] UNTIL next = NIL DO result _ CONS[DB.EntityInfo[e: DB.V2E[DB.GetF[t: next, field: 0 ]]].name, result] ENDLOOP; RETURN } ELSE relships _ DB.RelationSubset[r: relation, index: NIL, constraint: NIL]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[relships]; FOR next: DB.Relship _ DB.NextRelship[relships], DB.NextRelship[relships] UNTIL next = NIL DO IF Rope.Match[pattern: Canonicalize[propVal.val], object: DB.V2S[DB.GetF[next, 1]], case: FALSE] THEN result _ CONS[DB.EntityInfo[DB.V2E[DB.GetF[next, 0]]].name, result]; ENDLOOP END; DB.ReleaseRelshipSet[relships] }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGet] }; -- MatchMachineProperty GetMatchingPersons: PUBLIC PROC [pattern: Rope.ROPE] RETURNS [result: LIST OF Rope.ROPE] ~ { <<... returns the list of people that match the pattern. Tries to optimize for the degenerate pattern *.>> <<>> DoGet: INTERNAL PROC[] = { patn: Rope.ROPE _ Canonicalize[pattern]; setOfEntities: DB.EntitySet = DB.DomainSubset[d: person, lowName: RopeBracket[patn].low, highName: RopeBracket[patn].high]; finder: TextFind.Finder = TextFind.CreateFromRope[pattern: patn, ignoreCase: TRUE, addBounds: TRUE]; BEGIN ENABLE UNWIND => DB.ReleaseEntitySet[setOfEntities]; IF DevoPattern[value: patn] THEN { FOR entity: DB.Entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.EntityInfo[entity].name; result _ CONS[name, result] ENDLOOP; } ELSE { FOR entity: DB.Entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.EntityInfo[entity].name; IF TextFind.SearchRope[finder, name].found THEN result _ CONS[name, result] ENDLOOP }; DB.ReleaseEntitySet[setOfEntities] END }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGet] }; GetMatchingMachines: PUBLIC PROC [pattern: Rope.ROPE] RETURNS [result: LIST OF Rope.ROPE] = { DoGet: INTERNAL PROC[] = { patn: Rope.ROPE _ Canonicalize[pattern]; setOfEntities: DB.EntitySet = DB.DomainSubset[d: machine, lowName: RopeBracket[patn].low, highName: RopeBracket[patn].high]; finder: TextFind.Finder = TextFind.CreateFromRope[pattern: patn, ignoreCase: TRUE, addBounds: TRUE]; BEGIN ENABLE UNWIND => {DB.ReleaseEntitySet[setOfEntities]; CONTINUE}; IF DevoPattern[value: patn] THEN { FOR entity: DB.Entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.EntityInfo[entity].name; result _ CONS[name, result] ENDLOOP; } ELSE { FOR entity: DB.Entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.EntityInfo[entity].name; IF TextFind.SearchRope[finder, name].found THEN result _ CONS[name, result] ENDLOOP }; DB.ReleaseEntitySet[setOfEntities] END }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[DoGet] }; CurrentUsers: PUBLIC PROC[] RETURNS[userList: LIST OF Rope.ROPE] = { twoDaysInSeconds: INT = LONG[48] * 60 * 60; now: BasicTime.GMT = BasicTime.Now[]; twoDaysAgo: BasicTime.GMT = BasicTime.Update[now, -twoDaysInSeconds]; GetUsers: INTERNAL PROC[] = { activeSet: DB.RelshipSet = DB.RelationSubset[userRelation, timeIndex, DB.L2C[LIST[DB.ValueConstraint[entity[login]], DB.ValueConstraint[time[low: twoDaysAgo, high: now]]]]]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[activeSet]; FOR active: DB.Relship _ DB.NextRelship[activeSet], DB.NextRelship[activeSet] UNTIL active = NIL DO user: Rope.ROPE = DB.EntityInfo[DB.V2E[DB.GetF[active, userIs]]].name; userList _ CONS[user, userList] ENDLOOP END; DB.ReleaseRelshipSet[activeSet] }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[GetUsers] }; FreeMachines: PUBLIC PROC[] RETURNS[machineList: LIST OF Rope.ROPE] = { oneWeekInSeconds: INT = LONG[168] * 60 * 60; now: BasicTime.GMT = BasicTime.Now[]; oneWeekAgo: BasicTime.GMT = BasicTime.Update[now, -oneWeekInSeconds]; <> ListFree: INTERNAL PROC[] = { freeSet: DB.RelshipSet = DB.RelationSubset[userRelation, timeIndex, DB.L2C[LIST[DB.ValueConstraint[entity[logout]], DB.ValueConstraint[time[low: oneWeekAgo, high: now]]]]]; BEGIN ENABLE UNWIND => {DB.ReleaseRelshipSet[freeSet]; CONTINUE}; FOR free: DB.Relship _ DB.NextRelship[freeSet], DB.NextRelship[freeSet] UNTIL free = NIL DO machineList _ CONS[DB.EntityInfo[DB.V2E[DB.GetF[free, machineIs]]].name, machineList] ENDLOOP END; DB.ReleaseRelshipSet[freeSet] }; FingerSimpleOps.AttemptToPlayLog[]; CarefullyApply[ListFree] }; Canonicalize: PROC[name: Rope.ROPE] RETURNS[ lowerCase: Rope.ROPE ] = { newText: REF TEXT _ RefText.New[name.Size[]]; FOR i: INT IN [0..name.Size[]) DO newText _ RefText.AppendChar[newText, Ascii.Lower[name.Fetch[i]]] ENDLOOP; lowerCase _ RefText.TrustTextAsRope[newText] }; ReadMachineMap: PUBLIC PROC [file: Rope.ROPE, resetProperties: BOOLEAN _ FALSE] = { fileStream, tokenStream: IO.STREAM; DoReadMap: INTERNAL PROC = { <> <> line: REF TEXT _ RefText.New[500]; line _ fileStream.GetLine[line]; <> IF line[0] = Ascii.NUL THEN line _ fileStream.GetLine[line]; tokenStream _ IO.TIS[line, tokenStream]; IF resetProperties THEN { <> DB.SetF[machinePropVersionRelship, machinePropAttr, DB.I2V[0]]; <> BEGIN machinePropsSet: DB.EntitySet = DB.DomainSubset[machinePropNames]; FOR next: DB.Entity _ DB.NextEntity[machinePropsSet], DB.NextEntity[machinePropsSet] UNTIL next = NIL DO relationName: Rope.ROPE = Rope.Concat[DB.EntityInfo[next].name, DB.DomainInfo[machine].name]; relation: DB.Relation = DB.DeclareProperty[relationName, fingerSegment, machinePropType]; DB.DestroyRelation[relation]; [] _ RefTab.Delete[x: machineProps.table, key: Atom.MakeAtom[DB.EntityInfo[next].name]]; DB.MarkTransaction[fingerTransaction]; ENDLOOP; DB.ReleaseEntitySet[machinePropsSet]; END; }; BEGIN ENABLE IO.EndOfStream => CONTINUE; DO property: Rope.ROPE = tokenStream.GetRopeLiteral[]; index: BOOLEAN _ tokenStream.GetAtom[] = $Index; IF RefTab.Fetch[machineProps.table, Atom.MakeAtom[property]].found THEN LOOP; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[ property, DB.DomainInfo[machine].name]; relation: DB.Relation = DB.DeclareProperty[fullName, fingerSegment, machinePropType]; [] _ DB.DeclareEntity[machinePropNames, property]; [] _ RefTab.Store[ machineProps.table, Atom.MakeAtom[property], relation ]; IF index THEN [] _ DB.DeclareIndex[r: relation, fields: DB.L2F[LIST[1]]]; END ENDLOOP END; <> BEGIN ENABLE IO.EndOfStream => CONTINUE; machineName: Rope.ROPE; machineEntity: DB.Entity; machineRelship: DB.Relship; property: Rope.ROPE; propValue: Rope.ROPE; operationCount: INT _ 0; DO line _ fileStream.GetLine[line]; tokenStream _ IO.TIS[line, tokenStream]; machineName _ tokenStream.GetRopeLiteral[]; machineEntity _ DB.DeclareEntity[machine, Canonicalize[machineName]]; IF DB.NullRelship[DB.LookupProperty[userRelation, machineEntity]] THEN machineRelship _ DB.CreateRelship[userRelation, DB.L2VS[LIST[DB.E2V[machineEntity], DBDefs.NullValue, DB.E2V[unknown], DB.T2V[BasicTime.Now[]]]]]; DO line _ fileStream.GetLine[line]; TRUSTED{ IF Rope.Equal[LOOPHOLE[line], ""] THEN EXIT }; tokenStream _ IO.TIS[line, tokenStream]; property _ tokenStream.GetRopeLiteral[]; propValue _ Canonicalize[tokenStream.GetRopeLiteral[]]; BEGIN relation: DB.Relation = NARROW[RefTab.Fetch[machineProps.table, Atom.MakeAtom[property]].val]; IF relation = NIL THEN LOOP; BEGIN prop: DB.Relship = DB.LookupProperty[relation, machineEntity]; IF prop # NIL THEN DB.SetF[prop, 1, DB.S2V[propValue]] ELSE [] _ DB.CreateRelship[relation, DB.L2VS[LIST[DB.E2V[machineEntity], DB.S2V[propValue]]]] END; operationCount _ operationCount+1; IF operationCount = 20 THEN { DB.MarkTransaction[fingerTransaction]; operationCount _ 0 } END ENDLOOP; ENDLOOP END }; IF file = NIL THEN RETURN; fileStream _ FS.StreamOpen[fileName: file, accessOptions: $read ! FS.Error => {fileStream _ NIL; CONTINUE} ]; IF fileStream = NIL THEN RETURN; CarefullyApply[DoReadMap] }; WriteMachineMap: PUBLIC PROC [file: Rope.ROPE] ~ { stream: IO.STREAM; DoWriteMap: INTERNAL PROC = { setOfEntities: DB.EntitySet = DB.DomainSubset[machine]; entity: DB.Entity; WritePropName: RefTab.EachPairAction = { relation: DB.Relation = NARROW[val]; stream.Put[IO.rope[" "]]; stream.Put[IO.rope["\""]]; stream.Put[IO.rope[Atom.GetPName[NARROW[key]]]]; stream.Put[IO.rope["\""]]; IF DB.OtherIndices[r: relation] # NIL THEN stream.Put[IO.rope[" $Index"]] ELSE stream.Put[IO.rope[" $NoIndex"]]; quit _ FALSE }; -- WritePropName WritePropValue: RefTab.EachPairAction = { relation: DB.Relation = NARROW[val]; prop: DB.Relship = DB.LookupProperty[relation, entity]; propertyValue: Rope.ROPE = IF prop # NIL THEN DB.V2S[DB.GetF[prop, 1]] ELSE NIL; IF propertyValue # NIL THEN { stream.Put[IO.rope["\""]]; stream.Put[IO.rope[Atom.GetPName[NARROW[key]]]]; stream.Put[IO.rope["\""]]; stream.Put[IO.rope[" "], IO.rope[Convert.RopeFromRope[propertyValue]]]; stream.Put[IO.rope["\n"]] }; quit _ FALSE }; -- WritePropValue operationCount: INT _ 0; [] _ RefTab.Pairs[machineProps.table, WritePropName]; stream.Put[IO.rope["\n"]]; FOR entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.EntityInfo[entity].name; stream.Put[IO.rope[Convert.RopeFromRope[name]]]; stream.Put[IO.rope["\n"]]; [] _ RefTab.Pairs[machineProps.table, WritePropValue]; stream.Put[IO.rope["\n"]]; operationCount _ operationCount+1; IF operationCount = 20 THEN { DB.MarkTransaction[fingerTransaction]; operationCount _ 0 } ENDLOOP; DB.ReleaseEntitySet[setOfEntities]; stream.Close[] }; -- WritePropName IF file = NIL THEN RETURN; stream _ FS.StreamOpen[fileName: file, accessOptions: $create ! FS.Error => {stream _ NIL; CONTINUE }]; IF stream = NIL THEN RETURN; CarefullyApply[DoWriteMap] }; -- WriteMachineMap ReadUserMap: PUBLIC PROC [file: Rope.ROPE, resetProperties: BOOLEAN _ FALSE] ~ { <> fileStream, tokenStream: IO.STREAM; DoReadMap: INTERNAL PROC ~ { <> <> line: REF TEXT _ RefText.New[500]; line _ fileStream.GetLine[line]; <> IF line[0] = Ascii.NUL THEN line _ fileStream.GetLine[line]; tokenStream _ IO.TIS[line, tokenStream]; IF resetProperties THEN { <> DB.SetF[userPropVersionRelship, userPropAttr, DB.I2V[0]]; <> BEGIN userPropsSet: DB.EntitySet = DB.DomainSubset[userPropNames]; FOR next: DB.Entity _ DB.NextEntity[userPropsSet], DB.NextEntity[userPropsSet] UNTIL next = NIL DO relationName: Rope.ROPE = Rope.Concat[DB.EntityInfo[next].name, DB.DomainInfo[person].name]; relation: DB.Relation = DB.DeclareProperty[relationName, fingerSegment, userPropType]; DB.DestroyRelation[relation]; [] _ RefTab.Delete[x: userProps.table, key: Atom.MakeAtom[DB.EntityInfo[next].name]]; DB.MarkTransaction[fingerTransaction]; ENDLOOP; DB.ReleaseEntitySet[userPropsSet]; END; }; BEGIN ENABLE IO.EndOfStream => CONTINUE; DO property: Rope.ROPE = tokenStream.GetRopeLiteral[]; index: BOOLEAN _ tokenStream.GetAtom[] = $Index; IF RefTab.Fetch[userProps.table, Atom.MakeAtom[property]].found THEN LOOP; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[ property, DB.DomainInfo[person].name]; relation: DB.Relation = DB.DeclareProperty[fullName, fingerSegment, userPropType]; [] _ DB.DeclareEntity[userPropNames, property]; [] _ RefTab.Store[userProps.table, Atom.MakeAtom[property], relation ]; IF index THEN [] _ DB.DeclareIndex[r: relation, fields: DB.L2F[LIST[1]]]; END ENDLOOP END; <> BEGIN ENABLE IO.EndOfStream => CONTINUE; userName: Rope.ROPE; userEntity: DB.Entity; property: Rope.ROPE; propValue: Rope.ROPE; operationCount: INT _ 0; DO line _ fileStream.GetLine[line]; tokenStream _ IO.TIS[line, tokenStream]; userName _ tokenStream.GetRopeLiteral[]; userEntity _ DB.DeclareEntity[person, Canonicalize[userName]]; DO line _ fileStream.GetLine[line]; TRUSTED{ IF Rope.Equal[LOOPHOLE[line], ""] THEN EXIT }; tokenStream _ IO.TIS[line, tokenStream]; property _ tokenStream.GetRopeLiteral[]; propValue _ Canonicalize[tokenStream.GetRopeLiteral[]]; BEGIN relation: DB.Relation = NARROW[RefTab.Fetch[userProps.table, Atom.MakeAtom[property]].val]; IF relation = NIL THEN LOOP; BEGIN prop: DB.Relship = DB.LookupProperty[relation, userEntity]; IF prop # NIL THEN DB.SetF[prop, 1, DB.S2V[propValue]] ELSE [] _ DB.CreateRelship[relation, DB.L2VS[LIST[DB.E2V[userEntity], DB.S2V[propValue]]]] END; operationCount _ operationCount+1; IF operationCount = 20 THEN { DB.MarkTransaction[fingerTransaction]; operationCount _ 0 } END ENDLOOP; ENDLOOP END }; -- DoReadMap IF file = NIL THEN RETURN; fileStream _ FS.StreamOpen[fileName: file, accessOptions: $read ! FS.Error => {fileStream _ NIL; CONTINUE} ]; IF fileStream = NIL THEN RETURN; CarefullyApply[DoReadMap] }; -- ReadUserMap WriteUserMap: PUBLIC PROC [file: Rope.ROPE] ~ { <> stream: IO.STREAM; DoWriteMap: INTERNAL PROC = { setOfEntities: DB.EntitySet = DB.DomainSubset[person]; entity: DB.Entity; WritePropName: RefTab.EachPairAction = { relation: DB.Relation = NARROW[val]; stream.Put[IO.rope[" "]]; stream.Put[IO.rope["\""]]; stream.Put[IO.rope[Atom.GetPName[NARROW[key]]]]; stream.Put[IO.rope["\""]]; stream.Put[IO.rope[" "]]; IF DB.OtherIndices[r: relation] # NIL THEN stream.Put[IO.rope["$Index"]] ELSE stream.Put[IO.rope["$NoIndex"]]; quit _ FALSE }; -- WritePropName WritePropValue: RefTab.EachPairAction = { relation: DB.Relation = NARROW[val]; prop: DB.Relship = DB.LookupProperty[relation, entity]; propertyValue: Rope.ROPE = IF prop # NIL THEN DB.V2S[DB.GetF[prop, 1]] ELSE NIL; IF propertyValue # NIL THEN { stream.Put[IO.rope["\""]]; stream.Put[IO.rope[Atom.GetPName[NARROW[key]]]]; stream.Put[IO.rope["\""]]; stream.Put[IO.rope[" "], IO.rope[Convert.RopeFromRope[propertyValue]]]; stream.Put[IO.rope["\n"]] }; quit _ FALSE }; -- WritePropValue operationCount: INT _ 0; [] _ RefTab.Pairs[userProps.table, WritePropName]; stream.Put[IO.rope["\n"]]; FOR entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.EntityInfo[entity].name; stream.Put[IO.rope[Convert.RopeFromRope[name]]]; stream.Put[IO.rope["\n"]]; [] _ RefTab.Pairs[userProps.table, WritePropValue]; stream.Put[IO.rope["\n"]]; operationCount _ operationCount+1; IF operationCount = 20 THEN { DB.MarkTransaction[fingerTransaction]; operationCount _ 0 } ENDLOOP; DB.ReleaseEntitySet[setOfEntities]; stream.Close[] }; -- DoWriteMap IF file = NIL THEN RETURN; stream _ FS.StreamOpen[fileName: file, accessOptions: $create ! FS.Error => {stream _ NIL; CONTINUE }]; IF stream = NIL THEN RETURN; CarefullyApply[DoWriteMap] }; -- WriteUserMap CloseDown: ENTRY Booting.CheckpointProc = { CloseTransaction[] }; <> PlayLog: PUBLIC PROC [items: LIST OF LogEntry] RETURNS [success: BOOLEAN _ FALSE] ~ { DoPlay: INTERNAL PROC[] ~ TRUSTED { logEntry: LogEntry; WHILE items # NIL DO bogus: BOOLEAN _ FALSE; logEntry _ items.first; <> WITH logEntry: logEntry SELECT FROM StateChange => IF Rope.Equal[logEntry.user, NIL] OR Rope.Equal[logEntry.machine, NIL] THEN LOOP; UserPropChange => IF Rope.Equal[logEntry.user, NIL] THEN LOOP; MachinePropChange => IF Rope.Equal[logEntry.machine, NIL] THEN LOOP; ENDCASE; WITH logEntry: logEntry SELECT FROM StateChange => { user: DB.Entity = DB.DeclareEntity[person, Canonicalize[logEntry.user]]; thisMachine: DB.Entity _ DB.DeclareEntity[machine, Canonicalize[logEntry.machine]]; machineData: DB.Relship _ DB.LookupProperty[userRelation, thisMachine]; IF machineData = NIL THEN machineData _ DB.CreateRelship[userRelation, DB.L2VS[LIST[DB.E2V[thisMachine], DB.E2V[user], DB.E2V[login], DB.T2V[BasicTime.Now[]]]]]; IF logEntry.event = login THEN { DB.SetF[machineData, lastEventTimeIs, DB.T2V[logEntry.time]]; IF NOT DB.EntityEq[DB.V2E[DB.GetF[machineData, lastEventIs]], login] THEN DB.SetF[machineData, lastEventIs, DB.E2V[login]]; DB.SetF[machineData, userIs, DB.E2V[user]] } ELSE { DB.SetF[machineData, lastEventTimeIs, DB.T2V[logEntry.time]]; DB.SetF[machineData, lastEventIs, DB.E2V[logout]]; IF NOT DB.EntityEq[DB.V2E[DB.GetF[machineData, userIs]], user] THEN DB.SetF[machineData, userIs, DB.E2V[user]] } }; MachinePropChange => { m: DB.Entity = DB.DeclareEntity[machine, Canonicalize[logEntry.machine]]; relation: DB.Relation = NARROW[RefTab.Fetch[machineProps.table, Atom.MakeAtom[logEntry.name]].val]; IF relation = NIL THEN {items _ items.rest; LOOP}; BEGIN prop: DB.Relship = DB.LookupProperty[relation, m]; IF prop # NIL THEN DB.SetF[prop, 1, DB.S2V[logEntry.val]] ELSE [] _ DB.CreateRelship[relation, DB.L2VS[LIST[[entity[m]], [rope[logEntry.val]]]]] END }; UserPropChange => { user: DB.Entity = DB.DeclareEntity[person, Canonicalize[logEntry.user]]; DBVersion: INT = DB.V2I[DB.GetF[userPropVersionRelship, userPropAttr]]; relation: DB.Relation = NARROW[RefTab.Fetch[userProps.table, Atom.MakeAtom[logEntry.name]].val]; hisLastChangeProp: DB.Relship _ DB.LookupProperty[lastChangedProp, user]; lastChanged: BasicTime.GMT; IF DBVersion # userPropVersion THEN ERROR DB.Aborted; IF relation = NIL THEN {items _ items.rest; LOOP}; IF hisLastChangeProp = NIL THEN hisLastChangeProp _ DB.CreateRelship[lastChangedProp, DB.L2VS[LIST[DB.E2V[user], DB.T2V[BasicTime.Now[]]]]]; lastChanged _ DB.V2T[DB.GetF[hisLastChangeProp, lastChangedTime]]; IF lastChanged # BasicTime.nullGMT AND BasicTime.Period[from: lastChanged, to: logEntry.time] < 0 THEN { items _ items.rest; LOOP }; BEGIN prop: DB.Relship = DB.LookupProperty[relation, user]; IF prop # NIL THEN DB.SetF[prop, 1, DB.S2V[logEntry.val]] ELSE [] _ DB.CreateRelship[relation, DB.L2VS[LIST[[entity[user]], [rope[logEntry.val]]]]] END; DB.SetP[user, lastChangedProp, lastChangedTime, DB.T2V[logEntry.time]] }; AddMachineProp => { DBVersion: INT = DB.V2I[DB.GetF[machinePropVersionRelship, machinePropAttr]]; propAtom: ATOM = Atom.MakeAtom[logEntry.name]; IF DBVersion # logEntry.version THEN {items _ items.rest; LOOP}; <> IF RefTab.Fetch[machineProps.table, propAtom].found THEN {items _ items.rest; LOOP}; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[logEntry.name, DB.DomainInfo[machine].name]; relation: DB.Relation = DB.DeclareProperty[fullName, fingerSegment, machinePropType]; [] _ DB.DeclareEntity[machinePropNames, logEntry.name]; DB.SetF[machinePropVersionRelship, machinePropAttr, DB.I2V[DBVersion+1]]; [] _ RefTab.Store[ machineProps.table, propAtom, relation ]; machinePropVersion _ DBVersion+1 END }; AddUserProp => { DBVersion: INT = DB.V2I[DB.GetF[userPropVersionRelship, userPropAttr]]; propAtom: ATOM = Atom.MakeAtom[logEntry.name]; IF DBVersion # logEntry.version THEN {items _ items.rest; LOOP}; IF RefTab.Fetch[userProps.table, propAtom].found THEN {items _ items.rest; LOOP}; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[logEntry.name, DB.DomainInfo[person].name]; relation: DB.Relation = DB.DeclareProperty[fullName, fingerSegment, userPropType]; [] _ DB.DeclareEntity[userPropNames, logEntry.name]; DB.SetF[userPropVersionRelship, userPropAttr, DB.I2V[DBVersion+1]]; [] _ RefTab.Store[ userProps.table, propAtom, relation ]; userPropVersion _ DBVersion+1 END }; DeleteUserProp => { DBVersion: INT = DB.V2I[DB.GetF[userPropVersionRelship, userPropAttr]]; propAtom: ATOM = Atom.MakeAtom[logEntry.name]; relation: DB.Relation = NARROW[RefTab.Fetch[userProps.table, propAtom].val]; IF DBVersion # logEntry.version THEN {items _ items.rest; LOOP}; IF relation = NIL THEN {items _ items.rest; LOOP}; -- not there! BEGIN propEntity: DB.Entity = DB.DeclareEntity[userPropNames, logEntry.name]; DB.DestroyRelation[relation]; DB.DestroyEntity[propEntity]; DB.SetF[userPropVersionRelship, userPropAttr, DB.I2V[DBVersion+1]]; [] _ RefTab.Delete[userProps.table, propAtom]; userPropVersion _ DBVersion + 1 END}; DeleteMachineProp => { DBVersion: INT = DB.V2I[DB.GetF[machinePropVersionRelship, machinePropAttr]]; propAtom: ATOM = Atom.MakeAtom[logEntry.name]; relation: DB.Relation = NARROW[RefTab.Fetch[machineProps.table, propAtom].val]; IF DBVersion # logEntry.version THEN {items _ items.rest; LOOP}; IF relation = NIL THEN {items _ items.rest; LOOP}; -- not there! BEGIN propEntity: DB.Entity = DB.DeclareEntity[machinePropNames, logEntry.name]; DB.DestroyRelation[relation]; DB.DestroyEntity[propEntity]; DB.SetF[machinePropVersionRelship, machinePropAttr, DB.I2V[DBVersion+1]]; [] _ RefTab.Delete[machineProps.table, Atom.MakeAtom[logEntry.name]]; machinePropVersion _ DBVersion + 1 END}; ENDCASE; DB.MarkTransaction[fingerTransaction]; items _ items.rest ENDLOOP }; CarefullyApply[DoPlay]; <> success _ TRUE }; <<>> Booting.RegisterProcs[c: CloseDown]; InitFingerDB[]; TRUSTED{ Process.Detach[FORK WatchDBActivity[]] }; END. <> <> <> <> <<>> <> <> <<>> <<>> <<>>