<> <> <> 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.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; <> 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; <> fingerableObject, person, machine: DB.Domain; <> <> actualNameIs: DB.Attribute; planIs: DB.Attribute; pictureFileNameIs: DB.Attribute; <> mailReadTimeIs: DB.Attribute; <> 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; <> 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]; <> actualNameIs _ DB.DeclareProperty["ActualName", fingerableObject, DB.RopeType, fingerSegment]; planIs _ DB.DeclareProperty["FingerPlan", fingerableObject, DB.RopeType, fingerSegment]; pictureFileNameIs _ DB.DeclareProperty["PictureFileName", fingerableObject, DB.RopeType, fingerSegment]; <> mailReadTimeIs _ DB.DeclareProperty["TimeMailWasRead", person, DB.TimeType, fingerSegment]; <> 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] END; 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] = { <> 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]; <> 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] ~ { <> 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]; <> 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.