From f9f537e3dd28aa543770aea158e4eff65be0b261 Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 31 Jan 2019 14:48:28 -0500 Subject: impl GCalendar sync model --- src/gapi.js | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 151 insertions(+), 16 deletions(-) (limited to 'src/gapi.js') diff --git a/src/gapi.js b/src/gapi.js index 975d4d9..c66a9ff 100644 --- a/src/gapi.js +++ b/src/gapi.js @@ -12,28 +12,163 @@ export function getAuthToken() { } export function getCalendars(token) { - return fetch(gapi_base + '/users/me/calendarList?' + to_params({access_token: token}), + return fetch(`${gapi_base}/users/me/calendarList?${to_params({access_token: token})}`, { method: 'GET', async: true }) .then(response => response.json()) .then(data => data.items); } -export function genEventsGetter(calId, timeMin, timeMax) { - return token => fetch(gapi_base + '/calendars/' + calId + '/events?' + to_params({ - access_token: token, - timeMin, - timeMax - }), { method: 'GET', async: true }) - .then(response => { - if (response.status === 200) - return response.json() - else throw `got response ${response.status}`; - }) - .catch(e => { console.log(e); return []; }) - .then(data => data.items); +export function getColors(token) { + return fetch(`${gapi_base}/colors?${to_params({access_token: token})}`, + { method: 'GET', async: true }) + .then(response => response.json()); } -export function getColors(token) { - return fetch(gapi_base + '/colors?' + to_params({access_token: token}), { method: 'GET', async: true }) +function getEvent(calId, eventId, token) { + return fetch(`${gapi_base}/calendars/${calId}/events/${eventId}?${to_params({access_token: token})}`, + { method: 'GET', async: true }) .then(response => response.json()); } + +function getEvents(calId, token, syncToken, resultsPerRequest = '') { + let results = []; + const singleFetch = (pageToken, syncToken) => fetch(`${gapi_base}/calendars/${calId}/events?${to_params({ + access_token: token, + pageToken, + syncToken, + maxResults: resultsPerRequest + })}`, { method: 'GET', async: true }) + .then(response => { + if (response.status === 200) + return response.json(); + else throw {}; + }) + .catch(e => e) + .then(data => { + if (!data.items) + return ({ + nextSyncToken: '', + results: [] + }); + results.push(...data.items); + if (data.nextPageToken) { + return singleFetch(data.nextPageToken, ''); + } else { + return ({ + nextSyncToken: data.nextSyncToken, + results + }); + } + }) + + return singleFetch('', syncToken); +} + +export class GCalendar { + constructor(calId, name) { + this.calId = calId; + this.name = name; + this.token = getAuthToken(); + this.syncToken = ''; + this.cache = {}; + } + + static dateToCacheKey(date) { + return Math.floor(date / 8.64e7); + } + + getSlot(k) { + if (!this.cache[k]) + this.cache[k] = {}; + return this.cache[k]; + } + + static slotStartDate(k) { return new Date(k * 8.64e7); } + static slotEndDate(k) { return new Date((k + 1) * 8.64e7); } + + addEvent(e) { + let ks = GCalendar.dateToCacheKey(e.start); + let ke = GCalendar.dateToCacheKey(new Date(e.end.getTime() - 1)); + if (ks === ke) + this.getSlot(ks)[e.id] = { + start: e.start, + end: e.end, + id: e.id }; + else + { + this.getSlot(ks)[e.id] = { + start: e.start, + end: GCalendar.slotEndDate(ks), + id: e.id }; + this.getSlot(ke)[e.id] = { + start: GCalendar.slotStartDate(ke), + end: e.end, + 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 }; + } + } + + 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]; + } + + getSlotEvents(k, start, end) { + let s = this.getSlot(k); + let results = []; + for (let id in s) { + if (!(s[id].start >= end || s[id].end <= start)) + { + let nstart = s[id].start < start ? start: s[id].start; + let nend = s[id].end > end ? end: s[id].end; + if (nstart > nend) console.log(s[id], start, end); + results.push({ + id, + start: s[id].start < start ? start: s[id].start, + end: s[id].end > end ? end: s[id].end + }); + } + } + 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); + for (let k = ks + 1; k < ke; k++) + { + let s = this.getSlot(k); + for (let id in s) + results.push(s[id]); + } + if (ke > ks) + results.push(...this.getSlotEvents(ke, start, end)); + return results; + } + + sync() { + return this.token.then(token => getEvents(this.calId, token, this.syncToken).then(r => { + this.syncToken = r.nextSyncToken; + let pm_results = r.results.map(e => e.start ? Promise.resolve(e) : getEvent(this.calId, e.id, token)); + return Promise.all(pm_results).then(results => results.forEach(e => { + e.start = new Date(e.start.dateTime); + e.end = new Date(e.end.dateTime); + if (e.status === 'confirmed') + this.addEvent(e); + else if (e.status === 'cancelled') + this.removeEvent(e); + })); + })); + } + + getEvents(start, end) { + return this.sync().then(() => this.getCachedEvents(start, end)); + } +} -- cgit v1.2.3