From 9e5ffb8cc2095c95ab161827802bf0d0127c06a1 Mon Sep 17 00:00:00 2001 From: Gregor Wagner Date: Fri, 4 Jan 2013 16:04:28 -0800 Subject: [PATCH] Bug 821814 - Settings: upgrade settings DB after settings.json changed. r=bent --- dom/settings/SettingsDB.jsm | 61 +++++++--- dom/settings/SettingsManager.js | 115 +++++++++++-------- dom/settings/tests/test_settings_basics.html | 97 +++++++++++++--- 3 files changed, 195 insertions(+), 78 deletions(-) diff --git a/dom/settings/SettingsDB.jsm b/dom/settings/SettingsDB.jsm index 60f570641e14..bc4004855cfc 100644 --- a/dom/settings/SettingsDB.jsm +++ b/dom/settings/SettingsDB.jsm @@ -2,19 +2,21 @@ * 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/. */ +"use strict"; + let Cc = Components.classes; let Ci = Components.interfaces; let Cu = Components.utils; this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"]; -const DEBUG = false; +const DEBUG = true; function debug(s) { if (DEBUG) dump("-*- SettingsDB: " + s + "\n"); } this.SETTINGSDB_NAME = "settings"; -this.SETTINGSDB_VERSION = 1; +this.SETTINGSDB_VERSION = 2; this.SETTINGSSTORE_NAME = "settings"; Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); @@ -28,13 +30,17 @@ SettingsDB.prototype = { __proto__: IndexedDBHelper.prototype, upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) { - let objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, - { keyPath: "settingName" }); - objectStore.createIndex("settingValue", "settingValue", { unique: false }); - if (DEBUG) debug("Created object stores and indexes"); - - if (aOldVersion != 0) { - return; + let objectStore; + if (aOldVersion == 0) { + objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" }); + if (DEBUG) debug("Created object stores"); + } else if (aOldVersion == 1) { + if (DEBUG) debug("Get object store for upgrade and remove old index"); + objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME); + objectStore.deleteIndex("settingValue"); + } else { + if (DEBUG) debug("Get object store for upgrade"); + objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME); } // Loading resource://app/defaults/settings.json doesn't work because @@ -66,12 +72,39 @@ SettingsDB.prototype = { if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e); return; } + stream.close(); - for (let setting in settings) { - if (DEBUG) debug("Adding setting " + setting); - objectStore.put({ settingName: setting, - settingValue: settings[setting] }); - } + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + let value = cursor.value; + if (value.settingName in settings) { + if (DEBUG) debug("Upgrade " +settings[value.settingName]); + value.defaultValue = settings[value.settingName]; + delete settings[value.settingName]; + if ("settingValue" in value) { + value.userValue = value.settingValue; + delete value.settingValue; + } + cursor.update(value); + } else if ("userValue" in value || "settingValue" in value) { + value.defaultValue = undefined; + if (aOldVersion == 1 && value.settingValue) { + value.userValue = value.settingValue; + delete value.settingValue; + } + cursor.update(value); + } else { + cursor.delete(); + } + cursor.continue(); + } else { + for (let name in settings) { + if (DEBUG) debug("Set new:" + name +", " + settings[name]); + objectStore.add({ settingName: name, defaultValue: settings[name], userValue: undefined }); + } + } + }; }, init: function init(aGlobal) { diff --git a/dom/settings/SettingsManager.js b/dom/settings/SettingsManager.js index 3bd9d01250b7..578f770e03d7 100644 --- a/dom/settings/SettingsManager.js +++ b/dom/settings/SettingsManager.js @@ -4,7 +4,7 @@ "use strict"; -const DEBUG = false; +const DEBUG = true; function debug(s) { if (DEBUG) dump("-*- SettingsManager: " + s + "\n"); } @@ -30,6 +30,7 @@ const nsIDOMSettingsLock = Ci.nsIDOMSettingsLock; function SettingsLock(aSettingsManager) { this._open = true; + this._isBusy = false; this._requests = new Queue(); this._settingsManager = aSettingsManager; this._transaction = null; @@ -48,11 +49,15 @@ SettingsLock.prototype = { let request = info.request; switch (info.intent) { case "clear": - let req = store.clear(); - req.onsuccess = function() { this._open = true; - Services.DOMRequest.fireSuccess(request, 0); - this._open = false; }.bind(lock); - req.onerror = function() { Services.DOMRequest.fireError(request, 0) }; + let clearReq = store.clear(); + clearReq.onsuccess = function() { + this._open = true; + Services.DOMRequest.fireSuccess(request, 0); + this._open = false; + }.bind(lock); + clearReq.onerror = function() { + Services.DOMRequest.fireError(request, 0) + }; break; case "set": let keys = Object.getOwnPropertyNames(info.settings); @@ -60,49 +65,64 @@ SettingsLock.prototype = { let key = keys[i]; let last = i === keys.length - 1; if (DEBUG) debug("key: " + key + ", val: " + JSON.stringify(info.settings[key]) + ", type: " + typeof(info.settings[key])); - + lock._isBusy = true; let checkKeyRequest = store.get(key); + checkKeyRequest.onsuccess = function (event) { - if (!event.target.result) { - if (DEBUG) debug("MOZSETTINGS-SET-WARNING: " + key + " is not in the database. Please add it to build/settings.js\n"); + let defaultValue; + let userValue = info.settings[key]; + if (event.target.result) { + defaultValue = event.target.result.defaultValue; + } else { + defaultValue = null; + if (DEBUG) debug("MOZSETTINGS-SET-WARNING: " + key + " is not in the database.\n"); } + + let setReq; + if (typeof(info.settings[key]) != 'object') { + let obj = {settingName: key, defaultValue: defaultValue, userValue: userValue}; + if (DEBUG) debug("store1: " + JSON.stringify(obj)); + setReq = store.put(obj); + } else { + //Workaround for cloning issues + let defaultVal = JSON.parse(JSON.stringify(defaultValue)); + let userVal = JSON.parse(JSON.stringify(userValue)); + let obj = {settingName: key, defaultValue: defaultVal, userValue: userVal}; + if (DEBUG) debug("store2: " + JSON.stringify(obj)); + setReq = store.put(obj); + } + + setReq.onsuccess = function() { + lock._isBusy = false; + cpmm.sendAsyncMessage("Settings:Changed", { key: key, value: userValue }); + if (last && !request.error) { + lock._open = true; + Services.DOMRequest.fireSuccess(request, 0); + lock._open = false; + if (!lock._requests.isEmpty()) { + lock.process(); + } + } + }; + + setReq.onerror = function() { + if (!request.error) { + Services.DOMRequest.fireError(request, setReq.error.name) + } + }; } - - if(typeof(info.settings[key]) != 'object') { - req = store.put({settingName: key, settingValue: info.settings[key]}); - } else { - //Workaround for cloning issues - let obj = JSON.parse(JSON.stringify(info.settings[key])); - req = store.put({settingName: key, settingValue: obj}); - } - - req.onsuccess = function() { - if (last && !request.error) { - lock._open = true; - Services.DOMRequest.fireSuccess(request, 0); - lock._open = false; - } - cpmm.sendAsyncMessage("Settings:Changed", { key: key, value: info.settings[key] }); - }; - - req.onerror = function() { - if (!request.error) { - Services.DOMRequest.fireError(request, req.error.name) - } - }; } break; case "get": - req = (info.name === "*") ? store.mozGetAll() - : store.mozGetAll(info.name); + let getReq = (info.name === "*") ? store.mozGetAll() + : store.mozGetAll(info.name); - req.onsuccess = function(event) { + getReq.onsuccess = function(event) { if (DEBUG) debug("Request for '" + info.name + "' successful. " + "Record count: " + event.target.result.length); - if (DEBUG) debug("result: " + JSON.stringify(event.target.result)); if (event.target.result.length == 0) { - if (DEBUG) debug("MOZSETTINGS-GET-WARNING: " + info.name + " is not in the database. Please add it to build/settings.js\n"); + if (DEBUG) debug("MOZSETTINGS-GET-WARNING: " + info.name + " is not in the database.\n"); } let results = { @@ -113,7 +133,8 @@ SettingsLock.prototype = { for (var i in event.target.result) { let result = event.target.result[i]; var name = result.settingName; - var value = result.settingValue; + if (DEBUG) debug("VAL: " + result.userValue +", " + result.defaultValue + "\n"); + var value = result.userValue !== undefined ? result.userValue : result.defaultValue; results[name] = value; results.__exposedProps__[name] = "r"; // If the value itself is an object, expose the properties. @@ -129,14 +150,12 @@ SettingsLock.prototype = { this._open = false; }.bind(lock); - req.onerror = function() { + getReq.onerror = function() { Services.DOMRequest.fireError(request, 0) }; break; } } - if (!lock._requests.isEmpty()) - throw Components.results.NS_ERROR_ABORT; lock._open = true; }, @@ -148,10 +167,15 @@ SettingsLock.prototype = { let transactionType = this._settingsManager.hasWritePrivileges ? "readwrite" : "readonly"; lock._transaction = lock._settingsManager._settingsDB._db.transaction(SETTINGSSTORE_NAME, transactionType); } - lock.process(); + if (!lock._isBusy) { + lock.process(); + } else { + this._settingsManager._locks.enqueue(lock); + } } - if (!this._requests.isEmpty()) + if (!this._requests.isEmpty() && !this._isBusy) { this.process(); + } } }, @@ -181,7 +205,8 @@ SettingsLock.prototype = { if (this._settingsManager.hasWritePrivileges) { let req = Services.DOMRequest.createRequest(this._settingsManager._window); if (DEBUG) debug("send: " + JSON.stringify(aSettings)); - this._requests.enqueue({request: req, intent: "set", settings: aSettings}); + let settings = JSON.parse(JSON.stringify(aSettings)); + this._requests.enqueue({request: req, intent: "set", settings: settings}); this.createTransactionAndProcess(); return req; } else { @@ -266,7 +291,7 @@ SettingsManager.prototype = { this._locks.enqueue(lock); this._settingsDB.ensureDB( function() { lock.createTransactionAndProcess(); }, - function() { dump("ensureDB error cb!\n"); }, + function() { dump("Cannot open Settings DB. Trying to open an old version?\n"); }, myGlobal ); this.nextTick(function() { this._open = false; }, lock); return lock; diff --git a/dom/settings/tests/test_settings_basics.html b/dom/settings/tests/test_settings_basics.html index a879fb064f67..b7caac14a48b 100644 --- a/dom/settings/tests/test_settings_basics.html +++ b/dom/settings/tests/test_settings_basics.html @@ -35,11 +35,13 @@ function onFailure() { ok(false, "in on Failure!"); } -var wifi = {"net3g.apn": "internet.mnc012.mcc345.gprs"}; -var wifi2 = {"net3g.apn": "internet.mnc012.mcc345.test"}; +const wifi = {"net3g.apn": "internet.mnc012.mcc345.gprs"}; +const wifi2 = {"net3g.apn": "internet.mnc012.mcc345.test"}; +var wifi3 = {"net3g.apn2": "internet.mnc012.mcc345.test3"}; var wifiEnabled = {"wifi.enabled": true}; var wifiDisabled = {"wifi.enabled": false}; var screenBright = {"screen.brightness": 0.7}; +var screenBright2 = {"screen.brightness": 0.1}; var wifiNetworks0 = { "wifi.networks[0]": { ssid: "myfreenetwork", mac: "01:23:45:67:89:ab", passwd: "secret"}}; var wifiNetworks1 = { "wifi.networks[1]": { ssid: "myfreenetwork2", mac: "01:23:45:67:89:ab", passwd: "secret2"}}; @@ -47,7 +49,8 @@ var combination = { "wifi.enabled": false, "screen.brightness": 0.7, "wifi.networks[0]": { ssid: "myfreenetwork", mac: "01:23:45:67:89:ab", passwd: "secret" }, - "test.test": true + "test.test": true, + "net3g.apn2": "internet.mnc012.mcc345.gprs" } function equals(o1, o2) { @@ -101,14 +104,70 @@ var steps = [ req = lock.clear(); req.onsuccess = function () { ok(true, "Deleted the database"); + next(); + }; + }, + function () { + ok(true, "Setting wifi"); + var lock = mozSettings.createLock(); + req = lock.set(wifi); + req.onsuccess = function () { + ok(true, "set done"); + } + req.onerror = onFailure; + + var lock2 = mozSettings.createLock(); + req2 = lock2.get("net3g.apn"); + req2.onsuccess = function () { + is(Object.keys(req2.result).length, 1, "length 1"); + check(wifi, req2.result); + ok(true, "Get net3g.apn Done"); + next(); + }; + req2.onerror = onFailure; + }, + function () { + ok(true, "Change wifi1"); + var lock = mozSettings.createLock(); + req = lock.set(wifi2); + req.onsuccess = function () { + ok(true, "Set Done"); }; req.onerror = onFailure; - req2 = lock.set(screenBright); + ok(true, "Get changed net3g.apn"); + req2 = lock.get("net3g.apn"); req2.onsuccess = function () { + is(Object.keys(req2.result).length, 1, "length 1"); + check(wifi2, req2.result); + ok(true, "Get net3g.apn Done"); + next(); + }; + req2.onerror = onFailure; + }, + function () { + ok(true, "Set Combination"); + var lock = mozSettings.createLock(); + req3 = lock.set(combination); + req3.onsuccess = function () { ok(true, "set done"); + req4 = lock.get("net3g.apn2"); + req4.onsuccess = function() { + ok(true, "Done"); + check(combination["net3g.apn2"], req4.result["net3g.apn2"]); + next(); + } + } + req3.onerror = onFailure; + }, + function() { + var lock = mozSettings.createLock(); + req4 = lock.get("net3g.apn2"); + req4.onsuccess = function() { + ok(true, "Done"); + check(combination["net3g.apn2"], req4.result["net3g.apn2"]); next(); } - req2.onerror = onFailure; + req4.onerror = onFailure; }, function() { ok(true, "Get unknown key"); @@ -293,7 +352,7 @@ var steps = [ req2.onerror = onFailure; }, function () { - ok(true, "Change wifi"); + ok(true, "Change wifi1"); var lock = mozSettings.createLock(); req = lock.set(wifi2); req.onsuccess = function () { @@ -354,34 +413,34 @@ var steps = [ }; { var lock2 = mozSettings.createLock(); - req = lock2.get("*"); - req.onsuccess = function () { - is(Object.keys(req.result).length, 32, "length 12"); - ok(true, JSON.stringify(req.result)); + req2 = lock2.get("*"); + req2.onsuccess = function () { + is(Object.keys(req2.result).length, 32, "length 12"); + ok(true, JSON.stringify(req2.result)); ok(true, "Get all settings Done"); }; - req.onerror = onFailure; + req2.onerror = onFailure; } var lock2 = mozSettings.createLock(); var obj = {}; obj["wifi.enabled" + 30] = true; - req2 = lock2.set( obj ); - req2.onsuccess = function () { + req3 = lock2.set( obj ); + req3.onsuccess = function () { ok(true, "Set12 Done"); }; - req2.onerror = onFailure; + req3.onerror = onFailure; var lock3 = mozSettings.createLock(); // with one lock for (var i = 0; i < 30; i++) { - req3 = lock3.get("wifi.enabled" + i); + req4 = lock3.get("wifi.enabled" + i); var testObj = {}; testObj["wifi.enabled" + i] = true; - req3.onsuccess = function () { + req4.onsuccess = function () { check(this.request.result, this.testObj); ok(true, "Get1 Done"); - }.bind({testObj: testObj, request: req3}); - req3.onerror = onFailure; + }.bind({testObj: testObj, request: req4}); + req4.onerror = onFailure; } ok(true, "start next2!"); @@ -483,7 +542,7 @@ var steps = [ req.onerror = onFailure; }, function () { - ok(true, "Change wifi"); + ok(true, "Change wifi2"); var lock = mozSettings.createLock(); req = lock.set(wifi2); req.onsuccess = function () {