diff options
Diffstat (limited to '')
-rw-r--r-- | comm/calendar/base/src/CalDeletedItems.jsm | 200 |
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(); + }, +}; |