/* global sendAsyncMessage, addMessageListener, indexedDB */ "use strict"; const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; let imports = {}; Cu.import("resource://gre/modules/ContactDB.jsm", imports); Cu.import("resource://gre/modules/ContactService.jsm", imports); Cu.import("resource://gre/modules/Promise.jsm", imports); Cu.importGlobalProperties(["indexedDB"]); const { STORE_NAME, SAVED_GETALL_STORE_NAME, REVISION_STORE, DB_NAME, ContactService, } = imports; // |const| will not work because // it will make the Promise object immutable before assigning. // Using Object.defineProperty() instead. Object.defineProperty(this, "Promise", { value: imports.Promise, writable: false, configurable: false }); let DEBUG = false; function debug(str) { if (DEBUG){ dump("-*- TestMigrationChromeScript: " + str + "\n"); } } const DATA = { 12: { SCHEMA: function createSchema12(db, transaction) { let objectStore = db.createObjectStore(STORE_NAME, {keyPath: "id"}); objectStore.createIndex("familyName", "properties.familyName", { multiEntry: true }); objectStore.createIndex("givenName", "properties.givenName", { multiEntry: true }); objectStore.createIndex("familyNameLowerCase", "search.familyName", { multiEntry: true }); objectStore.createIndex("givenNameLowerCase", "search.givenName", { multiEntry: true }); objectStore.createIndex("telLowerCase", "search.tel", { multiEntry: true }); objectStore.createIndex("emailLowerCase", "search.email", { multiEntry: true }); objectStore.createIndex("tel", "search.exactTel", { multiEntry: true }); objectStore.createIndex("category", "properties.category", { multiEntry: true }); objectStore.createIndex("email", "search.email", { multiEntry: true }); objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true}); db.createObjectStore(SAVED_GETALL_STORE_NAME); db.createObjectStore(REVISION_STORE).put(0, "revision"); }, BAD: [ { // this contact is bad because its "name" property is not an array and // is the empty string (upgrade 16 to 17) "properties": { "name": "", "email": [], "url": [{ "type": ["source"], "value": "urn:service:gmail:uid:http://www.google.com/m8/feeds/contacts/XXX/base/4567894654" }], "category": ["gmail"], "adr": [], "tel": [{ "type": ["mobile"], "value": "+7 123 456-78-90" }], "sex": "undefined", "genderIdentity": "undefined" }, "search": { "givenName": [], "familyName": [], "email": [], "category": ["gmail"], "tel": ["+71234567890","71234567890","1234567890","234567890","34567890", "4567890","567890","67890","7890","890","90","0","81234567890"], "exactTel": ["+71234567890"], "parsedTel": ["+71234567890","1234567890","81234567890","34567890"] }, "updated": new Date("2013-07-27T16:47:40.974Z"), "published": new Date("2013-07-27T16:47:40.974Z"), "id": "bad-1" }, { // This contact is bad because its "name" property is not an array // (upgrade 14 to 15) "properties": { "name": "name-bad-2", "email": [], "url": [{ "type": ["source"], "value": "urn:service:gmail:uid:http://www.google.com/m8/feeds/contacts/XXX/base/4567894654" }], "category": ["gmail"], "adr": [], "tel": [{ "type": ["mobile"], "value": "+7 123 456-78-90" }], "sex": "undefined", "genderIdentity": "undefined" }, "search": { "givenName": [], "familyName": [], "email": [], "category": ["gmail"], "tel": ["+71234567890","71234567890","1234567890","234567890","34567890", "4567890","567890","67890","7890","890","90","0","81234567890"], "exactTel": ["+71234567890"], "parsedTel": ["+71234567890","1234567890","81234567890","34567890"] }, "updated": new Date("2013-07-27T16:47:40.974Z"), "published": new Date("2013-07-27T16:47:40.974Z"), "id": "bad-2" }, { // This contact is bad because its bday property is a String (upgrade 15 // to 16), and its anniversary property is an empty string (upgrade 16 // to 17) "properties": { "name": ["name-bad-3"], "email": [], "url": [{ "type": ["source"], "value": "urn:service:gmail:uid:http://www.google.com/m8/feeds/contacts/XXX/base/4567894654" }], "category": ["gmail"], "adr": [], "tel": [{ "type": ["mobile"], "value": "+7 123 456-78-90" }], "sex": "undefined", "genderIdentity": "undefined", "bday": "2013-07-27T16:47:40.974Z", "anniversary": "" }, "search": { "givenName": [], "familyName": [], "email": [], "category": ["gmail"], "tel": ["+71234567890","71234567890","1234567890","234567890","34567890", "4567890","567890","67890","7890","890","90","0","81234567890"], "exactTel": ["+71234567890"], "parsedTel": ["+71234567890","1234567890","81234567890","34567890"] }, "updated": new Date("2013-07-27T16:47:40.974Z"), "published": new Date("2013-07-27T16:47:40.974Z"), "id": "bad-3" }, { // This contact is bad because its tel property has a tel.type null // value (upgrade 16 to 17), and email.type not array value (upgrade 14 // to 15) "properties": { "name": ["name-bad-4"], "email": [{ "value": "toto@toto.com", "type": "home" }], "url": [{ "type": ["source"], "value": "urn:service:gmail:uid:http://www.google.com/m8/feeds/contacts/XXX/base/4567894654" }], "category": ["gmail"], "adr": [], "tel": [{ "type": null, "value": "+7 123 456-78-90" }], "sex": "undefined", "genderIdentity": "undefined" }, "search": { "givenName": [], "familyName": [], "email": [], "category": ["gmail"], "tel": ["+71234567890","71234567890","1234567890","234567890","34567890", "4567890","567890","67890","7890","890","90","0","81234567890"], "exactTel": ["+71234567890"], "parsedTel": ["+71234567890","1234567890","81234567890","34567890"] }, "updated": new Date("2013-07-27T16:47:40.974Z"), "published": new Date("2013-07-27T16:47:40.974Z"), "id": "bad-4" } ], GOOD: [ { "properties": { "name": ["name-good-1"], "email": [], "url": [{ "type": ["source"], "value": "urn:service:gmail:uid:http://www.google.com/m8/feeds/contacts/XXX/base/4567894654" }], "category": ["gmail"], "adr": [], "tel": [{ "type": ["mobile"], "value": "+7 123 456-78-90" }], "sex": "undefined", "genderIdentity": "undefined" }, "search": { "givenName": [], "familyName": [], "email": [], "category": ["gmail"], "tel": ["+71234567890","71234567890","1234567890","234567890","34567890", "4567890","567890","67890","7890","890","90","0","81234567890"], "exactTel": ["+71234567890"], "parsedTel": ["+71234567890","1234567890","81234567890","34567890"] }, "updated": new Date("2013-07-27T16:47:40.974Z"), "published": new Date("2013-07-27T16:47:40.974Z"), "id": "good-1" } ] } }; function DataManager(version) { if (!(version in DATA)) { throw new Error("Version " + version + " can't be found in our test datas."); } this.version = version; this.data = DATA[version]; } DataManager.prototype = { open: function() { debug("opening for version " + this.version); var deferred = Promise.defer(); let req = indexedDB.open(DB_NAME, this.version); req.onupgradeneeded = function() { let db = req.result; let transaction = req.transaction; this.createSchema(db, transaction); this.addContacts(db, transaction); }.bind(this); req.onsuccess = function() { debug("succeeded opening the db, let's close it now"); req.result.close(); deferred.resolve(this.contactsCount()); }.bind(this); req.onerror = function() { deferred.reject(this.error); }; return deferred.promise; }, createSchema: function(db, transaction) { debug("createSchema for version " + this.version); this.data.SCHEMA(db, transaction); }, addContacts: function(db, transaction) { debug("adding contacts for version " + this.version); var os = transaction.objectStore(STORE_NAME); this.data.GOOD.forEach(function(contact) { os.put(contact); }); this.data.BAD.forEach(function(contact) { os.put(contact); }); }, contactsCount: function() { return this.data.BAD.length + this.data.GOOD.length; } }; DataManager.delete = function() { debug("Deleting the database"); var deferred = Promise.defer(); /* forcibly close the db before deleting it */ ContactService._db.close(); var req = indexedDB.deleteDatabase(DB_NAME); req.onsuccess = function() { debug("Successfully deleted!"); deferred.resolve(); }; req.onerror = function() { debug("Not deleted, error is " + this.error.name); deferred.reject(this.error); }; req.onblocked = function() { debug("Waiting for the current users"); }; return deferred.promise; }; addMessageListener("createDB", function(version) { // Promises help handling gracefully exceptions Promise.resolve().then(function() { return new DataManager(version); }).then(function(manager) { return manager.open(); }).then(function onSuccess(count) { sendAsyncMessage("createDB.success", count); }, function onError(err) { sendAsyncMessage("createDB.error", "Failed to create the DB: " + "(" + err.name + ") " + err.message); }); }); addMessageListener("deleteDB", function() { Promise.resolve().then( DataManager.delete ).then(function onSuccess() { sendAsyncMessage("deleteDB.success"); }, function onError(err) { sendAsyncMessage("deleteDB.error", "Failed to delete the DB:" + err.name); }); });