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" èReminder.mesa Last Modified On January 11, 1984 12:23 pm By Paul Rovner Reminder reads a text file named Reminders.txt and FORKs a process to post notices as flashing, iconic viewers at or after the specified time. Reminder is an interim (!) hack until Hickory is ready for general use. The format of the file is a sequence of entries separated by CR, each entry being a time specification (e.g. July 29, 1982 3:25 pm) ending with CR, followed by zero or more (optional) parameter specifications (see list below), followed by a ROPE constant that is the text of the notice. The text following the END. below is an example Reminders.txt. Until a better date and time parser is available, an "optional parameter" facility (keyword including ': followed by parameter value) is provided as a stopgap. Case is ignored. REPEAT: {one of: HOURLY, DAILY, WEEKLY, YEARLY} this causes the notice to be posted at the specified interval. MONTHLY isn't implemented yet DURATION: {number of minutes} automatically kill the notice after this duration UNTIL: {timestamp} automatically kill the notice after this time You can open a reminder icon. It will resume blinking when you close it. You can delete a Reminder viewer. After editing your Reminders.txt file and saving it, Reminder will automatically discard the old list of reminders and re-read the file to construct a new one. This also happens automatically after a Rollback. here with keyword create a new viewer if there just occurred a new start time (the first qualifies) and its duration has not expired and there isn't already an active viewer for this event destroy an existing viewer if a duration is specified and it has lapsed blink the viewer if there is an active one and it is iconic MODULE INITIALIZATION Ê ˜J˜J˜Jšœ ™ Jšœ9™9J˜JšœŽ™ŽJ˜JšœG™GJ˜JšœÝ™ÝJ˜šœ°™°šœ0™0Jšœ>™>—Jšœ™šœ™Jšœ1™1—šœ™Jšœ-™-——J˜Jšœ½™½J˜šÏk ˜ Jš œœœœœ˜Jšœ œ˜*Jšœœ˜+Jšœœ$˜1Jšœœ˜Jšœœœ(˜7Jšœœ ˜-Jšœœœ"˜1Jšœœ ˜Jšœ œ!˜3Jšœ œ˜$Jšœ œ˜*Jšœ œ˜%J˜Jšœ œ˜J˜šœ"œ˜;Jšœ.˜.J˜—Jšœœœ5˜AJ˜Jšœœœ ˜šœ œœœ˜#Jšœ œ˜Jšœ#œ˜'Jšœœœ˜Jšœœ˜Jšœ œ˜Jšœœ˜—JšœœÏc œ˜KJ˜Jšœ œœ œ˜J˜Jšœ œœ˜J˜šÏn œœ˜'šœœœœ˜Jšœ œ œ˜Jšœ œœ˜J˜Jšœž˜šœ ˜š œœœœœ˜>š œœœœœ˜=Jšœ ˜$—Jšœ˜—J˜—Jšœ œ˜Jšœ œœ œ˜NJ˜šœ˜Jšœœ˜Jšœœ˜ J˜Jšœœ˜Jšœ œ˜J˜J˜$Jšœ œœœ˜šœ$˜$Jšœœ˜0—Jšœ:œ˜Lšœ˜šœœ˜šœ œ%œ˜7Jšœœ˜'Jšœœ$œ˜1Jšœœ ˜*Jšœœ%œ˜2Jšœœ ˜,Jšœœ%œ˜2Jšœœ˜/Jšœœ ˜—Jšœ5˜5šœ˜šœ˜šœ˜šœ˜Jšœœ˜&—Jšœ ˜ ———Jšœ˜Jšœ:œ˜L—Jšœ˜—J˜šœ˜Jšœœ˜šœ˜šœJ˜OJšœ7œ˜A——šœ œœ-˜AJ˜J˜ J˜'J˜J˜ ——J˜—Jšœ˜J˜šœ˜˜Jšœœœ6˜DJšœ œ˜J˜9J˜—šœ˜Jšœœœ6˜DJšœ œ˜J˜/J˜J˜J˜—šœ˜Jšœœœ6˜DJšœ œ˜J˜CJ˜——J˜—˜J˜———šŸœœ-œ˜GJšœœœ˜Jš œœ>œœ œ˜]J˜—š Ÿœœœœœ˜'Jšœœœœœœœœ œ ˜?J˜—šŸ œœ œœ˜*Jšœ1œœ˜Ešœœœœ˜Fšœ ˜Jšœœœ˜AJšœ˜—šœ˜ šœ œ˜šœ ˜šœ/˜1Jšœœœ˜2——Jšœ˜Jšœ™šœœ˜šœ%œ˜+˜šœ ˜Jšœœœ˜AJšœ˜—Jšœœ˜ šœ ˜šœ/˜1Jšœœœ˜2—Jšœ˜—J˜——šœ'œ˜-˜šœ ˜Jšœœœ˜AJšœ˜—Jšœœ˜ šœ ˜šœ/˜1Jšœœœ˜2—Jšœ˜—J˜——šœ$œ˜*˜J˜!——šœ#œ˜)˜J˜!——šœ&œ˜,˜Jšœ˜ Jšœœœ˜AJšœ˜Jšœœ˜ šœ˜ šœ/˜1Jšœœœ˜>—Jšœ˜—J˜——Jšœœ ˜Jšœ˜—J˜J˜——J˜Jšœœœ˜>šœ˜ šœ/˜1Jšœœœ˜>—Jšœ˜—Jšœœ ˜'—J˜J˜—šŸ œœ œœœœœ˜FJšœœœœ˜=š œœœœœ˜$Jšœœœ˜:Jšœ˜—šœœœ˜šœ-˜/Jšœœœ˜7—Jšœ˜—Jšœ œ˜J˜J˜—šŸ œœ˜Jšœ> ˜Išœ˜J˜——šŸœœœ˜šœœœœ˜Jšœœ˜%š œœœœ˜9šœž˜Jšœœ˜šœ!œž$˜NJšœœ!˜9šœS˜Xš˜Jšœ?˜?Jšœœ˜—Jšœ˜—Jšœ œ˜*Jšœ˜—J˜-šœ0˜2šœ˜šœª™ªšœ˜šœž˜,Jšœ@˜D—Jšœœœ˜8šœ˜Jšœœ˜˜/˜J˜L——J˜——šœG™Gšœž˜,Jšœ?˜BJšœ˜Jšœœ˜!—Jšœ ˜$J˜—Jšœ;™;šœ˜Jšœœ˜#Jšœ˜—Jšœ˜ —J˜———Jšœ˜——J˜J˜Jšœ™˜Jšœ4œ˜;Jšœ'˜.Jšœœ˜-J˜J˜——Jšœ˜J˜J˜Jšœ˜ Jšœ˜ J˜J˜J˜Jšœ˜Jšœ˜ J˜J˜J˜Jšœ˜ J˜J˜J˜—…—š2