DIRECTORY CedarSnapshot USING [After, CheckpointProc, RollbackProc, Register], FileIO USING [Open], IO USING [CreateProcsStream, CreateRefStreamProcs, CreateDribbleStream, CreateBufferedOutputStream, Close, Flush, GetBlock, GetIndex, GetLength, noWhereStream, PutBlock, PutChar, PutF, PutRope, rope, SetLength, SetIndex, STREAM, time], List USING [Nconc1], MessageWindow USING [Append, Clear], Process USING [ GetCurrent], Rope USING [Cat, Concat, Equal, ROPE], Time USING [Current], TiogaOps USING [FirstChild, ViewerDoc, GetRope], UserExec USING [GetExecHandle, ExecHandle], UserExecExtras USING [], UserExecPrivate USING [ExecPrivateRecord], UserProfile USING [Boolean, Token] ; UserExecLogImpl: CEDAR MONITOR LOCKS data USING data: REF MonitoredStreamRecord IMPORTS CedarSnapshot, FileIO, IO, List, MessageWindow, Process, Rope, Time, TiogaOps, UserExec, UserProfile EXPORTS UserExec, UserExecExtras, UserExecPrivate = BEGIN OPEN IO; ExecPrivateRecord: PUBLIC TYPE = UserExecPrivate.ExecPrivateRecord; sessionLog: IO.STREAM; sessionLogFileStream: IO.STREAM; GetSessionLog: PUBLIC PROCEDURE RETURNS [IO.STREAM] = { IF NOT UserProfile.Boolean["CreateSessionLog", TRUE] THEN RETURN[NIL]; IF sessionLog = NIL THEN CreateSessionLog[]; RETURN[IO.CreateBufferedOutputStream[stream: sessionLog, deliverWhen: NIL, bufferSize: 0]]; }; -- of GetSessionLog CreateSessionLog: PROCEDURE [] = { sessionLogFileStream _ CreateLog["Session.Log"]; sessionLogFileStream.PutF["New Session began at %t\n", time[]]; sessionLog _ CreateMonitoredStream[out: sessionLogFileStream, proc: SessionLogProc]; }; -- of CreateSessionLog lastLoggedExec: UserExec.ExecHandle; SessionLogProc: PROC[self: IO.STREAM] = TRUSTED { thisExec: UserExec.ExecHandle = UserExec.GetExecHandle[process: Process.GetCurrent[]]; IF self # sessionLogFileStream THEN ERROR; IF thisExec # lastLoggedExec THEN { IF thisExec # NIL THEN { private: REF UserExecPrivate.ExecPrivateRecord = thisExec.privateStuff; sessionLogFileStream.PutF["\n\n----------------------Work Area %g----------------------\n", rope[private.id]]; } ELSE sessionLogFileStream.PutRope["\n\n--------------------------------------------\n"]; -- e.g. somebody else, like Jam typescript, got a sessionlog stream. lastLoggedExec _ thisExec; }; }; -- of SessionLogPutBlock changesLog: IO.STREAM; changesLogFileStream: IO.STREAM; GetChangesLog: PUBLIC PROCEDURE RETURNS [IO.STREAM] = { IF NOT UserProfile.Boolean["CreateChangesLog", TRUE] THEN RETURN[NIL]; IF changesLog = NIL THEN CreateChangesLog[]; RETURN[IO.CreateBufferedOutputStream[stream: changesLog, deliverWhen: NIL, bufferSize: 0]]; }; -- of GetChangesLog CreateChangesLog: PROCEDURE = { MessageWindow.Append["Creating Changes Log...", TRUE]; changesLogFileStream _ CreateLog["Changes.Log"]; MessageWindow.Clear[]; changesLogFileStream.PutF["Changes log created at %t\n", time[]]; changesLog _ CreateMonitoredStream[out: changesLogFileStream]; }; MonitoredStreamRecord: TYPE = MONITORED RECORD[stream: IO.STREAM, proc: PROC[self: IO.STREAM] _ NIL]; CreateMonitoredStream: PUBLIC PROCEDURE [out: IO.STREAM, proc: PROC[self: IO.STREAM] _ NIL] RETURNS [IO.STREAM] = -- if supplied, proc is called each time characters are output -- { RETURN[IO.CreateProcsStream[ streamProcs: IO.CreateRefStreamProcs[putBlock: MonitoredPutBlock, putChar: MonitoredPutChar, name: "Monitored"], streamData: NEW[MonitoredStreamRecord _ [stream: out, proc: proc]] ]]; }; -- of CreateMonitoredStream MonitoredPutBlock: PROC[self: IO.STREAM, block: REF READONLY TEXT, startIndex: NAT _ 0, stopIndexPlusOne: NAT _ LAST[NAT]] = { data: REF MonitoredStreamRecord = NARROW[self.streamData]; DoIt: ENTRY PROC [data: REF MonitoredStreamRecord] = { ENABLE UNWIND => NULL; IF data.proc # NIL THEN data.proc[data.stream]; IO.PutBlock[data.stream, block, startIndex, stopIndexPlusOne]; IO.Flush[data.stream]; }; DoIt[data]; }; -- of MonitoredPutBlock MonitoredPutChar: PROC[self: IO.STREAM, char: CHARACTER] = { -- the main reason why Putchar has to be implemented is to support a default implementation of eraseChar, e.g. if user types ^W or ^A when nothing is in the buffer. This can occur if user types line{ESC}^W, since the {ESC} causes material to be flushed, i.e. sent from buffered stream through monitored stream to the backing file data: REF MonitoredStreamRecord = NARROW[self.streamData]; DoIt: ENTRY PROC [data: REF MonitoredStreamRecord] = { ENABLE UNWIND => NULL; IF data.proc # NIL THEN data.proc[data.stream]; IO.PutChar[data.stream, char]; IO.Flush[data.stream]; }; DoIt[data]; }; -- of MonitoredPutChar CreateExecLogFile: PUBLIC PROC [exec: UserExec.ExecHandle] RETURNS[stream: IO.STREAM] = TRUSTED { private: REF ExecPrivateRecord = exec.privateStuff; name: Rope.ROPE = Rope.Cat["WorkArea", private.id, ".log"]; sessionLog: IO.STREAM = GetSessionLog[]; IF NOT UserProfile.Boolean["CreateWorkAreaLogs", FALSE] THEN RETURN[sessionLog]; IF NOT Rope.Equal[private.id, "A"] THEN MessageWindow.Append[Rope.Cat["Creating log for Work Area ", private.id, "..."], TRUE]; stream _ CreateLog[name, exec]; IF sessionLog # NIL THEN stream _ IO.CreateDribbleStream[stream, sessionLog]; stream.PutF["Work Area %g created at %t\n", rope[private.id], time[]]; IF NOT Rope.Equal[private.id, "A"] THEN MessageWindow.Clear[]; }; CloseExecLogFile: PUBLIC PROC [exec: UserExec.ExecHandle] = { FOR l: LIST OF REF LogFileRecord _ activeLogs, l.rest UNTIL l = NIL DO IF l.first.exec = exec THEN { IF l.first.log # NIL THEN {IO.Close[l.first.log]; l.first.log _ NIL}; -- so won't try to reopen following a rollback. EXIT; }; ENDLOOP; }; activeLogs: PUBLIC LIST OF REF LogFileRecord _ NIL; LogFileRecord: TYPE = RECORD[log: IO.STREAM, name: Rope.ROPE, length: INT _ 0, exec: UserExec.ExecHandle]; CreateLog: PROC [name: Rope.ROPE, exec: UserExec.ExecHandle _ NIL, dontRegister: BOOL _ FALSE] RETURNS[stream: IO.STREAM] = { r: Rope.ROPE = UserProfile.Token["WhenLogFileExists", "Rename"]; IF Rope.Equal[r, "Append", FALSE] THEN { stream _ FileIO.Open[fileName: name, accessOptions: append]; stream.PutRope["\n\n#######################################"]; } ELSE IF Rope.Equal[r, "OverWrite", FALSE] THEN { stream _ FileIO.Open[fileName: name, accessOptions: write]; IO.SetLength[stream, 0]; } ELSE { stream _ FileIO.Open[fileName: name, accessOptions: write]; SaveOldLog[name, stream]; IO.SetLength[stream, 0]; }; IF NOT dontRegister THEN RegisterLog[log: stream, name: name, exec: exec]; }; SaveOldLog: PROC [name: Rope.ROPE, stream: IO.STREAM] = { len: INT = IO.GetLength[stream]; dollarName: Rope.ROPE = Rope.Concat[name, "$"]; dollarStream: IO.STREAM = FileIO.Open[fileName: dollarName, accessOptions: overwrite, createLength: len]; bufferSize: INT = 512; buffer: REF TEXT = NEW[TEXT[bufferSize]]; bytes: INT; IO.SetIndex[stream, 0]; WHILE (bytes _ stream.GetBlock[buffer]) > 0 DO dollarStream.PutBlock[buffer]; ENDLOOP; dollarStream.Close[]; }; RegisterLog: PROC [log: IO.STREAM, name: Rope.ROPE _ NIL, exec: UserExec.ExecHandle _ NIL] = TRUSTED { activeLogs _ LOOPHOLE[List.Nconc1[ LOOPHOLE[activeLogs], NEW[LogFileRecord _ [log: log, name: name, exec: exec]] ]]; }; CheckpointProc: CedarSnapshot.CheckpointProc = {AtCheckpoint[]}; RollbackProc: PROC [after: CedarSnapshot.After] -- CedarSnapshot.RollbackProc-- = {AtRollback[after]}; AtCheckpoint: PROC = { FOR l: LIST OF REF LogFileRecord _ activeLogs, l.rest UNTIL l = NIL DO IF l.first.exec # NIL THEN { private: REF ExecPrivateRecord = l.first.exec.privateStuff; IF private.execState = destroyed THEN l.first.log _ NIL; }; IF l.first.log # NIL THEN { IO.Flush[l.first.log]; l.first.length _ IO.GetIndex[l.first.log]; IO.Close[l.first.log]; l.first.log^ _ IO.noWhereStream^; }; ENDLOOP; }; AtRollback: PROC [after: CedarSnapshot.After] = { FOR l: LIST OF REF LogFileRecord _ activeLogs, l.rest UNTIL l = NIL DO IF l.first.log # NIL THEN { name: Rope.ROPE = l.first.name; log: IO.STREAM; log _ CreateLog[name: name, exec: l.first.exec, dontRegister: TRUE]; IF Rope.Equal[UserProfile.Token["WhenLogFileExists", "Rename"], "Append", FALSE] THEN NULL -- this sessions log will be appended. ELSE IF IO.GetLength[log] >= l.first.length THEN IO.SetLength[log, l.first.length] -- Truncate log back to the point of the checkpoint. If length < l.first.length, file was somehow clobbered. in case of exec, can recover from typescript ELSE IF l.first.exec # NIL THEN { private: REF ExecPrivateRecord = l.first.exec.privateStuff; name: Rope.ROPE = Rope.Cat["WorkArea", private.id, ".log"]; log _ FileIO.Open[fileName: name, accessOptions: overwrite]; log.PutRope[TiogaOps.GetRope[TiogaOps.FirstChild[TiogaOps.ViewerDoc[l.first.exec.viewer]]]]; -- assumes only one node in typescript } ELSE IF l.first.log = sessionLogFileStream THEN { IO.SetLength[log, 0]; sessionLogFileStream^ _ log^; sessionLogFileStream.PutRope["****Previous session log was lost!\n"]; } ELSE IF l.first.log = changesLogFileStream THEN { IO.SetLength[log, 0]; changesLogFileStream^ _ log^; changesLogFileStream.PutRope["****Previous changes log was lost!\n"]; } ELSE ERROR; -- shouldn't happen l.first.log^ _ log^; -- already done for last two cases, but safer to have it here anyway }; ENDLOOP; IF sessionLogFileStream # NIL THEN sessionLogFileStream.PutF["\n%g at %t\n", rope[IF after = checkpoint THEN "Returned from Checkpoint" ELSE "Rolled back"], time[] ]; }; TRUSTED {CedarSnapshot.Register[c: CheckpointProc, r: RollbackProc]}; END. -- of UserExecLogImpl ΞLast Edited by: Teitelman, June 20, 1983 3:01 pm so can access private.id Session Log each call to GetSessionLog returns a new buffered stream connected to the same monitored stream Changes Log Monitored Stream A monitored stream supports only two operations, PutBlock and PutChar. Furthermore, the call to putblock is monitored. The idea is that one can hook up several streams to the same monitored stream by using IO.CreateBufferedOutputStream. Typically, characters will be sent to the monitored stream only as a result of a Flush operation on the buffered stream. Creating log file for execs CheckPoint and rollback when monitored stream gets moved elsewhere, make RegisterLog monitored note that it is not necessary to do the dribble stream since we are going to smash this log into any existing streams that go to this file, and we have done the same for sessionLog. Therefore any outstanding dribble streams that point to both are automatically now valid Κ ‘– "Cedar" style˜J˜J™0šΟk ˜ Jšœœ1˜DJšœœ˜JšœœΥœ˜λJšœœ ˜Jšœœ˜$Jšœœ˜Jšœœœ˜&Jšœœ ˜Jšœ œ"˜0Jšœ œ˜+Jšœœ˜Jšœœ˜*Jšœ œ˜"Jšœ˜—J˜Jš Πblœœ œœœ˜OJ˜JšœœK˜lJ˜Jšœ+˜2J˜Jšœ˜Jš˜šΟnœœœ%˜CJ™—head™ JšŸ œœœ˜JšŸœœœ˜ J™J™_š Ÿ œœ œœœœ˜8Jš œœ)œœ œ˜FJšœœœ˜,Jš œR˜[JšœΟc˜—J˜šŸœ œ˜#Jšœ0˜0Jšœ?˜?JšœT˜TJšœ ˜—J˜JšŸœ˜%š Ÿœœœœœ˜2JšœV˜VJšœœœ˜*šœœ˜$šœ œœ˜Jšœ œ;˜GJšœn˜nJšœ˜—JšœV D˜žJšœ˜Jšœ˜—Jšœ ˜——™ JšŸ œœœ˜JšŸœœœ˜ J˜š Ÿ œœ œœœœ˜8Jš œœ)œœœœ˜FJšœœœ˜,Jš œR˜[Jšœ ˜—J˜šŸœ œ˜"Jšœ0œ˜6Jšœ0˜0Jšœ˜JšœA˜AJšœ>˜>Jšœ˜——™Ibody™εJ˜šŸœœ œœ œœœœœœ˜eJ™—šŸœœ œœœœœœœœœœ Aœ˜Άšœœ˜Jšœp˜pJšœ œ3˜BJšœ˜—Jšœ ˜—J˜šŸœœœœ œœœœœœœ˜~Jšœœœ˜:šŸœœœœ˜6Jšœœœ˜Jšœ œœ˜/Jšœ<˜>Jšœ˜J˜—Jšœ ˜ Jšœ ˜—J˜š Ÿœœœœ œ Ι˜‡Jšœœœ˜:šŸœœœœ˜6Jšœœœ˜Jšœ œœ˜/Jšœ˜Jšœ˜J˜—Jšœ ˜ Jšœ ˜——™š Ÿœ œœ  œœ˜aJšœ œ'˜3Jšœ œ,˜;Jšœ œœ˜(Jš œœ+œœœ ˜PJšœœœRœ˜J˜Jšœœœ œ)˜MJ˜FJšœœœ˜>J˜J˜—šŸœ œ ˜=š œœœœ$œœ˜Fšœœ˜Jš œœœœ#œ /˜vJš˜Jšœ˜—Jšœ˜—J˜——™Jš Ÿ œ œœœœ˜4Jš œœœœœ œ œ˜jJ˜šŸ œœ œœœœœ œœ˜}Jšœœ4˜@šœœœ˜(Jšœ<˜˜>Jšœ˜—šœœœœ˜0Jšœ;˜;J˜J˜—šœ˜Jšœ;˜;J˜J˜J˜—Jšœœœ2˜JJ˜J˜—š Ÿ œœ œ œœ˜9Jšœœœ˜ Jšœ  œ˜/JšœœœR˜iJšœ œ˜Jšœœ œœ˜)Jšœœ˜ Jšœ˜šœ'˜.Jšœ˜Jš˜—J˜Jšœ˜J˜—JšΟbF™FšŸ œœœœ œœœœ˜fšœ œ ˜"Jšœ ˜Jšœ4˜7—Jšœ˜J˜J˜—J˜JšŸœ2˜@JšŸ œœU˜gJ˜šŸ œœ˜š œœœœ$œœ˜Fšœœœ˜Jšœ œ/˜;Jšœœœ˜8J˜—šœœ˜Jšœ˜Jšœœ˜*Jšœ˜J˜!J˜—Jšœ˜—J˜J˜—šŸ œœ!˜1š œœœœ$œœ˜Fšœœ˜Jšœ œ˜Jšœœœ˜Jšœ>œ˜DJš œHœœœ &˜‚Jš œœ"œœ  ™˜μšœœ˜!Jšœ œ/˜;Jšœ œ,˜;Jšœ<˜˜EJ˜Jšœ ˜J™J™—…—$ψ3g