summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/src/CalDeletedItems.jsm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/calendar/base/src/CalDeletedItems.jsm200
1 files changed, 200 insertions, 0 deletions
diff --git a/comm/calendar/base/src/CalDeletedItems.jsm b/comm/calendar/base/src/CalDeletedItems.jsm
new file mode 100644
index 0000000000..2db386b7c5
--- /dev/null
+++ b/comm/calendar/base/src/CalDeletedItems.jsm
@@ -0,0 +1,200 @@
+/* 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 = ["CalDeletedItems"];
+
+var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+var { FileUtils } = ChromeUtils.importESModule("resource://gre/modules/FileUtils.sys.mjs");
+
+/**
+ * Handles remembering deleted items.
+ *
+ * This is (currently) not a real trashcan. Only ids and time deleted is stored.
+ * Note also that the code doesn't strictly check the calendar of the item,
+ * except when a calendar id is passed to getDeletedDate.
+ */
+function CalDeletedItems() {
+ this.wrappedJSObject = this;
+
+ this.completedNotifier = {
+ handleResult() {},
+ handleError() {},
+ handleCompletion() {},
+ };
+}
+
+var calDeletedItemsClassID = Components.ID("{8e6799af-e7e9-4e6c-9a82-a2413e86d8c3}");
+var calDeletedItemsInterfaces = [Ci.calIDeletedItems, Ci.nsIObserver, Ci.calIObserver];
+CalDeletedItems.prototype = {
+ classID: calDeletedItemsClassID,
+ QueryInterface: cal.generateQI(["calIDeletedItems", "nsIObserver", "calIObserver"]),
+ classInfo: cal.generateCI({
+ classID: calDeletedItemsClassID,
+ contractID: "@mozilla.org/calendar/deleted-items-manager;1",
+ classDescription: "Database containing information about deleted items",
+ interfaces: calDeletedItemsInterfaces,
+ flags: Ci.nsIClassInfo.SINGLETON,
+ }),
+
+ DB_SCHEMA_VERSION: 1,
+ STALE_TIME: (30 * 24 * 60 * 60) / 1000 /* 30 days */,
+
+ // To make the tests more failsafe, we have an internal notifier function.
+ // As the deleted items store is just meant to be a hint, this should not
+ // be used in real code.
+ completedNotifier: null,
+
+ flush() {
+ this.ensureStatements();
+ this.stmtFlush.params.stale_time = cal.dtz.now().nativeTime - this.STALE_TIME;
+ this.stmtFlush.executeAsync(this.completedNotifier);
+ },
+
+ getDeletedDate(aId, aCalId) {
+ this.ensureStatements();
+ let stmt;
+ if (aCalId) {
+ stmt = this.stmtGetWithCal;
+ stmt.params.calId = aCalId;
+ } else {
+ stmt = this.stmtGet;
+ }
+
+ stmt.params.id = aId;
+ try {
+ if (stmt.executeStep()) {
+ let date = cal.createDateTime();
+ date.nativeTime = stmt.row.time_deleted;
+ return date.getInTimezone(cal.dtz.defaultTimezone);
+ }
+ } catch (e) {
+ cal.ERROR(e);
+ } finally {
+ stmt.reset();
+ }
+ return null;
+ },
+
+ markDeleted(aItem) {
+ this.ensureStatements();
+ this.stmtMarkDelete.params.calId = aItem.calendar.id;
+ this.stmtMarkDelete.params.id = aItem.id;
+ this.stmtMarkDelete.params.time = cal.dtz.now().nativeTime;
+ this.stmtMarkDelete.params.rid = (aItem.recurrenceId && aItem.recurrenceId.nativeTime) || "";
+ this.stmtMarkDelete.executeAsync(this.completedNotifier);
+ },
+
+ unmarkDeleted(aItem) {
+ this.ensureStatements();
+ this.stmtUnmarkDelete.params.id = aItem.id;
+ this.stmtUnmarkDelete.executeAsync(this.completedNotifier);
+ },
+
+ initDB() {
+ if (this.mDB) {
+ // Looks like we've already initialized, exit early
+ return;
+ }
+
+ let file = FileUtils.getFile("ProfD", ["calendar-data", "deleted.sqlite"]);
+ this.mDB = Services.storage.openDatabase(file);
+
+ // If this database needs changing, please start using a real schema
+ // management, i.e using PRAGMA user_version and upgrading
+ if (!this.mDB.tableExists("cal_deleted_items")) {
+ const v1_schema = "cal_id TEXT, id TEXT, time_deleted INTEGER, recurrence_id INTEGER";
+ const v1_index =
+ "CREATE INDEX idx_deleteditems ON cal_deleted_items(id,cal_id,recurrence_id)";
+
+ this.mDB.createTable("cal_deleted_items", v1_schema);
+ this.mDB.executeSimpleSQL(v1_index);
+ this.mDB.executeSimpleSQL("PRAGMA user_version = 1");
+ }
+
+ // We will not init the statements now, we can still do that the
+ // first time this interface is used. What we should do though is
+ // to clean up at shutdown
+ cal.addShutdownObserver(this.shutdown.bind(this));
+ },
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "profile-after-change") {
+ // Make sure to observe calendar changes so we know when things are
+ // deleted. We don't initialize the statements until first use.
+ cal.manager.addCalendarObserver(this);
+ }
+ },
+
+ ensureStatements() {
+ if (!this.mDB) {
+ this.initDB();
+ }
+
+ if (!this.stmtMarkDelete) {
+ let stmt =
+ "INSERT OR REPLACE INTO cal_deleted_items (cal_id, id, time_deleted, recurrence_id) VALUES(:calId, :id, :time, :rid)";
+ this.stmtMarkDelete = this.mDB.createStatement(stmt);
+ }
+ if (!this.stmtUnmarkDelete) {
+ let stmt = "DELETE FROM cal_deleted_items WHERE id = :id";
+ this.stmtUnmarkDelete = this.mDB.createStatement(stmt);
+ }
+ if (!this.stmtGetWithCal) {
+ let stmt = "SELECT time_deleted FROM cal_deleted_items WHERE cal_id = :calId AND id = :id";
+ this.stmtGetWithCal = this.mDB.createStatement(stmt);
+ }
+ if (!this.stmtGet) {
+ let stmt = "SELECT time_deleted FROM cal_deleted_items WHERE id = :id";
+ this.stmtGet = this.mDB.createStatement(stmt);
+ }
+ if (!this.stmtFlush) {
+ let stmt = "DELETE FROM cal_deleted_items WHERE time_deleted < :stale_time";
+ this.stmtFlush = this.mDB.createStatement(stmt);
+ }
+ },
+
+ shutdown() {
+ try {
+ let stmts = [
+ this.stmtMarkDelete,
+ this.stmtUnmarkDelete,
+ this.stmtGet,
+ this.stmtGetWithCal,
+ this.stmtFlush,
+ ];
+ for (let stmt of stmts) {
+ stmt.finalize();
+ }
+
+ if (this.mDB) {
+ this.mDB.asyncClose();
+ this.mDB = null;
+ }
+ } catch (e) {
+ cal.ERROR("Error closing deleted items database: " + e);
+ }
+
+ cal.manager.removeCalendarObserver(this);
+ },
+
+ // calIObserver
+ onStartBatch() {},
+ onEndBatch() {},
+ onModifyItem() {},
+ onError() {},
+ onPropertyChanged() {},
+ onPropertyDeleting() {},
+
+ onAddItem(aItem) {
+ this.unmarkDeleted(aItem);
+ },
+
+ onDeleteItem(aItem) {
+ this.markDeleted(aItem);
+ },
+
+ onLoad() {
+ this.flush();
+ },
+};