BEGIN
machineNameRope: PUBLIC Rope.ROPE ← PupDefs.GetMyName[];
machineAddressRope: Rope.ROPE = PupDefs.PupAddressToRope[PupDefs.GetPupAddress[[0,0], machineNameRope]];
fingerSegmentRope: Rope.ROPE = "[Luther.Alpine]<Finger>Finger.segment";
fingerSegment: DB.Segment = $Finger;
fingerSegmentNumber: NAT = 260B;
fingerLogName: Rope.ROPE = "///Finger.Log";
fingerStream: IO.STREAM;
FingerError: PUBLIC ERROR[reason: FingerOps.Reason] ~ CODE;
fingerTransaction: DB.Transaction ← NIL;
idle: BOOL ← FALSE;
is the machine in the idle state; if so, don't bother connecting to the database to save any readMail log entries made (just collect them and replay them when you come out of idle)
activity: BOOL ← TRUE;
ticksToWait: Process.Ticks ← Process.SecondsToTicks[5*60];
LogEntry: TYPE = RECORD[event: FingerOps.StateChange, user: Rope.ROPE, time: BasicTime.GMT];
stateLog: LIST OF LogEntry ← NIL;
the log of all currently pending events that must be saved in the database
fingerableObject, person, machine: DB.Domain;
the domain of fingerableObjects includes the person and machine domains as subdomains
the properties of a fingerable object include the following
actualNameIs: DB.Attribute;
planIs: DB.Attribute;
pictureFileNameIs: DB.Attribute;
an additional property of a person is the last time he read his mail
mailReadTimeIs: DB.Attribute;
the interesting information for a machine is the last login/logout event on the machine
loginOrLogoutIs, loginOrLogoutTimeIs: DB.Attribute;
userRelation:
DB.Relation;
--this machineNameIs is a key of this relation
machineIs: DB.Attribute;
userIs: DB.Attribute;
WatchDBActivity:
PROC[] = {
WHILE
TRUE
DO
Process.Pause[ticksToWait];
CheckConnection[]
ENDLOOP
};
CheckConnection:
ENTRY
PROC[] = {
ENABLE UNWIND => NULL;
IF NOT activity THEN CloseTransaction[];
activity ← FALSE;
};
CloseTransaction:
INTERNAL
PROC [] = {
caughtAborted: BOOL ← FALSE;
BEGIN
ENABLE DB.Error, DB.Failure => CONTINUE;
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 DB.OpenTransaction[fingerSegment];
fingerTransaction ← DB.TransactionOf[fingerSegment] };
SetUpSegment:
INTERNAL
PROC[]
RETURNS [success:
BOOL] ~ {
ENABLE DB.Aborted, DB.Failure, DB.Error => {success ← FALSE; CONTINUE};
success ← TRUE;
DB.Initialize[nCachePages: 256];
DB.DeclareSegment[fingerSegmentRope, fingerSegment, fingerSegmentNumber, FALSE] };
InitFingerDB:
ENTRY
PROC =
BEGIN
ENABLE UNWIND => NULL;
[] ← SetUpSegment[]
END;
ResetSchema:
INTERNAL
PROC[] ~ {
OpenTransaction[];
IF NOT DB.Null[fingerableObject] THEN RETURN;
declare domains and subtypes:
fingerableObject ← DB.DeclareDomain["fingerableObject", fingerSegment];
person ← DB.DeclareDomain["person", fingerSegment];
DB.DeclareSubType[of: fingerableObject, is: person];
machine ← DB.DeclareDomain["machine", fingerSegment];
DB.DeclareSubType[of: fingerableObject, is: machine];
declare attributes as properties of fingerableObjects:
actualNameIs ← DB.DeclareProperty["ActualName", fingerableObject, DB.RopeType, fingerSegment];
planIs ← DB.DeclareProperty["FingerPlan", fingerableObject, DB.RopeType, fingerSegment];
pictureFileNameIs ← DB.DeclareProperty["PictureFileName", fingerableObject, DB.RopeType, fingerSegment];
additional properties of persons
mailReadTimeIs ← DB.DeclareProperty["TimeMailWasRead", person, DB.TimeType, fingerSegment];
additional properties of machines
userRelation ← DB.DeclareRelation["User", fingerSegment];
machineIs ← DB.DeclareAttribute[r: userRelation, name: "UserOf", type: machine, uniqueness: Key];
userIs ← DB.DeclareAttribute[r: userRelation, name: "UserIs", type: person];
loginOrLogoutIs ← DB.DeclareProperty["WhetherLoginOrLogout", machine, DB.RopeType, fingerSegment];
loginOrLogoutTimeIs ← DB.DeclareProperty["Login/LogoutTime", machine, DB.TimeType, fingerSegment]
};
SaveNewProps:
PUBLIC
ENTRY
PROC [object: FingerOps.FingerObject, actualName, plan, pictureFile: Rope.
ROPE ←
NIL] =
BEGIN
ENABLE UNWIND => NULL;
DoPut:
INTERNAL
PROC[] ~ {
fingerEntity:
DB.Entity =
DB.DeclareEntity [IF object.type = person THEN person ELSE machine, object.name];
IF actualName #
NIL
THEN
[] ← DB.SetP[fingerEntity, actualNameIs, DB.S2V[actualName]];
IF plan #
NIL
THEN
[] ← DB.SetP[fingerEntity, planIs, DB.S2V[plan]];
IF pictureFile #
NIL
THEN
[] ← DB.SetP[fingerEntity, pictureFileNameIs, DB.S2V[pictureFile]];
DB.MarkTransaction[fingerTransaction] };
IF stateLog # NIL THEN PlayLog[];
CarefullyApply[DoPut]
CarefullyApply:
INTERNAL
PROC [proc:
PROC[]] ~ {
ENABLE
BEGIN
DB.Error => GOTO Error;
DB.Failure => GOTO Failure;
END;
aborted: BOOL ← FALSE;
BEGIN
ENABLE DB.Aborted => { aborted ← TRUE; CONTINUE };
ResetSchema[];
proc[]
END;
IF NOT aborted THEN RETURN; -- no aborted occurred
DB.AbortTransaction[fingerTransaction]; -- now try again
BEGIN
ENABLE DB.Aborted => GOTO Aborted;
ResetSchema[];
proc[]
END;
EXITS
Aborted => {
DB.AbortTransaction[fingerTransaction];
fingerTransaction ← NIL;
ERROR FingerError[Aborted] };
Error => {
CloseTransaction[];
ERROR FingerError[Error] };
Failure => {
CloseTransaction[ ! DB.Failure => CONTINUE];
ERROR FingerError[Failure] } };
GetProps:
PUBLIC
ENTRY
PROC [object: FingerOps.FingerObject]
RETURNS [actualName, plan, pictureFile: Rope.
ROPE ←
NIL, data: FingerOps.ObjectData] =
BEGIN
ENABLE UNWIND => NULL;
DoGet:
INTERNAL PROC[] = {
domain: DB.Domain = IF object.type = machine THEN machine ELSE person;
entity: DB.Entity = DB.DeclareEntity[domain, object.name];
actualName ← DB.V2S[DB.GetP[entity, actualNameIs]];
plan ← DB.V2S[DB.GetP[entity, planIs]];
pictureFile ← DB.V2S[DB.GetP[entity, pictureFileNameIs]];
IF object.type = machine THEN data ← GetMachineData[entity]
ELSE data ← GetUserData[entity] };
IF stateLog # NIL THEN PlayLog[];
CarefullyApply[DoGet]
END;
GetMachineData:
INTERNAL
PROC[entity:
DB.Entity]
RETURNS[info: FingerOps.ObjectData] =
BEGIN
logOp: Rope.ROPE = DB.V2S[DB.GetP[entity, loginOrLogoutIs]];
operation: FingerOps.LoginLogout = IF Rope.Equal[logOp, "logout"] THEN logout ELSE login;
time: BasicTime.GMT = DB.V2T[DB.GetP[entity, loginOrLogoutTimeIs]];
userEntity: DB.Entity = DB.V2E[DB.GetP[entity, userIs, machineIs]];
info ← NEW[FingerOps.ObjectRecord ← [data: machine[lastUser: DB.NameOf[userEntity], operation: operation, time: time]]]
END;
GetUserData:
INTERNAL
PROC [entity:
DB.Entity]
RETURNS[info: FingerOps.ObjectData] =
BEGIN
attr: DB.AttributeValue = [attribute: userIs, lo: entity];
machineSet: DB.RelshipSet = DB.RelationSubset[userRelation, LIST[attr]];
machineData: LIST OF FingerOps.MachineData;
FOR nextRelship:
DB.Relship ←
DB.NextRelship[machineSet],
DB.NextRelship[machineSet]
UNTIL nextRelship =
NIL
DO
nextMachine: DB.Entity = DB.V2E[DB.GetF[nextRelship, machineIs]];
operation: FingerOps.LoginLogout = IF Rope.Equal[DB.V2S[DB.GetP[nextMachine, loginOrLogoutIs]], "logout"] THEN logout ELSE login;
time: BasicTime.GMT = DB.V2T[DB.GetP[nextMachine, loginOrLogoutTimeIs]];
machineData ← CONS[[DB.NameOf[nextMachine], operation, time], machineData];
ENDLOOP;
DB.ReleaseRelshipSet[machineSet];
info ← NEW[FingerOps.ObjectRecord ← [data: person[mailRead: DB.V2T[DB.GetP[entity, mailReadTimeIs]], used: machineData]]];
END;
GetMatchingObjects:
PUBLIC
ENTRY
PROC [pattern: Rope.
ROPE]
RETURNS [result: FingerOps.FingerList] = {
Find all of the fingerable objects with names matching the given pattern
ENABLE UNWIND => NULL;
DoGet:
PROC[] ~ {
fingeredEntity: DB.Entity;
setOfEntities: DB.EntitySet = DB.DomainSubset[fingerableObject];
finder: TextFind.Finder ← TextFind.CreateFromRope[pattern: pattern, ignoreCase: TRUE];
WHILE (fingeredEntity ←
DB.NextEntity[setOfEntities]) #
NIL
DO
name: Rope.ROPE = DB.NameOf[fingeredEntity];
IF TextFind.SearchRope[finder, name].found
THEN {
domain: DB.Domain = DB.DomainOf[fingeredEntity];
fingeredObject: FingerOps.FingerObject =
[name: name, type: IF DB.Eq[domain, machine] THEN machine ELSE person];
result ← CONS[fingeredObject, result] }
ENDLOOP;
DB.ReleaseEntitySet[setOfEntities] };
CarefullyApply[DoGet]
};
PlayLog:
INTERNAL
PROC[] ~ {
DoPlay:
INTERNAL
PROC[] ~ {
thisMachine: DB.Entity = DB.DeclareEntity[machine, machineNameRope];
WHILE stateLog #
NIL
DO
user: DB.Entity = DB.DeclareEntity[person, stateLog.first.user];
SELECT stateLog.first.event
FROM
$mailRead => {
oldTime: BasicTime.GMT = DB.V2T[DB.GetP[user, mailReadTimeIs]];
IF oldTime # BasicTime.nullGMT
AND BasicTime.Period[from: oldTime, to: stateLog.first.time] < 0
THEN
-- throw away the log entry
{ stateLog ← stateLog.rest; LOOP };
[] ← DB.SetP[user, mailReadTimeIs, DB.T2V[[stateLog.first.time]]] };
$login => {
[] ← DB.SetP[thisMachine, loginOrLogoutTimeIs, DB.T2V[[stateLog.first.time]]];
[] ← DB.SetP[thisMachine, loginOrLogoutIs, DB.S2V["login"]];
[] ← DB.SetP[thisMachine, userIs, user, machineIs] };
$logout => {
[] ← DB.SetP[thisMachine, loginOrLogoutTimeIs, DB.T2V[[stateLog.first.time]]];
[] ← DB.SetP[thisMachine, loginOrLogoutIs, DB.S2V["logout"]];
[] ← DB.SetP[thisMachine, userIs, user, machineIs] };
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
fingerStream.SetLength[0] };
AttemptToPlayLog:
ENTRY
PROC[] = {
[] ← SetUpSegment[];
PlayLog[! FingerError => CONTINUE] };
RegisterLoginOrLogout:
ENTRY IdleExtras.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;
newEntry: LogEntry = [change, name, time];
first write it to the log
IF fingerStream = NIL THEN fingerStream ← FS.StreamOpen[fingerLogName, $append];
fingerStream.Put[IO.rope["\n"]];
fingerStream.Put[IO.atom[change], IO.rope[Rope.Cat[" ", name, " "]], IO.time[time]];
fingerStream.Flush[];
IF stateLog = NIL THEN stateLog ← LIST[newEntry]
ELSE {
log: LIST OF LogEntry ← stateLog;
WHILE log.rest # NIL DO log ← log.rest ENDLOOP;
log.rest ← LIST[newEntry]
}
};
RegisterMailRead:
PUBLIC
ENTRY
PROC =
BEGIN
ENABLE UNWIND => NULL;
InternalPutStateChange[$mailRead, BasicTime.Now[]];
IF NOT idle THEN PlayLog[! FingerError => CONTINUE];
END;
RegisterThisMachine:
ENTRY
PROC =
BEGIN
ENABLE UNWIND => NULL;
DoRegistration:
PROC[] = {
thisMachine: DB.Entity = DB.DeclareEntity[machine, machineNameRope];
[] ← DB.SetP[thisMachine, actualNameIs, DB.S2V[machineAddressRope]];
DB.MarkTransaction[fingerTransaction] };
CarefullyApply[DoRegistration]
END;
RegisterWithWalnut:
PROC =
TRUSTED
BEGIN
DO
IF PrincOpsUtils.IsBound[WalnutMailExtras.NotifyAfterNewMail]
THEN {
WalnutMailExtras.NotifyAfterNewMail[proc: RegisterMailRead];
EXIT };
Process.Pause[Process.SecondsToTicks[60]]
ENDLOOP
END;
ParseLog:
INTERNAL
PROC[] ~ {
ENABLE FS.Error => CONTINUE;
fingerStream: IO.STREAM = FS.StreamOpen[fingerLogName, $read];
BEGIN
ENABLE IO.EndOfStream, IO.Error => CONTINUE;
time: BasicTime.GMT;
op: FingerOps.StateChange;
user: Rope.ROPE;
stateLog ← NIL; -- throw away the contents of the current log (it's all in the file anyhow!)
WHILE
TRUE
DO
op ← fingerStream.GetAtom[];
user ← fingerStream.GetID[];
time ← fingerStream.GetTime[];
IF stateLog = NIL THEN stateLog ← LIST[LogEntry[op, user, time]]
ELSE {
log: LIST OF LogEntry ← stateLog;
WHILE log.rest # NIL DO log ← log.rest ENDLOOP;
log.rest ← LIST[LogEntry[op, user, time]]
}
ENDLOOP
END;
IF fingerStream # NIL THEN fingerStream.Close[! IO.Error, FS.Error => CONTINUE]
};
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 RegisterWithWalnut[]];
Process.Detach[FORK WatchDBActivity[]] };
[] ← IdleExtras.RegisterIdleHandler[handler: RegisterLoginOrLogout]