aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDeterminant <ted.sybil@gmail.com>2019-02-02 22:15:54 -0500
committerDeterminant <ted.sybil@gmail.com>2019-02-02 22:15:54 -0500
commit580990a5eb4a79892c48e3a3fce3386fe80e6cc2 (patch)
treeedc4e19741cf6ff50a45ba7c81cb1cb2f1611c66 /src
parent848c4f0e60340e5fc6079a0bff1bf516845576e0 (diff)
finish cache design
Diffstat (limited to 'src')
-rw-r--r--src/App.js18
-rw-r--r--src/background.js2
-rw-r--r--src/gapi.js175
3 files changed, 151 insertions, 44 deletions
diff --git a/src/App.js b/src/App.js
index efdb446..43beb53 100644
--- a/src/App.js
+++ b/src/App.js
@@ -174,22 +174,28 @@ class Dashboard extends React.Component {
let event_pms = [];
let cals = this.state.calendars;
for (let id in cals)
- event_pms.push(this.getCalEvents(id, start, end)
- .then(r => { return { id, events: r }; }));
- console.log(cals);
+ {
+ let patterns = filterPatterns(this.state.patterns, cals[id].name);
+ if (patterns.length > 0)
+ event_pms.push(this.getCalEvents(id, start, end)
+ .then(r => { return { id, events: r, patterns }; }));
+ }
Promise.all(event_pms).then(all_events => {
console.log(all_events);
let events = {};
+ let patterns = {};
let results = {}; // pattern idx => time
let cal_results = {}; // cal id => time
- all_events.forEach(e => events[e.id] = e.events);
+ all_events.forEach(e => {
+ events[e.id] = e.events;
+ patterns[e.id] = e.patterns;
+ });
for (let i = 0; i < this.state.patterns.length; i++)
results[i] = 0;
for (let id in cals) {
if (!events[id]) continue;
- let patterns = filterPatterns(this.state.patterns, cals[id].name);
events[id].forEach(event => {
- patterns.forEach(p => {
+ patterns[id].forEach(p => {
if (!p.event.regex.test(event.summary)) return;
if (!cal_results.hasOwnProperty(id)) {
cal_results[id] = 0;
diff --git a/src/background.js b/src/background.js
index 7bc0318..4fefceb 100644
--- a/src/background.js
+++ b/src/background.js
@@ -23,7 +23,7 @@ chrome.runtime.onConnect.addListener(function(port) {
else if (msg.type == 4) {
calData[msg.data.id].getEvents(new Date(msg.data.start), new Date(msg.data.end))
.catch(e => {
- console.log(`cannot load calendar ${msg.data.id}`);
+ console.log(`cannot load calendar ${msg.data.id}`, e);
return [];
})
.then(data => {
diff --git a/src/gapi.js b/src/gapi.js
index 67884c9..b5816ca 100644
--- a/src/gapi.js
+++ b/src/gapi.js
@@ -1,4 +1,5 @@
/* global chrome */
+import LRU from "lru-cache";
const gapi_base = 'https://www.googleapis.com/calendar/v3';
const GApiError = {
@@ -7,7 +8,7 @@ const GApiError = {
};
function to_params(dict) {
- return Object.entries(dict).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
+ return Object.entries(dict).filter(([k, v]) => v).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
}
export function getAuthToken() {
@@ -35,12 +36,14 @@ function getEvent(calId, eventId, token) {
.then(response => response.json());
}
-function getEvents(calId, token, syncToken, resultsPerRequest=100) {
+function getEvents(calId, token, syncToken=null, timeMin=null, timeMax=null, resultsPerRequest=100) {
let results = [];
const singleFetch = (pageToken, syncToken) => fetch(`${gapi_base}/calendars/${calId}/events?${to_params({
access_token: token,
pageToken,
syncToken,
+ timeMin,
+ timeMax,
maxResults: resultsPerRequest
})}`, { method: 'GET', async: true })
.then(response => {
@@ -66,66 +69,106 @@ function getEvents(calId, token, syncToken, resultsPerRequest=100) {
}
export class GCalendar {
- constructor(calId, name) {
+ constructor(calId, name, options={maxCachedItems: 100, nDaysPerSlot: 10, largeQuery: 10}) {
this.calId = calId;
this.name = name;
this.token = getAuthToken();
this.syncToken = '';
- this.cache = {};
+ this.cache = new LRU({
+ max: options.maxCachedItems,
+ dispose: (k, v) => this.onRemoveSlot(k, v)
+ });
+ this.eventMeta = {};
+ this.options = options;
+ this.divider = 8.64e7 * this.options.nDaysPerSlot;
+ }
+
+ dateToCacheKey(date) {
+ return Math.floor(date / this.divider);
}
- static dateToCacheKey(date) {
- return Math.floor(date / 8.64e7);
+ dateRangeToCacheKeys(range) {
+ return {
+ start: this.dateToCacheKey(range.start),
+ end: this.dateToCacheKey(new Date(range.end.getTime() - 1))
+ };
}
getSlot(k) {
- if (!this.cache[k])
- this.cache[k] = {};
- return this.cache[k];
+ if (!this.cache.has(k))
+ {
+ let res = {};
+ this.cache.set(k, res);
+ return res;
+ }
+ else return this.cache.get(k);
}
- static slotStartDate(k) { return new Date(k * 8.64e7); }
- static slotEndDate(k) { return new Date((k + 1) * 8.64e7); }
+ onRemoveSlot(k, v) {
+ for (let id in v) {
+ console.assert(this.eventMeta[id]);
+ let keys = this.eventMeta[id].keys;
+ keys.delete(k);
+ if (keys.size === 0)
+ delete this.eventMeta[id];
+ }
+ }
+
+ slotStartDate(k) { return new Date(k * this.divider); }
+ slotEndDate(k) { return new Date((k + 1) * this.divider); }
- addEvent(e) {
- let ks = GCalendar.dateToCacheKey(e.start);
- let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1));
+ addEvent(e, evict = false) {
+ //console.log('adding event', e);
+ if (this.eventMeta.hasOwnProperty(e.id))
+ this.removeEvent(e);
+ let r = this.dateRangeToCacheKeys(e);
+ let ks = r.start;
+ let ke = r.end;
+ let t = this.cache.length;
+ let keys = new Set();
+ for (let i = ks; i <= ke; i++)
+ {
+ keys.add(i);
+ if (!this.cache.has(i)) t++;
+ }
+ this.eventMeta[e.id] = {
+ keys,
+ summary: e.summary,
+ };
+ if (!evict && t > this.options.maxCachedItems) return;
if (ks === ke)
this.getSlot(ks)[e.id] = {
start: e.start,
end: e.end,
- id: e.id,
- summary: e.summary};
+ id: e.id };
else
{
this.getSlot(ks)[e.id] = {
start: e.start,
- end: GCalendar.slotEndDate(ks),
- id: e.id,
- summary: e.summary};
+ end: this.slotEndDate(ks),
+ id: e.id };
this.getSlot(ke)[e.id] = {
- start: GCalendar.slotStartDate(ke),
+ start: this.slotStartDate(ke),
end: e.end,
- id: e.id,
- summary: e.summary};
+ id: e.id };
for (let k = ks + 1; k < ke; k++)
this.getSlot(k)[e.id] = {
- start: GCalendar.slotStartDate(k),
- end: GCalendar.slotEndDate(k),
- id: e.id,
- summary: e.summary};
+ start: this.slotStartDate(k),
+ end: this.slotEndDate(k),
+ id: e.id};
}
}
removeEvent(e) {
- let ks = GCalendar.dateToCacheKey(e.start);
- let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1));
- for (let k = ks; k <= ke; k++)
- delete this.getSlot(k)[e.id];
+ let keys = this.eventMeta[e.id].keys;
+ console.assert(keys);
+ keys.forEach(k => delete this.getSlot(k)[e.id]);
+ delete this.eventMeta[e.id];
}
getSlotEvents(k, start, end) {
let s = this.getSlot(k);
+ //console.log(s);
let results = [];
for (let id in s) {
if (!(s[id].start >= end || s[id].end <= start))
@@ -134,17 +177,18 @@ export class GCalendar {
id,
start: s[id].start < start ? start: s[id].start,
end: s[id].end > end ? end: s[id].end,
- summary: s[id].summary
+ summary: this.eventMeta[id].summary
});
}
}
return results;
}
- getCachedEvents(start, end) {
- let ks = GCalendar.dateToCacheKey(start);
- let ke = GCalendar.dateToCacheKey(new Date(end.getTime() - 1));
- let results = this.getSlotEvents(ks, start, end);
+ getCachedEvents(_r) {
+ let r = this.dateRangeToCacheKeys(_r);
+ let ks = r.start;
+ let ke = r.end;
+ let results = this.getSlotEvents(ks, _r.start, _r.end);
for (let k = ks + 1; k < ke; k++)
{
let s = this.getSlot(k);
@@ -152,7 +196,7 @@ export class GCalendar {
results.push(s[id]);
}
if (ke > ks)
- results.push(...this.getSlotEvents(ke, start, end));
+ results.push(...this.getSlotEvents(ke, _r.start, _r.end));
return results;
}
@@ -177,6 +221,63 @@ export class GCalendar {
}
getEvents(start, end) {
- return this.sync().then(() => this.getCachedEvents(start, end));
+ let r = this.dateRangeToCacheKeys({ start, end });
+ let query = {};
+ for (let k = r.start; k <= r.end; k++)
+ if (!this.cache.has(k))
+ {
+ if (!query.hasOwnProperty('start'))
+ query.start = k;
+ query.end = k;
+ }
+ console.log(`start: ${start} end: ${end}`);
+ if (query.hasOwnProperty('start'))
+ {
+ console.assert(query.start <= query.end);
+ if (query.end - query.start + 1 > this.options.largeQuery) {
+ console.log(`encounter large query, use direct fetch`);
+ return this.token.then(token => getEvents(this.calId, token, null,
+ start.toISOString(), end.toISOString()).then(r => {
+ let results = [];
+ r.results.forEach(e => {
+ console.assert(e.start);
+ e.start = new Date(e.start.dateTime);
+ e.end = new Date(e.end.dateTime);
+ results.push(e);
+ });
+ return results.filter(e => !(e.start >= end || e.end <= start)).map(e => {
+ return {
+ id: e.id,
+ start: e.start < start ? start: e.start,
+ end: e.end > end ? end: e.end,
+ summary: e.summary,
+ };
+ });
+ }));
+ }
+
+ console.log(`fetching short event list`);
+ return this.token.then(token => getEvents(this.calId, token, null,
+ this.slotStartDate(query.start).toISOString(),
+ this.slotEndDate(query.end).toISOString()).then(r => {
+ if (this.syncToken === '')
+ this.syncToken = r.nextSyncToken;
+ return r.results.forEach(e => {
+ if (e.status === 'confirmed')
+ {
+ console.assert(e.start);
+ e.start = new Date(e.start.dateTime);
+ e.end = new Date(e.end.dateTime);
+ this.addEvent(e, true);
+ }
+ });
+ })).then(() => this.sync())
+ .then(() => this.getCachedEvents({ start, end }));
+ }
+ else
+ {
+ console.log(`cache hit`);
+ return this.sync().then(() => this.getCachedEvents({ start, end }));
+ }
}
}