summaryrefslogtreecommitdiffstats
path: root/comm/calendar/providers/memory
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/calendar/providers/memory
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/calendar/providers/memory')
-rw-r--r--comm/calendar/providers/memory/CalMemoryCalendar.jsm538
-rw-r--r--comm/calendar/providers/memory/components.conf14
-rw-r--r--comm/calendar/providers/memory/moz.build12
3 files changed, 564 insertions, 0 deletions
diff --git a/comm/calendar/providers/memory/CalMemoryCalendar.jsm b/comm/calendar/providers/memory/CalMemoryCalendar.jsm
new file mode 100644
index 0000000000..cd810285d8
--- /dev/null
+++ b/comm/calendar/providers/memory/CalMemoryCalendar.jsm
@@ -0,0 +1,538 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["CalMemoryCalendar"];
+
+var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+
+var { CalReadableStreamFactory } = ChromeUtils.import(
+ "resource:///modules/CalReadableStreamFactory.jsm"
+);
+
+var cICL = Ci.calIChangeLog;
+
+function CalMemoryCalendar() {
+ this.initProviderBase();
+ this.initMemoryCalendar();
+}
+var calMemoryCalendarClassID = Components.ID("{bda0dd7f-0a2f-4fcf-ba08-5517e6fbf133}");
+var calMemoryCalendarInterfaces = [
+ "calICalendar",
+ "calISchedulingSupport",
+ "calIOfflineStorage",
+ "calISyncWriteCalendar",
+ "calICalendarProvider",
+];
+CalMemoryCalendar.prototype = {
+ __proto__: cal.provider.BaseClass.prototype,
+ classID: calMemoryCalendarClassID,
+ QueryInterface: cal.generateQI(calMemoryCalendarInterfaces),
+ classInfo: cal.generateCI({
+ classID: calMemoryCalendarClassID,
+ contractID: "@mozilla.org/calendar/calendar;1?type=memory",
+ classDescription: "Calendar Memory Provider",
+ interfaces: calMemoryCalendarInterfaces,
+ }),
+
+ mItems: null,
+ mOfflineFlags: null,
+ mObservers: null,
+ mMetaData: null,
+
+ initMemoryCalendar() {
+ this.mObservers = new cal.data.ObserverSet(Ci.calIObserver);
+ this.mItems = {};
+ this.mOfflineFlags = {};
+ this.mMetaData = new Map();
+ },
+
+ //
+ // calICalendarProvider interface
+ //
+
+ get displayName() {
+ return cal.l10n.getCalString("memoryName");
+ },
+
+ get shortName() {
+ return this.displayName;
+ },
+
+ deleteCalendar(calendar, listener) {
+ calendar = calendar.wrappedJSObject;
+ calendar.mItems = {};
+ calendar.mMetaData = new Map();
+
+ try {
+ listener.onDeleteCalendar(calendar, Cr.NS_OK, null);
+ } catch (ex) {
+ // Don't bail out if the listener fails
+ }
+ },
+
+ detectCalendars() {
+ throw Components.Exception(
+ "CalMemoryCalendar does not implement detectCalendars",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ },
+
+ mRelaxedMode: undefined,
+ get relaxedMode() {
+ if (this.mRelaxedMode === undefined) {
+ this.mRelaxedMode = this.getProperty("relaxedMode");
+ }
+ return this.mRelaxedMode;
+ },
+
+ //
+ // calICalendar interface
+ //
+
+ getProperty(aName) {
+ switch (aName) {
+ case "cache.supported":
+ case "requiresNetwork":
+ return false;
+ case "capabilities.priority.supported":
+ return true;
+ case "removemodes":
+ return ["delete"];
+ }
+ return this.__proto__.__proto__.getProperty.apply(this, arguments);
+ },
+
+ get supportsScheduling() {
+ return true;
+ },
+
+ getSchedulingSupport() {
+ return this;
+ },
+
+ // readonly attribute AUTF8String type;
+ get type() {
+ return "memory";
+ },
+
+ // Promise<calIItemBase> addItem(in calIItemBase aItem);
+ async addItem(aItem) {
+ let newItem = aItem.clone();
+ return this.adoptItem(newItem);
+ },
+
+ // Promise<calIItemBase> adoptItem(in calIItemBase aItem);
+ async adoptItem(aItem) {
+ if (this.readOnly) {
+ throw Ci.calIErrors.CAL_IS_READONLY;
+ }
+ if (aItem.id == null && aItem.isMutable) {
+ aItem.id = cal.getUUID();
+ }
+
+ if (aItem.id == null) {
+ this.notifyOperationComplete(
+ null,
+ Cr.NS_ERROR_FAILURE,
+ Ci.calIOperationListener.ADD,
+ aItem.id,
+ "Can't set ID on non-mutable item to addItem"
+ );
+ return Promise.reject(
+ new Components.Exception("Can't set ID on non-mutable item to addItem", Cr.NS_ERROR_FAILURE)
+ );
+ }
+
+ // Lines below are commented because of the offline bug 380060, the
+ // memory calendar cannot assume that a new item should not have an ID.
+ // calCachedCalendar could send over an item with an id.
+
+ /*
+ if (this.mItems[aItem.id] != null) {
+ if (this.relaxedMode) {
+ // we possibly want to interact with the user before deleting
+ delete this.mItems[aItem.id];
+ } else {
+ this.notifyOperationComplete(aListener,
+ Ci.calIErrors.DUPLICATE_ID,
+ Ci.calIOperationListener.ADD,
+ aItem.id,
+ "ID already exists for addItem");
+ return;
+ }
+ }
+ */
+
+ let parentItem = aItem.parentItem;
+ if (parentItem != aItem) {
+ parentItem = parentItem.clone();
+ parentItem.recurrenceInfo.modifyException(aItem, true);
+ }
+ parentItem.calendar = this.superCalendar;
+
+ parentItem.makeImmutable();
+ this.mItems[aItem.id] = parentItem;
+
+ // notify observers
+ this.mObservers.notify("onAddItem", [aItem]);
+
+ return aItem;
+ },
+
+ // Promise<calIItemBase> modifyItem(in calIItemBase aNewItem, in calIItemBase aOldItem)
+ async modifyItem(aNewItem, aOldItem) {
+ if (this.readOnly) {
+ throw Ci.calIErrors.CAL_IS_READONLY;
+ }
+ if (!aNewItem) {
+ throw Components.Exception("aNewItem must be set", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ let reportError = (errStr, errId = Cr.NS_ERROR_FAILURE) => {
+ this.notifyOperationComplete(
+ null,
+ errId,
+ Ci.calIOperationListener.MODIFY,
+ aNewItem.id,
+ errStr
+ );
+ return Promise.reject(new Components.Exception(errStr, errId));
+ };
+
+ if (!aNewItem.id) {
+ // this is definitely an error
+ return reportError("ID for modifyItem item is null");
+ }
+
+ let modifiedItem = aNewItem.parentItem.clone();
+ if (aNewItem.parentItem != aNewItem) {
+ modifiedItem.recurrenceInfo.modifyException(aNewItem, false);
+ }
+
+ // If no old item was passed, then we should overwrite in any case.
+ // Pick up the old item from our items array and use this as an old item
+ // later on.
+ if (!aOldItem) {
+ aOldItem = this.mItems[aNewItem.id];
+ }
+
+ if (this.relaxedMode) {
+ // We've already filled in the old item above, if this doesn't exist
+ // then just take the current item as its old version
+ if (!aOldItem) {
+ aOldItem = modifiedItem;
+ }
+ aOldItem = aOldItem.parentItem;
+ } else if (!this.relaxedMode) {
+ if (!aOldItem || !this.mItems[aNewItem.id]) {
+ // no old item found? should be using addItem, then.
+ return reportError(
+ "ID for modifyItem doesn't exist, is null, or is from different calendar"
+ );
+ }
+
+ // do the old and new items match?
+ if (aOldItem.id != modifiedItem.id) {
+ return reportError("item ID mismatch between old and new items");
+ }
+
+ aOldItem = aOldItem.parentItem;
+ let storedOldItem = this.mItems[aOldItem.id];
+
+ // compareItems is not suitable here. See bug 418805.
+ // Cannot compare here due to bug 380060
+ if (!cal.item.compareContent(storedOldItem, aOldItem)) {
+ return reportError(
+ "old item mismatch in modifyItem. storedId:" +
+ storedOldItem.icalComponent +
+ " old item:" +
+ aOldItem.icalComponent
+ );
+ }
+ // offline bug
+
+ if (aOldItem.generation != storedOldItem.generation) {
+ return reportError("generation mismatch in modifyItem");
+ }
+
+ if (aOldItem.generation == modifiedItem.generation) {
+ // has been cloned and modified
+ // Only take care of incrementing the generation if relaxed mode is
+ // off. Users of relaxed mode need to take care of this themselves.
+ modifiedItem.generation += 1;
+ }
+ }
+
+ modifiedItem.makeImmutable();
+ this.mItems[modifiedItem.id] = modifiedItem;
+
+ this.notifyOperationComplete(
+ null,
+ Cr.NS_OK,
+ Ci.calIOperationListener.MODIFY,
+ modifiedItem.id,
+ modifiedItem
+ );
+
+ // notify observers
+ this.mObservers.notify("onModifyItem", [modifiedItem, aOldItem]);
+ return modifiedItem;
+ },
+
+ // Promise<void> deleteItem(in calIItemBase item);
+ async deleteItem(item) {
+ let onError = async (message, exception) => {
+ this.notifyOperationComplete(
+ null,
+ exception,
+ Ci.calIOperationListener.DELETE,
+ item.id,
+ message
+ );
+ return Promise.reject(new Components.Exception(message, exception));
+ };
+
+ if (this.readOnly) {
+ return onError("Calendar is readonly", Ci.calIErrors.CAL_IS_READONLY);
+ }
+
+ if (item.id == null) {
+ return onError("ID is null in deleteItem", Cr.NS_ERROR_FAILURE);
+ }
+
+ let oldItem;
+ if (this.relaxedMode) {
+ oldItem = item;
+ } else {
+ oldItem = this.mItems[item.id];
+ if (oldItem.generation != item.generation) {
+ return onError("generation mismatch in deleteItem", Cr.NS_ERROR_FAILURE);
+ }
+ }
+
+ delete this.mItems[item.id];
+ this.mMetaData.delete(item.id);
+
+ this.notifyOperationComplete(null, Cr.NS_OK, Ci.calIOperationListener.DELETE, item.id, item);
+ // notify observers
+ this.mObservers.notify("onDeleteItem", [oldItem]);
+ return null;
+ },
+
+ // Promise<calIItemBase|null> getItem(in string id);
+ async getItem(aId) {
+ return this.mItems[aId] || null;
+ },
+
+ // ReadableStream<calIItemBase> getItems(in unsigned long itemFilter,
+ // in unsigned long count,
+ // in calIDateTime rangeStart,
+ // in calIDateTime rangeEnd)
+ getItems(itemFilter, count, rangeStart, rangeEnd) {
+ const calICalendar = Ci.calICalendar;
+
+ let itemsFound = [];
+
+ //
+ // filters
+ //
+
+ let wantUnrespondedInvitations =
+ (itemFilter & calICalendar.ITEM_FILTER_REQUEST_NEEDS_ACTION) != 0;
+ let superCal;
+ try {
+ superCal = this.superCalendar.QueryInterface(Ci.calISchedulingSupport);
+ } catch (exc) {
+ wantUnrespondedInvitations = false;
+ }
+ function checkUnrespondedInvitation(item) {
+ let att = superCal.getInvitedAttendee(item);
+ return att && att.participationStatus == "NEEDS-ACTION";
+ }
+
+ // item base type
+ let wantEvents = (itemFilter & calICalendar.ITEM_FILTER_TYPE_EVENT) != 0;
+ let wantTodos = (itemFilter & calICalendar.ITEM_FILTER_TYPE_TODO) != 0;
+ if (!wantEvents && !wantTodos) {
+ // bail.
+ return CalReadableStreamFactory.createEmptyReadableStream();
+ }
+
+ // completed?
+ let itemCompletedFilter = (itemFilter & calICalendar.ITEM_FILTER_COMPLETED_YES) != 0;
+ let itemNotCompletedFilter = (itemFilter & calICalendar.ITEM_FILTER_COMPLETED_NO) != 0;
+ function checkCompleted(item) {
+ item.QueryInterface(Ci.calITodo);
+ return item.isCompleted ? itemCompletedFilter : itemNotCompletedFilter;
+ }
+
+ // return occurrences?
+ let itemReturnOccurrences = (itemFilter & calICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0;
+
+ rangeStart = cal.dtz.ensureDateTime(rangeStart);
+ rangeEnd = cal.dtz.ensureDateTime(rangeEnd);
+ let startTime = -9223372036854775000;
+ if (rangeStart) {
+ startTime = rangeStart.nativeTime;
+ }
+
+ let requestedFlag = 0;
+ if ((itemFilter & calICalendar.ITEM_FILTER_OFFLINE_CREATED) != 0) {
+ requestedFlag = cICL.OFFLINE_FLAG_CREATED_RECORD;
+ } else if ((itemFilter & calICalendar.ITEM_FILTER_OFFLINE_MODIFIED) != 0) {
+ requestedFlag = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
+ } else if ((itemFilter & calICalendar.ITEM_FILTER_OFFLINE_DELETED) != 0) {
+ requestedFlag = cICL.OFFLINE_FLAG_DELETED_RECORD;
+ }
+
+ let matchOffline = function (itemFlag, reqFlag) {
+ // Same as storage calendar sql query. For comparison:
+ // reqFlag is :offline_journal (parameter),
+ // itemFlag is offline_journal (field value)
+ // ...
+ // AND (:offline_journal IS NULL
+ // AND (offline_journal IS NULL
+ // OR offline_journal != ${cICL.OFFLINE_FLAG_DELETED_RECORD}))
+ // OR offline_journal == :offline_journal
+
+ return (
+ (!reqFlag && (!itemFlag || itemFlag != cICL.OFFLINE_FLAG_DELETED_RECORD)) ||
+ itemFlag == reqFlag
+ );
+ };
+
+ let self = this;
+ return CalReadableStreamFactory.createBoundedReadableStream(
+ count,
+ CalReadableStreamFactory.defaultQueueSize,
+ {
+ async start(controller) {
+ return new Promise(resolve => {
+ cal.iterate.forEach(
+ self.mItems,
+ ([id, item]) => {
+ let isEvent_ = item.isEvent();
+ if (isEvent_) {
+ if (!wantEvents) {
+ return cal.iterate.forEach.CONTINUE;
+ }
+ } else if (!wantTodos) {
+ return cal.iterate.forEach.CONTINUE;
+ }
+
+ let hasItemFlag = item.id in self.mOfflineFlags;
+ let itemFlag = hasItemFlag ? self.mOfflineFlags[item.id] : 0;
+
+ // If the offline flag doesn't match, skip the item
+ if (!matchOffline(itemFlag, requestedFlag)) {
+ return cal.iterate.forEach.CONTINUE;
+ }
+
+ if (itemReturnOccurrences && item.recurrenceInfo) {
+ if (item.recurrenceInfo.recurrenceEndDate < startTime) {
+ return cal.iterate.forEach.CONTINUE;
+ }
+
+ let startDate = rangeStart;
+ if (!rangeStart && item.isTodo()) {
+ startDate = item.entryDate;
+ }
+ let occurrences = item.recurrenceInfo.getOccurrences(
+ startDate,
+ rangeEnd,
+ count ? count - itemsFound.length : 0
+ );
+ if (wantUnrespondedInvitations) {
+ occurrences = occurrences.filter(checkUnrespondedInvitation);
+ }
+ if (!isEvent_) {
+ occurrences = occurrences.filter(checkCompleted);
+ }
+ itemsFound = itemsFound.concat(occurrences);
+ } else if (
+ (!wantUnrespondedInvitations || checkUnrespondedInvitation(item)) &&
+ (isEvent_ || checkCompleted(item)) &&
+ cal.item.checkIfInRange(item, rangeStart, rangeEnd)
+ ) {
+ // This needs fixing for recurring items, e.g. DTSTART of parent may occur before rangeStart.
+ // This will be changed with bug 416975.
+ itemsFound.push(item);
+ }
+ if (controller.maxTotalItemsReached) {
+ return cal.iterate.forEach.BREAK;
+ }
+ return cal.iterate.forEach.CONTINUE;
+ },
+ () => {
+ controller.enqueue(itemsFound);
+ controller.close();
+ resolve();
+ }
+ );
+ });
+ },
+ }
+ );
+ },
+
+ //
+ // calIOfflineStorage interface
+ //
+ async addOfflineItem(aItem) {
+ this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_CREATED_RECORD;
+ },
+
+ async modifyOfflineItem(aItem) {
+ let oldFlag = this.mOfflineFlags[aItem.id];
+ if (
+ oldFlag != cICL.OFFLINE_FLAG_CREATED_RECORD &&
+ oldFlag != cICL.OFFLINE_FLAG_DELETED_RECORD
+ ) {
+ this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
+ }
+
+ this.notifyOperationComplete(null, Cr.NS_OK, Ci.calIOperationListener.MODIFY, aItem.id, aItem);
+ return aItem;
+ },
+
+ async deleteOfflineItem(aItem) {
+ let oldFlag = this.mOfflineFlags[aItem.id];
+ if (oldFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
+ delete this.mItems[aItem.id];
+ delete this.mOfflineFlags[aItem.id];
+ } else {
+ this.mOfflineFlags[aItem.id] = cICL.OFFLINE_FLAG_DELETED_RECORD;
+ }
+
+ // notify observers
+ this.observers.notify("onDeleteItem", [aItem]);
+ },
+
+ async getItemOfflineFlag(aItem) {
+ return aItem && aItem.id in this.mOfflineFlags ? this.mOfflineFlags[aItem.id] : null;
+ },
+
+ async resetItemOfflineFlag(aItem) {
+ delete this.mOfflineFlags[aItem.id];
+ },
+
+ //
+ // calISyncWriteCalendar interface
+ //
+ setMetaData(id, value) {
+ this.mMetaData.set(id, value);
+ },
+ deleteMetaData(id) {
+ this.mMetaData.delete(id);
+ },
+ getMetaData(id) {
+ return this.mMetaData.get(id);
+ },
+ getAllMetaDataIds() {
+ return [...this.mMetaData.keys()];
+ },
+ getAllMetaDataValues() {
+ return [...this.mMetaData.values()];
+ },
+};
diff --git a/comm/calendar/providers/memory/components.conf b/comm/calendar/providers/memory/components.conf
new file mode 100644
index 0000000000..a898b8ed8b
--- /dev/null
+++ b/comm/calendar/providers/memory/components.conf
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/
+
+Classes = [
+ {
+ 'cid': '{bda0dd7f-0a2f-4fcf-ba08-5517e6fbf133}',
+ 'contract_ids': ['@mozilla.org/calendar/calendar;1?type=memory'],
+ 'jsm': 'resource:///modules/CalMemoryCalendar.jsm',
+ 'constructor': 'CalMemoryCalendar',
+ },
+] \ No newline at end of file
diff --git a/comm/calendar/providers/memory/moz.build b/comm/calendar/providers/memory/moz.build
new file mode 100644
index 0000000000..c7a6d9ff31
--- /dev/null
+++ b/comm/calendar/providers/memory/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES += [
+ "CalMemoryCalendar.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]