<> <> <> <> <> <> <> <> <> <> <> <> <> <> DIRECTORY Ascii USING[SP, TAB, CR], BasicTime USING[Period, Now, Update, GMT], Booting USING[RollbackProc, RegisterProcs], Convert USING[CardFromRope, TimeFromRope, Error], FS USING[Error, StreamOpen], IO USING[STREAM, GetChar, Close, PutRope, EndOfStream], Process USING[Pause, SecondsToTicks, Detach], Rope USING[ROPE, Concat, FromChar, Equal, Match], ViewerClasses USING[Viewer], ViewerEvents USING[RegisterEventProc, ViewerEvent], ViewerIO USING[CreateViewerStreams], ViewerOps USING[BlinkIcon, DestroyViewer], ViewerTools USING[MakeNewTextViewer]; Reminder: CEDAR MONITOR IMPORTS BasicTime, Booting, Convert, FS, IO, Process, Rope, ViewerEvents, ViewerIO, ViewerOps, ViewerTools = BEGIN OPEN Ascii, Rope, ViewerClasses, ViewerEvents, ViewerOps; Event: TYPE = REF EventObj; EventObj: TYPE = RECORD[text: ROPE, timeRope: ROPE, timeToStartNotification: BasicTime.GMT, newStartTime: BOOL _ TRUE, repetitionInterval: INT _ 0, duration: INT _ 0, viewer: Viewer _ NIL]; ParameterClass: TYPE = {message, repeat, duration--minutes--, until, time}; eventList: LIST OF Event _ NIL; FormatError: ERROR = CODE; Construct: ENTRY Booting.RollbackProc = { ENABLE UNWIND => NULL; fileStream: IO.STREAM _ NIL; timeRope: ROPE _ NIL; { -- for EXITS clause IF eventList # NIL THEN FOR el: LIST OF Event _ eventList, el.rest UNTIL el = NIL DO IF el.first.viewer # NIL AND NOT el.first.viewer.destroyed THEN DestroyViewer[el.first.viewer]; ENDLOOP; eventList _ NIL; fileStream _ FS.StreamOpen["Reminders.txt" ! FS.Error => GOTO noReminderFile]; DO time: BasicTime.GMT; text: ROPE; class: ParameterClass; repetitionInterval: INT _ 0; duration: INT _ 0; timeRope _ ReadTimeRope[fileStream]; IF timeRope = NIL THEN EXIT; time _ Convert.TimeFromRope[timeRope ! Convert.Error => GOTO timeRopeFormatError]; [class, text] _ ReadParameter[fileStream ! FormatError => GOTO formatError]; UNTIL class = message DO SELECT class FROM repeat => IF Equal[s1: text, s2: "HOURLY", case: FALSE] THEN {repetitionInterval _ LONG[60*60]} ELSE IF Equal[s1: text, s2: "DAILY", case: FALSE] THEN {repetitionInterval _ LONG[60*60]*24} ELSE IF Equal[s1: text, s2: "WEEKLY", case: FALSE] THEN {repetitionInterval _ LONG[60*60]*24*7} ELSE IF Equal[s1: text, s2: "YEARLY", case: FALSE] THEN {repetitionInterval _ LONG[60*60]*24*7*52} ELSE GOTO formatError; duration => duration _ Convert.CardFromRope[text]*60; until => duration _ BasicTime.Period[ to: Convert.TimeFromRope[text ! Convert.Error => GOTO formatError], from: time]; ENDCASE; [class, text] _ ReadParameter[fileStream ! FormatError => GOTO formatError]; ENDLOOP; { latest: BasicTime.GMT _ time; IF repetitionInterval > 0 THEN WHILE BasicTime.Period[from: latest, to: BasicTime.Now[]] >= repetitionInterval DO latest _ BasicTime.Update[latest, repetitionInterval] ENDLOOP; eventList _ CONS[NEW[EventObj _ [timeToStartNotification: latest, timeRope: timeRope, text: text, repetitionInterval: repetitionInterval, duration: duration]], eventList]; }; ENDLOOP; fileStream.Close[]; EXITS formatError => { out: IO.STREAM _ ViewerIO.CreateViewerStreams["Reminder Error"].out; eventList _ NIL; out.PutRope["Reminder: Format error in Reminders.txt\n"]; }; timeRopeFormatError => { out: IO.STREAM _ ViewerIO.CreateViewerStreams["Reminder Error"].out; eventList _ NIL; out.PutRope["Reminder: Unintelligible time: "]; out.PutRope[timeRope]; out.PutRope["\n"]; }; noReminderFile => { out: IO.STREAM _ ViewerIO.CreateViewerStreams["Reminder Error"].out; eventList _ NIL; out.PutRope["Reminder: Can't find the file named Reminders.txt\n"]; }; }; }; CheckSavedFile: PROC[viewer: Viewer, event: ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] = {IF Match[object: viewer.name, pattern: "*Reminders.txt*", case: FALSE] THEN Construct[NIL]}; IsBreak: PROC[ch: CHAR] RETURNS[BOOL] = {RETURN[ch = SP OR ch = TAB OR ch = CR OR ch = '[ OR ch = ']]}; ReadParameter: PROC[fileStream: IO.STREAM] RETURNS[parameterClass: ParameterClass _ message, text: ROPE _ NIL] = { ch: CHAR _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; WHILE IsBreak[ch] DO ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; ENDLOOP; IF ch # '" THEN {text _ NIL; UNTIL IsBreak[ch] DO text _ Concat[base: text, rest: FromChar[ch]]; ch _ fileStream.GetChar[! IO.EndOfStream => EXIT]; ENDLOOP; <> SELECT TRUE FROM Equal[s1: text, s2: "REPEAT:", case: FALSE] => {parameterClass _ repeat; WHILE IsBreak[ch] DO ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; ENDLOOP; text _ NIL; UNTIL IsBreak[ch] DO text _ Concat[base: text, rest: FromChar[ch]]; ch _ fileStream.GetChar[! IO.EndOfStream => EXIT]; ENDLOOP; }; Equal[s1: text, s2: "DURATION:", case: FALSE] => {parameterClass _ duration; WHILE IsBreak[ch] DO ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; ENDLOOP; text _ NIL; UNTIL IsBreak[ch] DO text _ Concat[base: text, rest: FromChar[ch]]; ch _ fileStream.GetChar[! IO.EndOfStream => EXIT]; ENDLOOP; }; Equal[s1: text, s2: "UNTIL:", case: FALSE] => {parameterClass _ until; text _ ReadTimeRope[fileStream]}; Equal[s1: text, s2: "TIME:", case: FALSE] => {parameterClass _ time; text _ ReadTimeRope[fileStream]}; Equal[s1: text, s2: "MESSAGE:", case: FALSE] => {parameterClass _ message; UNTIL ch = '" DO ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; ENDLOOP; text _ NIL; UNTIL ch = '" DO text _ Concat[base: text, rest: FromChar[ch]]; ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; ENDLOOP; }; ENDCASE => ERROR FormatError; RETURN; }; parameterClass _ message; ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; UNTIL ch = '" DO text _ Concat[base: text, rest: FromChar[ch]]; ch _ fileStream.GetChar[! IO.EndOfStream => GOTO formatError]; ENDLOOP; EXITS formatError => ERROR FormatError; }; ReadTimeRope: PROC[fileStream: IO.STREAM] RETURNS[ans: ROPE _ NIL] = { ch: CHAR _ fileStream.GetChar[! IO.EndOfStream => GOTO exit]; WHILE ch = SP OR ch = TAB OR ch = CR DO ch _ fileStream.GetChar[! IO.EndOfStream => GOTO exit]; ENDLOOP; UNTIL ch = CR OR ch = '] DO ans _ Concat[base: ans, rest: FromChar[ch]]; ch _ fileStream.GetChar[! IO.EndOfStream => GOTO exit]; ENDLOOP; EXITS exit => NULL; }; EventMinder: PROC = { DO EnterEventMinder[]; Process.Pause[Process.SecondsToTicks[3]]; ENDLOOP; }; EnterEventMinder: ENTRY PROC = { ENABLE UNWIND => NULL; now: BasicTime.GMT = BasicTime.Now[]; FOR el: LIST OF Event _ eventList, el.rest UNTIL el = NIL DO -- for each event. startTime: BasicTime.GMT; IF el.first.repetitionInterval > 0 THEN { -- compute the most recent startTime latest: BasicTime.GMT _ el.first.timeToStartNotification; WHILE BasicTime.Period[from: latest, to: BasicTime.Now[]] >= el.first.repetitionInterval DO latest _ BasicTime.Update[latest, el.first.repetitionInterval]; el.first.newStartTime _ TRUE; ENDLOOP; el.first.timeToStartNotification _ latest; }; startTime _ el.first.timeToStartNotification; IF BasicTime.Period[from: startTime, to: now] > 0 THEN { <> IF el.first.newStartTime AND (el.first.duration = 0 -- means forever OR BasicTime.Period[from: startTime, to: now] < el.first.duration) AND (el.first.viewer = NIL OR el.first.viewer.destroyed) THEN { el.first.newStartTime _ FALSE; el.first.viewer _ ViewerTools.MakeNewTextViewer [[ name: el.first.text, data: Concat[base: el.first.timeRope, rest: Concat[": ", el.first.text]] ]]; }; <> IF el.first.duration # 0 -- 0 means forever AND BasicTime.Period[from: startTime, to: now] > el.first.duration AND el.first.viewer # NIL AND NOT el.first.viewer.destroyed THEN DestroyViewer[el.first.viewer]; <> IF el.first.viewer # NIL AND (NOT el.first.viewer.destroyed) AND el.first.viewer.iconic THEN BlinkIcon[el.first.viewer]; }; ENDLOOP; }; <> [] _ RegisterEventProc[CheckSavedFile, save, $Text, FALSE]; TRUSTED {Booting.RegisterProcs[r: Construct]}; TRUSTED {Process.Detach[FORK EventMinder[]]}; Construct[NIL]; END. July 29, 1982 5:00 pm REPEAT: DAILY DURATION: 60 "Time to go home." July 28, 1982 1:00 pm REPEAT: WEEKLY DURATION: 30 "Dealer at 1:15" July 30, 1982 1:10 pm DURATION: 30 "Mike Feuer arriving at 1:30"