DIRECTORY
BasicTime USING [ GMT, Unpacked, Unpack, Now, Period, Pack],
Calendar USING [ Date, Days, Months, Years, ZoomLevel],
CalStorage USING [ protData, ProtectedData, YearNode, DayNode, MonthNode],
CalSupport USING [ GetTimeInterval, GetMonthInfo, ExtractDate],
Hickory USING [ Event, EventTuple, RestoreEvents, EventList, All, ExpandRepetitions, Options, NoOptions, Reason, OrderEventList, SameEvent],
RopeSets USING [ RopeSet],
Tempus USING [ Adjust],
ViewerClasses USING [ Viewer],
ViewerOps USING [ DestroyViewer]
;
=
BEGIN
OPEN Calendar, CalStorage, CalSupport, CalStorage.protData;
FirstMonth: INTEGER = -6; -- the nbr of month before today that we prefetch events.
LastMonth: INTEGER = + 8;
protData: PUBLIC ProtectedData;
NewYearNode:
PROCEDURE [ date: Date]
RETURNS [ tree:
REF YearNode] =
INLINE
BEGIN
to initialize a year tree properly
tree ← NEW[ YearNode];
tree.Year ← date.Year;
FOR m: Months
IN [ January..December]
DO
tree.MonthNodes[ m].Month← m;
FOR d: Days
IN [ 1..31]
DO
tree.MonthNodes[ m].DayNodes[ d].Day ← d;
ENDLOOP;
ENDLOOP;
END; -- NewYearNode
InsertYearTree:
INTERNAL
PROCEDURE [ tree:
REF YearNode] =
BEGIN
to insert a new tree into the list of year nodes pointed to by yearTrees
prev, cur: REF YearNode;
IF yearTrees =
NIL
THEN
BEGIN
yearTrees ← tree;
RETURN;
END;
prev ← NIL; cur ← yearTrees;
DO
IF cur = NIL THEN EXIT;
IF cur.Year >= tree.Year THEN EXIT;
prev ← cur;
cur ← cur.Next;
ENDLOOP;
IF cur =
NIL
THEN
-- we have at least made one step!
prev.Next ← tree
ELSE
IF cur.Year > tree.Year
THEN
IF prev =
NIL
THEN
BEGIN
-- insert at head of list
tree.Next ← yearTrees;
yearTrees ← tree;
END
ELSE
BEGIN
-- insert in middle of list
tree.Next ← cur;
prev.Next ← tree;
END;
END; -- InsertYearTree
FindYearTree:
INTERNAL
PROCEDURE [ yr: Years]
RETURNS [ tree:
REF YearNode] =
BEGIN
returns nil if year not in list of yearnodes
yp: REF YearNode ← yearTrees;
DO
IF yp = NIL THEN RETURN[ yp];
IF yp.Year = yr THEN RETURN[ yp];
yp ← yp.Next;
ENDLOOP;
END; -- FindYearTree
GetEventsOfDay:
PUBLIC
INTERNAL
PROCEDURE [ date: Date, zoomLevel: ZoomLevel]
RETURNS [ evl:
LIST
OF Hickory.EventTuple] =
BEGIN
first look into year tree on the given date, if node not initialized query Hickory and
initialize node
thisYear: REF YearNode ← FindYearTree[ date.Year];
IF thisYear =
NIL
THEN
BEGIN
thisYear ← NewYearNode[ date];
InsertYearTree[ thisYear];
END;
BEGIN
OPEN thisYear.MonthNodes[ date.Month].DayNodes[ date.Day];
IF Initialized THEN RETURN[ EvList]
ELSE
BEGIN
-- go and get it from hickory
SELECT zoomLevel
FROM
Day => FetchADay[ date, thisYear];
Month => [] ← FetchAMonth[ date, thisYear, NIL];
Year => FetchAYear[ date, thisYear];
ENDCASE;
RETURN[ EvList];
END; -- IF initialized...
END; -- OPEN
END; -- GetEventsOfDay
FetchADay:
INTERNAL
PROCEDURE [ date: Date, thisYear:
REF YearNode] =
BEGIN
here we access Hickory for just one day.
OPEN thisYear.MonthNodes[ date.Month].DayNodes[ date.Day];
from, to: BasicTime.GMT;
options: Hickory.Options ← Hickory.NoOptions;
options[ Hickory.ExpandRepetitions] ← TRUE;
options[ Hickory.All] ← FALSE;
[ from, to] ← GetTimeInterval[ date, Day];
EvList ← Hickory.RestoreEvents[ from, to, options];
Initialized ← TRUE;
END; -- FetchADay
FetchAMonth:
INTERNAL
PROCEDURE [ date: Date, thisYear:
REF YearNode, evl: Hickory.EventList ←
NIL, query:
BOOLEAN ←
TRUE]
RETURNS [ truncatedEvl: Hickory.EventList] =
BEGIN
here we try to get back the month's events. If evl non NIL then we use the supplied
list, else we call Hickory, if query is set.
options: Hickory.Options ← Hickory.NoOptions;
options[ Hickory.ExpandRepetitions] ← TRUE;
options[ Hickory.All] ← FALSE;
IF evl =
NIL
AND query
THEN
BEGIN
-- must access Hickory
from, to: BasicTime.GMT;
[ from, to] ← GetTimeInterval[ date, Month];
evl ← Hickory.RestoreEvents[ from, to, options];
END;
BEGIN
OPEN thisYear.MonthNodes[ date.Month];
FOR d: Days
IN [ 1..GetMonthInfo[ date.Month, date.Year].nbrOfDays]
DO
dayList: Hickory.EventList;
[ dayList, evl] ← GetDayList[ evl, [ d, date.Month, date.Year]];
DayNodes[ d].EvList ← dayList;
DayNodes[ d].Initialized ← TRUE;
ENDLOOP;
Initialized ← TRUE;
RETURN[ evl];
END;
GetDayList:
INTERNAL
PROCEDURE [ evl: Hickory.EventList, date: Date]
RETURNS [ dayList, remainder: Hickory.EventList] =
BEGIN
truncate the dayList off from evl, which is ordered...
from, to: BasicTime.GMT;
cur, prev, head: Hickory.EventList;
IF evl = NIL THEN RETURN[ NIL, NIL];
[ from, to] ← GetTimeInterval[ date, Day];
IF BasicTime.Period[ to, evl.first.EventTime] > 0 THEN RETURN[ NIL, evl];
make at least one step...
cur ← evl; prev ← NIL;
WHILE cur #
NIL
AND BasicTime.Period[ cur.first.EventTime, from] > 0
DO
prev ← cur; cur ← cur.rest;
ENDLOOP;
head ← cur;
WHILE cur #
NIL
AND BasicTime.Period[ cur.first.EventTime, to] >= 0
DO
prev ← cur; cur ← cur.rest;
ENDLOOP;
evl ← prev.rest;
prev.rest ← NIL;
RETURN[ head, evl];
END; -- GetDayList
FetchMonths:
INTERNAL
PROCEDURE [ m1, m2: Months, year: Years]
RETURNS [ evl: Hickory.EventList] =
BEGIN
to get a period of several months..
from, to: BasicTime.GMT;
options: Hickory.Options ← Hickory.NoOptions;
options[ Hickory.ExpandRepetitions] ← TRUE;
options[ Hickory.All] ← FALSE;
[ from, ] ← GetTimeInterval[ [ 1, m1, year], Month];
[ , to] ← GetTimeInterval[ [ 1, m2, year], Month];
evl ← Hickory.RestoreEvents[ from, to, options];
END; -- FetchMonths
FetchAYear:
INTERNAL
PROCEDURE [ date: Date, thisYear:
REF YearNode] =
BEGIN
fetch a whole year, taking in account possibly initialized months..
month1, month2: Months;
evl1, evl2, cur: Hickory.EventList;
options: Hickory.Options ← Hickory.NoOptions;
GetInitializedMonths:
INTERNAL
PROCEDURE [ thisYear:
REF YearNode]
RETURNS [ from, to: Months] =
BEGIN
returns the months within a year that are initialized [ inclusive]!
OPEN thisYear;
m: Months;
from ← unspecified;
to ← unspecified;
FOR m ← January,
SUCC[ m]
UNTIL m = unspecified
OR MonthNodes[ m].Initialized
DO
NULL;
ENDLOOP;
IF m # unspecified THEN from ← m ELSE RETURN[ from, to];
FOR m ← m,
SUCC[ m]
UNTIL m = unspecified
OR
NOT MonthNodes[ m].Initialized
DO
NULL;
ENDLOOP;
IF m = unspecified THEN RETURN [ from, December]
ELSE RETURN[ from, PRED[ m]];
END; -- GetInitializedMonths
options[ Hickory.ExpandRepetitions] ← TRUE;
options[ Hickory.All] ← FALSE;
[ month1, month2] ← GetInitializedMonths[ thisYear];
IF month1 = January
AND month2 = December
THEN
BEGIN
thisYear.Initialized ← TRUE; RETURN;
END;
IF month1 # unspecified
THEN
BEGIN
-- there is at least one initialized month
IF month1 # January
THEN
evl1 ← FetchMonths[ January, PRED[ month1], date.Year] -- not inclusive initialized months...
ELSE evl1 ← NIL;
IF month2 # December
THEN
evl2 ← FetchMonths[ SUCC[ month2], December, date.Year]
ELSE evl2 ← NIL;
IF evl2 #
NIL
THEN
IF evl1 #
NIL
THEN
BEGIN
-- concatenate both lists.
cur ← evl1;
DO
IF cur.rest = NIL THEN EXIT;
cur ← cur.rest;
ENDLOOP;
cur.rest ← evl2;
END
ELSE evl1 ← evl2; -- evl1 was empty
END
ELSE
BEGIN
-- get the entire year
from, to: BasicTime.GMT;
[ from, to] ← GetTimeInterval[ date, Year];
evl1 ← Hickory.RestoreEvents[ from, to, options];
END;
evl1 is valid list..
FOR m: Months
IN [ January..December]
DO
IF
NOT thisYear.MonthNodes[ m].Initialized
THEN
evl1 ← FetchAMonth[ [ 1, m, date.Year], thisYear, evl1, FALSE];
ENDLOOP;
thisYear.Initialized ← TRUE;
END; -- FetchAYear
RetrieveViewer:
PUBLIC
INTERNAL
PROCEDURE [ date: Date, zoom: ZoomLevel]
RETURNS [ viewer: ViewerClasses.Viewer] =
BEGIN
thisYear: REF YearNode;
IF zoom = Day THEN RETURN[ NIL];
thisYear ← FindYearTree[ date.Year];
IF thisYear = NIL THEN RETURN[ NIL];
IF zoom = Month THEN RETURN[ thisYear.MonthNodes[ date.Month].Viewer]
ELSE RETURN[ thisYear.Viewer];
END; -- RetrieveViewer
StoreViewer:
PUBLIC
INTERNAL
PROCEDURE [ date: Date, zoom: ZoomLevel, viewer: ViewerClasses.Viewer] =
BEGIN
thisYear: REF YearNode;
IF zoom = Day THEN RETURN; -- day viewers not stored!
thisYear ← FindYearTree[ date.Year];
IF thisYear = NIL THEN RETURN;
IF zoom = Month THEN thisYear.MonthNodes[ date.Month].Viewer ← viewer
ELSE thisYear.Viewer ← viewer;
END; -- StoreViewer
DestroyAllViewers:
PUBLIC
INTERNAL
PROCEDURE =
BEGIN
to go through all years and throw away the browsing viewers that we have
kept around. To be called when top level viewer was destroyed.
FOR years:
REF YearNode ← yearTrees, years.Next
UNTIL years =
NIL
DO
IF years.Viewer #
NIL
THEN
BEGIN
ViewerOps.DestroyViewer[ years.Viewer];
years.Viewer ← NIL;
END;
FOR mo: Months
IN [ January..December]
DO
OPEN years;
IF MonthNodes[ mo].Viewer #
NIL
THEN
BEGIN
ViewerOps.DestroyViewer[ MonthNodes[ mo].Viewer];
MonthNodes[mo].Viewer ← NIL;
END;
ENDLOOP;
ENDLOOP;
END; -- DestroyAllViewers
FindEvent:
PUBLIC
INTERNAL
PROCEDURE [ date: Date, key: Hickory.Event]
RETURNS [ ev:
REF Hickory.EventTuple] =
BEGIN
return the pointer onto the event in storage. and make a copy..!
cur: Hickory.EventList;
IF key = NIL THEN RETURN[ NIL];
cur ← GetEventsOfDay[ date, Day];
WHILE cur #
NIL
DO
IF Hickory.SameEvent[ key, cur.first.Key]
THEN
BEGIN
new: REF Hickory.EventTuple ← NEW[ Hickory.EventTuple ← cur.first];
RETURN [ new];
END;
cur ← cur.rest;
ENDLOOP;
RETURN[ NIL];
END; -- FindEvent
InitStorage:
ENTRY
PROCEDURE =
BEGIN
to initialize all global variables
now, from, to: BasicTime.GMT ← BasicTime.Now[];
time: BasicTime.Unpacked ← BasicTime.Unpack[ now];
evl: Hickory.EventList;
options: Hickory.Options ← Hickory.NoOptions;
date: Date;
curDate.Year ← time.year; curDate.Month ← time.month; curDate.Day ← time.day;
curMode ← Initial;
yearTrees ← NewYearNode[ curDate]; -- we need at least one!
options[ Hickory.All] ← FALSE;
options[ Hickory.ExpandRepetitions] ← TRUE;
from ← Tempus.Adjust[ months: FirstMonth, baseTime: now, precisionOfResult: days].time;
date ← CalSupport.ExtractDate[ from];
time.hour ← 0; time.minute ← 0; time.second ← 0;
time.day ← 1; time.month ← date.Month; time.year ← date.Year;
from ← BasicTime.Pack[ time];
to ← Tempus.Adjust[ months: LastMonth, baseTime: now, precisionOfResult: days].time;
date ← CalSupport.ExtractDate[ to];
time.hour ← 23; time.minute ← 59; time.second ← 59;
time.month ← date.Month; time.year ← date.Year;
time.day ← CalSupport.GetMonthInfo[ date.Month, date.Year].nbrOfDays;
to ← BasicTime.Pack[ time];
evl ← Hickory.RestoreEvents[ from: from, to: to, options: options];
FOR i:
INT
IN [ FirstMonth..LastMonth]
DO
-- preload cache
date: Date ← CalSupport.ExtractDate[ Tempus.Adjust[ months: i, baseTime: now, precisionOfResult: days].time];
thisYear: REF YearNode ← FindYearTree[ date.Year];
IF thisYear =
NIL
THEN
BEGIN
thisYear ← NewYearNode[ date];
InsertYearTree[ thisYear];
END;
date.Day ← 1;
evl ← FetchAMonth[ date, thisYear, evl, FALSE];
ENDLOOP;
END; --InitStorage;
maintaining the storage ( except viewers) when hickory is updated...
HickoryChange:
PUBLIC
INTERNAL
PROCEDURE [ reason: Hickory.Reason, ev: Hickory.Event, data: RopeSets.RopeSet]
RETURNS [ oldEvl, newEvl: Hickory.EventList] =
BEGIN
here we update hickory storage and return the list of events that have indeed
changed
options: Hickory.Options ← Hickory.NoOptions;
from, to: BasicTime.GMT;
options[ Hickory.ExpandRepetitions] ← TRUE;
options[ Hickory.All] ← FALSE;
[ from, to] ← MinMaxCalDates[];
oldEvl ← NIL; newEvl ← NIL;
SELECT reason
FROM
NewEvent, UnForget =>
BEGIN
-- assume forgotten events are not in CalStorage
newEvl ← Hickory.RestoreEvents[ from: from, to: to, options: options, key: ev];
UpdateStorage[ reason, newEvl];
END;
Edit =>
BEGIN
oldEvl ← RemoveFromStorage[ ev]; -- because of those bloody repetitions...
newEvl ← Hickory.RestoreEvents[ from: from, to: to, options: options, key: ev];
UpdateStorage[ reason, newEvl];
END;
Forget =>
BEGIN
-- retrieve the events that are now forgotten in db...
options[ Hickory.ExpandRepetitions] ← TRUE;
options[ Hickory.All] ← TRUE;
oldEvl ← Hickory.RestoreEvents[ from: from, to: to, options: options, key: ev];
UpdateStorage[ reason, oldEvl];
END;
Destroy =>
BEGIN
oldEvl ← RemoveFromStorage[ ev];
END;
ENDCASE;
RETURN[oldEvl, newEvl];
END; -- HickoryChange
MinMaxCalDates:
INTERNAL
PROCEDURE
RETURNS [ t1, t2: BasicTime.
GMT] =
BEGIN
to find out what are the extreme points in calendar. We make a rough estimate..
time: BasicTime.Unpacked;
yp: REF YearNode;
time.year ← yearTrees.Year;
time.month ← January; time.day ← 1;
time.hour ← 0; time.minute ← 0; time.second ← 0;
t1 ← BasicTime.Pack[ time];
yp ← yearTrees;
WHILE yp.Next # NIL DO yp ← yp.Next; ENDLOOP;
time.year ← yp.Year;
time.month ← December; time.day ← 31; -- again: Anne's birthday
time.hour ← 23; time.minute ← 59; time.second ← 59;
t2 ← BasicTime.Pack[ time];
RETURN[ t1, t2];
END; -- MinMaxCalDates
UpdateStorage:
INTERNAL
PROCEDURE [ reason: Hickory.Reason, evl: Hickory.EventList] =
BEGIN
here we traverse the storage and update the event lists elements
it's easy to find the right spot since we can find out about the date..
FOR l: Hickory.EventList ← evl, l.rest
UNTIL l =
NIL
DO
SELECT reason
FROM
Edit, NewEvent, UnForget => InsertEvent[ l.first, CalSupport.ExtractDate[ l.first.EventTime]];
Forget => ForgetEvent[ l.first, CalSupport.ExtractDate[ l.first.EventTime]];
ENDCASE;
ENDLOOP;
END; -- UpdateStorage
InsertEvent:
INTERNAL
PROCEDURE [ evt: Hickory.EventTuple, date: Date] =
BEGIN
in case a new event appeared in Hickory... This is also the case when editing an
event, since we have previously deleted all occurrences of the old unedited event.
Reason is repeated events...!
thisYear: REF YearNode ← FindYearTree[ date.Year];
IF thisYear = NIL THEN RETURN; -- this shouldn't really happen..
BEGIN
OPEN thisYear.MonthNodes[ date.Month].DayNodes[ date.Day];
IF NOT Initialized THEN RETURN; -- well, since our min,max dates migh be wrong...
IF EvList = NIL THEN EvList ← CONS[ evt, NIL]
ELSE
BEGIN
evl: Hickory.EventList ← CONS[ evt, EvList];
EvList ← Hickory.OrderEventList[ evl];
END;
END; -- OPEN
END; -- InsertEvent
ForgetEvent:
INTERNAL
PROCEDURE [ evt: Hickory.EventTuple, date: Date] =
BEGIN
if an event was forgotten. It's still in data base, but we must remove it here.
it's easier than destroy since we know the exact dates where event occurred
thisYear: REF YearNode ← FindYearTree[ date.Year];
IF thisYear = NIL THEN RETURN; -- this shouldn't really happen..
BEGIN
OPEN thisYear.MonthNodes[ date.Month].DayNodes[ date.Day];
IF NOT Initialized THEN RETURN; -- well, since our min,max dates migh be wrong...
IF EvList = NIL THEN RETURN
ELSE
BEGIN
prev: Hickory.EventList ← NIL;
cur: Hickory.EventList ← EvList;
WHILE cur #
NIL
DO
-- in case several repetitions on same date ( day)
WHILE cur #
NIL
AND
NOT Hickory.SameEvent[ evt.Key, cur.first.Key]
DO
prev ← cur; cur ← cur.rest;
ENDLOOP;
IF cur = NIL THEN RETURN; -- not found ?!
IF prev #
NIL
THEN
BEGIN
-- delete in middle
prev.rest ← cur.rest; cur.rest ← NIL; -- and GC takes care of the rest
cur ← prev.rest;
END
ELSE
BEGIN
-- prev = NIL -> delete at head
EvList ← cur.rest; cur.rest ← NIL;
IF EvList # NIL THEN cur ← EvList.rest
ELSE cur ← NIL;
END
ENDLOOP;
END;
END; -- OPEN
END; -- ForgetEvent
RemoveFromStorage:
INTERNAL
PROCEDURE [ ev: Hickory.Event]
RETURNS [ evl: Hickory.EventList] =
BEGIN
here we must traverse all the trees and figure out where the events to be deleted
are hidden. Since db no longer has the events, we can't get at the dates..
evl is the list of events that we found and have deleted from storage
yp: REF YearNode ← yearTrees;
evl ← NIL;
WHILE yp #
NIL
DO
FOR m: Months
IN [ January..December]
DO
FOR d: Days
IN [ 1..31]
DO
OPEN yp^.MonthNodes[ m].DayNodes[ d];
IF NOT Initialized THEN LOOP
ELSE IF EvList = NIL THEN LOOP
ELSE
BEGIN
prev: Hickory.EventList ← NIL;
cur: Hickory.EventList ← EvList;
WHILE cur #
NIL
DO
IF Hickory.SameEvent[ cur.first.Key, ev]
THEN
BEGIN
-- delete the sucker
IF prev #
NIL
THEN
BEGIN
prev.rest ← cur.rest;
cur.rest ← evl; evl ← cur; -- insert in front of evl
cur ← prev.rest; -- next elm.
END
ELSE
BEGIN
-- delete cur at head of EvList
EvList ← cur.rest; cur.rest ← evl; evl ← cur; -- insert in front of evl
IF EvList # NIL THEN cur ← EvList.rest
ELSE cur ← NIL;
END
END
ELSE
BEGIN
prev ← cur; cur ← cur.rest;
END;
ENDLOOP;
END; -- If not initialized
ENDLOOP; -- for all days
ENDLOOP; -- for all months
yp ← yp.Next; -- next year
ENDLOOP; -- for all years
RETURN[ evl];
END; -- CalStorageImpl
InitStorage[];
END.