FingerOpsImpl.mesa
Last Edited by: Khall, August 31, 1984 1:26:35 pm PDT
Last Edited by: Donahue, November 13, 1984 9:15:30 am PST
DIRECTORY
BasicTime USING [GMT, Now, nullGMT, Period],
Booting,
DB,
FingerOps,
FS USING[StreamOpen, Error],
IdleExtras USING [IdleReason, IdleHandler, RegisterIdleHandler],
IO USING[STREAM, GetAtom, GetID, GetTime, Error, EndOfStream, atom, rope, time, Put, Close, Flush, SetLength],
PupDefs USING [GetMyName, PupAddressToRope, GetPupAddress],
Process USING [Detach, Ticks, SecondsToTicks, Pause],
Rope USING [ROPE, Equal, Cat],
PrincOpsUtils USING[IsBound],
TextFind USING [CreateFromRope, Finder, SearchRope],
UserCredentials USING [Get],
WalnutMailExtras USING [NotifyAfterNewMail];
FingerOpsImpl: CEDAR MONITOR
IMPORTS
BasicTime, Booting, DB, FS, IdleExtras, IO, PupDefs, Process, PrincOpsUtils, Rope, TextFind, UserCredentials, WalnutMailExtras
EXPORTS
FingerOps =
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: BOOLFALSE;
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: BOOLTRUE;
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: BOOLFALSE;
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.ROPENIL] =
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]
END;
CarefullyApply: INTERNAL PROC [proc: PROC[]] ~ {
ENABLE BEGIN
DB.Error => GOTO Error;
DB.Failure => GOTO Failure;
END;
aborted: BOOLFALSE;
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.ROPENIL, 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]
END.