Last Edited by: Teitelman, June 20, 1983 3:01 pm
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;
so can access private.id
Session Log
sessionLog: IO.STREAM;
sessionLogFileStream: IO.STREAM;
each call to GetSessionLog returns a new buffered stream connected to the same monitored 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
Changes Log
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];
};
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.
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: NATLAST[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
Creating log file for execs
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;
};
CheckPoint and rollback
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: BOOLFALSE] 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[];
};
when monitored stream gets moved elsewhere, make RegisterLog monitored
RegisterLog: PROC [log: IO.STREAM, name: Rope.ROPENIL, 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];
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
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