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
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;
Types
Error: PUBLIC ERROR [ec: ErrorCode, msg: Rope.ROPENIL] = CODE;
Global Variables
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 BOOLFALSE;
peanutUser: PUBLIC BOOLFALSE;
debugging: BOOLEANTRUE;
Parsing
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: BOOLEANFALSE;
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] =
to build the parameter list for each event read from data base.
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: ROPENIL] = {
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];
};
Registering Events
EnterEvent: ENTRY PROCEDURE [ key: Hickory.Event] = BEGIN
gets called when a new event was entered into Hickory or unforgotten
ENABLE UNWIND => NULL;
evl: Hickory.EventList;
paramList: ParameterList ← NIL;
options: Hickory.Options ← Hickory.NoOptions;
options[ Hickory.Reminders] ← TRUE;
check if we don't already have this event cached
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
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....
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...
rip the old event out of eventList
IF prev # NIL THEN BEGIN
prev.rest ← cur.rest; cur.rest ← NIL;
END
ELSE BEGIN
eventList ← cur.rest; cur.rest ← NIL;
END;
END;
get the new version and insert it if necessary, i.e. if it is to be scheduled in next 6 hours
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
must substitute contents of l.first with evl.first...
paramList ← BuildParamList[ evl.first];
[] ← Register[ paramList, NIL, key];
END; -- EditEvent
RegisterEvent: PUBLIC ENTRY PROC [parameters: ParameterList, scanThis: Rope.ROPENIL, 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.ROPENIL, 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.ROPENIL] = 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: ROPENIL;
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 startAt = NIL AND timeToStartNotification = 0 THEN startAt ← Lookup[startTime]; -- defaults may specify a startAt
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;
timeToStartNotification ← [timeToStartNotification - leadTime];
}
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];
};
Saving Events
Save: PUBLIC ENTRY PROC = {
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.
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;
--purge those events no longer interesting
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
gets called when an event in hickory was destroyed or forgotten
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
User Profile Options
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];
};
Miscellaneous
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[];
};
Initialization
UserProfile.CallWhenProfileChanges[Options];
[] ← ViewerEvents.RegisterEventProc[proc: WasAReminderDestroyed, filter: $Text, event: destroy];
Hickory.RegisterNotifyProc[ HickoryChange];
Construct[];
TRUSTED {Booting.RegisterProcs[c: CheckpointProc, r: RollbackProc]};
END.