<> <> <> <> DIRECTORY Atom, BasicTime USING [GMT, Now, nullGMT, Period], Booting, Convert, DB, FingerLog, FingerOps, FS USING[StreamOpen, Error], Idle USING [IdleReason, IdleHandler, RegisterIdleHandler], IO, Process USING [Detach, Ticks, SecondsToTicks, Pause], ThisMachine USING[Name], RefText USING[New], RefTab, Rope, TextFind USING [CreateFromRope, Finder, SearchRope], UserCredentials USING [Get], UserProfile USING [Token]; FingerOpsImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Booting, Convert, DB, FingerLog, FS, Idle, IO, Process, ThisMachine, RefTab, RefText, Rope, TextFind, UserCredentials, UserProfile EXPORTS FingerOps = BEGIN OPEN FingerOps, FingerLog, DB; <> machineName: Rope.ROPE = ThisMachine.Name[$Pup]; <> fingerSegmentRope: Rope.ROPE = UserProfile.Token[key: "Finger.segment", default: "[Luther.Alpine]Finger.segment"]; fingerSegment: DB.Segment = $Finger; fingerSegmentNumber: NAT = 260B; fingerTransaction: DB.Transaction _ NIL; <> FingerError: PUBLIC ERROR[reason: FingerOps.Reason] ~ CODE; idle: BOOL _ FALSE; <> activity: BOOL _ TRUE; ticksToWait: Process.Ticks _ Process.SecondsToTicks[10]; SomethingHappened: CONDITION; stateLog: LIST OF LogEntry _ NIL; <> person, machine: DB.Domain; <> userRelation: DB.Relation; machineIs: DB.Attribute; -- a key of the relation userIs: DB.Attribute; -- the person performing the last event lastEventIs: DB.Attribute; -- whether the last event was login or logout lastEventTimeIs: DB.Attribute; -- when the last event occurred <> lastChangedProp: DB.Attribute; <> machinePropVersion, userPropVersion: INT _ -1; <> machinePropRelation: DB.Relation; machinePropAttr: DB.Attribute; machinePropVersionRelship: DB.Relship; -- the single relship of this relation <> userPropRelation: DB.Relation; userPropAttr: DB.Attribute; userPropVersionRelship: DB.Relship; -- the single relship of this relation <<>> <> userPropNames: DB.Relation; userPropNameIs: DB.Attribute; machinePropNames: DB.Relation; machinePropNameIs: DB.Attribute; <<>> <> login: INT = 2; logout: INT = 1; <> PropertyTable: TYPE = RECORD[names: DB.Relation _ NIL, nameAttr: DB.Attribute _ NIL, table: RefTab.Ref]; <> machineProps: PropertyTable _ [table: RefTab.Create[]]; userProps: PropertyTable _ [table: RefTab.Create[]]; <> AttributeRecord: TYPE = REF AttrRecordObject; AttrRecordObject: TYPE = RECORD[attr: DB.Attribute, of: DB.Relation]; 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; BEGIN ENABLE DB.Error, DB.Failure => CONTINUE; IF fingerTransaction = NIL THEN fingerTransaction _ DB.GetSegmentInfo[fingerSegment].trans; IF fingerTransaction # NIL THEN DB.CloseTransaction[fingerTransaction ! DB.Aborted => { caughtAborted _ TRUE; CONTINUE }]; IF caughtAborted THEN DB.AbortTransaction[fingerTransaction] END; fingerTransaction _ NIL }; OpenTransaction: INTERNAL PROC [] = { IF fingerTransaction = NIL THEN fingerTransaction _ DB.GetSegmentInfo[fingerSegment].trans; IF fingerTransaction = NIL THEN DB.OpenTransaction[fingerSegment]; fingerTransaction _ DB.GetSegmentInfo[fingerSegment].trans }; AbortTransaction: INTERNAL PROC [] = { IF fingerTransaction = NIL THEN fingerTransaction _ DB.GetSegmentInfo[fingerSegment].trans; IF fingerTransaction # NIL THEN DB.AbortTransaction[fingerTransaction ! DB.Failure, DB.Error => 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] END; ResetSchema: INTERNAL PROC[] ~ { OpenTransaction[]; IF NOT DB.Null[person] THEN RETURN; <> person _ DB.DeclareDomain["person", fingerSegment]; machine _ DB.DeclareDomain["machine", fingerSegment]; <<>> <> machinePropRelation _ DB.DeclareRelation[name: "MachineVersion", segment: fingerSegment]; machinePropAttr _ DB.DeclareAttribute[r: machinePropRelation, name: "stamp", type: IntType]; <> BEGIN machineProps: DB.RelshipSet = DB.RelationSubset[machinePropRelation]; machinePropVersionRelship _ DB.NextRelship[machineProps]; IF machinePropVersionRelship = NIL THEN machinePropVersionRelship _ DB.DeclareRelship[r: machinePropRelation]; DB.ReleaseRelshipSet[machineProps] END; userPropRelation _ DB.DeclareRelation[name: "UserVersion", segment: fingerSegment]; userPropAttr _ DB.DeclareAttribute[r: userPropRelation, name: "stamp", type: IntType]; <> BEGIN userProps: DB.RelshipSet = DB.RelationSubset[userPropRelation]; userPropVersionRelship _ DB.NextRelship[userProps]; IF userPropVersionRelship = NIL THEN userPropVersionRelship _ DB.DeclareRelship[r: userPropRelation] END; lastChangedProp _ DB.DeclareProperty[relationName: "lastChanged", of: person, is: TimeType, segment: fingerSegment]; <<>> <> machinePropNames _ DB.DeclareRelation[name: "MachineProps", segment: fingerSegment]; machinePropNameIs _ DB.DeclareAttribute[r: machinePropNames, name: "name", type: RopeType]; machineProps.names _ machinePropNames; machineProps.nameAttr _ machinePropNameIs; userPropNames _ DB.DeclareRelation[name: "UserProps", segment: fingerSegment]; userPropNameIs _ DB.DeclareAttribute[r: userPropNames, name: "name", type: RopeType]; userProps.names _ userPropNames; userProps.nameAttr _ userPropNameIs; <<>> <> userRelation _ DB.DeclareRelation[name: "UserInfo", segment: fingerSegment]; machineIs _ DB.DeclareAttribute[r: userRelation, name: "machine", type: machine, uniqueness: Key]; userIs _ DB.DeclareAttribute[r: userRelation, name: "user", type: person]; lastEventIs _ DB.DeclareAttribute[r: userRelation, name: "lastEvent", type: IntType]; lastEventTimeIs _ DB.DeclareAttribute[r: userRelation, name: "lastEventTime", type: TimeType]; <> [] _ DB.DeclareIndex[userRelation, LIST[lastEventIs, userIs]]; <<>> <> 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.RelshipSet = DB.RelationSubset[propTable.names]; FOR attrRel: DB.Relship _ DB.NextRelship[attrSet], DB.NextRelship[attrSet] UNTIL attrRel = NIL DO propName: Rope.ROPE = DB.V2S[DB.GetF[attrRel, propTable.nameAttr]]; [] _ RefTab.Store[x: propTable.table, key: Atom.MakeAtom[propName], val: NIL] ENDLOOP}; SetAttributes: INTERNAL PROC[propTable: PropertyTable, domain: DB.Domain] = { <> EachProp: RefTab.EachPairAction = { name: Rope.ROPE = Atom.GetPName[NARROW[key]]; fullName: Rope.ROPE = Rope.Concat[name, DB.NameOf[domain]]; propRelation: DB.Relation = DB.DeclareRelation[fullName, fingerSegment]; propOfAttr: DB.Attribute = DB.DeclareAttribute[propRelation, "of", domain, Key]; propIsAttr: DB.Attribute = DB.DeclareAttribute[propRelation, "is", RopeType]; [] _ RefTab.Store[ propTable.table, key, NEW[AttrRecordObject _ [attr: propIsAttr, of: propRelation]] ]; quit _ FALSE }; [] _ RefTab.Pairs[propTable.table, EachProp] }; ListUserProps: PUBLIC ENTRY PROC[] RETURNS[propList: LIST OF ATOM] = { <> ENABLE UNWIND => NULL; EachProp: RefTab.EachPairAction = { propList _ CONS[NARROW[key], propList]; quit _ FALSE }; IF stateLog # NIL THEN PlayLog[]; [] _ RefTab.Pairs[userProps.table, EachProp]; }; ListMachineProps: PUBLIC ENTRY PROC[] RETURNS[propList: LIST OF ATOM] = { ENABLE UNWIND => NULL; EachProp: RefTab.EachPairAction = { propList _ CONS[NARROW[key], propList]; quit _ FALSE }; IF stateLog # NIL THEN PlayLog[]; [] _ RefTab.Pairs[machineProps.table, EachProp]; }; AddUserProp: PUBLIC ENTRY PROC[name: Rope.ROPE] = { ENABLE UNWIND => NULL; Log[ logEntry: NEW[AddUserProp LogEntryObject _ [AddUserProp [name: name, version: userPropVersion]]] ]; PlayLog[] }; AddMachineProp: PUBLIC ENTRY PROC[name: Rope.ROPE] = { ENABLE UNWIND => NULL; Log[ NEW[AddMachineProp LogEntryObject _ [AddMachineProp[name: name, version: machinePropVersion]]] ]; PlayLog[] }; DeleteUserProp: PUBLIC ENTRY PROC[name: Rope.ROPE] = { ENABLE UNWIND => NULL; Log[ NEW[DeleteUserProp LogEntryObject _ [ DeleteUserProp[name: name, version: userPropVersion]]] ]; PlayLog[] }; DeleteMachineProp: PUBLIC ENTRY PROC[name: Rope.ROPE] = { ENABLE UNWIND => NULL; Log[ NEW[DeleteMachineProp LogEntryObject _ [DeleteMachineProp[name: name, version: machinePropVersion]]] ]; PlayLog[] }; SetUserProps: PUBLIC ENTRY PROC [user: Rope.ROPE, props: LIST OF FingerOps.PropPair] = { ENABLE UNWIND => NULL; now: BasicTime.GMT = BasicTime.Now[]; FOR p: LIST OF PropPair _ props, p.rest UNTIL p = NIL DO Log[NEW[UserPropChange LogEntryObject _ [UserPropChange [user: user, name: Atom.GetPName[p.first.prop], val: p.first.val, time: now]]] ] ENDLOOP; PlayLog[] }; SetMachineProps: PUBLIC ENTRY PROC[machine: Rope.ROPE, props: LIST OF PropPair] = { ENABLE UNWIND => NULL; FOR p: LIST OF PropPair _ props, p.rest UNTIL p = NIL DO Log[NEW[MachinePropChange LogEntryObject _ [ MachinePropChange[machine: machine, name: Atom.GetPName[p.first.prop], val: p.first.val]]] ] ENDLOOP; PlayLog[] }; GetUserProps: PUBLIC ENTRY PROC[user: Rope.ROPE] RETURNS[props: LIST OF PropPair] = { ENABLE UNWIND => NULL; DoGetProps: INTERNAL PROC[] = { entity: DB.Entity = DB.DeclareEntity[person, user, OldOnly]; EachProp: RefTab.EachPairAction = { attr: DB.Attribute = DB.V2E[NARROW[val, AttributeRecord].attr]; propertyValue: Rope.ROPE = DB.V2S[DB.GetP[entity, DB.V2E[attr]]]; props _ CONS[NEW[PropPairObject _ [prop: NARROW[key], val: propertyValue]], props]; quit _ FALSE }; IF entity = NIL THEN RETURN; [] _ RefTab.Pairs[userProps.table, EachProp] }; IF stateLog # NIL THEN PlayLog[]; -- make sure no pending updates exist CarefullyApply[DoGetProps] }; GetMachineProps: PUBLIC ENTRY PROC[machineName: Rope.ROPE] RETURNS[props: LIST OF PropPair] = { ENABLE UNWIND => NULL; DoGetProps: INTERNAL PROC[] = { entity: DB.Entity = DB.DeclareEntity[machine, machineName, OldOnly]; EachProp: RefTab.EachPairAction = { attr: DB.Attribute = DB.V2E[NARROW[val, AttributeRecord].attr]; propertyValue: Rope.ROPE = DB.V2S[DB.GetP[entity, attr]]; props _ CONS[NEW[PropPairObject _ [prop: NARROW[key], val: propertyValue]], props]; quit _ FALSE }; IF entity = NIL THEN RETURN; [] _ RefTab.Pairs[machineProps.table, EachProp] }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGetProps] }; CarefullyApply: INTERNAL PROC [proc: PROC[]] ~ { 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 }; ResetSchema[]; proc[] END; IF NOT aborted THEN RETURN; -- no aborted occurred AbortTransaction[]; -- now try again BEGIN ENABLE DB.Aborted => GOTO Aborted; ResetSchema[]; proc[] END EXITS Error => { CloseTransaction[]; ERROR FingerError[Error] }; Failure => ERROR FingerError[Failure]; Aborted => { CloseTransaction[]; ERROR FingerError[Aborted] } }; PersonExists: PUBLIC ENTRY PROC [name: Rope.ROPE] RETURNS[result: BOOLEAN] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { result _ DB.DeclareEntity[person, name, OldOnly] # NIL }; CarefullyApply[DoGet] }; MachineExists: PUBLIC ENTRY PROC [name: Rope.ROPE] RETURNS[result: BOOLEAN] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { result _ DB.DeclareEntity[machine, name, OldOnly] # NIL }; CarefullyApply[DoGet] }; GetMachineData: PUBLIC ENTRY PROC [name: Rope.ROPE] RETURNS[lastChange: StateChange, time: BasicTime.GMT, user: Rope.ROPE _ NIL] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { theMachine: DB.Entity = DB.DeclareEntity[machine, name]; machineRel: DB.Relship = DB.DeclareRelship[userRelation, LIST[AttributeValue[machineIs, theMachine]]]; lastChange _ IF DB.V2I[DB.GetF[machineRel, lastEventIs]] = login THEN FingerOps.StateChange[login] ELSE FingerOps.StateChange[logout]; time _ DB.V2T[DB.GetF[machineRel, lastEventTimeIs]]; IF time # BasicTime.nullGMT THEN user _ DB.NameOf[DB.V2E[DB.GetF[machineRel, userIs]]] }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGet] }; GetUserData: PUBLIC ENTRY PROC [name: Rope.ROPE] RETURNS[machineList: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { user: DB.Entity = DB.DeclareEntity[person, name]; attrList: LIST OF DB.AttributeValue = LIST[[userIs, user]]; usedMachines: DB.RelshipSet = DB.RelationSubset[userRelation, attrList]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[usedMachines]; FOR machines: DB.Relship _ DB.NextRelship[usedMachines], DB.NextRelship[usedMachines] UNTIL machines = NIL DO machineList _ CONS[DB.NameOf[DB.V2E[DB.GetF[machines, machineIs]]], machineList] ENDLOOP; END; DB.ReleaseRelshipSet[usedMachines] }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGet] }; MatchMachineProperty: PUBLIC ENTRY PROC[propVal: PropPair] RETURNS[result: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { attr: AttributeRecord = NARROW[RefTab.Fetch[machineProps.table, propVal.prop].val]; relation: DB.Relation = attr.of; machineAttribute: DB.Attribute = DB.DeclareAttribute[relation, "of"]; relships: DB.RelshipSet = DB.RelationSubset[relation, LIST[AttributeValue[attr.attr, propVal.val]]]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[relships]; FOR next: DB.Relship _ DB.NextRelship[relships], DB.NextRelship[relships] UNTIL next = NIL DO machine: DB.Entity = DB.V2E[DB.GetF[next, machineAttribute]]; result _ CONS[DB.NameOf[machine], result]; ENDLOOP END }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGet] }; GetMatchingPersons: PUBLIC ENTRY PROC [pattern: Rope.ROPE] RETURNS [result: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { setOfEntities: DB.EntitySet = DB.DomainSubset[person]; finder: TextFind.Finder = TextFind.CreateFromRope[pattern: pattern, ignoreCase: TRUE]; BEGIN ENABLE UNWIND => {DB.ReleaseEntitySet[setOfEntities]; CONTINUE}; FOR entity: DB.Entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.NameOf[entity]; IF TextFind.SearchRope[finder, name].found THEN result _ CONS[name, result] ENDLOOP; DB.ReleaseEntitySet[setOfEntities] END }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGet] }; GetMatchingMachines: PUBLIC ENTRY PROC [pattern: Rope.ROPE] RETURNS [result: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { setOfEntities: DB.EntitySet = DB.DomainSubset[machine]; finder: TextFind.Finder = TextFind.CreateFromRope[pattern: pattern, ignoreCase: TRUE]; BEGIN ENABLE UNWIND => {DB.ReleaseEntitySet[setOfEntities]; CONTINUE}; FOR entity: DB.Entity _ DB.NextEntity[setOfEntities], DB.NextEntity[setOfEntities] UNTIL entity = NIL DO name: Rope.ROPE = DB.NameOf[entity]; IF TextFind.SearchRope[finder, name].found THEN result _ CONS[name, result] ENDLOOP; DB.ReleaseEntitySet[setOfEntities] END }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGet] }; MatchUserProperty: PUBLIC ENTRY PROC[propVal: PropPair] RETURNS[result: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; DoGet: INTERNAL PROC[] = { attr: AttributeRecord = NARROW[RefTab.Fetch[userProps.table, propVal.prop].val]; relships: DB.RelshipSet = DB.RelationSubset[attr.of, LIST[AttributeValue[attr.attr, propVal.val]]]; userAttribute: DB.Attribute = DB.DeclareAttribute[attr.of, "of"]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[relships]; FOR next: DB.Relship _ DB.NextRelship[relships], DB.NextRelship[relships] UNTIL next = NIL DO user: DB.Entity = DB.V2E[DB.GetF[next, userAttribute]]; result _ CONS[DB.NameOf[user], result]; ENDLOOP; DB.ReleaseRelshipSet[relships] END }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[DoGet] }; CurrentUsers: PUBLIC ENTRY PROC[] RETURNS[userList: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; twoDaysInSeconds: INT = LONG[48] * 60 * 60; now: BasicTime.GMT = BasicTime.Now[]; GetUsers: INTERNAL PROC[] = { activeSet: DB.RelshipSet = DB.RelationSubset[userRelation, LIST[AttributeValue[lastEventIs, DB.I2V[login]]]]; BEGIN ENABLE UNWIND => DB.ReleaseRelshipSet[activeSet]; thisUser: Rope.ROPE; FOR active: DB.Relship _ DB.NextRelship[activeSet], DB.NextRelship[activeSet] UNTIL active = NIL DO user: Rope.ROPE = DB.NameOf[DB.V2E[DB.GetF[active, userIs]]]; time: BasicTime.GMT = DB.V2T[DB.GetF[active, lastEventTimeIs]]; <> IF BasicTime.Period[from: time, to: now] > twoDaysInSeconds THEN LOOP; <> IF NOT Rope.Equal[thisUser, user] THEN { thisUser _ user; userList _ CONS[user, userList] } ENDLOOP END; DB.ReleaseRelshipSet[activeSet] }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[GetUsers] }; FreeMachines: PUBLIC ENTRY PROC[] RETURNS[machineList: LIST OF Rope.ROPE] = { ENABLE UNWIND => NULL; oneWeekInSeconds: INT = LONG[168] * 60 * 60; now: BasicTime.GMT = BasicTime.Now[]; ListFree: INTERNAL PROC[] = { freeSet: DB.RelshipSet = DB.RelationSubset[userRelation, LIST[AttributeValue[lastEventIs, DB.I2V[logout]]]]; BEGIN ENABLE UNWIND => {DB.ReleaseRelshipSet[freeSet]; CONTINUE}; FOR free: DB.Relship _ DB.NextRelship[freeSet], DB.NextRelship[freeSet] UNTIL free = NIL DO time: BasicTime.GMT = DB.V2T[DB.GetF[free, lastEventTimeIs]]; IF BasicTime.Period[from: time, to: now] > oneWeekInSeconds THEN LOOP; <> machineList _ CONS[DB.NameOf[DB.V2E[DB.GetF[free, machineIs]]], machineList] ENDLOOP END; DB.ReleaseRelshipSet[freeSet] }; IF stateLog # NIL THEN PlayLog[]; CarefullyApply[ListFree] }; PlayLog: INTERNAL PROC[] ~ { DoPlay: INTERNAL PROC[] ~ TRUSTED { logEntry: LogEntry; thisMachine: DB.Entity = IF NOT Rope.Equal[machineName, ""] THEN DB.DeclareEntity[machine, machineName] ELSE NIL; machineData: DB.Relship = IF NOT DB.Null[thisMachine] THEN DB.DeclareRelship[userRelation, LIST[AttributeValue[machineIs, thisMachine]]] ELSE NIL; WHILE stateLog # NIL DO logEntry _ stateLog.first; WITH logEntry: logEntry SELECT FROM StateChange => { user: DB.Entity = DB.DeclareEntity[person, logEntry.user]; IF machineData = NIL THEN {stateLog _ stateLog.rest; LOOP}; IF logEntry.event = login THEN { [] _ DB.SetF[machineData, lastEventTimeIs, DB.T2V[[logEntry.time]]]; [] _ DB.SetF[machineData, lastEventIs, DB.I2V[login]]; [] _ DB.SetF[machineData, userIs, user] } ELSE { [] _ DB.SetF[machineData, lastEventTimeIs, DB.T2V[[logEntry.time]]]; [] _ DB.SetF[machineData, lastEventIs, DB.I2V[logout]]; [] _ DB.SetF[machineData, userIs, user] } }; MachinePropChange => { m: DB.Entity = DB.DeclareEntity[machine, logEntry.machine]; attrRecord: AttributeRecord = NARROW[RefTab.Fetch[machineProps.table, Atom.MakeAtom[logEntry.name]].val]; IF attrRecord = NIL THEN {stateLog _ stateLog.rest; LOOP}; -- not a property anymore [] _ DB.SetP[m, DB.V2E[attrRecord.attr], DB.S2V[logEntry.val]] }; UserPropChange => { user: DB.Entity = DB.DeclareEntity[person, logEntry.user]; lastChanged: BasicTime.GMT = DB.V2T[DB.GetP[user, lastChangedProp]]; DBVersion: INT = DB.V2I[DB.GetF[userPropVersionRelship, userPropAttr]]; prop: AttributeRecord = NARROW[RefTab.Fetch[userProps.table, Atom.MakeAtom[logEntry.name]].val]; IF DBVersion # userPropVersion THEN ERROR DB.Aborted[fingerTransaction]; IF prop = NIL THEN {stateLog _ stateLog.rest; LOOP}; -- not a property anymore IF lastChanged # BasicTime.nullGMT AND BasicTime.Period[from: lastChanged, to: logEntry.time] < 0 THEN { stateLog _ stateLog.rest; LOOP }; [] _ DB.SetP[user, DB.V2E[prop.attr], DB.S2V[logEntry.val]]; [] _ DB.SetP[user, lastChangedProp, DB.T2V[[logEntry.time]]] }; AddMachineProp => { DBVersion: INT = DB.V2I[DB.GetF[machinePropVersionRelship, machinePropAttr]]; propAtom: ATOM = Atom.MakeAtom[logEntry.name]; IF DBVersion # logEntry.version THEN {stateLog _ stateLog.rest; LOOP}; <> IF RefTab.Fetch[machineProps.table, propAtom].found THEN {stateLog _ stateLog.rest; LOOP}; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[logEntry.name, DB.NameOf[machine]]; relation: DB.Relation = DB.DeclareRelation[fullName, fingerSegment]; propOfAttr: DB.Attribute = DB.DeclareAttribute[relation, "of", machine, Key]; propIsAttr: DB.Attribute = DB.DeclareAttribute[relation, "is", RopeType]; [] _ DB.DeclareRelship[machinePropNames, LIST[DB.AttributeValue[machinePropNameIs, DB.S2V[logEntry.name]]]]; DB.SetF[machinePropVersionRelship, machinePropAttr, NEW[INT _ DBVersion+1]]; [] _ RefTab.Store[ machineProps.table, propAtom, NEW[AttrRecordObject _ [attr: propIsAttr, of: 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 {stateLog _ stateLog.rest; LOOP}; IF RefTab.Fetch[userProps.table, propAtom].found THEN {stateLog _ stateLog.rest; LOOP}; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[logEntry.name, DB.NameOf[person]]; relation: DB.Relation = DB.DeclareRelation[fullName, fingerSegment]; propOfAttr: DB.Attribute = DB.DeclareAttribute[relation, "of", person, Key]; propIsAttr: DB.Attribute = DB.DeclareAttribute[relation, "is", RopeType]; [] _ DB.DeclareRelship[userPropNames, LIST[DB.AttributeValue[userPropNameIs, DB.S2V[logEntry.name]]]]; DB.SetF[userPropVersionRelship, userPropAttr, NEW[INT _ DBVersion+1]]; [] _ RefTab.Store[ userProps.table, propAtom, NEW[AttrRecordObject _ [attr: propIsAttr, of: relation]] ]; userPropVersion _ DBVersion+1 END }; DeleteUserProp => { DBVersion: INT = DB.V2I[DB.GetF[userPropVersionRelship, userPropAttr]]; propAtom: ATOM = Atom.MakeAtom[logEntry.name]; IF DBVersion # logEntry.version THEN {stateLog _ stateLog.rest; LOOP}; IF NOT RefTab.Fetch[userProps.table, propAtom].found THEN {stateLog _ stateLog.rest; LOOP}; -- not there! BEGIN fullName: Rope.ROPE = Rope.Concat[logEntry.name, DB.NameOf[person]]; nameRelship: DB.Relship = DB.DeclareRelship[userPropNames, LIST[DB.AttributeValue[userPropNameIs, DB.S2V[logEntry.name]]]]; DB.DestroyRelation[DB.DeclareRelation[fullName, fingerSegment]]; DB.DestroyRelship[nameRelship]; DB.SetF[userPropVersionRelship, userPropAttr, NEW[INT _ 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]; IF DBVersion # logEntry.version THEN {stateLog _ stateLog.rest; LOOP}; IF NOT RefTab.Fetch[userProps.table, propAtom].found THEN {stateLog _ stateLog.rest; LOOP}; -- not there! BEGIN fullName: Rope.ROPE = Rope.Concat[logEntry.name, DB.NameOf[machine]]; nameRelship: DB.Relship = DB.DeclareRelship[machinePropNames, LIST[DB.AttributeValue[machinePropNameIs, DB.S2V[logEntry.name]]]]; DB.DestroyRelation[DB.DeclareRelation[fullName, fingerSegment]]; DB.DestroyRelship[nameRelship]; DB.SetF[machinePropVersionRelship, machinePropAttr, NEW[INT _ DBVersion+1]]; [] _ RefTab.Delete[machineProps.table, Atom.MakeAtom[logEntry.name]]; machinePropVersion _ DBVersion + 1 END}; ENDCASE; DB.MarkTransaction[fingerTransaction]; stateLog _ stateLog.rest ENDLOOP }; CarefullyApply[DoPlay]; <> FingerLog.FlushLog[] }; AttemptToPlayLog: ENTRY PROC[] = { ENABLE UNWIND => NULL; PlayLog[! FingerError => CONTINUE] }; RegisterLoginOrLogout: ENTRY Idle.IdleHandler = TRUSTED BEGIN ENABLE UNWIND => NULL; idle _ reason = becomingIdle; InternalPutStateChange[IF idle THEN $logout ELSE $login, BasicTime.Now[]]; Process.Detach[FORK AttemptToPlayLog[]] END; PutStateChange: PUBLIC ENTRY PROC [change: FingerOps.StateChange, time: BasicTime.GMT] ~ { <> ENABLE UNWIND => NULL; InternalPutStateChange[change, time]; IF NOT idle THEN PlayLog[! FingerError => CONTINUE] }; InternalPutStateChange: INTERNAL PROC [change: FingerOps.StateChange, time: BasicTime.GMT] ~ { name: Rope.ROPE = UserCredentials.Get[].name; Log[logEntry: NEW[StateChange LogEntryObject _ [StateChange[event: change, user: name, time: time]]]]; }; RegisterThisMachine: ENTRY PROC = BEGIN ENABLE UNWIND => NULL; DoRegistration: PROC[] = { thisMachine: DB.Entity = DB.DeclareEntity[machine, machineName]; itsRelation: DB.Relship = DB.DeclareRelship[userRelation, LIST[AttributeValue[machineIs, thisMachine]]]; DB.MarkTransaction[fingerTransaction] }; IF NOT Rope.Equal[machineName, ""] THEN CarefullyApply[DoRegistration] END; Log: INTERNAL PROC[ logEntry: LogEntry ] = TRUSTED { <> FingerLog.Log[ logEntry ]; IF stateLog = NIL THEN stateLog _ LIST[logEntry] ELSE { log: LIST OF LogEntry _ stateLog; WHILE log.rest # NIL DO log _ log.rest ENDLOOP; log.rest _ LIST[logEntry] } }; ParseLog: INTERNAL PROC[] ~ { stateLog _ FingerLog.ParseLog[] }; ReadMachineMap: PUBLIC ENTRY PROC [file: Rope.ROPE, resetProperties: BOOLEAN _ FALSE] = { fileStream, tokenStream: IO.STREAM; DoReadMap: INTERNAL PROC = { <> <> line: REF TEXT _ RefText.New[500]; line _ fileStream.GetLine[line]; tokenStream _ IO.TIS[line, tokenStream]; IF resetProperties THEN { <> DB.SetF[machinePropVersionRelship, machinePropAttr, NEW[INT _ 0]]; <> BEGIN machineProps: DB.RelshipSet = DB.RelationSubset[machinePropNames]; FOR next: DB.Relship _ DB.NextRelship[machineProps], DB.NextRelship[machineProps] UNTIL next = NIL DO relationName: Rope.ROPE = DB.V2S[DB.GetF[next, machinePropNameIs]]; relation: DB.Relation = DB.DeclareRelation[relationName, fingerSegment]; DB.DestroyRelation[relation]; DB.MarkTransaction[fingerTransaction]; ENDLOOP; DB.ReleaseRelshipSet[machineProps]; END; }; BEGIN ENABLE IO.EndOfStream => CONTINUE; DO atom: ATOM = tokenStream.GetAtom[]; IF RefTab.Fetch[machineProps.table, atom].found THEN LOOP; -- already done! BEGIN fullName: Rope.ROPE = Rope.Concat[Atom.GetPName[atom], DB.NameOf[machine]]; relation: DB.Relation = DB.DeclareRelation[fullName, fingerSegment]; propOfAttr: DB.Attribute = DB.DeclareAttribute[relation, "of", machine, Key]; propIsAttr: DB.Attribute = DB.DeclareAttribute[relation, "is", RopeType]; [] _ DB.DeclareRelship[machinePropNames, LIST[DB.AttributeValue[machinePropNameIs, DB.S2V[Atom.GetPName[atom]]]]]; [] _ RefTab.Store[ machineProps.table, atom, NEW[AttrRecordObject _ [attr: propIsAttr, of: relation]] ]; END ENDLOOP END; <> BEGIN ENABLE IO.EndOfStream => CONTINUE; machineName: Rope.ROPE; machineEntity: DB.Entity; machineRelship: DB.Relship; propAtom: ATOM; propValue: Rope.ROPE; operationCount: INT _ 0; DO line _ fileStream.GetLine[line]; tokenStream _ IO.TIS[line, tokenStream]; machineName _ tokenStream.GetRopeLiteral[]; machineEntity _ DB.DeclareEntity[machine, machineName]; machineRelship _ DB.DeclareRelship[userRelation, LIST[AttributeValue[machineIs, machineEntity]]]; DO line _ fileStream.GetLine[line]; TRUSTED{ IF Rope.Equal[LOOPHOLE[line], ""] THEN EXIT }; tokenStream _ IO.TIS[line, tokenStream]; propAtom _ tokenStream.GetAtom[]; propValue _ tokenStream.GetRopeLiteral[]; BEGIN attrRecord: AttributeRecord = NARROW[RefTab.Fetch[machineProps.table, propAtom].val]; IF attrRecord = NIL THEN LOOP; [] _ DB.SetP[machineEntity, DB.V2E[attrRecord.attr], DB.S2V[propValue]]; 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 ENTRY PROC [file: Rope.ROPE] = { ENABLE UNWIND => NULL; stream: IO.STREAM; DoWriteMap: INTERNAL PROC = { setOfEntities: DB.EntitySet = DB.DomainSubset[machine]; entity: DB.Entity; WritePropName: RefTab.EachPairAction = { stream.Put[IO.rope[" "], IO.rope[Convert.RopeFromAtom[NARROW[key]]]]; quit _ FALSE }; WritePropValue: RefTab.EachPairAction = { attr: DB.Attribute = DB.V2E[NARROW[val, AttributeRecord].attr]; propertyValue: Rope.ROPE = DB.V2S[DB.GetP[entity, attr]]; stream.Put[IO.rope[Convert.RopeFromAtom[NARROW[key]]]]; stream.Put[IO.rope[" "], IO.rope[Convert.RopeFromRope[propertyValue]]]; stream.Put[IO.rope["\n"]]; quit _ FALSE }; 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.NameOf[entity]; rel: DB.Relship = DB.DeclareRelship[userRelation, LIST[AttributeValue[machineIs, entity]]]; 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[] }; IF file = NIL THEN RETURN; stream _ FS.StreamOpen[fileName: file, accessOptions: $create ! FS.Error => {stream _ NIL; CONTINUE }]; IF stream = NIL THEN RETURN; CarefullyApply[DoWriteMap] }; GetLogAndLoginUser: ENTRY PROC[] ~ { ENABLE UNWIND => NULL; ParseLog[]; InternalPutStateChange[$login, BasicTime.Now[]]; PlayLog[! FingerError => CONTINUE] }; OpenUp: Booting.RollbackProc = TRUSTED{ Process.Detach[FORK GetLogAndLoginUser[]] }; CloseDown: ENTRY Booting.CheckpointProc = { CloseTransaction[] }; Booting.RegisterProcs[r: OpenUp, c: CloseDown]; InitFingerDB[]; RegisterThisMachine[]; GetLogAndLoginUser[]; TRUSTED{ Process.Detach[FORK WatchDBActivity[]] }; [] _ Idle.RegisterIdleHandler[handler: RegisterLoginOrLogout] END.