RemindImpl.mesa
Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved.
David Goldberg November 13, 1989 7:59:49 pm PST
Peter B. Kessler, January 17, 1990 9:11:14 pm PST
Last changed by Pavel on February 12, 1990 5:40:45 pm PST
Kenneth A. Pier, April 19, 1990 10:56 am PDT
Theimer, September 17, 1990 11:22 pm PDT
Last tweaked by Mike Spreitzer August 27, 1993 8:19 am PDT
Willie-s, May 6, 1992 1:11 pm PDT
DIRECTORY
Arpa USING [Address],
Basics,
BasicTime,
Commander, CommanderOps,
Convert,
FS USING [StreamOpen],
IntHashTable,
IO,
List USING [LORA, Remove],
LoganBerry USING [Attribute, BuildIndices, Close, CompactLogs, Cursor, DeleteEntry, EndGenerate, Entry, Error, GenerateEntries, NextEntry, nullDB, Open, OpenDB, ReadEntry, RegisterWriteProc, UnregisterWriteProc, WriteEntry],
NetworkName,
RedBlackTree,
Remind,
Rope,
rtable3,
SimpleFeedback,
SunPMap,
SunPMapClient,
SunRPC,
SunRPCAuth,
SunRPCOnUDP,
SymTab,
SystemNames USING [UserCedarDir, UserName],
TimeParse USING [Adjust],
UnixSysCalls USING [GetPID],
UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Token],
UXTime;
RemindImpl: CEDAR PROGRAM
IMPORTS Basics, BasicTime, Commander, CommanderOps, Convert, FS, IntHashTable, IO, List, LoganBerry, NetworkName, RedBlackTree, Rope, rtable3, SimpleFeedback, SunPMapClient, SunRPC, SunRPCAuth, SunRPCOnUDP, SymTab, SystemNames, TimeParse, UnixSysCalls, UserProfile, UXTime
EXPORTS Remind
Implements the remind interface, and also contains the PostReminders proc, which runs in the background and pops up reminders
= BEGIN OPEN Remind, RT:rtable3;
ROPE: TYPE ~ Rope.ROPE;
RTable: TYPE ~ RT.TABLEPROG3;
repetitionNames:
ARRAY Repetitions
OF
ROPE = [
"once", "daily", "weekdays", "weekly", "biweekly", "monthly", "yearly", "nthWeekday", "everyNthDay", "everyNthWeek", "everyNthMonth", "other"];
RopeFromRepetition:
PUBLIC
PROC[rep: Repetitions]
RETURNS [
ROPE] = {
RETURN[repetitionNames[rep]];
};
RepetitionFromRope:
PUBLIC
PROC[str:
ROPE]
RETURNS [Repetitions] = {
FOR rep: Repetitions
IN Repetitions
DO
IF Rope.Equal[str, repetitionNames[rep], FALSE] THEN RETURN[rep];
ENDLOOP;
RETURN[once];
};
meetingTypeNames: ARRAY MeetingType OF ROPE = [ "none", "meeting", "command", "seminar", "protectedCmd"];
RopeFromMeetingType:
PUBLIC
PROC[type: MeetingType]
RETURNS [
ROPE] = {
RETURN[meetingTypeNames[type]];
};
MeetingTypeFromRope:
PUBLIC
PROC[str:
ROPE]
RETURNS [MeetingType] = {
FOR type: MeetingType
IN MeetingType
DO
IF Rope.Equal[str, meetingTypeNames[type], FALSE] THEN RETURN[type];
ENDLOOP;
RETURN[meeting];
};
BadData: PUBLIC ERROR = CODE;
RepsToPeriod: ARRAY Repetitions OF RT.Interval ~ [once: single, daily: daily, weekdays: daily, weekly: weekly, biweekly: biweekly, monthly: monthly, yearly: yearly, nthWeekday: nthWeekday, everyNthDay: everyNthDay, everyNthWeek: everyNthWeek, everyNthMonth: everyNthMonth, other: otherPeriod];
PeriodToReps: ARRAY RT.Interval OF Repetitions ~ [single: once, daily: daily, weekly: weekly, biweekly: biweekly, monthly: monthly, yearly: yearly, nthWeekday: nthWeekday, everyNthDay: everyNthDay, everyNthWeek: everyNthWeek, everyNthMonth: everyNthMonth, otherPeriod: other];
- Next 3 routines are candidates for INLINE
IntFromTime:
PROC[time: BasicTime.
GMT]
RETURNS [
INT] = {
RETURN [BasicTime.Period[BasicTime.earliestGMT, time]];
};
TimeFromInt:
PROC[int:
INT]
RETURNS [BasicTime.
GMT] = {
RETURN[BasicTime.Update[BasicTime.earliestGMT, int]];
};
MyRopeFromInt:
PROC[int:
INT]
RETURNS [
ROPE] = {
RETURN[IO.PutFR1["%010g", IO.int[int]]];
};
TimeFromUnixCmd:
PROC [cmd: Commander.Handle]
RETURNS [result:
REF
ANY ¬
NIL, msg:
ROPE ¬
NIL]
--Commander.CommandProc-- ~ {
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
i: INT;
t: BasicTime.GMT;
IF argv.argc # 2 THEN RETURN [$Failure, Rope.Cat["Usage: ", argv[0], " <an integer>"]];
i ¬ Convert.IntFromRope[argv[1]];
t ¬ UnixToBasicTime[i];
cmd.out.PutF["%g ~ %g\n", [integer[i]], [rope[Convert.RopeFromTime[from: t, end: seconds]]] ];
RETURN};
TimeFromIntCmd:
PROC [cmd: Commander.Handle]
RETURNS [result:
REF
ANY ¬
NIL, msg:
ROPE ¬
NIL]
--Commander.CommandProc-- ~ {
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
i: INT;
t: BasicTime.GMT;
IF argv.argc # 2 THEN RETURN [$Failure, Rope.Cat["Usage: ", argv[0], " <an integer>"]];
i ¬ Convert.IntFromRope[argv[1]];
t ¬ TimeFromInt[i];
cmd.out.PutF["%g ~ %g\n", [integer[i]], [rope[Convert.RopeFromTime[from: t, end: seconds]]] ];
RETURN};
GMTUpCmd:
PROC [cmd: Commander.Handle]
RETURNS [result:
REF
ANY ¬
NIL, msg:
ROPE ¬
NIL]
--Commander.CommandProc-- ~ {
argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd];
i: INT;
t: BasicTime.GMT;
IF argv.argc # 2 THEN RETURN [$Failure, Rope.Cat["Usage: ", argv[0], " <an integer>"]];
i ¬ Convert.IntFromRope[argv[1]];
t ¬ LOOPHOLE[i];
cmd.out.PutF["%g ~ %g\n", [integer[i]], [rope[Convert.RopeFromTime[from: t, end: seconds]]] ];
RETURN};
baseGMT: BasicTime.GMT ¬ UXTime.ToGMT[UXTime.earliestUXTIME];
limitUnixTime: INT ¬ BasicToUnixTime[BasicTime.latestGMT];
BasicToUnixTime:
PROC [gmt: BasicTime.
GMT]
RETURNS [
INT] ~ {
IF BasicTime.Period[from: gmt, to: baseGMT] > 0 THEN gmt ¬ baseGMT;
TRUSTED {RETURN [LOOPHOLE[UXTime.FromGMT[gmt]]]}};
UnixToBasicTime:
PROC [t:
INT]
RETURNS [BasicTime.
GMT] ~ {
t ¬ MIN[t, limitUnixTime];
TRUSTED {RETURN UXTime.ToGMT[LOOPHOLE[t]]}};
RegisterChangeProc:
PUBLIC
PROC[proc:
PROC[data:
REF], clientData:
REF, ident:
ATOM, dbName:
ROPE ¬
NIL] = {
block: ProcBlock ¬ NEW[ProcBlockRec];
block.proc ¬ proc;
block.data ¬ clientData;
block.dbName ¬ dbName;
block.ident ¬ ident;
changeProcList ¬ CONS[block, changeProcList];
};
UnregisterChangeProc:
PUBLIC
PROC[ident:
ATOM, dbName:
ROPE ¬
NIL] = {
block: ProcBlock ¬ NEW[ProcBlockRec];
l: List.LORA;
blk: ProcBlock;
WHILE
TRUE
DO
FOR l ¬ changeProcList, l.rest
UNTIL l =
NIL
DO
blk ¬ NARROW[l.first];
IF blk.ident=ident
AND Rope.Equal[blk.dbName, dbName,
FALSE]
THEN {
changeProcList ¬ List.Remove[l.first, changeProcList];
EXIT;
}
ENDLOOP;
ENDLOOP;
};
DatabaseChanged:
PROC[db: LoganBerry.OpenDB, entry: LoganBerry.Entry, ident:
ATOM, clientData:
REF]
--LoganBerryExtras.WriteProc-- = {
name: ROPE;
blk: ProcBlock;
name ¬ NARROW[clientData];
FOR l: List.
LORA ¬ changeProcList, l.rest
UNTIL l =
NIL
DO
blk ¬ NARROW[l.first];
IF Rope.Equal[blk.dbName, name, FALSE] THEN blk.proc[blk.data];
ENDLOOP;
};
GetDefaultDbName:
PUBLIC
PROC
RETURNS [
ROPE]
~ {RETURN [defaultDbName]};
OpenDb:
PROC[create:
BOOLEAN ¬
FALSE, name:
ROPE ¬
NIL]
RETURNS [db: LoganBerry.OpenDB ¬ LoganBerry.nullDB, rt: RTable ¬
NIL, usedDbName:
ROPE ¬
NIL] = {
dbName: ROPE;
gotBadIndex: BOOL ¬ FALSE;
nl, atpos, retried: INT ¬ 0;
IF name = NIL THEN dbName ¬ defaultDbName ELSE dbName ¬ name;
usedDbName ¬ dbName;
nl ¬ dbName.Length[];
IF (atpos ¬ dbName.Index[s2: "@"]) < nl
THEN {
hostName: ROPE ~ dbName.Substr[start: atpos+1];
hostAddrRope:
ROPE ~ NetworkName.AddressFromName[$ARPA, hostName,
NIL, host !NetworkName.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "NetworkName.Error[%g] looking up host part of db name %g", LIST[[rope[msg]], [rope[dbName]]] ];
GOTO done}].addr;
hostAddr: Arpa.Address ¬ Convert.ArpaAddressFromRope[hostAddrRope
!Convert.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "Convert.Error[VAL[%g], near %g] parsing address rope %g from db name %g", LIST[[cardinal[reason.ORD]], [integer[index]], [rope[hostAddrRope]], [rope[dbName]]] ];
GOTO done}];
rhPM: SunRPC.Handle ¬ SunRPCOnUDP.Create[hostAddr, Basics.HFromCard16[SunPMap.udpPort]];
conv: SunRPCAuth.Conversation ¬ SunRPCAuth.Initiate[SunRPCAuth.unixFlavor, SystemNames.UserName[]];
port: CARDINAL ¬ SunPMapClient.GetPort[rhPM, conv, RT.TABLEPROG, RT.TABLEVERS, SunPMap.ipProtocolUDP];
rh: SunRPC.Handle;
IF port=0
THEN {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "no Sun calendar manager daemon (supporting version 3) on %g", [rope[hostName]] ];
GOTO done};
rh ¬ SunRPCOnUDP.SetRemote[rhPM, hostAddr, Basics.HFromCard16[Basics.LowHalf[port]]];
rt ¬
RT.MakeTABLEPROG3Client[rh, conv !
SunRPC.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "SunRPC.Error[%g] importing cm V3 from %g", LIST[[atom[code]], [rope[hostName]]] ];
GOTO done};
SunRPCAuth.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "SunRPCAuth.Error[%g] importing cm V3 from %g", LIST[[atom[code]], [rope[hostName]]] ];
GOTO done}];
rt.rtableping[rt !
SunRPC.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "SunRPC.Error[%g] pinging cm V3 on %g", LIST[[atom[code]], [rope[hostName]]] ];
GOTO done};
SunRPCAuth.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "SunRPCAuth.Error[%g] pinging cm V3 on %g", LIST[[atom[code]], [rope[hostName]]] ];
GOTO done}];
target ¬ dbName;
}
ELSE {
target ¬ NIL;
db ¬ LoganBerry.Open[dbName: dbName ! LoganBerry.Error => {
IF ec=$BadIndex
AND retried < 3
THEN {
retried ¬ retried + 1;
gotBadIndex ¬ TRUE;
RETRY;
}
ELSE {
IF ec = $CantOpenSchema
OR ec=$CantOpenLog
THEN {
IF create
AND retried < 3
THEN {
Create[];
retried ¬ retried + 1;
RETRY;
}
ELSE GOTO done;
};
};
}];
IF gotBadIndex
THEN
BEGIN
LoganBerry.BuildIndices[db: db];
END;
LoganBerry.RegisterWriteProc[DatabaseChanged, db, $Remind, name];
};
RETURN;
};
AddMeeting:
PUBLIC
PROC[meeting: Meeting, closeDb:
BOOLEAN ¬
TRUE, dbName:
ROPE ¬
NIL]
RETURNS [id:
INT] = {
db: LoganBerry.OpenDB;
rt: RTable;
[db, rt] ¬ OpenDb[create: TRUE, name: dbName];
IF db#LoganBerry.nullDB
THEN {
Meetings are stored with meeting.start converted to a 0-padded INT in the start field. The zero-padding is so that loganberry compares yield time order.
entry: LoganBerry.Entry ¬ NIL;
Link:
PROC[atom:
ATOM, str:
ROPE] = {
IF str #
NIL
AND
NOT Rope.IsEmpty[str]
THEN {
attr: LoganBerry.Attribute;
attr.type ¬ atom;
attr.value ¬ str;
entry ¬ CONS[attr, entry];
};
};
convert time to an integer and use this as a unique record id
id ¬ IntFromTime[BasicTime.Now[]];
Link[$uniqID, Convert.RopeFromInt[id]];
XXX: will changing timezones goof this up?
Link[$type, meetingTypeNames[meeting.type]];
Link[$start, MyRopeFromInt[IntFromTime[meeting.start]]];
Link[$duration, Convert.RopeFromCard[meeting.duration]];
Link[$explanation, meeting.explanation];
Link[$repeat, repetitionNames[meeting.repeat]];
FOR rl: RemindList ¬ meeting.reminders, rl.rest
WHILE rl#
NIL
DO
WITH rl.first
SELECT
FROM
x:
REF ReminderRecord[alert] => {
Link[$stopReminding, MyRopeFromInt[IntFromTime[x.stop]]];
Link[$startReminding, MyRopeFromInt[IntFromTime[x.start]]];
};
x:
REF ReminderRecord[mail] => {
Link[$mailTo, x.to];
Link[$mailAt, MyRopeFromInt[IntFromTime[x.when]]];
};
ENDCASE => ERROR;
ENDLOOP;
Link[$more, meeting.more];
Link[$iconLabel, meeting.iconLabel];
Link[$iconFlavor, meeting.iconFlavor];
Link[$public, Convert.RopeFromBool[meeting.public]];
-- I think the check for DBClosed is obsolete
LoganBerry.WriteEntry[db: db, entry: entry ! LoganBerry.Error =>
{IF ec = $DBClosed THEN {OpenLoganBerry[name: dbName]; RETRY} ELSE REJECT}
];
LoganBerry.WriteEntry[db: db, entry: entry];
XXX:XXX
IF closeDb THEN CloseLoganBerry[db]; -- so autobackup will see a change
}
ELSE
IF rt#
NIL
THEN {
appt: REF RT.Appt;
args: RT.Args;
res: RT.TableRes;
pd: RT.Period ¬ [period: RepsToPeriod[meeting.repeat], nth: 0];
tag: REF RT.Tag ¬ NEW [RT.Tag ¬ [tag: appointment, showtime: 1, next: NIL]];
attrs:
REF
RT.Attribute ¬ PushReminders[meeting, meeting.reminders,
NEW[
RT.Attribute ¬ [
next: NIL,
attr: "cedar-more",
value: meeting.more,
clientdata: NIL]] ];
TRUSTED {appt ¬
NEW[
RT.Appt ¬ [
apptid: [tick: BasicToUnixTime[meeting.start], key: 0],
tag: tag,
duration: meeting.duration*60,
ntimes: IF meeting.repeat=once THEN 0 ELSE 999999999,
what: meeting.explanation,
period: pd,
author: NIL,
clientdata: NIL,
exception: NIL,
attr: attrs,
apptstatus: active,
privacy: IF meeting.public THEN public ELSE private,
next: NIL]]};
args ¬ NEW [RT.ArgsObject[APPT] ¬ [APPT[appt]] ];
res ¬ rt.rtableinsert[rt, target, args, UnixSysCalls.GetPID[] ! SunRPC.Error => {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "insert of cm appt failed (%g)", [atom[code]] ];
GOTO dun}];
IF watchAS
THEN
SELECT res.status
FROM
accessok, accessadded => NULL;
ENDCASE => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "Insertion into DB %g of meeting at %g failed (%g)", LIST[[rope[dbName]], [time[meeting.start]], [rope[RT.AccessStatusNames[res.status]]]] ];
GOTO dun};
WITH res.res
SELECT
FROM
x: REF RT.TableResListObject[AP] => id ¬ x.a.apptid.key;
x: REF RT.TableResListObject[ID] => id ¬ x.i.apptid.key;
ENDCASE => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "Insertion into DB %g of meeting at %g returned unexpected variant", LIST[[rope[dbName]], [time[meeting.start]]] ];
GOTO dun};
EXITS dun => id ¬ 0;
}
ELSE {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "Unable to open DB %g", [rope[dbName]] ];
id ¬ 0};
};
PushReminders:
PROC [meeting: Meeting, rl: RemindList, attr:
REF
RT.Attribute]
RETURNS [
REF
RT.Attribute] ~ {
FOR rl ¬ rl, rl.rest
WHILE rl#
NIL
DO
nu:
REF
RT.Attribute ~
NEW [
RT.Attribute ¬
WITH rl.first
SELECT
FROM
x:
REF ReminderRecord[alert] => [
next: attr,
attr: "op",
value: Convert.RopeFromInt[BasicTime.Period[from: x.start, to: meeting.start]],
clientdata: NIL],
x:
REF ReminderRecord[mail] => [
next: attr,
attr: "ml",
value: Convert.RopeFromInt[BasicTime.Period[from: x.when, to: meeting.start]],
clientdata: x.to],
ENDCASE => ERROR];
attr ¬ nu;
ENDLOOP;
RETURN [attr]};
breakStart: INT ¬ 0;
breakUID: INT ¬ 0;
breaksSeen: INT ¬ 0;
InterpLbAttrs:
PROC [aList:
LIST
OF LoganBerry.Attribute]
RETURNS [meeting: Meeting] ~ {
break: BOOL ¬ FALSE;
meeting ¬ NEW[MeetingRec];
WHILE aList#
NIL
DO
atom: ATOM ~ aList.first.type;
value: ROPE ~ aList.first.value;
aList ¬ aList.rest;
SELECT atom
FROM
$uniqID => {
meeting.uniqID ¬ Convert.IntFromRope[value];
IF meeting.uniqID=breakUID THEN break ¬ TRUE};
$type => meeting.type ¬ MeetingTypeFromRope[value];
$start => {i:
INT ~ Convert.IntFromRope[value];
meeting.start ¬ TimeFromInt[i];
IF i=breakStart THEN break ¬ TRUE};
$duration => meeting.duration ¬ Convert.CardFromRope[value];
$explanation => meeting.explanation ¬ value;
$repeat => meeting.repeat ¬ RepetitionFromRope[value];
$startReminding => {
x: REF ReminderRecord[alert] ~ NEW [ReminderRecord[alert]];
x.start ¬ TimeFromInt[Convert.IntFromRope[value]];
IF aList#
NIL
AND aList.first.type=$stopReminding
THEN {
x.stop ¬ TimeFromInt[Convert.IntFromRope[aList.first.value]];
aList ¬ aList.rest;
meeting.reminders ¬ CONS[x, meeting.reminders]}
ELSE ERROR BadData[]};
$stopReminding => {
x: REF ReminderRecord[alert] ~ NEW [ReminderRecord[alert]];
x.stop ¬ TimeFromInt[Convert.IntFromRope[value]];
IF aList#
NIL
AND aList.first.type=$startReminding
THEN {
x.start ¬ TimeFromInt[Convert.IntFromRope[aList.first.value]];
aList ¬ aList.rest;
meeting.reminders ¬ CONS[x, meeting.reminders]}
ELSE ERROR BadData[]};
$more => meeting.more ¬ value;
$iconLabel => meeting.iconLabel ¬ value;
$iconFlavor => meeting.iconFlavor ¬ value;
$public => meeting.public ¬ Rope.Equal[value, "TRUE"];
$mailAt => {
x: REF ReminderRecord[mail] ~ NEW [ReminderRecord[mail]];
x.when ¬ TimeFromInt[Convert.IntFromRope[value]];
IF aList#
NIL
AND aList.first.type=$mailTo
THEN {
x.to ¬ aList.first.value;
aList ¬ aList.rest}
ELSE x.to ¬ "user@host";
meeting.reminders ¬ CONS[x, meeting.reminders]};
ENDCASE => ERROR BadData[];
ENDLOOP;
IF break THEN breaksSeen ¬ breaksSeen + 1;
RETURN};
make two passes
ListMeetings:
PUBLIC
PROC [
from, to: BasicTime.GMT,
all: BOOLEAN ¬ FALSE,
dbName: ROPE ¬ NIL
]
RETURNS [ls:
LIST
OF Meeting ¬
NIL] = {
cursor: LoganBerry.Cursor;
entry: LoganBerry.Entry;
meeting, meeting1: Meeting;
lim: INT;
db: LoganBerry.OpenDB;
rt: RTable;
usedDbName: ROPE;
[db, rt, usedDbName] ¬ OpenDb[name: dbName];
-XXX: this is a royal kludge: FirstOccurrenceAfter substracts by 1 year, so add 1 year here to avoid BasicTimeImpl.OutOfRange ERROR (note that TimeParse.Adjust won't work, because it interprets earliestGMT as Now).
IF BasicTime.Period[from: BasicTime.earliestGMT, to: from] < BasicTime.secondsPerYear THEN from ¬ BasicTime.Update[BasicTime.earliestGMT, BasicTime.secondsPerYear];
lim ¬ IntFromTime[to];
IF db#LoganBerry.nullDB
THEN {
first pass: find all one time meetings
cursor ¬ LoganBerry.GenerateEntries[
db: db,
start: MyRopeFromInt[IntFromTime[from]],
end: MyRopeFromInt[IntFromTime[to]],
key: $start
];
WHILE (entry ¬ LoganBerry.NextEntry[cursor: cursor]) #
NIL
DO
meeting ¬ InterpLbAttrs[entry];
IF meeting.repeat = once THEN ls ¬ CONS[meeting, ls];
ENDLOOP;
LoganBerry.EndGenerate[cursor: cursor];
second pass: find all repeated meetings
FOR rep: Repetitions
IN [daily..
LAST[Repetitions]]
DO
cursor ¬ LoganBerry.GenerateEntries[
db: db,
start: repetitionNames[rep], -- RopeFromRepetition[rep]:
end: repetitionNames[rep],
key: $repeat];
WHILE (entry ¬ LoganBerry.NextEntry[cursor: cursor]) #
NIL
DO
newReminders: BOOL ¬ FALSE;
newStart: BasicTime.GMT;
meeting ¬ InterpLbAttrs[entry];
newStart ¬ FirstOccurrenceAfter[rep, meeting.nth, meeting.start, from];
WHILE IntFromTime[newStart] <= lim
DO
UpdateMeeting[meeting, newStart, TRUE, newReminders];
ls ¬ CONS[meeting, ls];
IF NOT all THEN EXIT;
meeting1 ¬ meeting;
meeting ¬ NEW[MeetingRec ¬ meeting1];
newStart ¬ NextOccurrence[rep, meeting.nth, meeting.start];
newReminders ¬ TRUE;
ENDLOOP;
ENDLOOP;
LoganBerry.EndGenerate[cursor: cursor];
ENDLOOP;
}
ELSE
IF rt#
NIL
THEN {
range:
RT.Range ~ [
key1: BasicToUnixTime[from],
key2: BasicToUnixTime[to],
next: NIL];
seen: IntHashTable.Table ~ IF all THEN NIL ELSE IntHashTable.Create[];
PassAppt:
PROC [appt:
REF
RT.Appt]
RETURNS [
BOOL] ~ {
IF range.key1<=appt.apptid.tick
AND appt.apptid.tick<=range.key2
AND (all
OR seen.Insert[appt.apptid.key, $T])
THEN ls ¬ CONS[ApptToMtg[appt], ls];
RETURN [FALSE]};
ScanAppts[rt, usedDbName, PassAppt, "ListMeetings", range.key1, range.key2];
RETURN};
};
ApptToMtg:
PROC [appt:
REF
RT.Appt]
RETURNS [mtg: Meeting] ~ {
mtg ¬
NEW [MeetingRec ¬ [
uniqID: appt.apptid.key,
type: meeting,
start: UnixToBasicTime[appt.apptid.tick],
duration: MAX[(appt.duration+30)/60, 1],
explanation: appt.what,
repeat: PeriodToReps[appt.period.period],
public: appt.privacy = public,
other: NIL]];
FOR attr:
REF
RT.Attribute ¬ appt.attr, attr.next
WHILE attr#
NIL
DO
IF attr.attr.Equal["cedar-more"] THEN mtg.more ¬ attr.value
ELSE
IF attr.attr.Length>0
THEN
SELECT attr.attr.Fetch[0]
FROM
'b, 'f, 'm, 'o => {r:
ROPE ¬ attr.attr;
alert, mail: BOOL ¬ FALSE;
IF Rope.IsPrefix["bp", r] THEN {r ¬ r.Substr[2]; alert ¬ TRUE};
IF Rope.IsPrefix["fl", r] THEN {r ¬ r.Substr[2]; alert ¬ TRUE};
IF Rope.IsPrefix["op", r] THEN {r ¬ r.Substr[2]; alert ¬ TRUE};
IF Rope.IsPrefix["ml", r] THEN {r ¬ r.Substr[2]; mail ¬ TRUE};
IF r.Length[] = 0
THEN {
adv: INT ¬ Convert.IntFromRope[attr.value !Convert.Error => GOTO Not];
start: BasicTime.GMT ¬ BasicTime.Update[mtg.start, -adv];
IF alert
THEN {
x: Reminder ~ NEW [ReminderRecord ¬ [NIL, alert[start, mtg.start]]];
mtg.reminders ¬ CONS[x, mtg.reminders]};
IF mail
THEN {
x: Reminder ~ NEW [ReminderRecord ¬ [NIL, mail[start, attr.clientdata]]];
mtg.reminders ¬ CONS[x, mtg.reminders]};
};
EXITS Not => mtg ¬ mtg};
ENDCASE => NULL;
ENDLOOP;
RETURN};
EnumerateAppts:
PROC [rt: RTable, res:
RT.TableResList,
PerAppt:
PROC [
REF
RT.Appt]
RETURNS [
BOOL]]
RETURNS [empty:
BOOL ¬
TRUE] ~ {
TryLookup:
PROC [of:
RT.Args, apptid:
RT.Id]
RETURNS [stop:
BOOL ¬
FALSE] ~ {
ires: RT.TableRes;
ires ¬ rt.rtablelookup[rt, target, of, UnixSysCalls.GetPID[] !SunRPC.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "lookup of abbreviated appointment (tick=%x=%g, key=%g) => RPC.Error[%g]", LIST[[integer[apptid.tick]], [time[UnixToBasicTime[apptid.tick]]], [integer[apptid.key]], [atom[code]]] ];
GOTO Fale}];
IF watchAS
THEN
SELECT ires.status
FROM
accessok, accessexists => NULL;
ENDCASE => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "lookup of abbreviated appointment (tick=%x=%g, key=%g) failed, with status=%g", LIST[[integer[apptid.tick]], [time[UnixToBasicTime[apptid.tick]]], [integer[apptid.key]], [rope[RT.AccessStatusNames[ires.status]]]] ];
RETURN [TRUE]};
WITH ires.res
SELECT
FROM
y: REF RT.TableResListObject[AP] => stop ¬ PerAppt[y.a];
ENDCASE => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "lookup of abbreviated appointment (tick=%x=%g, key=%g) returned unexpected variant (%g)", LIST[[integer[apptid.tick]], [time[UnixToBasicTime[apptid.tick]]], [integer[apptid.key]], [rope[RT.TableResTypeNames[ires.res.tag]]]] ];
RETURN [TRUE]};
RETURN;
EXITS Fale => stop ¬ TRUE};
WITH res
SELECT
FROM
x:
REF
RT.TableResListObject[AP] => {
FOR appt:
REF
RT.Appt ¬ x.a, appt.next
WHILE appt#
NIL
DO
empty ¬ FALSE;
SELECT appt.tag.tag
FROM
appointment, otherTag, toDo => {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "Enum appt[tick=%x=%g, key=%g, what=%g]", LIST[[integer[appt.apptid.tick]], [time[UnixToBasicTime[appt.apptid.tick]]], [integer[appt.apptid.key]], [rope[appt.what]]] ];
IF PerAppt[appt] THEN EXIT;
};
reminder => {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "Enum reminder[tick=%x=%g, key=%g, what=%g]", LIST[[integer[appt.apptid.tick]], [time[UnixToBasicTime[appt.apptid.tick]]], [integer[appt.apptid.key]], [rope[appt.what]]] ];
};
holiday => {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "Enum holiday[tick=%x=%g, key=%g, what=%g]", LIST[[integer[appt.apptid.tick]], [time[UnixToBasicTime[appt.apptid.tick]]], [integer[appt.apptid.key]], [rope[appt.what]]] ];
};
ENDCASE => ERROR;
ENDLOOP};
x:
REF
RT.TableResListObject[AB] => {
FOR aba:
REF
RT.AbbAppt ¬ x.b, aba.next
WHILE aba#
NIL
DO
empty ¬ FALSE;
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "Enum abb-appt[tick=%x=%g, key=%g, what=%g]", LIST[[integer[aba.apptid.tick]], [time[UnixToBasicTime[aba.apptid.tick]]], [integer[aba.apptid.key]], [rope[aba.what]]] ];
IF TryLookup[NEW[RT.ArgsObject[UID] ¬ [UID[NEW[RT.Uid ¬ [aba.apptid, NIL]] ]]], aba.apptid ] THEN EXIT;
ENDLOOP};
x:
REF
RT.TableResListObject[ID] => {
FOR uid:
REF
RT.Uid ¬ x.i, uid.next
WHILE uid#
NIL
DO
empty ¬ FALSE;
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "Enum uid[tick=%x=%g, key=%g]", LIST[[integer[uid.apptid.tick]], [time[UnixToBasicTime[uid.apptid.tick]]], [integer[uid.apptid.key]]] ];
IF TryLookup[NEW[RT.ArgsObject[UID] ¬ [UID[uid]]], uid.apptid ] THEN EXIT;
ENDLOOP};
ENDCASE => SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "enumerating unexpected variant (%g)", [rope[RT.TableResTypeNames[res.tag]]] ];
};
secsPerWeek: INT ¬ 3600*168;
watchAS: BOOL ¬ FALSE;
debug: BOOL ¬ FALSE;
scanStyle: {dualRanges, cachedIntervals, nextLarger} ¬ dualRanges;
useAbb: BOOL ¬ FALSE;
ScanAppts:
PROC [rt: RTable, usedDbName:
ROPE,
PerAppt:
PROC [
REF
RT.Appt]
RETURNS [
BOOL], for:
ROPE, min:
INT ¬ 0, max:
INT ¬ limitUnixTime-1] ~ {
SELECT scanStyle FROM
dualRanges => {
now: INT ~ BasicToUnixTime[BasicTime.Now[]];
delt: INT ¬ secsPerWeek;
sofar: INT ¬ MIN[now-1, max]+1;
WHILE min < sofar
DO
-- [sofar, MIN[now-1, max]] already done
qmin: INT ¬ MAX[MAX[min, sofar-delt]-1, 0];
qmax: INT ¬ sofar;
args: RT.Args ¬ NEW [RT.ArgsObject[RANGE] ¬ [RANGE[NEW[RT.Range ¬ [qmin, qmax, NIL]] ]]];
res:
RT.TableRes ¬
(IF useAbb THEN rt.rtableabbreviatedlookuprange ELSE rt.rtablelookuprange)
[rt, target, args, UnixSysCalls.GetPID[]
!SunRPC.Error => {
SimpleFeedback.Append[$RemindImpl, oneLiner, $Error, IO.PutFLR["cm lookuprange[%x=%g, %x=%g] for %g on %g => RPC.Error[%g]", LIST[ [integer[qmin]], [time[UnixToBasicTime[qmin]]], [integer[qmax]], [time[UnixToBasicTime[qmax]]], [rope[for]], [rope[usedDbName]], [atom[code]] ]]];
EXIT}];
IF watchAS
THEN
SELECT res.status
FROM
accessok => IF debug THEN SimpleFeedback.Append[$RemindImpl, oneLiner, $Debug, IO.PutFLR["cm lookuprange[%x=%g, %x=%g] for %g succeeds", LIST[ [integer[qmin]], [time[UnixToBasicTime[qmin]]], [integer[qmax]], [time[UnixToBasicTime[qmax]]], [rope[for]] ]]];
ENDCASE => {
SimpleFeedback.Append[$RemindImpl, oneLiner, $Error, IO.PutFLR["cm lookuprange[%x=%g, %x=%g] for %g on %g => status = %g", LIST[ [integer[qmin]], [time[UnixToBasicTime[qmin]]], [integer[qmax]], [time[UnixToBasicTime[qmax]]], [rope[for]], [rope[usedDbName]], [rope[RT.AccessStatusNames[res.status]]] ]]];
EXIT};
IF EnumerateAppts[rt, res.res, PerAppt] THEN delt ¬ 2*MIN[delt, LAST[INT]/4];
sofar ¬ qmin;
ENDLOOP;
NULL -- [min, MIN[now-1, max]] already queried--;
sofar ¬ MAX[now, min]-1;
delt ¬ secsPerWeek;
WHILE sofar < max
DO
--[MAX[now, min], sofar] already done
qmin: INT ¬ sofar;
qmax: INT ¬ MIN[max, sofar+delt]+1;
args: RT.Args ¬ NEW [RT.ArgsObject[RANGE] ¬ [RANGE[NEW[RT.Range ¬ [qmin, qmax, NIL]] ]]];
res:
RT.TableRes ¬
(IF useAbb THEN rt.rtableabbreviatedlookuprange ELSE rt.rtablelookuprange)
[rt, target, args, UnixSysCalls.GetPID[]
!SunRPC.Error => {
SimpleFeedback.Append[$RemindImpl, oneLiner, $Error, IO.PutFLR["cm lookuprange[%x=%g, %x=%g] for %g on %g => RPC.Error[%g]", LIST[ [integer[qmin]], [time[UnixToBasicTime[qmin]]], [integer[qmax]], [time[UnixToBasicTime[qmax]]], [rope[for]], [rope[usedDbName]], [atom[code]] ]]];
EXIT}];
IF watchAS
THEN
SELECT res.status
FROM
accessok => IF debug THEN SimpleFeedback.Append[$RemindImpl, oneLiner, $Debug, IO.PutFLR["cm lookuprange[%x=%g, %x=%g] for %g succeeds", LIST[ [integer[qmin]], [time[UnixToBasicTime[qmin]]], [integer[qmax]], [time[UnixToBasicTime[qmax]]], [rope[for]] ]]];
ENDCASE => {
SimpleFeedback.Append[$RemindImpl, oneLiner, $Error, IO.PutFLR["cm lookuprange[%x=%g, %x=%g] for %g on %g => status = %g", LIST[ [integer[qmin]], [time[UnixToBasicTime[qmin]]], [integer[qmax]], [time[UnixToBasicTime[qmax]]], [rope[for]], [rope[usedDbName]], [rope[RT.AccessStatusNames[res.status]]] ]]];
EXIT};
IF EnumerateAppts[rt, res.res, PerAppt] THEN delt ¬ 2*MIN[delt, (LAST[INT]-sofar)/4];
sofar ¬ qmax;
ENDLOOP;
NULL--[MAX[now, min], max] queried--};
cachedIntervals => {
stack: REF RT.Range ¬ NEW [RT.Range ¬ [min, max, NIL]];
WHILE stack#
NIL
DO
probe: REF RT.Range ¬ stack;
bad: BOOL ¬ IntervalIsBad[usedDbName, probe];
stack ¬ stack.next;
probe.next ¬ NIL;
IF
NOT bad
THEN {
args: RT.Args ~ NEW [RT.ArgsObject[RANGE] ¬ [RANGE[probe]]];
res:
RT.TableRes ~ rt.rtableabbreviatedlookuprange[rt, target, args, UnixSysCalls.GetPID[]
!SunRPC.Error =>
IF probe.key2-probe.key1 <= 86400
THEN {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "cm lookuprange[%g, %g] for %g on %g => RPC.Error[%g]", LIST[[integer[probe.key1]], [integer[probe.key2]], [rope[for]], [rope[usedDbName]], [atom[code]]] ];
EXIT}
ELSE {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Warning, "cm lookuprange[%g, %g] for %g on %g -> RPC.Error[%g], trying smaller range", LIST[[integer[probe.key1]], [integer[probe.key2]], [rope[for]], [rope[usedDbName]], [atom[code]]] ];
GOTO Bad}];
IF watchAS
THEN
SELECT res.status
FROM
accessok => NULL;
accesspartial => GOTO Bad;
ENDCASE =>
IF probe.key2-probe.key1 <= 86400
THEN {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "cm lookuprange[%g, %g] for %g on %g => status = %g", LIST[[integer[probe.key1]], [integer[probe.key2]], [rope[for]], [rope[usedDbName]], [rope[RT.AccessStatusNames[res.status]]]] ];
EXIT}
ELSE {
mid: CARD ~ (CARD[probe.key1]+CARD[probe.key2])/2;
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Warning, "cm lookuprange[%g, %g] for %g on %g -> status = %g, trying smaller range", LIST[[integer[probe.key1]], [integer[probe.key2]], [rope[for]], [rope[usedDbName]], [rope[RT.AccessStatusNames[res.status]]]] ];
GOTO Bad};
[] ¬ EnumerateAppts[rt, res.res, PerAppt];
EXITS Bad => {
AssertBad[usedDbName, probe];
bad ¬ TRUE}};
IF bad
THEN {
mid: CARD ~ (CARD[probe.key1]+CARD[probe.key2])/2;
stack ¬
NEW[
RT.Range ¬ [probe.key1, mid,
NEW[RT.Range ¬ [mid, probe.key2, stack]] ]]};
ENDLOOP};
nextLarger => {
seekTick: INT ¬ min;
args: RT.Args ¬ NEW [RT.ArgsObject[TICK3] ¬ [TICK3[min]]];
DO
res:
RT.TableRes ~ rt.rtablelookupnextlarger[rt, target, args, UnixSysCalls.GetPID[]
!SunRPC.Error => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "lookup[%x=%g] for scan (for %g) failed (%g)", LIST[[integer[seekTick]], [time[UnixToBasicTime[seekTick]]], [rope[for]], [atom[code]]] ];
EXIT}];
IF watchAS
THEN
SELECT res.status
FROM
accessok => NULL;
ENDCASE => {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "LookupNextLarger[tick=%x=%g] => status=%g", LIST[[integer[seekTick]], [time[UnixToBasicTime[seekTick]]], [rope[RT.AccessStatusNames[res.status]]]] ];
EXIT};
WITH res.res
SELECT
FROM
x:
REF
RT.TableResListObject[AP] =>
IF x.a=
NIL
THEN {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "LookupNextLarger[tick=%x=%g] => NIL", LIST[[integer[seekTick]], [time[UnixToBasicTime[seekTick]]]] ];
EXIT}
ELSE {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "LookupNextLarger[tick=%x=%g] => tick=%x=%g key=%g", LIST[[integer[seekTick]], [time[UnixToBasicTime[seekTick]]], [integer[x.a.apptid.tick]], [time[UnixToBasicTime[x.a.apptid.tick]]], [integer[x.a.apptid.key]]] ];
IF x.a.apptid.tick > max THEN EXIT;
IF PerAppt[x.a] THEN EXIT;
seekTick ¬ x.a.apptid.tick;
args ¬ NEW[RT.ArgsObject[TICK3] ¬ [TICK3[x.a.apptid.tick]]]};
ENDCASE => {
SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "scanning unexpected variant (%g) (for %g)", LIST[[rope[RT.TableResTypeNames[res.res.tag]]], [rope[for]]] ];
EXIT};
ENDLOOP};
ENDCASE => ERROR;
RETURN};
DeleteMeeting:
PUBLIC
PROC[start: BasicTime.
GMT, id:
INT, closeDb:
BOOLEAN ¬
TRUE, dbName: Rope.
ROPE ¬
NIL]
RETURNS [found:
BOOLEAN ¬
FALSE]
= {RETURN DeleteMeetingInRange[start, start, id, closeDb, dbName]};
DeleteMeetingInRange:
PUBLIC
PROC[from, to: BasicTime.
GMT, id:
INT, closeDb:
BOOLEAN ¬
TRUE, dbName: Rope.
ROPE ¬
NIL]
RETURNS [found:
BOOLEAN ¬
FALSE] = {
db: LoganBerry.OpenDB;
rt: RTable;
usedDbName: ROPE;
DeleteThis:
PROC [start: BasicTime.
GMT, unixStart:
INT]
RETURNS [found:
BOOL ¬
FALSE] ~ {
args: RT.Args ¬ NEW [RT.ArgsObject[UID] ¬ [UID[NEW[RT.Uid ¬ [[tick: unixStart, key: id], NIL]] ]]];
res:
RT.TableRes ¬ rt.rtabledelete[rt, target, args, UnixSysCalls.GetPID[] !SunRPC.Error => {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "delete rpc failed (%g)", [atom[code]] ];
GOTO Bail}];
SELECT res.status
FROM
accessok, accessremoved => found ¬ TRUE;
ENDCASE => {
found ¬ FALSE;
IF watchAS THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "Delete (by id) of message (tick=%xH=%g, key=%g) failed (status = %g)", LIST[[integer[unixStart]], [time[UnixToBasicTime[unixStart]]], [integer[id]], [rope[RT.AccessStatusNames[res.status]]]] ];
};
EXITS Bail => found ¬ FALSE};
[db, rt, usedDbName] ¬ OpenDb[name: dbName];
IF db # LoganBerry.nullDB
THEN {
entry: LoganBerry.Entry;
idVal: ROPE ~ Convert.RopeFromInt[id];
others: BOOL;
[entry, others] ¬ LoganBerry.ReadEntry[db: db, key: $uniqID, value: idVal];
IF others THEN ERROR;
IF entry#
NIL
THEN {
LoganBerry.DeleteEntry[db: db, key: $uniqID, value: idVal];
found ¬ TRUE;
};
XXX:XXX
IF closeDb THEN CloseLoganBerry[db]; -- so autobackup will see a change
RETURN [found]}
ELSE IF rt=NIL THEN RETURN [FALSE]
ELSE IF from=to THEN RETURN DeleteThis[from, BasicToUnixTime[from]]
ELSE {
unixFrom: INT ~ BasicToUnixTime[from];
unixTo: INT ~ BasicToUnixTime[to];
args: RT.Args ¬ NEW [RT.ArgsObject[KEYRANGE] ¬ [KEYRANGE[NEW[RT.Keyrange ¬ [key: id, tick1: unixFrom, tick2: unixTo, next: NIL]] ]]];
res:
RT.TableRes ¬ rt.rtableabbreviatedlookupkeyrange[rt, target, args, UnixSysCalls.GetPID[] !SunRPC.Error => {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "abb-lookup-keyrange rpc failed (%g)", [atom[code]] ];
GOTO Bail}];
IF watchAS
THEN
SELECT res.status
FROM
accessok => NULL;
ENDCASE => {
IF debug THEN SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Debug, "abb-lookup-keyrange[key=%g, tick1=%g, tick2=%g] => status=%g", LIST[[integer[id]], [time[from]], [time[to]], [rope[RT.AccessStatusNames[res.status]]]] ];
GOTO Bail};
WITH res.res
SELECT
FROM
x:
REF
RT.TableResListObject[
AB] => {
IF x.b=NIL THEN found ¬ FALSE
ELSE found ¬ DeleteThis[UnixToBasicTime[x.b.apptid.tick], x.b.apptid.tick]};
ENDCASE => SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "delete-in-range got unexpected variant (%g)", [rope[RT.TableResTypeNames[res.res.tag]]] ];
RETURN;
EXITS Bail => found ¬ FALSE};
};
<<
DeleteMeeting:
PUBLIC
PROC[
meeting: Meeting,
closeDb:
BOOLEAN ¬
TRUE,
dbName:
ROPE ¬
NIL]
RETURNS [found:
BOOLEAN ¬
FALSE] = {
cursor: LoganBerry.Cursor;
id: ROPE;
entry: LoganBerry.Entry;
db: LoganBerry.OpenDB;
rt: RTable; usedDbName: ROPE;
[db, rt, usedDbName] ¬ OpenDb[name: dbName];
IF db # LoganBerry.nullDB
THEN {
cursor ¬ LoganBerry.GenerateEntries[db: db, key: $uniqID];
WHILE (entry ¬ LoganBerry.NextEntry[cursor: cursor]) #
NIL
DO
startMatch: BOOLEAN ¬ FALSE;
explMatch: BOOL ¬ meeting.explanation.Length[] = 0;
id ¬ NIL;
FOR aList:
LIST
OF LoganBerry.Attribute ¬ entry, aList.rest
UNTIL aList =
NIL
DO
SELECT aList.first.type
FROM
$uniqID => id ¬ aList.first.value;
$start => IF TimeFromInt[Convert.IntFromRope[aList.first.value]] = meeting.start THEN startMatch ¬ TRUE;
$explanation => IF Rope.Equal[meeting.explanation, aList.first.value] THEN explMatch ¬ TRUE;
ENDCASE => {};
ENDLOOP;
IF id.Equal[stopID] THEN stops ¬ stops+1;
IF explMatch
AND startMatch
AND id#
NIL
THEN {
LoganBerry.DeleteEntry[db: db, key: $uniqID, value: id];
found ¬ TRUE;
};
ENDLOOP;
LoganBerry.EndGenerate[cursor: cursor];
IF closeDb THEN CloseLoganBerry[db]; -- so autobackup will see a change
RETURN [found]}
ELSE
IF rt#
NIL
THEN {
doomedTicks: INT ¬ BasicToUnixTime[meeting.start];
doomed: LIST OF REF RT.Appt ¬ NIL;
NoteAppt:
PROC [appt:
REF
RT.Appt]
RETURNS [
BOOL] ~ {
IF appt.apptid.key = meeting.uniqID
THEN {
doomed ¬ CONS[appt, doomed];
found ¬ TRUE};
RETURN[FALSE]};
ScanAppts[rt, usedDbName, NoteAppt, "DeleteMeeting", doomedTicks, doomedTicks];
FOR dl:
LIST
OF
REF
RT.Appt ¬ doomed, dl.rest
WHILE dl#
NIL
DO
args: RT.Args ¬ NEW [RT.ArgsObject[UID] ¬ [UID[NEW[RT.Uid ¬ [dl.first.apptid, NIL]] ]]];
res:
RT.TableRes ¬ rt.rtabledelete[rt, target, args, UnixSysCalls.GetPID[] !SunRPC.Error => {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "delete rpc failed (%g)", [atom[code]] ];
EXIT}];
IF watchAS
THEN
SELECT res.status
FROM
accessok, accessremoved => NULL;
ENDCASE => SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "Delete of message (tick=%xH=%g, key=%g) failed (status = %g)", LIST[[integer[dl.first.apptid.tick]], [time[UnixToBasicTime[dl.first.apptid.tick]]], [integer[dl.first.apptid.key]], [rope[RT.AccessStatusNames[res.status]]]] ];
ENDLOOP;
}
ELSE RETURN [FALSE]};
stopID: ROPE ¬ NIL;
stops: CARD ¬ 0;
DeleteMeetingByID:
PUBLIC
PROC[from, to: BasicTime.
GMT, id:
INT, closeDb:
BOOLEAN ¬
TRUE, dbName: Rope.
ROPE ¬
NIL]
RETURNS [found:
BOOLEAN ¬
FALSE] = {
db: LoganBerry.OpenDB;
rt: RTable;
usedDbName: ROPE;
[db, rt, usedDbName] ¬ OpenDb[name: dbName];
IF db # LoganBerry.nullDB
THEN {
cursor: LoganBerry.Cursor;
entry: LoganBerry.Entry;
cursor ¬ LoganBerry.GenerateEntries[db: db, key: $uniqID];
BEGIN -- establish scope for EXIT clause
WHILE (entry ¬ LoganBerry.NextEntry[cursor: cursor]) #
NIL
DO
FOR aList:
LIST
OF LoganBerry.Attribute ¬ entry, aList.rest
UNTIL aList =
NIL
DO
IF aList.first.type = $uniqID
THEN {
IF stopID.Equal[aList.first.value] THEN stops ¬ stops+1;
IF Convert.IntFromRope[aList.first.value] = id
THEN {
LoganBerry.DeleteEntry[db: db, key: $uniqID, value: Convert.RopeFromInt[id]];
found ¬ TRUE;
GOTO done;
}
};
ENDLOOP;
ENDLOOP;
END;
LoganBerry.EndGenerate[cursor: cursor];
XXX:XXX
IF closeDb THEN CloseLoganBerry[db]; -- so autobackup will see a change
RETURN [found]}
ELSE
IF rt#
NIL
THEN {
qmin: INT ~ BasicToUnixTime[from];
qmax: INT ~ BasicToUnixTime[to];
doomed: LIST OF REF RT.Appt ¬ NIL;
NoteAppt:
PROC [appt:
REF
RT.Appt]
RETURNS [
BOOL] ~ {
IF appt.apptid.key = id THEN doomed ¬ CONS[appt, doomed];
RETURN[FALSE]};
ScanAppts[rt, usedDbName, NoteAppt, "DeleteMeetingByID", qmin, qmax];
FOR dl:
LIST
OF
REF
RT.Appt ¬ doomed, dl.rest
WHILE dl#
NIL
DO
args: RT.Args ¬ NEW [RT.ArgsObject[UID] ¬ [UID[NEW[RT.Uid ¬ [dl.first.apptid, NIL]] ]]];
res:
RT.TableRes ¬ rt.rtabledelete[rt, target, args, UnixSysCalls.GetPID[] !SunRPC.Error => {
SimpleFeedback.PutF[$RemindImpl, oneLiner, $Error, "delete rpc failed (%g)", [atom[code]] ];
EXIT}];
IF watchAS
THEN
SELECT res.status
FROM
accessok, accessremoved => NULL;
ENDCASE => SimpleFeedback.PutFL[$RemindImpl, oneLiner, $Error, "Delete (by id) of message (tick=%xH=%g, key=%g) failed (status = %g)", LIST[[integer[dl.first.apptid.tick]], [time[UnixToBasicTime[dl.first.apptid.tick]]], [integer[dl.first.apptid.key]], [rope[RT.AccessStatusNames[res.status]]]] ];
ENDLOOP;
}
ELSE RETURN [FALSE]};>>
Create:
PROC = {
userCred: ROPE ¬ SystemNames.UserName[];
s: IO.STREAM;
baseName, logName: ROPE;
credIndex: INT;
ind: INT ¬ Rope.FindBackward[defaultDbName, "/"];
SimpleFeedback.PutF[$Remind, oneLiner, $Info, "Creating CalendarTool database file: %g\n", [rope[defaultDbName]] ];
IF ind # -1 THEN baseName ¬ Rope.Substr[defaultDbName, ind+1] ELSE baseName ¬ defaultDbName;
baseName ¬ Rope.Substr[baseName, 0, Rope.Index[baseName, 0, "."]];
s ¬ FS.StreamOpen[defaultDbName, $create];
s.PutF1["-- %g\n\n", IO.rope[defaultDbName]];
credIndex ¬ Rope.FindBackward[userCred, ".pa"];
IF credIndex # -1 THEN userCred ¬ Rope.Substr[userCred, 0, credIndex];
s.PutF1["Directory [User]<%g>Top>\n", IO.rope[userCred]];
s.PutF1["\t\t%g\n", IO.rope[Rope.Substr[defaultDbName, Rope.FindBackward[defaultDbName, "/"]+1]]];
s.PutF["Directory [User]<%g>%g>\n", IO.rope[userCred], IO.rope[baseName]];
s.PutF1[" --> log 0 readwrite\n\t\t%g.lblog\n", IO.rope[baseName]];
s.PutF1[" --> index \"uniqID\" primary\n\t\t%g.uniqID.lbindex\n", IO.rope[baseName]];
s.PutF1[" --> index \"start\" secondary\n\t\t%g.start.lbindex\n", IO.rope[baseName]];
s.PutF1[" --> index \"repeat\" secondary\n\t\t%g.repeat.lbindex\n", IO.rope[baseName]];
s.PutF1[" --> index \"stopReminding\" secondary\n\t\t%g.stopReminding.lbindex\n", IO.rope[baseName]];
IO.Close[s];
logName ¬ Rope.Cat[Rope.Substr[defaultDbName, 0, ind+1], baseName, ".lblog"];
s ¬ FS.StreamOpen[logName, $create];
s.PutRope["\377"];
IO.Close[s];
};
NextOccurrence:
PUBLIC
PROC[repeat: Repetitions, nth:
INT, time: BasicTime.
GMT]
RETURNS [BasicTime.
GMT] = {
SELECT repeat
FROM
once => RETURN[time];
daily, everyNthDay =>
RETURN[TimeParse.Adjust[baseTime: time, precisionOfResult: seconds, days: IF repeat=daily THEN 1 ELSE nth].time];
weekly, everyNthWeek =>
RETURN[TimeParse.Adjust[baseTime: time, precisionOfResult: seconds, days: IF repeat=weekly THEN 7 ELSE (nth*7)].time];
biweekly =>
RETURN[TimeParse.Adjust[baseTime: time, precisionOfResult: seconds, days: 14].time];
monthly, everyNthMonth =>
RETURN[TimeParse.Adjust[baseTime: time, precisionOfResult: seconds, months: IF repeat=monthly THEN 1 ELSE nth].time];
yearly =>
RETURN[TimeParse.Adjust[baseTime: time, precisionOfResult: seconds, years: 1].time];
weekdays, nthWeekday => {
t: BasicTime.GMT ¬ time;
IF repeat=weekdays THEN nth ¬ 1;
WHILE
TRUE
DO
t ¬ TimeParse.Adjust[baseTime: t, precisionOfResult: seconds, days: 1].time;
IF BasicTime.Unpack[t].weekday
IN [Monday .. Friday]
THEN {
nth ¬ nth - 1;
IF nth=0 THEN EXIT};
ENDLOOP;
RETURN[t];
};
ENDCASE => RETURN[time]; -- should never get here
};
FirstOccurrenceAfter:
PUBLIC
PROC[repeat: Repetitions, nth:
INT, init, now: BasicTime.
GMT]
RETURNS [BasicTime.
GMT] = {
initMinus: BasicTime.GMT;
secondsPerDay: INT = BasicTime.secondsPerMinute*BasicTime.minutesPerHour*BasicTime.hoursPerDay;
secondsPerWeek: INT = secondsPerDay*7;
secondsPerTwoWeeks: INT = secondsPerWeek*2;
secondsPerYear: INT = secondsPerDay*365;
diff: INT;
ByDay:
PROC [days:
INT]
RETURNS [BasicTime.
GMT] ~ {
secsPerPeriod: INT ~ secondsPerDay*days;
diff: INT ¬ BasicTime.Period[init, now]/secsPerPeriod + 1;
init ¬ TimeParse.Adjust[baseTime: init, days: diff*days, precisionOfResult: seconds].time;
because of daylights savings time, might be off by 1
IF BasicTime.Period[now, init] < 0 THEN init ¬ TimeParse.Adjust[baseTime: init, days: days, precisionOfResult: seconds].time
ELSE {
initMinus ¬ TimeParse.Adjust[baseTime: init, days: -days, precisionOfResult: seconds].time;
IF BasicTime.Period[now, initMinus] >= 0 THEN init ¬ initMinus;
};
RETURN[init]};
SELECT repeat
FROM
once => RETURN[init];
daily => RETURN ByDay[1];
everyNthDay => RETURN ByDay[nth];
weekly => RETURN ByDay[7];
biweekly => RETURN ByDay[14];
everyNthWeek => RETURN ByDay[7*nth];
monthly, everyNthMonth => {
uInit, uNow: BasicTime.Unpacked;
secsInit, secsNow: INT;
IF repeat=monthly THEN nth ¬ 1;
uInit ¬ BasicTime.Unpack[init];
uNow ¬ BasicTime.Unpack[now];
diff ¬ (uNow.year - uInit.year)*12 + ORD[uNow.month] - ORD[uInit.month];
diff ¬ ((diff+nth-1)/nth)*nth;
compute seconds since beginning of month
secsInit ¬ uInit.second + 60*uInit.minute + 60*60*uInit.hour + secondsPerDay*ORD[uInit.day];
secsNow ¬ uNow.second + 60*uNow.minute + 60*60*uNow.hour + secondsPerDay*ORD[uNow.day];
IF secsInit < secsNow THEN diff ¬ diff + nth;
init ¬ TimeParse.Adjust[baseTime: init, months: diff, precisionOfResult: seconds].time;
RETURN[init];
};
yearly => {
diff ¬ BasicTime.Period[init, now]/secondsPerYear + 1;
init ¬ TimeParse.Adjust[baseTime: init, years: diff, precisionOfResult: seconds].time;
because of leap years, might be off by 1
IF BasicTime.Period[now, init] < 0 THEN init ¬ TimeParse.Adjust[baseTime: init, years: 1, precisionOfResult: seconds].time
ELSE {
initMinus ¬ TimeParse.Adjust[baseTime: init, years: -1, precisionOfResult: seconds].time;
IF BasicTime.Period[now, initMinus] >= 0 THEN
init ¬ initMinus;
};
RETURN[init];
};
weekdays, nthWeekday => {
u: BasicTime.Unpacked;
IF repeat=weekdays THEN nth ¬ 1;
DO
init ¬ ByDay[1];
u ¬ BasicTime.Unpack[init];
IF u.weekday= Saturday
THEN
init ¬ TimeParse.Adjust[baseTime: init, days: 2, precisionOfResult: seconds].time
ELSE
IF u.weekday= Sunday
THEN
init ¬ TimeParse.Adjust[baseTime: init, days: 1, precisionOfResult: seconds].time;
IF (nth ¬ nth-1) = 0 THEN RETURN[init];
ENDLOOP;
};
ENDCASE => RETURN[init]; -- should never get here
};
UpdateMeeting:
PROC[meeting: Meeting, newTime: BasicTime.
GMT, remindersToo, newReminders:
BOOL] = {
diff: INT ¬ BasicTime.Period[meeting.start, newTime];
UpdateReminders:
PROC [rl: RemindList]
RETURNS [RemindList] ~ {
IF rl=NIL THEN RETURN [NIL];
WITH rl.first
SELECT
FROM
x: AlertReminder =>
RETURN [
CONS[
NEW [ReminderRecord[alert] ¬ [x.other, alert[
start: BasicTime.Update[x.start, diff],
stop: BasicTime.Update[x.stop, diff] ]]],
UpdateReminders[rl.rest] ]];
x: MailReminder =>
RETURN [
CONS[
NEW [ReminderRecord[mail] ¬ [x.other, mail[
when: BasicTime.Update[x.when, diff],
to: x.to ]]],
UpdateReminders[rl.rest] ]];
ENDCASE => ERROR};
meeting.start ¬ newTime;
SELECT
TRUE
FROM
NOT remindersToo => NULL;
NOT newReminders =>
FOR rl: RemindList ¬ meeting.reminders, rl.rest
WHILE rl#
NIL
DO
WITH rl.first
SELECT
FROM
x: AlertReminder => {
x.start ¬ BasicTime.Update[x.start, diff];
x.stop ¬ BasicTime.Update[x.stop, diff]};
x: MailReminder => {
x.when ¬ BasicTime.Update[x.when, diff]};
ENDCASE => ERROR;
ENDLOOP;
ENDCASE => meeting.reminders ¬ UpdateReminders[meeting.reminders];
RETURN};
CompactDb:
PUBLIC
PROC[dbName:
ROPE ¬
NIL] = {
db: LoganBerry.OpenDB;
rt: RTable;
[db, rt, ] ¬ OpenDb[name: dbName];
IF db = LoganBerry.nullDB THEN RETURN;
LoganBerry.CompactLogs[db: db];
};
dbnToBadIntervals
: SymTab.Ref--usedDbName => badIntervals: RedBlackTree.Table--
~ SymTab.Create[case: TRUE];
No interval in a badIntervals is a sub-interval of another.
GetTable:
PROC [usedDbName:
ROPE]
RETURNS [bads: RedBlackTree.Table ¬
NIL] ~ {
AddIfNeeded:
PROC [found:
BOOL, val:
REF
ANY]
RETURNS [op: SymTab.UpdateOperation ¬ none, new:
REF
ANY ¬
NIL] ~ {
IF found
THEN {bads ¬ NARROW[val]; op ¬ none}
ELSE { new ¬ bads ¬ RedBlackTree.Create[GetIntervalKey, CompareIntervals]; op ¬ store}};
dbnToBadIntervals.Update[usedDbName, AddIfNeeded];
RETURN};
AssertBad:
PROC [usedDbName:
ROPE, i:
REF
RT.Range] ~ {
badIntervals: RedBlackTree.Table ~ GetTable[usedDbName];
--Assert: i.next = NIL-- badIntervals.Insert[i, i];
RETURN};
IntervalIsBad:
PROC [usedDbName:
ROPE, i:
REF
RT.Range]
RETURNS [
BOOL] ~ {
badIntervals: RedBlackTree.Table ~ GetTable[usedDbName];
data, eqData: REF ANY;
[, eqData, data] ¬ badIntervals.Lookup3[i];
IF eqData#NIL THEN RETURN [TRUE];
WHILE data#
NIL
DO
fr: REF RT.Range ~ NARROW[data];
IF fr.key1 > i.key2 THEN RETURN [FALSE];
IF fr.key2 <= i.key2 THEN RETURN [TRUE];
data ¬ badIntervals.LookupNextLarger[data];
ENDLOOP;
RETURN [FALSE]};
GetIntervalKey:
PROC [data:
REF
ANY]
RETURNS [
REF
ANY]
~ {RETURN [data]};
CompareIntervals:
PROC [k, data:
REF
ANY]
RETURNS [Basics.Comparison] ~ {
k1: REF RT.Range ~ NARROW[k];
k2: REF RT.Range ~ NARROW[data];
SELECT
TRUE
FROM
k1.key1<k2.key1 => RETURN [less];
k1.key1>k2.key1 => RETURN [greater];
k1.key2>k2.key2 => RETURN [less];
k1.key2<k2.key2 => RETURN [greater];
ENDCASE => RETURN [equal]};
CloseDb:
PUBLIC
PROC[dbName:
ROPE ¬
NIL] = {
db: LoganBerry.OpenDB;
rt: RTable;
[db, rt, ] ¬ OpenDb[name: dbName];
IF db = LoganBerry.nullDB THEN RETURN;
CloseLoganBerry[db];
};
CloseLoganBerry:
PROC[db: LoganBerry.OpenDB] = {
LoganBerryExtras.UnregisterWriteProc[db, $Remind];
LoganBerry.Close[db: db];
};
ChangeLoganBerryDbName: UserProfile.ProfileChangedProc = {
PROC [reason: UserProfile.ProfileChangeReason]
newDbName: ROPE ¬ UserProfile.Token["Remind.DbName", UserProfile.Token["Remind.DfFile", NIL]];
IF Rope.IsEmpty[newDbName] THEN
newDbName ¬ Rope.Concat[SystemNames.UserCedarDir[NIL], defaultName];
IF
NOT Rope.Equal[s1: defaultDbName, s2: newDbName, case:
FALSE]
THEN {
IF (
NOT Rope.IsEmpty[defaultDbName])
AND (defaultDbName.Find["@"] < 0)
THEN {
db: LoganBerry.OpenDB ¬ OpenDb[name: defaultDbName].db;
IF db # LoganBerry.nullDB
THEN {
LoganBerry.UnregisterWriteProc[db, $Remind];
LoganBerry.Close[db: db];
};
};
defaultDbName ¬ newDbName;
DatabaseChanged[LoganBerry.nullDB, NIL, NIL, NIL];
};
};
defaultDbName: ROPE ¬ NIL;
target: ROPE ¬ NIL;
defaultName: ROPE ¬ "CalendarBerry.df";
ProcBlockRec:
TYPE =
RECORD[
proc: PROC[data: REF],
dbName: ROPE, -- perhaps should instead have blocklist per database?
data: REF ANY,
ident: ATOM];
ProcBlock: TYPE = REF ProcBlockRec;
changeProcList: List.LORA ¬ NIL; -- LIST OF ProcBlock
Commander.Register["RemindImpl.TimeFromUnix", TimeFromUnixCmd, "<an integer> -- invokes RemindImpl.UnixToBasicTime"];
Commander.Register["RemindImpl.TimeFromInt", TimeFromIntCmd, "<an integer> -- invokes RemindImpl.TimeFromInt"];
Commander.Register["GMT", GMTUpCmd, "<an integer> -- the abstraction function of the BasicTime.GMT representation scheme"];
UserProfile.CallWhenProfileChanges[ChangeLoganBerryDbName];
END.