DIRECTORY Atom, BasicTime USING[nullGMT, Unpack, earliestGMT, latestGMT, Now, Update], Booting USING[CheckpointProc, RegisterProcs, RollbackProc], Convert USING[CardFromRope, RopeFromLiteral], FS USING [ StreamOpen], Hickory USING [ EventTuple, nullEvent, Event, SameEvent, RestoreEvents, EnterEvent, RegisterNotifyProc, Reason, NotifyProc, EventList, GroupSet, Options, NoOptions, Reminders], IO USING[GetCedarTokenRope, TokenKind, BreakProc, Close, EndOf, EndOfStream, Error, Flush, GetChar, PeekChar, GetIndex, GetTokenRope, int, PutF, PutRope, PutR, RIS, rope, SetIndex, SkipWhitespace, STREAM, time, GetLineRope, GetRopeLiteral], List USING [Sort, CompareProc], Process USING[ Detach], RememberDefs, RememberDefsPrivate, Rope USING[ROPE, Cat, Fetch, Equal, Substr], RopeSets USING [ InsertValueIntoSet], Tempus USING [Adjust, defaultTime, Error, MakeRope, Packed, PackedSeconds, Seconds, PackedToSeconds, SecondsToPacked, Parse, Precision, Unintelligible], UserProfile USING [Boolean, Number, Token, ProfileChangedProc, CallWhenProfileChanges], ViewerClasses USING[Viewer], ViewerEvents USING[RegisterEventProc, ViewerEvent, EventProc], ViewerOps USING[ FetchProp] ; RememberImpl: CEDAR MONITOR LOCKS RememberDefsPrivate.protData IMPORTS Atom, BasicTime, Booting, Convert, FS, Hickory, IO, List, Process, RememberDefsPrivate, Rope, RopeSets, Tempus, UserProfile, ViewerEvents, ViewerOps EXPORTS RememberDefs, RememberDefsPrivate = BEGIN OPEN RememberDefs, RememberDefsPrivate, RememberDefsPrivate.protData; Error: PUBLIC ERROR [ec: ErrorCode, msg: Rope.ROPE _ NIL] = CODE; protData: PUBLIC ProtectedData; itIsNow: PUBLIC Tempus.Packed _ Tempus.defaultTime; -- defaultTime used in Register, EnterEventTime. Is a variable so that can be reset for debugging. walnutUser: PUBLIC BOOL _ FALSE; peanutUser: PUBLIC BOOL _ FALSE; debugging: BOOLEAN _ TRUE; Construct: PUBLIC ENTRY PROC = -- to rebuild the list of events from database; avoiding "duplicate" events based on DB key. BEGIN ENABLE UNWIND => NULL; evList: LIST OF Hickory.EventTuple _ NIL; paramList: ParameterList _ NIL; options: Hickory.Options _ Hickory.NoOptions; options[ Hickory.Reminders] _ TRUE; evList _ Hickory.RestoreEvents[ from: BasicTime.Now[], to: BasicTime.Update[ BasicTime.Now[], 2*3600], options: options]; FOR l: LIST OF Hickory.EventTuple _ evList, l.rest UNTIL l = NIL DO duplicate: BOOLEAN _ FALSE; FOR j: LIST OF Event _ eventList, j.rest UNTIL j = NIL DO IF Hickory.SameEvent[ j.first.dbKey, l.first.Key] THEN BEGIN duplicate _ TRUE; EXIT; END; ENDLOOP; IF NOT duplicate THEN BEGIN paramList _ BuildParamList[ l.first]; [] _ Register[ paramList, NIL, l.first.Key ! Error => ERROR]; END; ENDLOOP; END; -- Construct BuildParamList: INTERNAL PROCEDURE [ ev: Hickory.EventTuple] RETURNS [ parList: ParameterList _ NIL] = BEGIN IF ev.RepeatType # None THEN BEGIN r: ROPE; SELECT ev.RepeatType FROM Hourly => r _ "hourly"; Daily => r _ "daily"; Weekly => r _ "weekly"; Weekdays => r _ "weekdays"; Monthly => r _ "monthly"; Yearly => r _ "yearly"; Complicated => r _ ev.RepeatTime; ENDCASE => NULL; parList _ CONS[ NEW[ Parameter _ [ repeat, r]], parList]; END; -- if ev.RepeatType... IF ev.Duration > 0 THEN parList _ CONS[ NEW[ Parameter _ [ duration, IO.PutR[ IO.int[ ev.Duration]]]], parList]; IF ev.NagTime > 0 THEN parList _ CONS[ NEW[ Parameter _ [ getSeriousAfter, IO.PutR[ IO.int[ ev.NagTime]]]], parList]; IF ev.LeadTime # 0 THEN BEGIN parList _ CONS[ NEW[ Parameter _ [ leadTime, IO.PutR[ IO.int[ ev.LeadTime]]]], parList]; END; IF ev.EventTime # BasicTime.nullGMT THEN parList _ CONS[ NEW[ Parameter _ [ startTime, Tempus.MakeRope[ time: LOOPHOLE[ ev.EventTime]]]] , parList]; IF ev.Text # NIL THEN parList _ CONS[ NEW[ Parameter _ [ text, ev.Text]], parList]; IF ev.Message # NIL THEN parList _ CONS[ NEW[ Parameter _ [ message, ev.Message]], parList]; IF ev.IconFlavor # NIL THEN parList _ CONS[ NEW[ Parameter _ [ iconFlavor, ev.IconFlavor]], parList]; IF ev.IconLabel # NIL THEN parList _ CONS[ NEW[ Parameter _ [ iconLabel, ev.IconLabel]], parList]; IF ev.Place # NIL THEN parList _ CONS[ NEW[ Parameter _ [ place, ev.Place]], parList]; IF ev.KeepUntil # BasicTime.nullGMT THEN BEGIN r: Rope.ROPE _ Tempus.MakeRope[ time: LOOPHOLE[ ev.KeepUntil], includeDayOfWeek: FALSE]; parList _ CONS[ NEW[ Parameter _ [ keepUntil, r]], parList]; END; IF ev.RepeatUntil # BasicTime.nullGMT THEN BEGIN r: Rope.ROPE _ Tempus.MakeRope[ time: LOOPHOLE[ ev.RepeatUntil], includeDayOfWeek: FALSE]; parList _ CONS[ NEW[ Parameter _ [ until, r]], parList]; END; END; -- BuildParamList ReadParameters: PUBLIC PROC [stream: IO.STREAM] RETURNS[parameters: ParameterList] = { UNTIL IO.EndOf[stream] DO r: ROPE; class: ParameterClass; [class, r] _ ReadParameter[stream]; parameters _ CONS[NEW[Parameter _ [class, r]], parameters]; [] _ IO.SkipWhitespace[stream]; ENDLOOP; }; ReadParameter: PROC[stream: IO.STREAM] RETURNS[parameterClass: ParameterClass _ text, value: ROPE _ NIL] = { ENABLE { IO.EndOfStream => GOTO formatError; IO.Error => GOTO formatError; }; key: ROPE; kind: IO.TokenKind; [kind, value, ] _ IO.GetCedarTokenRope[stream]; SELECT kind FROM tokenROPE => RETURN[text, Convert.RopeFromLiteral[value]]; tokenID => key _ value; tokenSINGLE => { -- e.g. , ; etc. left over on line. [parameterClass, value] _ ReadParameter[stream]; RETURN; }; ENDCASE => IO.Error[SyntaxError, stream]; [] _ IO.SkipWhitespace[stream]; IF NOT stream.EndOf[] AND stream.PeekChar[] = ': THEN [] _ stream.GetChar[]; SELECT TRUE FROM Rope.Equal[s1: key, s2: "REPEAT", case: FALSE] => parameterClass _ repeat; Rope.Equal[s1: key, s2: "DURATION", case: FALSE] => parameterClass _ duration; Rope.Equal[s1: key, s2: "GETSERIOUSAFTER", case: FALSE], Rope.Equal[s1: key, s2: "NAGTIME", case: FALSE] => parameterClass _ getSeriousAfter; Rope.Equal[s1: key, s2: "UNTIL", case: FALSE] => parameterClass _ until; Rope.Equal[s1: key, s2: "LEADTIME", case: FALSE] => parameterClass _ leadTime; Rope.Equal[s1: key, s2: "TIME", case: FALSE] => parameterClass _ startTime; Rope.Equal[s1: key, s2: "TEXT", case: FALSE] => parameterClass _ text; Rope.Equal[s1: key, s2: "ICONFLAVOR", case: FALSE] => parameterClass _ iconFlavor; Rope.Equal[s1: key, s2: "ICONLABEL", case: FALSE] => parameterClass _ iconLabel; Rope.Equal[s1: key, s2: "ICONLABELTYPE", case: FALSE] => parameterClass _ iconLabelType; Rope.Equal[s1: key, s2: "MESSAGE", case: FALSE] => parameterClass _ message; Rope.Equal[ s1: key, s2: "PLACE", case: FALSE] => parameterClass _ place; Rope.Equal[ s1: key, s2: "KEEPUNTIL", case: FALSE] => parameterClass _ keepUntil; ENDCASE => Error [formatError, key]; [] _ IO.SkipWhitespace[stream]; IF stream.EndOf[] THEN value _ NIL ELSE IF stream.PeekChar[] = '" THEN value _ IO.GetRopeLiteral[stream] ELSE value _ IO.GetTokenRope[stream].token; RETURN; EXITS formatError => Error[formatError]; }; EnterEvent: ENTRY PROCEDURE [ key: Hickory.Event] = BEGIN ENABLE UNWIND => NULL; evl: Hickory.EventList; paramList: ParameterList _ NIL; options: Hickory.Options _ Hickory.NoOptions; options[ Hickory.Reminders] _ TRUE; FOR l: LIST OF Event _ eventList, l.rest UNTIL l = NIL DO IF Hickory.SameEvent[ key, l.first.dbKey] THEN RETURN; ENDLOOP; evl _ Hickory.RestoreEvents[ from: BasicTime.Now[], to: BasicTime.Update[ BasicTime.Now[], 2*3600], key: key, options: options]; IF evl = NIL THEN RETURN; -- event not to be scheduled soon paramList _ BuildParamList[ evl.first]; [] _ Register[ paramList, NIL, evl.first.Key ! Error => ERROR]; END; -- EnterEvent EditEvent: ENTRY PROCEDURE [ key: Hickory.Event] = BEGIN ENABLE UNWIND => NULL; evl: Hickory.EventList; paramList: ParameterList _ NIL; cur, prev: LIST OF Event _ NIL; options: Hickory.Options _ Hickory.NoOptions; cur _ eventList; prev _ NIL; WHILE cur # NIL DO IF Hickory.SameEvent[ key, cur.first.dbKey] THEN EXIT; cur _ cur.rest; ENDLOOP; IF cur # NIL THEN BEGIN -- cur.first is the event that has been edited... IF cur.first.viewer # NIL THEN RETURN; -- it's too late for editing this one... IF prev # NIL THEN BEGIN prev.rest _ cur.rest; cur.rest _ NIL; END ELSE BEGIN eventList _ cur.rest; cur.rest _ NIL; END; END; options[ Hickory.Reminders] _ TRUE; evl _ Hickory.RestoreEvents[ from: BasicTime.Now[], to: BasicTime.Update[ BasicTime.Now[], 2*3600], key: key, options: options]; IF evl = NIL THEN RETURN; -- edited event not in interesting interval paramList _ BuildParamList[ evl.first]; [] _ Register[ paramList, NIL, key]; END; -- EditEvent RegisterEvent: PUBLIC ENTRY PROC [parameters: ParameterList, scanThis: Rope.ROPE _ NIL, out: IO.STREAM] = TRUSTED { ENABLE UNWIND => NULL; event: Event _ Register[parameters, scanThis]; IF out # NIL THEN { out.PutF["Reminder will be posted at %g", IO.rope[Tempus.MakeRope[time: Tempus.SecondsToPacked[[event.timeToStartNotification - event.leadTime]], includeDayOfWeek: TRUE]]]; IF event.durationTime > 0 THEN out.PutF[", for %d minutes", IO.int[event.durationTime/60]]; IF event.repeat # NIL THEN out.PutF[", repeated %g", IO.rope[event.repeat]]; IF event.getSeriousAfter # 0 THEN out.PutF[", start nagging after %d minutes", IO.int[event.getSeriousAfter/60]]; out.PutRope["\n"]; }; Process.Detach[FORK Save[]]; }; Register: INTERNAL PROC [parameters: ParameterList, scanThis: Rope.ROPE _ NIL, dbKey: Hickory.Event _ Hickory.nullEvent] RETURNS [ event: Event] = { ENABLE Tempus.Unintelligible => Error[timeRopeFormatError, Rope.Cat[ SELECT ec FROM invalid => "Invalid: ", overConstrained => "Over Constrained: ", tooVague => "Too Vague: ", nothingSpecified => "Not a time: ", notImplemented => "Not Implemented: ", ENDCASE => ERROR, Rope.Substr[base: rope, len: vicinity], "<>", Rope.Substr[base: rope, start: vicinity] ]]; startAt, text, lead, until, duration, getSerious, repeat, iconFlavor, iconLabel, keyword, keep: Rope.ROPE; timeToStartNotification, nextNotification: PackedSeconds _ [0]; durationTime, leadTime, getSeriousTime: Seconds _ 0; precision: Tempus.Precision; keepUntilTime: Tempus.Packed _ LOOPHOLE[ BasicTime.nullGMT]; untilTime: Tempus.Packed _ LOOPHOLE[ BasicTime.nullGMT]; Lookup: PROC [class: ParameterClass] RETURNS[value: Rope.ROPE _ NIL] = INLINE { FOR l: ParameterList _ parameters, l.rest UNTIL l = NIL DO IF l.first.class = class THEN RETURN[l.first.value]; ENDLOOP; }; LookupKeyWord: PROC [word: Rope.ROPE] RETURNS[ParameterList] = INLINE { FOR l: KeyWordList _ keyWordList, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first.key, word, FALSE] THEN RETURN[l.first.defaults]; ENDLOOP; RETURN[NIL]; }; text _ Lookup[text]; startAt _ Lookup[startTime]; -- i.e. specified in parameters iconFlavor _ Lookup[iconFlavor]; iconLabel _ Lookup[iconLabel]; keyword _ NIL; IF scanThis # NIL THEN { h: IO.STREAM = IO.RIS[scanThis]; token: ROPE _ NIL; tokens: LIST OF ROPE; UNTIL h.EndOf[] DO breakProc: IO.BreakProc = { RETURN[SELECT char FROM ', , '. , ';, ': => sepr, ' , '\n, '\t, '\l => sepr, -- white space ENDCASE => other ]; }; defaults: ParameterList; iconLabelType: Rope.ROPE; token _ h.GetTokenRope[breakProc ! IO.EndOfStream => EXIT].token; tokens _ CONS[token, tokens]; IF (defaults _ LookupKeyWord[token]) # NIL THEN { -- found a keyword in input rope keyword _ token; -- add defaults to end of parameters, i.e. if parameters are specified by / .., -- they take precedence over those specified via keywords. FOR l: ParameterList _ parameters, l.rest UNTIL l = NIL DO defaults _ CONS[l.first, defaults]; ENDLOOP; parameters _ defaults; IF iconLabel = NIL AND (iconLabel _ Lookup[iconLabel]) = NIL AND (iconLabelType _ Lookup[iconLabelType]) # NIL THEN -- this check must be done here because iconLabelTypes may require knowing exactly where in the rope the corresponding key word appeared. SELECT TRUE FROM Rope.Equal[iconLabelType, "this", FALSE] => iconLabel _ token; Rope.Equal[iconLabelType, "next", FALSE] => { iconLabel _ h.GetTokenRope[breakProc ! IO.EndOfStream => EXIT].token; IF Rope.Fetch[iconLabel, 0] IN ['A..'Z] THEN { pos: INT = h.GetIndex[]; r: ROPE; WHILE Rope.Fetch[r _ h.GetTokenRope[breakProc ! IO.EndOfStream => EXIT].token, 0] IN ['A..'Z] DO iconLabel _ Rope.Cat[iconLabel, " ", r]; ENDLOOP; h.SetIndex[pos]; }; }; Rope.Equal[iconLabelType, "prev", FALSE] => IF tokens.rest # NIL THEN { iconLabel _ tokens.rest.first; IF Rope.Fetch[iconLabel, 0] IN ['A..'Z] THEN { FOR l: LIST OF ROPE _ tokens.rest.rest, l.rest UNTIL l = NIL DO IF NOT (Rope.Fetch[l.first, 0] IN ['A..'Z]) THEN EXIT; iconLabel _ Rope.Cat[l.first, " ", iconLabel]; ENDLOOP; }; }; ENDCASE => Error[formatError]; }; ENDLOOP; -- end of loop processing scan this IF startAt = NIL AND timeToStartNotification = 0 THEN { -- look for a time in scanThis t: Packed _ BasicTime.nullGMT; start, len: INT; startAt _ Lookup[startTime]; [t, precision, start, len] _ Tempus.Parse[rope: scanThis, baseTime: itIsNow ! Tempus.Unintelligible => IF ec # overConstrained THEN CONTINUE]; -- time may still be specified via keywords. Used to just check for nothingSpecified, but invalid can be raised in case that the word 'week' or 'day' appears in the text. IF t # BasicTime.nullGMT THEN { IF startAt # NIL THEN -- time specified both in text and keyword, e.g. keyword says Wednesday 1:15PM, user says Today, or the actual date. [t, precision] _ Tempus.Parse[rope: Rope.Cat[startAt, " ", Rope.Substr[base: scanThis, start: start, len: len]], baseTime: itIsNow ! Tempus.Unintelligible => CONTINUE]; timeToStartNotification _ Tempus.PackedToSeconds[t]; }; }; IF text = NIL THEN text _ scanThis; }; -- end of IF scanThis # NIL IF timeToStartNotification = 0 AND startAt # NIL THEN { -- startAt specified either in parameters, or via keyword. t: Packed; [t, precision] _ Tempus.Parse[startAt, itIsNow]; timeToStartNotification _ Tempus.PackedToSeconds[t]; }; IF timeToStartNotification = 0 THEN Error[timeNotSpecified]; IF (repeat _ Lookup[repeat]) # NIL THEN nextNotification _ ComputeRepetitionInterval[repeat, timeToStartNotification]; -- call ComputeRepetitionInterval here just so any errors will get noticed at registration time, rather than in event minder IF (duration _ Lookup[duration]) # NIL THEN { IF NOT Rope.Equal[duration, "NIL"] THEN -- so user can override defaultDuration by saying Duration: NIL durationTime _ Convert.CardFromRope[duration]*60 } ELSE IF defaultDuration # -1 AND precision > days THEN durationTime _ defaultDuration * 60; -- if user says Thursday, then don't default duration, but if he says a time do so IF (lead _ Lookup[leadTime]) # NIL THEN { leadTime _ Convert.CardFromRope[lead]*60; } ELSE IF precision <= days THEN NULL -- similarly if user says "See somebody Thursday", don't use leadtime. ELSE IF defaultLeadTime > 0 THEN leadTime _ (defaultLeadTime * 60); IF durationTime > 0 THEN durationTime _ MAX[durationTime, leadTime]; --show the reminder up to the event time IF (until _ Lookup[until]) # NIL THEN untilTime _ LOOPHOLE[ Tempus.Parse[ until, Tempus.SecondsToPacked[ timeToStartNotification]].time]; IF (getSerious _ Lookup[getSeriousAfter]) # NIL THEN getSeriousTime _ Convert.CardFromRope[getSerious]*60; IF ( keep _ Lookup[ keepUntil]) # NIL THEN keepUntilTime _ LOOPHOLE[ Tempus.Parse[ keep, Tempus.SecondsToPacked[ timeToStartNotification]].time]; event _ NEW[EventRecord _ [ timeToStartNotification: timeToStartNotification, text: text, repeat: repeat, untilTime: untilTime, nextNotification: nextNotification, durationTime: durationTime, leadTime: leadTime, getSeriousAfter: getSeriousTime, message: Lookup[message], iconLabel: iconLabel, iconFlavor: Lookup[iconFlavor], place: Lookup[ place], keepUntil: keepUntilTime, keyWord: keyword, dbKey: dbKey ]]; FOR l: LIST OF Event _ eventList, l.rest UNTIL l = NIL DO e: Event = l.first; IF Rope.Equal[e.message, event.message, FALSE] AND Rope.Equal[e.text, event.text, FALSE] THEN { -- i.e. if an event already exists which differs only in start time, duration, icon label, etc. then this event is considered to replace the original e^ _ event^; EXIT; }; REPEAT --in case of normal termination FINISHED => eventList _ CONS[event, eventList]; ENDLOOP; }; AddEvent: PUBLIC ENTRY PROC [event: Event] = { -- for use by buttons eventList _ CONS[event, eventList]; }; Save: PUBLIC ENTRY PROC = { OPEN IO; ENABLE UNWIND => NULL; Compare: List.CompareProc = { e1, e2: Event; e1 _ NARROW[ref1]; e2 _ NARROW[ref2]; RETURN[IF e1.timeToStartNotification < e2.timeToStartNotification THEN less ELSE greater]; }; newEventList: LIST OF Event _ NIL; now: PackedSeconds = Tempus.PackedToSeconds[]; hGroups: Hickory.GroupSet _ [ NIL, NIL]; FOR l: LIST OF Event _ eventList, l.rest UNTIL l = NIL DO event: Event _ l.first; IF event.restoreTrueTimes THEN { --fix up pretended repeated events event.timeToStartNotification _ event.trueTimeToStartNotification; event.nextNotification _ event.trueNextNotification; event.restoreTrueTimes _ FALSE }; ENDLOOP; FOR l: LIST OF Event _ eventList, l.rest UNTIL l = NIL DO IF Hickory.SameEvent[ l.first.dbKey, Hickory.nullEvent] THEN BEGIN hEv: Hickory.EventTuple _ BuildHickEvent[ l.first]; IF l.first.keyWord # NIL AND NOT Rope.Equal[ l.first.keyWord, ""] THEN hGroups _ RopeSets.InsertValueIntoSet[ l.first.keyWord, [ NIL, NIL]]; hEv _ Hickory.EnterEvent[ ev: hEv, groups: hGroups]; l.first.dbKey _ hEv.Key; END; ENDLOOP; FOR l: LIST OF Event _ eventList, l.rest UNTIL l = NIL DO event: Event _ l.first; IF event.repeat # NIL THEN NULL -- if this is a repeated event, can't purge it ELSE IF event.justPretending THEN NULL -- this event posted as a result of a justpretend ELSE IF event.destroyed -- reminder was posted and user has destroyed the viewer. the check for destroyed, rather than viewer.destroyed, is because SAVE may be called as a result of the destroy operation, i.e. from WasAReminderDestroyed, in which case the viewer.destroyed has not yet been set to TRUE. OR (event.durationTime # 0 AND event.timeToStartNotification + event.durationTime < now) -- this is an old event and no longer interesting THEN { IF logEvents THEN { eventLogStream.PutF["(Following Event deleted at %t.)\n", IO.time[]]; LogEvent[event, eventLogStream]; eventLogStream.Flush[]; }; LOOP; }; newEventList _ CONS[event, newEventList]; ENDLOOP; TRUSTED {eventList _ LOOPHOLE[List.Sort[list: LOOPHOLE[newEventList], compareProc: Compare]]}; }; BuildHickEvent: PUBLIC PROC [ ev: Event] RETURNS [ hEv: Hickory.EventTuple] = BEGIN evTime: Tempus.PackedSeconds _ [ ev.timeToStartNotification + ev.leadTime]; hEv.Key _ ev.dbKey; hEv.Protection _ Private; hEv.Remind _ TRUE; IF evTime = 0 THEN hEv.EventTime _ BasicTime.nullGMT ELSE hEv.EventTime _ LOOPHOLE[ Tempus.SecondsToPacked[ LOOPHOLE[ evTime]]]; IF ev.keepUntil = LOOPHOLE[ BasicTime.nullGMT] THEN hEv.KeepUntil _ BasicTime.latestGMT ELSE hEv.KeepUntil _ LOOPHOLE[ ev.keepUntil]; hEv.Duration _ ev.durationTime/60; hEv.LeadTime _ ev.leadTime/60; hEv.NagTime _ ev.getSeriousAfter/60; hEv.Message _ ev.message; hEv.Text _ ev.text; hEv.IconFlavor _ ev.iconFlavor; hEv.IconLabel _ ev.iconLabel; hEv.Place _ ev.place; IF ev.repeat = NIL THEN hEv.RepeatType _ None ELSE SELECT TRUE FROM Rope.Equal[ ev.repeat, "hourly", FALSE] => hEv.RepeatType _ Hourly; Rope.Equal[ ev.repeat, "daily", FALSE] => hEv.RepeatType _ Daily; Rope.Equal[ ev.repeat, "weekdays", FALSE] => hEv.RepeatType _ Weekdays; Rope.Equal[ ev.repeat, "weekly", FALSE] => hEv.RepeatType _ Weekly; Rope.Equal[ ev.repeat, "monthly", FALSE] => hEv.RepeatType _ Monthly; Rope.Equal[ ev.repeat, "yearly", FALSE] => hEv.RepeatType _ Yearly; ENDCASE => BEGIN hEv.RepeatType _ Complicated; hEv.RepeatTime _ ev.repeat; END; hEv.RepeatUntil _ LOOPHOLE[ ev.untilTime]; RETURN[ hEv]; END; -- BuildHickoryList LogEvent: PUBLIC PROC [event: Event, stream: IO.STREAM] = { -- to keep track of altered and posted events in log file OPEN IO; timeToStartNotification: PackedSeconds _ event.timeToStartNotification; IF event.leadTime # 0 THEN timeToStartNotification _ [timeToStartNotification + event.leadTime]; stream.PutF["%g\n", rope[Tempus.MakeRope[time: Tempus.SecondsToPacked[timeToStartNotification], includeDayOfWeek: TRUE]]]; IF event.repeat # NIL THEN stream.PutF["Repeat: %g\n", rope[event.repeat]]; IF event.durationTime # 0 THEN stream.PutF["Duration: %d\n", int[event.durationTime/60]]; IF event.leadTime # 0 THEN stream.PutF["LeadTime: %d\n", int[event.leadTime/60]]; IF event.getSeriousAfter # 0 THEN stream.PutF["NagTime: %d\n", int[event.getSeriousAfter/60]]; IF event.iconFlavor # NIL THEN stream.PutF["IconFlavor: \"%g\"\n", rope[event.iconFlavor]]; IF event.iconLabel # NIL THEN stream.PutF["IconLabel: \"%q\"\n", rope[event.iconLabel]]; IF event.place # NIL THEN stream.PutF[ "Place: \"%g\"\n", rope[ event.place]]; IF event.keepUntil # LOOPHOLE[ BasicTime.nullGMT] THEN stream.PutF[ "KeepUntil: \"%g\"\n", rope[ Tempus.MakeRope[ time: event.keepUntil, includeDayOfWeek: FALSE]]]; IF event.untilTime # LOOPHOLE[ BasicTime.nullGMT] THEN stream.PutF[ "RepeatUntil: \"%g\"\n", rope[ Tempus.MakeRope[ time: event.untilTime, includeDayOfWeek: FALSE]]]; IF event.message # NIL THEN stream.PutF["Message: \"%q\"\n", rope[event.message]]; stream.PutF["\"%q\"\n\n", rope[event.text]]; }; DeleteEvent: ENTRY PROC [ key: Hickory.Event] = BEGIN ENABLE UNWIND => NULL; FOR l: LIST OF Event _ eventList, l.rest UNTIL l = NIL DO IF Hickory.SameEvent[ key, l.first.dbKey] THEN BEGIN l.first.repeat _ NIL; l.first.justPretending _ FALSE; l.first.destroyed _ TRUE; RETURN; END; ENDLOOP; END; -- DeleteEvent defaultDuration: INT _ -1; defaultLeadTime: INT _ 0; KeyWordList: TYPE = LIST OF REF KeyWord; KeyWord: TYPE = RECORD [key: ROPE, defaults: ParameterList]; keyWordList: KeyWordList _ NIL; Options: UserProfile.ProfileChangedProc = { --PROC [reason: ProfileChangeReason]-- keyWords: Rope.ROPE; logEvents _ UserProfile.Boolean["Remember.CreateLog", FALSE]; IF logEvents AND reason=firstTime THEN eventLogStream _ FS.StreamOpen[fileName: "Remember.log", accessOptions: create]; --Rollback PROC will open log stream if needed defaultLeadTime _ UserProfile.Number["Remember.LeadTime", 0]; defaultDuration _ UserProfile.Number["Remember.Duration", -1]; keyWords _ UserProfile.Token["Remember.KeyWords"]; keyWordList _ NIL; IF keyWords # NIL THEN { stream: IO.STREAM = IO.RIS[keyWords]; scratchStream: IO.STREAM; UNTIL IO.EndOf[stream] DO key, line: Rope.ROPE; line _ IO.GetLineRope[stream]; scratchStream _ IO.RIS[line, scratchStream]; key _ IO.GetTokenRope[scratchStream ! IO.EndOfStream => CONTINUE].token; IF key # NIL THEN keyWordList _ CONS[NEW[KeyWord _ [key, ReadParameters[scratchStream]]], keyWordList]; ENDLOOP; }; }; RegisterKeyWord: PUBLIC PROC [keyWord: ROPE, parameters: ParameterList] = { keyWordList _ CONS[NEW[KeyWord _ [keyWord, parameters]], keyWordList]; }; ComputeRepetitionInterval: PUBLIC PROCEDURE [r: Rope.ROPE, time: Tempus.PackedSeconds] RETURNS[Tempus.PackedSeconds] = { ENABLE Tempus.Error => ERROR Tempus.Unintelligible[rope: r, vicinity: -1, ec: ec]; t: Tempus.Packed = Tempus.SecondsToPacked[time]; RETURN[Tempus.PackedToSeconds[ IF Rope.Equal[r, "HOURLY", FALSE] THEN Tempus.Adjust[baseTime: t, hours: 1, precisionOfResult: minutes].time ELSE IF Rope.Equal[r, "DAILY", FALSE] THEN Tempus.Adjust[baseTime: t, days: 1, precisionOfResult: minutes].time ELSE IF Rope.Equal[r, "WEEKDAYS", FALSE] THEN Tempus.Adjust[baseTime: t, days: IF BasicTime.Unpack[t].weekday = Friday THEN 3 ELSE 1, precisionOfResult: minutes].time ELSE IF Rope.Equal[r, "WEEKLY", FALSE] THEN Tempus.Adjust[baseTime: t, days: 7, precisionOfResult: minutes].time ELSE IF Rope.Equal[r, "MONTHLY", FALSE] THEN Tempus.Adjust[baseTime: t, months: 1, precisionOfResult: minutes].time ELSE IF Rope.Equal[r, "YEARLY", FALSE] THEN Tempus.Adjust[baseTime: t, years: 1, precisionOfResult: minutes].time ELSE Tempus.Parse[baseTime: t, rope: r].time]]; }; ReadTimeRope: PROC [stream: IO.STREAM] RETURNS[ROPE] = { RETURN[IO.GetLineRope[stream]]; }; WasAReminderDestroyed: ViewerEvents.EventProc -- [viewer: ViewerClasses.Viewer, event: ViewerEvent] -- = TRUSTED { prop: REF ANY _ ViewerOps.FetchProp[viewer, $Remember]; IF prop # NIL THEN { event: Event = NARROW[prop]; IF event.destroyed THEN RETURN ELSE IF event.justPretending THEN { event.justPretending _ FALSE; event.newStartTime _ TRUE; event.viewer _ NIL; RETURN; } ELSE IF event.newStartTime THEN RETURN; -- e.g. reset via snooze alarm event.destroyed _ TRUE; IF event.repeat # NIL THEN { IF event.nextNotification = 0 THEN ERROR; event.timeToStartNotification _ event.nextNotification; }; Process.Detach[FORK Save[]]; }; }; HickoryChange: Hickory.NotifyProc -- [ reason: Reason, ev: Event, data: RopeSets.RopeSet] -- = BEGIN SELECT reason FROM NewEvent, UnForget => EnterEvent[ ev]; Destroy, Forget => DeleteEvent[ ev]; Edit => EditEvent[ ev]; ENDCASE; TRUSTED { Process.Detach[FORK Save[]]; }; END; CheckpointProc: Booting.CheckpointProc = { IF eventLogStream # NIL THEN { IO.Close[eventLogStream]; eventLogStream _ NIL; }; DisableEventMinder[]; }; RollbackProc: Booting.RollbackProc = { IF logEvents THEN eventLogStream _ FS.StreamOpen[fileName: "Remember.log", accessOptions: append]; Construct[]; EnableEventMinder[]; }; UserProfile.CallWhenProfileChanges[Options]; [] _ ViewerEvents.RegisterEventProc[proc: WasAReminderDestroyed, filter: $Text, event: destroy]; Hickory.RegisterNotifyProc[ HickoryChange]; Construct[]; TRUSTED {Booting.RegisterProcs[c: CheckpointProc, r: RollbackProc]}; END. t RememberImpl.mesa. See Remember.Tioga for documentation and examples of how to use the new remember package. Last Edited by: Teitelman, July 21, 1983 10:50 am Converted by: Maxwell, January 6, 1984 1:36 pm Last Edited by: Pier, May 2, 1984 3:59:07 pm PDT Last Edited by: Binding, June 21, 1984 6:00:57 pm PDT Types Global Variables Parsing to build the parameter list for each event read from data base. Registering Events gets called when a new event was entered into Hickory or unforgotten check if we don't already have this event cached gets called when an event was edited in Hickory. Note that an event that has a viewer, i.e. whose time has come, can not be edited anymore.... rip the old event out of eventList get the new version and insert it if necessary, i.e. if it is to be scheduled in next 6 hours must substitute contents of l.first with evl.first... IF startAt = NIL AND timeToStartNotification = 0 THEN startAt _ Lookup[startTime]; -- defaults may specify a startAt timeToStartNotification _ [timeToStartNotification - leadTime]; Saving Events this procedure updates the cached list of events "eventList". if a new event is found it is stored in the hickory data base. No elements in data base get deleted. --purge those events no longer interesting gets called when an event in hickory was destroyed or forgotten User Profile Options Miscellaneous Initialization Κ@˜J™mJ™1J™.J™0J™5šΟk ˜ Jšœ˜Jšœ œ7˜FJšœœ.˜;Jšœœ ˜-Jšœœ˜J˜°Jšœœ˜œ"œ%˜πJšœœ˜Jšœœ ˜Jšœ ˜ Jšœ˜Jšœœœ˜,Jšœ œ˜%JšœœŒ˜˜Jšœ œF˜WJšœœ ˜Jšœ œ,˜>Jšœ œ ˜Jšœ˜J˜—J˜šΟl œœ˜J˜Jšœ˜"J˜Jšœ$œ œb˜œJ˜Jšœ"˜)—J˜JšœœœB˜Nhead™Jš œœœœœœ˜AJ˜—™Jšœ œ˜JšΟbœœ&Οcb˜—Jšœ œœœ˜ Jšœ œœœ˜ Jšœ œœ˜—šœ™šΟn œ œœ˜Jš \˜\š˜Jšœœœ˜Jšœœœœ˜)Jšœœ˜J˜-J˜Jšœœ˜#Jšœy˜yš œœœ%œœ˜CJšœ œœ˜š œœœœœ˜9šœ0œ˜=Jšœ œ˜Jšœ˜Jšœ˜—Jšœ˜—šœœ œ˜Jšœ%˜%Jšœœœ˜=Jšœ˜—Jšœ˜—Jšœ  ˜——J˜š ‘œœ œ-œœ˜xJšœ@™@—š˜šœœ˜"Jšœœ˜šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ!˜!Jšœœ˜—Jšœ œœ&˜9Jšœ ˜—Jš œœ œœœœ"˜rJš œœœœ!œœ˜ušœœ˜Jš œ œœœœ ˜XJšœ˜—Jš œ"œœœ2œ˜”Jš œ œœœœ*˜SJš œœœœœ0˜\Jš œœœœœ6˜eJš œœœœœ4˜bJš œ œœ œœ,˜Všœ"œ˜/Jšœœœ#œ˜XJšœ œœ)˜˜Dšœœ˜Jšœ˜Jšœ(˜(Jšœ˜J˜#Jšœ&˜&Jšœœ˜—J•StartOfExpansion9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]˜'J–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]˜J˜(Jšœ˜—Jšœeœ˜jJšœ?˜?Jšœ4˜4J˜Jšœœ˜šœ"œ˜-Jšœ'œœ˜Ešœœ œ˜.Jšœœ˜Jšœœ˜š œ+œœ œ ˜`J˜(Jšœ˜—J˜J˜—J˜—š œ"œœœœ˜GJšœ˜šœœ œ˜.š œœœœœœ˜?Jš œœœ œœ˜6Jšœ.˜.Jšœ˜—J˜—J˜—Jšœ˜——Jšœ˜—Jšœ $˜/—š œ œœœ ˜VJ˜Jšœ œ˜Jšœ˜Jšœgœœœ ͺ˜Ίšœœ˜šœ œœ t˜‹Jšœžœ˜¨—Jšœ4˜4Jšœ˜—Jšœ˜—Jšœœœ˜#Jšœ ˜—š œœ œœ :˜sJ˜ Jšœ0˜0Jšœ4˜4J˜—Jšœœ˜ .˜—Jšœ=˜=Jšœ>˜>Jšœ2˜2Jšœœ˜šœ œœ˜Jš œœœœœ ˜%Jšœœœ˜šœœ˜Jšœœ˜Jšœœ˜Jšœœœ˜,Jšœœœœ˜HJš œœœœœ?˜gJšœ˜Jšœ˜——Jšœ˜—J˜š‘œœœ œ ˜KJšœœœ0˜FJ˜——™procš‘œœ œœ˜yJšœœ6˜RJ˜0šœ˜JšœœœF˜l—JšœœœœE˜ošœœœœ˜.Jšœ!œ&œœ$˜x—JšœœœœE˜pJšœœœœG˜sJšœœœœF˜qJšœ-˜1Jšœ˜J˜—J˜š ‘ œœ œœœœ˜8Jšœœ˜Jšœ˜—J˜š‘œ 8œœ˜sJšœœœ*˜7šœœœ˜Jšœœ˜Jšœœ˜šœœœ˜#Jšœœ˜Jšœœ˜Jšœœ˜Jšœ˜J˜—Jš œœœœ ˜GJšœœ˜šœœœ˜Jšœœœ˜)Jšœ7˜7J˜—Jšœœ ˜J˜—˜J˜——šœ" :œ˜dšœ˜J˜&Jšœ$˜$J˜Jšœ˜—Jšœœ ˜)Jšœ˜J˜—šŸœ˜*šœœœ˜Jšœ˜Jšœœ˜J˜—J˜Jšœ˜—J˜šŸ œ˜&Jšœ œœ=˜bJšœ ˜ Jšœ˜Jšœ˜——J™™Jšœ,˜,Jšœ`˜`J˜+Jšœ ˜ Jšœ=˜DJ™—Jšœ˜—…—cT„