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