<> <> <> <> DIRECTORY Commander USING [CommandProc, Register], EditedStream USING [Rubout], FS USING [StreamOpen], Icons USING [IconFlavor, NewIconFromFile], InputFocus USING [Focus, GetInputFocus, PopInputFocus, PushInputFocus], IO, Process USING [Detach, Pause, MsecToTicks, SetTimeout, SecondsToTicks, Abort, CheckForAbort, DisableTimeout], Rope USING [ROPE, Find, Match, Equal], RuntimeError USING [UNCAUGHT], LogViewer, TypeScript USING [Create, PutRope], ViewerClasses USING [Viewer], ViewerIO USING [CreateViewerStreams, GetViewerFromStream], ViewerOps USING [PaintViewer, OpenIcon, EnumerateViewers, EnumProc], ViewerTools USING [SetSelection]; LogViewerImpl: CEDAR MONITOR IMPORTS Commander, EditedStream, FS, Icons, InputFocus, IO, Process, Rope, RuntimeError, TypeScript, ViewerIO, ViewerOps, ViewerTools EXPORTS LogViewer = BEGIN ROPE: TYPE = Rope.ROPE; UserAbort: PUBLIC SIGNAL = CODE; TimedOut: PUBLIC SIGNAL = CODE; viewer: ViewerClasses.Viewer _ NIL; in, out: IO.STREAM _ NIL; normalName: ROPE = "Log Viewer"; inputName: ROPE = "Log Viewer [Input Expected]"; normalIcon, inputIcon: Icons.IconFlavor; del: ROPE = "\177"; <<>> log: IO.STREAM; <<--using a backing file for viewer makes log unaccessible>> shouldFlush: BOOL _ FALSE; <<--whether log file should be flushed>> <<>> <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--locking procedures>> occupied: BOOL _ FALSE; --module occupied next: CONDITION _ [timeout: Process.MsecToTicks[4000]]; --another client might try entering Enter: ENTRY PROC [] = { ENABLE UNWIND => NULL; WHILE occupied DO WAIT next ENDLOOP; occupied _ TRUE; }; Leave: ENTRY PROC [] = { ENABLE UNWIND => NULL; occupied _ FALSE; BROADCAST next; }; EnterReader: PROC [] = { v: ViewerClasses.Viewer; Lock[]; Enter[]; v _ viewer; WHILE v=NIL OR v.destroyed DO InternalCreate[FALSE]; v _ viewer ENDLOOP; v.inhibitDestroy _ TRUE; WHILE v=NIL OR v.destroyed DO InternalCreate[TRUE]; v _ viewer ENDLOOP; v.icon _ inputIcon; v.name _ inputName; ViewerOps.PaintViewer[v, caption]; ViewerTools.SetSelection[v]; InputFocus.PushInputFocus[v, $LogViewer]; }; LeaveReader: PROC [] = { v: ViewerClasses.Viewer _ viewer; foc: InputFocus.Focus _ InputFocus.GetInputFocus[]; IF v#NIL THEN { v.icon _ normalIcon; v.name _ normalName; v.inhibitDestroy _ FALSE; --conveniant here; usefull in all places where called ViewerOps.PaintViewer[v, caption]; }; IF foc#NIL AND foc.info = $LogViewer THEN InputFocus.PopInputFocus[]; Leave[]; Free[]; }; ProcList: TYPE = LIST OF PROC; lockList: ProcList _ NIL; freeList: ProcList _ NIL; AddLock: PUBLIC ENTRY PROC [lock, free: PROC] = { ENABLE UNWIND => NULL; IF free#NIL THEN freeList _ CONS[free, freeList]; IF lock#NIL THEN IF lockList=NIL THEN lockList _ LIST[lock] ELSE FOR l: ProcList _ lockList, l.rest DO IF l.rest=NIL THEN {l.rest _ LIST[lock]; EXIT} ENDLOOP; }; Lock: PROC = { FOR l: ProcList _ lockList, l.rest WHILE l#NIL DO l.first[! RuntimeError.UNCAUGHT => CONTINUE] ENDLOOP }; Free: PROC = { FOR l: ProcList _ freeList, l.rest WHILE l#NIL DO l.first[! RuntimeError.UNCAUGHT => CONTINUE] ENDLOOP }; ReaderProtected: PROC [p: PROC] = { ENABLE UNWIND => LeaveReader[]; EnterReader[]; p[]; LeaveReader[]; }; <<>> <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--creation of viewer>> Create: PROC [protected: BOOL] = { <<--its ok to risk creating accidently too many streams or viewer's >> <<--since this won't cause any wedge but only discomfort [orphaned viewers lying around] >> InternalCreate[protected] }; InternalCreate: PROC [protected: BOOL] = { <<--afterwards in any case: viewer #NIL (doesnt really mean good, but certainly not NIL) >> FindOldGuys: ViewerOps.EnumProc = { MakeOrphan: PROC [v: ViewerClasses.Viewer] = { IF v#NIL THEN { v.inhibitDestroy _ FALSE; v.icon _ typescript; v.name _ "orphaned split log viewer"; IO.PutRope[log, "log viewer orphaned\n"]; IO.Flush[log]; ViewerOps.PaintViewer[v, caption]; TypeScript.PutRope[v, "\n... log viewer orphaned ...\n"]; }; }; IF ~v.destroyed AND v.file=NIL AND v.class.flavor=$Typescript THEN IF Rope.Equal[v.name, normalName] OR Rope.Equal[v.name, inputName] THEN MakeOrphan[v ! RuntimeError.UNCAUGHT => CONTINUE]; }; ReallyCreate: PROC [] = { v: ViewerClasses.Viewer _ TypeScript.Create[info: [name: normalName, inhibitDestroy: protected, iconic: FALSE, icon: normalIcon, column: right]]; [in: in, out: out] _ ViewerIO.CreateViewerStreams[name: normalName, viewer: v, editedStream: TRUE]; viewer _ v; IO.PutRope[log, "log viewer created\n"]; IO.Flush[log]; }; <<--try re-using old pieces, but only if not from input>> oldOut: IO.STREAM _ out; oldIn: IO.STREAM _ in; IF oldOut#NIL AND oldIn#NIL AND ~protected THEN { v: ViewerClasses.Viewer; err: BOOL _ FALSE; v _ ViewerIO.GetViewerFromStream[oldOut]; IF v#NIL AND ~v.destroyed AND (v=ViewerIO.GetViewerFromStream[oldIn]) THEN { <<--we dare trying to reuse a split viewer, >> <<--but first we try whether it wont cause errors>> IO.Flush[oldOut ! RuntimeError.UNCAUGHT => {err _ TRUE; CONTINUE}]; IF ~err THEN IO.Reset[oldOut ! RuntimeError.UNCAUGHT => {err _ TRUE; CONTINUE}]; IF ~err THEN IO.Reset[oldIn ! RuntimeError.UNCAUGHT => {err _ TRUE; CONTINUE}]; IF ~err THEN { v.icon _ normalIcon; IF ~v.destroyed THEN { [in: in, out: out] _ ViewerIO.CreateViewerStreams[name: normalName, viewer: v, editedStream: TRUE]; viewer _ v; IO.PutRope[log, "usage of different LogViewer viewer\n"]; IO.Flush[log]; RETURN; }; }; }; }; <<--try to mark other [split] viewers as bad>> ViewerOps.EnumerateViewers[FindOldGuys ! RuntimeError.UNCAUGHT => CONTINUE]; <<--the actual creation>> ReallyCreate[] }; <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--short cuts>> InternalPut: PROC [text: ROPE] = { IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE]; <<--asking for viewer.destroyed helps to discover bad splits, but is not mandatory >> IO.PutRope[out, text ! IO.Error => { IF ec=StreamClosed THEN { InternalCreate[TRUE]; IO.PutRope[out, text]; CONTINUE } }]; IO.PutRope[log, text]; shouldFlush _ TRUE; }; PutRope: PUBLIC PROC [text: ROPE] = { ENABLE UNWIND => Leave[]; Enter[]; InternalPut[text]; Leave[] }; PutRopes: PUBLIC PROC [t1, t2, t3: ROPE] = { ENABLE UNWIND => Leave[]; Enter[]; InternalPut[t1]; InternalPut[t2]; InternalPut[t3]; Leave[] }; PutF: PUBLIC PROC [format: ROPE _ NIL, v1, v2, v3, v4, v5: IO.Value] = { <<--actual inline usage allow client usage of formatting>> ENABLE UNWIND => Leave[]; Enter[]; IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE]; IO.PutF[out, format, v1, v2, v3, v4, v5 ! IO.Error => { IF ec=StreamClosed THEN { InternalCreate[TRUE]; IO.PutF[out, format, v1, v2, v3, v4, v5]; CONTINUE } } ]; IO.PutF[log, format, v1, v2, v3, v4, v5]; shouldFlush _ TRUE; Leave[] }; PutF1: PUBLIC PROC [format: ROPE _ NIL, value: IO.Value] = { <<--actual inline usage to allow client usage of formatting>> ENABLE UNWIND => Leave[]; Enter[]; IF viewer=NIL OR viewer.destroyed THEN InternalCreate[FALSE]; IO.PutF1[out, format, value ! IO.Error => { IF ec=StreamClosed THEN { InternalCreate[TRUE]; IO.PutF1[out, format, value]; CONTINUE } } ]; IO.PutF[log, format, value]; shouldFlush _ TRUE; Leave[] }; <<>> <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--input procedures>> RequestRope: PUBLIC PROC [prompt: ROPE _ NIL, timeOut: NAT] RETURNS [ROPE] = { RETURN [IF timeOut=0 THEN RequestRope0[prompt] ELSE RequestRope1[prompt, timeOut]] }; IOState: TYPE = {ok, repeat, abort, undef}; <<>> InternalGetLine: PROC [promp: ROPE] RETURNS [line: ROPE, state: IOState_ok] = { ENABLE { IO.Error => IF ec=StreamClosed THEN {state_repeat; CONTINUE}; EditedStream.Rubout => {state_abort; IO.Reset[in]; CONTINUE}; RuntimeError.UNCAUGHT => {state_repeat; CONTINUE}; }; IF viewer.iconic THEN ViewerOps.OpenIcon[viewer]; InternalPut[promp]; IO.Flush[out ! RuntimeError.UNCAUGHT => CONTINUE]; ViewerTools.SetSelection[viewer]; line _ IO.GetLineRope[in]; IF Rope.Find[line, del]>=0 THEN state_abort; }; RequestRope0: PROC [prompt: ROPE _ NIL] RETURNS [r: ROPE] = { DoIt: PROC [] = {[r, state] _ InternalGetLine[prompt]}; state: IOState; DO ReaderProtected[DoIt]; SELECT state FROM ok => RETURN; repeat => PutRope["XXX\n"]; abort => {PutRope[" ..user abort\n"]; SIGNAL UserAbort} ; ENDCASE => {PutRope[" ..??\n"]; SIGNAL UserAbort}; ENDLOOP; }; xProcess: PROCESS; xStateCondition: CONDITION; xState: IOState _ undef; xResult: ROPE; RequestRope1: PUBLIC PROC [prompt: ROPE, timeOut: NAT] RETURNS [result: ROPE] = { state: IOState; DoIt: PROC [] = { xState _ undef; IF timeOut=0 THEN TRUSTED {Process.DisableTimeout[@xStateCondition]} ELSE TRUSTED { Process.SetTimeout[@xStateCondition, Process.SecondsToTicks[timeOut]] }; ForkRopeGetter[prompt]; TRUSTED {JOIN xProcess}; state _ xState; result _ xResult; IF state=ok THEN IF Rope.Find[result, del]>=0 THEN state _ abort }; DO ReaderProtected[DoIt]; SELECT state FROM ok => RETURN; repeat => PutRope["XXX\n"]; abort => {PutRope[" ..user abort\n"]; SIGNAL UserAbort} ; undef => SIGNAL TimedOut ENDCASE => {PutRope[" ..??\n"]; SIGNAL UserAbort}; ENDLOOP }; ForkRopeGetter: ENTRY PROC [prompt: ROPE_NIL] = { ENABLE UNWIND => NULL; xProcess _ FORK ForkedTryToGetRope[prompt]; WAIT xStateCondition; IF xState#ok AND xState#abort THEN TRUSTED {Process.Abort[xProcess]}; }; SetGlobalState: ENTRY PROC [s: IOState] = { ENABLE UNWIND => NULL; Process.CheckForAbort[]; xState _ s; BROADCAST xStateCondition; }; ForkedTryToGetRope: PROC [prompt: ROPE_NIL] = { ReallyTryToGetRope: PROC [prompt: ROPE] = { d: IOState; DoIt: PROC [] = { ENABLE { UNWIND => IO.Reset[in]; IO.Error => IF ec=StreamClosed THEN {d_repeat; IO.Reset[in]; CONTINUE}; EditedStream.Rubout => {d_abort; IO.Reset[in]; CONTINUE}; }; InternalPut[prompt]; IF viewer.iconic THEN ViewerOps.OpenIcon[viewer]; xResult _ IO.GetLineRope[in]; d _ ok; }; DO d _ undef; DoIt[]; IF d=ok OR d=abort THEN {SetGlobalState[d]; RETURN}; IF d=repeat THEN InternalPut["XXX\n"] ENDLOOP; }; <<>> ReallyTryToGetRope[prompt ! ABORTED => GOTO NeverMind] EXITS NeverMind => NULL; }; <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--stream output stuff>> CreateStream: PUBLIC PROC [] RETURNS [stream: IO.STREAM] = { IF viewer=NIL OR viewer.destroyed THEN Create[FALSE]; RETURN[IO.CreateStream[streamProcs: myStreamProcs, streamData: NIL]]; }; myStreamProcs: REF IO.StreamProcs _ IO.CreateStreamProcs[ variety: $output, class: $LogViewer, putChar: MyPutChar, putBlock: MyPutBlock, unsafePutBlock: MyUnsafePutBlock, flush: MyFlush, eraseChar: MyEraseChar, reset: MyFlush, close: MyClose ]; MyPutChar: PROC [self: IO.STREAM, char: CHAR] = { ENABLE UNWIND => Leave[]; Enter[]; IO.PutChar[out, char ! IO.Error => { IF ec=StreamClosed THEN { InternalCreate[TRUE]; IO.PutChar[out, char]; CONTINUE } }]; IO.PutChar[log, char]; shouldFlush _ TRUE; Leave[]; }; MyPutBlock: PROC [self: IO.STREAM, block: REF READONLY TEXT, startIndex: NAT, count: NAT] = { ENABLE UNWIND => Leave[]; Enter[]; IO.PutBlock[out, block, startIndex, count ! IO.Error => { IF ec=StreamClosed THEN { InternalCreate[TRUE]; IO.PutBlock[out, block, startIndex, count]; CONTINUE } }]; IO.PutBlock[log, block, startIndex, count]; shouldFlush _ TRUE; Leave[]; }; MyUnsafePutBlock: PROC [self: IO.STREAM, block: IO.UnsafeBlock] = { ENABLE UNWIND => Leave[]; Enter[]; IO.UnsafePutBlock[out, block ! IO.Error => { IF ec=StreamClosed THEN { InternalCreate[TRUE]; IO.UnsafePutBlock[out, block]; CONTINUE } }]; IO.UnsafePutBlock[log, block]; shouldFlush _ TRUE; Leave[]; }; MyFlush: PROC [self: IO.STREAM] = { IO.Flush[log]; }; MyEraseChar: PROC [self: IO.STREAM, char: CHAR] = { }; MyClose: PROC [self: IO.STREAM, abort: BOOL] = { IO.Flush[log]; }; <<-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->> <<--log and commands>> CreateLog: PROC [] = { log _ FS.StreamOpen[fileName: "LogViewer.log", accessOptions: create, keep: 5]; IO.PutF1[log, "log file for LogViewer created %g\n", IO.time[]] }; Command: Commander.CommandProc = { IF Rope.Match["*open*", cmd.commandLine, FALSE] THEN { PutRope["open LogViewer viewer\n"]; --side effect: creates viewer ViewerOps.OpenIcon[viewer]; }; IF Rope.Match["*new*", cmd.commandLine, FALSE] THEN { oldLog: IO.STREAM _ log; PutRope["start new LogViewer.log file\n"]; CreateLog[]; IO.Close[oldLog]; }; }; CheckInhibitDestroy: ENTRY PROC [] = { v: ViewerClasses.Viewer _ viewer; IF v#NIL AND ~occupied THEN v.inhibitDestroy _ FALSE; }; Flusher: PROC [] = { <<--directly flushing after each output would make LogViewer far to slow>> flush: BOOL; DO Process.Pause[Process.SecondsToTicks[40]]; IF flush _ shouldFlush THEN { shouldFlush _ FALSE; IO.Flush[log ! RuntimeError.UNCAUGHT => CONTINUE]; }; CheckInhibitDestroy[]; ENDLOOP }; CreateLog[]; normalIcon _ Icons.NewIconFromFile["LogViewer.icons", 0 ! RuntimeError.UNCAUGHT => CONTINUE]; inputIcon _ Icons.NewIconFromFile["LogViewer.icons", 1 ! RuntimeError.UNCAUGHT => CONTINUE]; Commander.Register["///Commands/LogViewer", Command, "usage {LogViewer ~ | open | new}"]; TRUSTED {Process.Detach[FORK Flusher]}; END.