/ivy/binding/calendar/calStorageImpl.mesa
providing the storage for calendar data, i.e. mainly caching the Hickory events
but also keeping the viewers we create for browsers...
Last edited by: Binding, August 16, 1984 2:50:59 pm PDT
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]
;
CalStorageImpl: CEDAR MONITOR
LOCKS CalStorage.protData
IMPORTS BasicTime, Hickory, CalStorage, CalSupport, ViewerOps, Tempus
EXPORTS CalStorage
SHARES CalSupport
= 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: BOOLEANTRUE] 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;
END; -- FetchAMonth
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.