BEGIN
OPEN FingerOps, FingerLog, DB;
The name of the machine currently running Finger
machineName: Rope.ROPE = ThisMachine.Name[$Pup];
All of the data for the Finger database: its name and segment type and (if non-NIL), the current transaction open on the database
fingerSegmentRope: Rope.ROPE = UserProfile.Token[key: "Finger.segment", default: "[Luther.Alpine]<Finger>Finger.segment"];
fingerSegment: DB.Segment = $Finger;
fingerSegmentNumber: NAT = 260B;
fingerTransaction: DB.Transaction ← NIL;
If something goes wrong, this is what gets raised. If the reason is Failure, then can't contact the server; if it's Aborted, then the transaction was aborted after attempting to recover; if it's Error, then it should be a program error (but this may not always be true)
FingerError: PUBLIC ERROR[reason: FingerOps.Reason] ~ CODE;
idle: BOOL ← FALSE;
is the machine in the idle state; if so, don't bother connecting to the database to save any log entries made (just collect them and replay them when you come out of idle)
activity: BOOL ← TRUE;
ticksToWait: Process.Ticks ← Process.SecondsToTicks[10];
SomethingHappened: CONDITION;
stateLog: LIST OF LogEntry ← NIL;
The database stores information about people and machines
person, machine: DB.Domain;
The most important relation is which user is currently using (or being used by) a machine
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
We also record the time at which updates to the properties of any user were last changed; if entries exist on the log of some machine for updates that precede the last change recorded in the database, then we do not perform the changes (they're out of date!)
lastChangedProp: DB.Attribute;
The database records version numbers for the properties of machines and users (so that when recovering from aborts, we don't need to completely rebuild the property tables). These property numbers are updated each time new properties are added to the database. On recovering from an aborted transaction, if the version numbers in the database and program match, then the RefTabs for the machine properties and user properties are enumerated to reset the attribute entries; if the version numbers disagree, then the list of properties must be completely recovered from the new contents of the database
machinePropVersion, userPropVersion: INT ← -1;
the database information to compute the stored machine property version stamp
machinePropRelation: DB.Relation;
machinePropAttr: DB.Attribute;
machinePropVersionRelship: DB.Relship; -- the single relship of this relation
the database information to compute the stored user property version stamp
userPropRelation: DB.Relation;
userPropAttr: DB.Attribute;
userPropVersionRelship: DB.Relship; -- the single relship of this relation
The names of all of the attributes of people and machines are stored in the following relation:
userPropNames: DB.Relation;
userPropNameIs: DB.Attribute;
machinePropNames: DB.Relation;
machinePropNameIs: DB.Attribute;
The mapping between login/logout and the integer actually stored in the database
login: INT = 2;
logout: INT = 1;
the property lists for machines and users
PropertyTable: TYPE = RECORD[names: DB.Relation ← NIL, nameAttr: DB.Attribute ← NIL, table: RefTab.Ref];
a property table consists of a DB.Relation that can be used enumerate all of the properties to be stored in the table and a RefTab containing all of the property attributes (ie., the DB.Attribute for each name); the values of the names and nameAttr fields is set when performing a ResetSchema
machineProps: PropertyTable ← [table: RefTab.Create[]];
userProps: PropertyTable ← [table: RefTab.Create[]];
What is stored as the values of the entries in a property table is the pair of attributes for the relation
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;
The Domains
person ← DB.DeclareDomain["person", fingerSegment];
machine ← DB.DeclareDomain["machine", fingerSegment];
The property version stamp relations
machinePropRelation ← DB.DeclareRelation[name: "MachineVersion", segment: fingerSegment];
machinePropAttr ← DB.DeclareAttribute[r: machinePropRelation, name: "stamp", type: IntType];
Make sure that only one relationship exists in the relation
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];
Make sure that only one relationship exists in the relation
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];
The property name relations
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;
The basic relation between users and machines
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];
The index maintained for the database sorts the userRelation by the last event (so that all the currently free machines can be found) and user (so all the machines currently in use by a single user will be given in order)
[] ← DB.DeclareIndex[userRelation, LIST[lastEventIs, userIs]];
check the version stamps for the properties of machines and users. If they agree, then just enumerate the entries already in the property tables to set up the attributes; otherwise, enumerate the property name relations in the database to determine what the proper set of property entries should be
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[];
throw away the old property list
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] = {
here fill in the attributes from the contents of the propTable
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] = {
the list in the table is good enough (it may be out of date, but you'll find out when attempting to use one of the properties
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 the login event is real old, discard it (probably someone didn't logout through Finger)
IF BasicTime.Period[from: time, to: now] > twoDaysInSeconds THEN LOOP;
Be sure to check for duplicates
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;
If the time is more than a week, then the machine record is probably bogus
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 the version numbers don't match, then it's not wise to perform the update
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];
if this succeeds, then the contents of the log file can be thrown away, since they have all been played to the database
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] ~ {
record that the current user has logged off of this machine (entries are changed for both the user and the machine)
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 {
Write the entry on the log file and also on the internal log list
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 = {
the first line contains the list of properties for machines
the successive line contain the name and net address of each machine followed by a lines of property value pairs; a blank line separates the data for each machine
line: REF TEXT ← RefText.New[500];
line ← fileStream.GetLine[line];
tokenStream ← IO.TIS[line, tokenStream];
IF resetProperties
THEN {
Set the machine property version stamp back to zero, since the entire database is about to be loaded from the contents of the dump file.
DB.SetF[machinePropVersionRelship, machinePropAttr, NEW[INT ← 0]];
Throw away all of the current machine property relations
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;
Each machine entry begins with the name of the machine, followed by a sequence of line with property value pairs, followed by a blank line
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]