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.  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 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) the log of all currently pending events that must be saved in the database the domain of fingerableObjects includes the person and machine domains as subdomains the properties of a fingerable object include the following an additional property of a person is the last time he read his mail the interesting information for a machine is the last login/logout event on the machine declare domains and subtypes: declare attributes as properties of fingerableObjects: additional properties of persons additional properties of machines Find all of the fingerable objects with names matching the given pattern if this succeeds, then the contents of the log file can be thrown away, since they have all been played to the database record that the current user has logged off of this machine (entries are changed for both the user and the machine) first write it to the log Κ‘˜codešœ™Kšœ5™5K™9—K˜šΟk ˜ Kšœ œœ˜,K˜Kšœ˜Kšœ ˜ Kšœœ˜Kšœ œ0˜@Kšœœœ_˜nKšœœ.˜;Kšœœ(˜5Kšœœœ˜Kšœœ ˜Kšœ œ&˜4Kšœœ˜Kšœœ˜,—K˜šœœ˜K˜š˜Kšœœh˜~—K˜š˜Kšœ ˜ —K˜š˜K˜Kšœœœ˜8KšœœK˜hK˜Kšœœ+˜GKšœœ˜$Kšœœ˜ K˜Kšœœ˜+Kšœœœ˜K˜Jšœ œœœ˜;J˜Jšœœœ˜(J˜Jšœœœ˜Jšœ΄™΄J˜Jšœ œœ˜J˜J˜:J˜Jš œ œœ*œœ˜\J˜Jšœ œœ œ˜!JšœJ™JJ˜Kšœ#œ˜-KšœU™UK˜Kšœ;™;Kšœœ ˜Kšœœ ˜Kšœœ ˜ K˜KšœD™DKšœœ ˜K˜KšœW™WKšœ&œ ˜3šœœ Οc.˜JKšœ œ ˜Kšœœ ˜—K˜šΟnœœ˜šœœ˜ Jšœ˜Jšœ˜Jš˜—Jšœ˜J˜—šŸœœœ˜!Jšœœœ˜Jšœœ œ˜(Jšœ œ˜J˜—J˜šŸœœœ˜&Jšœœœ˜š˜Jšœœœ œ˜(šœœ˜šœ#˜%Jšœœœœ˜4——Jšœœœ$˜<—Jšœ˜Jšœœ˜J˜—šŸœœœ˜%Jšœœœ ˜BJšœœ ˜6J˜—š Πbn œœœœ œ˜9Jš œœ œ œœœ˜GJšœ œ˜Jšœ˜ JšœGœ˜R—K˜šŸ œœœ˜š˜Kšœœœ˜K˜—Kšœ˜—K˜šŸ œœœ˜ Kšœ˜Kš œœœœœ˜-šœ™Kšœœ2˜GKšœ œ(˜3Kšœ2˜4Kšœ œ)˜5Kšœ3˜5—šœ6™6Kšœœ1œ˜^Kšœ œ1œ˜XKšœœ6œ˜h—šœ ™ Kšœœ,œ˜[—šœ!™!Kšœœ(˜9Kšœ œT˜bKšœ œA˜LKšœœ2œ˜bKšœœ.œ˜a—K˜—K˜š Ÿ œœœœFœœ˜rš˜Kšœœœ˜šŸœœœ˜šœœ ˜Kšœœœœ˜Q—šœœ˜Kšœœ"œ˜=—šœœ˜Kšœœœ ˜1—šœœ˜Kšœœ'œ˜C—Kšœ&˜(—Kšœ œœ ˜!K˜—šœ˜K˜——šŸœœœœ˜0šœ˜ Jšœ œ˜Jšœ œ ˜Jšœ˜—Jšœ œœ˜š˜Jšœœœœ˜2Jšœ˜Jšœ˜Jšœ˜—Jš œœ œœž˜2Jšœ&ž˜8š˜Jšœœ œ˜"Jšœ˜Jšœ˜Jšœ˜—š˜šœ ˜ Jšœ%˜'Jšœœ˜Jšœ˜—šœ ˜ Jšœ˜Jšœ˜—šœ ˜ Jšœœ œ˜,Jšœ˜——J˜—šŸœœœœ"œ&œœ˜“š˜Kšœœœ˜šŸœ œ˜Kš œœ œœ œ˜FKšœœ œ$˜:Kšœ œœ˜3Kšœœœ˜'Kšœœœ"˜9Kšœœ˜;Kšœ˜"—Kšœ œœ ˜!K˜—Kšœ˜—K˜š Ÿœœœ œ œ˜Vš˜Kšœ œœœ ˜Kšœ œœ˜,šœ)œ˜1Kšœœ œ˜0šœ(˜(Kš œœœœ œ ˜G—Kšœ œ˜'——Kšœ˜Kšœ#˜%—K˜K˜—K˜šŸœœœ˜šŸœœœ˜Kšœ œ œ)˜Dšœ œ˜Kšœœ œ,˜@šœ˜ šœ˜Kšœœœœ˜?šœœ>œž˜€Kšœœ˜#—Kšœœœ˜D—šœ ˜ Kšœœ(œ˜NKšœœ$œ˜š˜Jšœœœ œ˜,Kšœœ˜Kšœ˜Kšœ œ˜Kšœ œžL˜\šœœ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ œœ œ˜@šœ˜Kšœœœ˜!Kšœ œœœ˜/Kšœ œ˜)K˜—Kš˜—Kšœ˜—Kš œœœœœ œ˜OJ˜—J˜šŸœœœ˜$Jšœœœ˜Jšœ ˜ Jšœ0˜0Jšœœ˜"Jšœ˜—J˜Jšœœœ˜TJ˜šœ œ1˜AJ˜—Jšœ/˜/Kšœ˜K˜Kšœ˜šœ˜Kšœœ˜*Kšœœ˜)—KšœC˜C—Kšœ˜——…—0²Eσ