summaryrefslogtreecommitdiffstats
path: root/comm/calendar/providers/storage/CalStorageCachedItemModel.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/providers/storage/CalStorageCachedItemModel.jsm')
-rw-r--r--comm/calendar/providers/storage/CalStorageCachedItemModel.jsm219
1 files changed, 219 insertions, 0 deletions
diff --git a/comm/calendar/providers/storage/CalStorageCachedItemModel.jsm b/comm/calendar/providers/storage/CalStorageCachedItemModel.jsm
new file mode 100644
index 0000000000..80a367f2af
--- /dev/null
+++ b/comm/calendar/providers/storage/CalStorageCachedItemModel.jsm
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXPORTED_SYMBOLS = ["CalStorageCachedItemModel"];
+
+const { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+const { CalReadableStreamFactory } = ChromeUtils.import(
+ "resource:///modules/CalReadableStreamFactory.jsm"
+);
+const { CalStorageItemModel } = ChromeUtils.import(
+ "resource:///modules/calendar/CalStorageItemModel.jsm"
+);
+
+/**
+ * CalStorageCachedItemModel extends CalStorageItemModel to add caching support
+ * for items. Most of the methods here are overridden from the parent class to
+ * either add or retrieve items from the cache.
+ */
+class CalStorageCachedItemModel extends CalStorageItemModel {
+ /**
+ * Cache for all items.
+ *
+ * @type {Map<string, calIItemBase>}
+ */
+ itemCache = new Map();
+
+ /**
+ * Cache for recurring events.
+ *
+ * @type {Map<string, calIEvent>}
+ */
+ #recurringEventsCache = new Map();
+
+ /**
+ * Cache for recurring events offline flags.
+ *
+ * @type {Map<string, number>}
+ */
+ #recurringEventsOfflineFlagCache = new Map();
+
+ /**
+ * Cache for recurring todos.
+ *
+ * @type {Map<string, calITodo>}
+ */
+ #recurringTodosCache = new Map();
+
+ /**
+ * Cache for recurring todo offline flags.
+ *
+ * @type {Map<string, number>}
+ */
+ #recurringTodosOfflineCache = new Map();
+
+ /**
+ * Promise that resolves when the caches have been built up.
+ *
+ * @type {Promise<void>}
+ */
+ #recurringCachePromise = null;
+
+ /**
+ * Build up recurring event and todo cache with its offline flags.
+ */
+ async #ensureRecurringItemCaches() {
+ if (!this.#recurringCachePromise) {
+ this.#recurringCachePromise = this.#buildRecurringItemCaches();
+ }
+ return this.#recurringCachePromise;
+ }
+
+ async #buildRecurringItemCaches() {
+ // Retrieve items and flags for recurring events and todos before combining
+ // storing them in the item cache. Items need to be expunged from the
+ // existing item cache to avoid get(Event|Todo)FromRow providing stale
+ // values.
+ let expunge = id => this.itemCache.delete(id);
+ let [events, eventFlags] = await this.getRecurringEventAndFlagMaps(expunge);
+ let [todos, todoFlags] = await this.getRecurringTodoAndFlagMaps(expunge);
+ let itemsMap = await this.getAdditionalDataForItemMap(new Map([...events, ...todos]));
+
+ this.itemCache = new Map([...this.itemCache, ...itemsMap]);
+ this.#recurringEventsCache = new Map([...this.#recurringEventsCache, ...events]);
+ this.#recurringEventsOfflineFlagCache = new Map([
+ ...this.#recurringEventsOfflineFlagCache,
+ ...eventFlags,
+ ]);
+ this.#recurringTodosCache = new Map([...this.#recurringTodosCache, ...todos]);
+ this.#recurringTodosOfflineCache = new Map([...this.#recurringTodosOfflineCache, ...todoFlags]);
+ }
+
+ /**
+ * Overridden here to build the recurring item caches when needed.
+ *
+ * @param {CalStorageQuery} query
+ *
+ * @returns {ReadableStream<calIItemBase>
+ */
+ getItems(query) {
+ let self = this;
+ let getStream = () => super.getItems(query);
+ return CalReadableStreamFactory.createReadableStream({
+ async start(controller) {
+ // HACK because recurring offline events/todos objects don't have offline_journal information
+ // Hence we need to update the offline flags caches.
+ // It can be an expensive operation but is only used in Online Reconciliation mode
+ if (
+ (query.filters.wantOfflineCreatedItems ||
+ query.filters.wantOfflineDeletedItems ||
+ query.filters.wantOfflineModifiedItems) &&
+ self.mRecItemCachePromise
+ ) {
+ // If there's an existing Promise and it's not complete, wait for it - something else is
+ // already waiting and we don't want to break that by throwing away the caches. If it IS
+ // complete, we'll continue immediately.
+ let recItemCachePromise = self.mRecItemCachePromise;
+ await recItemCachePromise;
+ await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+ // Check in case someone else already threw away the caches.
+ if (self.mRecItemCachePromise == recItemCachePromise) {
+ self.mRecItemCachePromise = null;
+ }
+ }
+ await self.#ensureRecurringItemCaches();
+
+ for await (let value of cal.iterate.streamValues(getStream())) {
+ controller.enqueue(value);
+ }
+ controller.close();
+ },
+ });
+ }
+
+ /**
+ * Overridden here to provide the events from the cache.
+ *
+ * @returns {[Map<string, calIEvent>, Map<string, number>]}
+ */
+ async getFullRecurringEventAndFlagMaps() {
+ return [this.#recurringEventsCache, this.#recurringEventsOfflineFlagCache];
+ }
+
+ /**
+ * Overridden here to provide the todos from the cache.
+ *
+ * @returns {[Map<string, calITodo>, Map<string, number>]}
+ */
+ async getFullRecurringTodoAndFlagMaps() {
+ return [this.#recurringTodosCache, this.#recurringTodosOfflineCache];
+ }
+
+ async getEventFromRow(row, getAdditionalData = true) {
+ let item = this.itemCache.get(row.getResultByName("id"));
+ if (item) {
+ return item;
+ }
+
+ item = await super.getEventFromRow(row, getAdditionalData);
+ if (getAdditionalData) {
+ this.#cacheItem(item);
+ }
+ return item;
+ }
+
+ async getTodoFromRow(row, getAdditionalData = true) {
+ let item = this.itemCache.get(row.getResultByName("id"));
+ if (item) {
+ return item;
+ }
+
+ item = await super.getTodoFromRow(row, getAdditionalData);
+ if (getAdditionalData) {
+ this.#cacheItem(item);
+ }
+ return item;
+ }
+
+ async addItem(item) {
+ await super.addItem(item);
+ this.#cacheItem(item);
+ }
+
+ async getItemById(id) {
+ await this.#ensureRecurringItemCaches();
+ let item = this.itemCache.get(id);
+ if (item) {
+ return item;
+ }
+ return super.getItemById(id);
+ }
+
+ async deleteItemById(id, keepMeta) {
+ await super.deleteItemById(id, keepMeta);
+ this.itemCache.delete(id);
+ this.#recurringEventsCache.delete(id);
+ this.#recurringTodosCache.delete(id);
+ }
+
+ /**
+ * Adds an item to the relevant caches.
+ *
+ * @param {calIItemBase} item
+ */
+ #cacheItem(item) {
+ if (item.recurrenceId) {
+ // Do not cache recurring item instances. See bug 1686466.
+ return;
+ }
+ this.itemCache.set(item.id, item);
+ if (item.recurrenceInfo) {
+ if (item.isEvent()) {
+ this.#recurringEventsCache.set(item.id, item);
+ } else {
+ this.#recurringTodosCache.set(item.id, item);
+ }
+ }
+ }
+}