BEGIN
OPEN FingerOps, FingerLog;
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>7.0>Finger.segment"];
fingerSegment: DBDefs.Segment = $Finger;
fingerSegmentNumber: NAT = 260B;
fingerTransaction: DBCommon.TransactionHandle ← 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;
activity: BOOL ← TRUE;
ticksToWait: Process.Ticks ← Process.SecondsToTicks[10];
SomethingHappened: CONDITION;
The database stores information about people and machines
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;
login, logout, and unknown are the three events in the event domain
The most important relation is which user is currently using (or being used by) a machine
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
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.Relation;
lastChangedTime: CARDINAL = 1;
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
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;
the database information to compute the stored machine property version stamp
machinePropRelation: DBDefs.Relation;
machinePropAttr: CARDINAL = 0;
machinePropVersionRelship: DBDefs.Relship; -- the single relship of this relation
the database information to compute the stored user property version stamp
userPropRelation: DBDefs.Relation;
userPropAttr: CARDINAL = 0;
userPropVersionRelship: DBDefs.Relship; -- the single relship of this relation
The names of all of the attributes of people and machines are stored in the following domains:
userPropNames: DB.Domain;
machinePropNames: DB.Domain;
the property lists for machines and users
PropertyTable: TYPE = RECORD[names: DB.Domain ← NIL, table: RefTab.Ref];
a property table consists of a DB.Domain that can be used enumerate all of the properties to be stored in the table and a RefTab containing the DB.Relation for each property
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 <literal>*.
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 <string>* the replace with the first possible match (<string>) and the first mismatch (replace the last character of <string> 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[] ~ {
The Domains
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"]];
The property version stamp relations
machinePropRelation ← DB.DeclareRelation[name: "MachineVersion", segment: fingerSegment, fields: versionType];
Make sure that only one relationship exists in the relation
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];
The property name relations
machinePropNames ← DB.DeclareDomain[name: "MachineProps", segment: fingerSegment];
machineProps.names ← machinePropNames;
userPropNames ← DB.DeclareDomain[name: "UserProps", segment: fingerSegment];
userProps.names ← userPropNames;
The basic relation between users and machines
userRelation ← DB.DeclareProperty[name: "UserInfo", segment: fingerSegment, fields: userRelationType];
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)
timeIndex ← DB.DeclareIndex[userRelation, DB.L2F[LIST[lastEventIs, lastEventTimeIs]]];
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.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] = {
here fill in the attributes from the contents of the propTable
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] = {
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
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 <literal>*.
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];
If the time is more than a week, then the machine record is probably bogus
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 = {
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];
This nasty little hack is designed to get around the problem that the input file may be a tioga file and so has a root node containing nothing at the beginning of the file.--edt
IF line[0] = Ascii.NUL THEN 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, DB.I2V[0]];
Throw away all of the current machine property relations
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;
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;
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]
~ {
This is the equivalent of ReadMachineMap. --edt
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];
This nasty little hack is designed to get around the problem that the input file may be a tioga file and so has a root node containing nothing at the beginning of the file.--edt
IF line[0] = Ascii.NUL THEN line ← fileStream.GetLine[line];
tokenStream ← IO.TIS[line, tokenStream];
IF resetProperties
THEN {
Set the user property version stamp back to zero, since the entire database is about to be loaded from the contents of the dump file.
DB.SetF[userPropVersionRelship, userPropAttr, DB.I2V[0]];
Throw away all of the current user property relations
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;
Each user 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;
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] ~ {
This is the equivalent of WriteMachineMap that I hacked together. --edt
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[] };
Procedures exported to RemoteFingerOps
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;
First a little sanity check. If the entry is bogus, just ignore it and count it as being successfully processed.
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 the version numbers don't match, then it's not wise to perform the update
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];
if this succeeds, the client can be told all is well: the entire request has been processed.
success ← TRUE
};
Booting.RegisterProcs[c: CloseDown];
InitFingerDB[];
TRUSTED{ Process.Detach[FORK WatchDBActivity[]] };