summaryrefslogtreecommitdiff
path: root/calendar/base/src/calDeletedItems.js
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/base/src/calDeletedItems.js')
-rw-r--r--calendar/base/src/calDeletedItems.js199
1 files changed, 199 insertions, 0 deletions
diff --git a/calendar/base/src/calDeletedItems.js b/calendar/base/src/calDeletedItems.js
new file mode 100644
index 000000000..73c05ae85
--- /dev/null
+++ b/calendar/base/src/calDeletedItems.js
@@ -0,0 +1,199 @@
+/* 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/. */
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+/**
+ * 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: function() {},
+ handleError: function() {},
+ handleCompletion: function() {},
+ };
+}
+
+var calDeletedItemsClassID = Components.ID("{8e6799af-e7e9-4e6c-9a82-a2413e86d8c3}");
+var calDeletedItemsInterfaces = [
+ Components.interfaces.calIDeletedItems,
+ Components.interfaces.nsIObserver,
+ Components.interfaces.calIObserver
+];
+calDeletedItems.prototype = {
+
+ classID: calDeletedItemsClassID,
+ QueryInterface: XPCOMUtils.generateQI(calDeletedItemsInterfaces),
+ classInfo: XPCOMUtils.generateCI({
+ classID: calDeletedItemsClassID,
+ contractID: "@mozilla.org/calendar/deleted-items-manager;1",
+ classDescription: "Database containing information about deleted items",
+ interfaces: calDeletedItemsInterfaces,
+ flags: Components.interfaces.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: function() {
+ this.ensureStatements();
+ this.stmtFlush.params.stale_time = cal.now().nativeTime - this.STALE_TIME;
+ this.stmtFlush.executeAsync(this.completedNotifier);
+ },
+
+ getDeletedDate: function(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.calendarDefaultTimezone());
+ }
+ } catch (e) {
+ cal.ERROR(e);
+ } finally {
+ stmt.reset();
+ }
+ return null;
+ },
+
+ markDeleted: function(aItem) {
+ this.ensureStatements();
+ this.stmtMarkDelete.params.calId = aItem.calendar.id;
+ this.stmtMarkDelete.params.id = aItem.id;
+ this.stmtMarkDelete.params.time = cal.now().nativeTime;
+ this.stmtMarkDelete.params.rid = (aItem.recurrenceId && aItem.recurrenceId.nativeTime) || "";
+ this.stmtMarkDelete.executeAsync(this.completedNotifier);
+ },
+
+ unmarkDeleted: function(aItem) {
+ this.ensureStatements();
+ this.stmtUnmarkDelete.params.id = aItem.id;
+ this.stmtUnmarkDelete.executeAsync(this.completedNotifier);
+ },
+
+ initDB: function() {
+ 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: function(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.getCalendarManager().addCalendarObserver(this);
+ }
+ },
+
+ ensureStatements: function() {
+ 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: function() {
+ 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.getCalendarManager().removeCalendarObserver(this);
+ },
+
+ // calIObserver
+ onStartBatch: function() {},
+ onEndBatch: function() {},
+ onModifyItem: function() {},
+ onError: function() {},
+ onPropertyChanged: function() {},
+ onPropertyDeleting: function() {},
+
+ onAddItem: function(aItem) {
+ this.unmarkDeleted(aItem);
+ },
+
+ onDeleteItem: function(aItem) {
+ this.markDeleted(aItem);
+ },
+
+ onLoad: function() {
+ this.flush();
+ }
+};