diff options
author | Determinant <[email protected]> | 2019-02-17 21:45:55 -0500 |
---|---|---|
committer | Determinant <[email protected]> | 2019-02-17 21:45:55 -0500 |
commit | e285bc7722f776155b7ae4477bfa7f8c6601a22b (patch) | |
tree | 492ab0d880ebb78826b9cd253282664b2882b31c /src | |
parent | ea3cbbe3fab9ac6882d4d25c258cb175081c66d7 (diff) |
support offline cache for calendar events
Diffstat (limited to 'src')
-rw-r--r-- | src/background.ts | 72 | ||||
-rw-r--r-- | src/gapi.ts | 131 |
2 files changed, 166 insertions, 37 deletions
diff --git a/src/background.ts b/src/background.ts index e4e2824..39c10c8 100644 --- a/src/background.ts +++ b/src/background.ts @@ -18,6 +18,8 @@ let config = { }; let mainGraphData: GraphData[] = []; let dirtyMetadata = false; +let dirtyCalData = false; +let loadPromise: Promise<void> = null; enum ChromeError { storageGetError = "storageGetError", @@ -45,13 +47,13 @@ async function loadMetadata() { console.log("no saved metadata"); else { - console.log('metadata loaded'); config = { trackedPeriods: items.config.trackedPeriods.map((p: TrackedPeriodFlat) => TrackedPeriod.inflate(p)) }; calendars = items.calendars; mainPatterns = items.mainPatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); analyzePatterns = items.analyzePatterns.map((p: PatternEntryFlat) => PatternEntry.inflate(p)); + console.log('metadata loaded'); } } catch (_) { console.error("error while loading saved metadata"); @@ -70,16 +72,53 @@ async function saveMetadata() { console.log('metadata saved'); } +async function loadCachedCals() { + try { + let items = await chromeStorageGet(['calData']); + if (!items.hasOwnProperty('calData')) + console.log("no cached cals"); + else + { + let calDataFlat: {[id: string]: gapi.GCalendarFlat} = items.calData; + console.log(calDataFlat); + for (let id in calDataFlat) { + calData[id] = gapi.GCalendar.inflate(calDataFlat[id]); + } + console.log("cached cals loaded"); + } + } catch (e) { + console.log(e); + console.error("error while loading cached cals"); + } +} + async function saveCachedCals() { + let calDataFlat: {[id: string]: gapi.GCalendarFlat} = {}; + for (let id in calData) { + if (!(calendars.hasOwnProperty(id) && + calendars[id].enabled)) continue; + calDataFlat[id] = calData[id].deflate(); + } + try { + await chromeStorageSet({ calData: calDataFlat }); + console.log('cached data saved'); + } catch (_) { + console.log("failed to save cached data"); + } } -async function getCalEvents(id: string, start: Date, end: Date) { +function getCalData(id: string) { if (!calData.hasOwnProperty(id)) calData[id] = new gapi.GCalendar(id, calendars[id].name); + return calData[id]; +} + +async function getCalEvents(id: string, start: Date, end: Date) { + let gcal = getCalData(id); try { - let res = await calData[id].getEvents(new Date(start), new Date(end)); - console.log(res); - return res; + let res = await gcal.getEvents(new Date(start), new Date(end)); + dirtyCalData = res.changed; + return res.events; } catch(err) { console.log(`cannot load calendar ${id}`, err); calendars[id].enabled = false; @@ -126,9 +165,23 @@ function updateMainGraphData() { async function pollSync() { console.log('poll'); + /* sync all enabled calendars */ + let pms = []; + for (let id in calendars) { + if (!calendars[id].enabled) continue; + pms.push(getCalData(id).sync()); + } + await Promise.all(pms); + /* update the tracked graph data */ await updateMainGraphData(); + pms = []; + /* save the storage if state is changed */ if (dirtyMetadata) - await saveMetadata().then(() => dirtyMetadata = false); + pms.push(saveMetadata().then(() => dirtyMetadata = false)); + if (dirtyCalData) + pms.push(saveCachedCals().then(() => dirtyCalData = false)); + await Promise.all(pms); + /* setup the next loop */ return new Promise(resolver => ( window.setTimeout(() => { resolver(); pollSync();}, 10000) )); @@ -204,7 +257,10 @@ function handleMsg(port: chrome.runtime.Port) { (async () => { await (msg.data.sync ? updateMainGraphData().then(() => {}) : Promise.resolve()); if (mainGraphData.length === 0) + { + await loadPromise; await updateMainGraphData(); + } port.postMessage(msg.genResp(mainGraphData.map(d => ({ name: d.name, start: d.start.toISOString(), @@ -219,8 +275,8 @@ function handleMsg(port: chrome.runtime.Port) { }); } -(async () => { - await loadMetadata(); +loadPromise = (async () => { + await Promise.all([loadMetadata(), loadCachedCals()]); pollSync(); })(); diff --git a/src/gapi.ts b/src/gapi.ts index d601e64..2252230 100644 --- a/src/gapi.ts +++ b/src/gapi.ts @@ -147,15 +147,39 @@ export type GCalendarOptions = { largeQuery: number }; -type Event = { - start: Date, - end: Date, +type EventFlat = { + start: number, + end: number, id: string }; +class Event { + start: Date; + end: Date; + id: string; + + constructor(start: Date, end: Date, id: string) { + this.start = start; + this.end = end; + this.id = id; + } + + deflate() { + return { + start: this.start.getTime(), + end: this.end.getTime(), + id: this.id + }; + } + + static inflate = (obj: EventFlat) => ( + new Event(new Date(obj.start), new Date(obj.end), obj.id) + ); +} + export type GCalendarEventFlat = { - start: string, - end: string, + start: number, + end: number, id: string, summary: string }; @@ -175,8 +199,8 @@ export class GCalendarEvent { deflate() { return { - start: this.start.toISOString(), - end: this.end.toISOString(), + start: this.start.getTime(), + end: this.end.getTime(), id: this.id, summary: this.summary }; @@ -188,6 +212,17 @@ export class GCalendarEvent { } type GCalendarSlot = { [id: string]: Event }; +type GCalendarSlotFlat = { [id: string]: EventFlat }; + +export type GCalendarFlat = { + calId: string, + name: string, + syncToken: string, + cache: {k: number, v: GCalendarSlotFlat, e: number}[], + eventMeta: { [id: string]: { keys: number[], summary: string } }, + options: GCalendarOptions, + divider: number +}; export class GCalendar { calId: string; @@ -212,6 +247,53 @@ export class GCalendar { this.divider = 8.64e7 * this.options.nDaysPerSlot; } + deflate() { + let cache = this.cache.dump().map(t => { + let slot: GCalendarSlotFlat = {}; + for (let id in t.v) + slot[id] = t.v[id].deflate(); + return { k: t.k, v: slot, e: t.e }; + }); + + let eventMeta: { [id: string]: { keys: number[], summary: string } } = {}; + for (let id in this.eventMeta) { + let m = this.eventMeta[id]; + eventMeta[id] = { keys: Array.from(m.keys), summary: m.summary }; + } + + return { + calId: this.calId, + name: this.name, + syncToken: this.syncToken, + cache, + eventMeta, + options: this.options, + divider: this.divider + } + } + + static inflate(obj: GCalendarFlat) { + let cache = obj.cache.map(t => { + let slot: GCalendarSlot = {}; + for (let id in t.v) + slot[id] = Event.inflate(t.v[id]); + return { k: t.k, v: slot, e: t.e }; + }); + + let eventMeta: { [id: string]: { keys: Set<number>, summary: string } } = {}; + for (let id in obj.eventMeta) { + let m = obj.eventMeta[id]; + eventMeta[id] = { keys: new Set(m.keys), summary: m.summary }; + } + + let gcal = new GCalendar(obj.calId, obj.name, obj.options); + gcal.syncToken = obj.syncToken; + gcal.cache.load(cache); + gcal.eventMeta = eventMeta; + gcal.divider = obj.divider; + return gcal; + } + get token() { return getAuthToken(); } dateToCacheKey(date: Date) { @@ -268,25 +350,13 @@ export class GCalendar { }; if (!evict && t > this.options.maxCachedItems) return; if (ks === ke) - this.getSlot(ks)[e.id] = { - start: e.start, - end: e.end, - id: e.id }; + this.getSlot(ks)[e.id] = new Event(e.start, e.end, e.id); else { - this.getSlot(ks)[e.id] = { - start: e.start, - end: this.slotEndDate(ks), - id: e.id }; - this.getSlot(ke)[e.id] = { - start: this.slotStartDate(ke), - end: e.end, - id: e.id }; + this.getSlot(ks)[e.id] = new Event(e.start, this.slotEndDate(ks), e.id); + this.getSlot(ke)[e.id] = new Event(this.slotStartDate(ke), e.end, e.id); for (let k = ks + 1; k < ke; k++) - this.getSlot(k)[e.id] = { - start: this.slotStartDate(k), - end: this.slotEndDate(k), - id: e.id}; + this.getSlot(k)[e.id] = new Event(this.slotStartDate(k), this.slotEndDate(k), e.id); } } @@ -357,7 +427,7 @@ export class GCalendar { } } - async getEvents(start: Date, end: Date): Promise<GCalendarEvent[]> { + async getEvents(start: Date, end: Date, sync = false): Promise<{ events: GCalendarEvent[], changed: boolean }> { let r = this.dateRangeToCacheKeys({ start, end }); let query = { start: null as number, @@ -379,7 +449,7 @@ export class GCalendar { let token = await this.token; let r = await getEvents(this.calId, token, null, start.toISOString(), end.toISOString()); - return r.results.map(e => { + let events = r.results.map(e => { console.assert(e.start); e.start = new Date(e.start.dateTime); e.end = new Date(e.end.dateTime); @@ -391,6 +461,7 @@ export class GCalendar { e.id, e.summary) )); + return { events, changed: false }; } console.log(`fetching short event list`); @@ -409,14 +480,16 @@ export class GCalendar { }); if (this.syncToken === '') this.syncToken = r.nextSyncToken; - await this.sync(); - return this.getCachedEvents({ start, end }); + if (sync) await this.sync(); + let events = await this.getCachedEvents({ start, end }); + return { events, changed: true }; } else { console.log(`cache hit`); - await this.sync(); - return this.getCachedEvents({ start, end }); + if (sync) await this.sync(); + let events = await this.getCachedEvents({ start, end }); + return { events, changed: false }; } } } |