michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict" michael@0: michael@0: /* static functions */ michael@0: let DEBUG = 0; michael@0: let debug; michael@0: if (DEBUG) michael@0: debug = function (s) { dump("-*- SettingsService: " + s + "\n"); } michael@0: else michael@0: debug = function (s) {} michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/SettingsQueue.jsm"); michael@0: Cu.import("resource://gre/modules/SettingsDB.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: const nsIClassInfo = Ci.nsIClassInfo; michael@0: michael@0: const SETTINGSSERVICELOCK_CONTRACTID = "@mozilla.org/settingsServiceLock;1"; michael@0: const SETTINGSSERVICELOCK_CID = Components.ID("{d7a395a0-e292-11e1-834e-1761d57f5f99}"); michael@0: const nsISettingsServiceLock = Ci.nsISettingsServiceLock; michael@0: michael@0: function SettingsServiceLock(aSettingsService) michael@0: { michael@0: if (DEBUG) debug("settingsServiceLock constr!"); michael@0: this._open = true; michael@0: this._busy = false; michael@0: this._requests = new Queue(); michael@0: this._settingsService = aSettingsService; michael@0: this._transaction = null; michael@0: } michael@0: michael@0: SettingsServiceLock.prototype = { michael@0: michael@0: callHandle: function callHandle(aCallback, aName, aValue) { michael@0: try { michael@0: aCallback ? aCallback.handle(aName, aValue) : null; michael@0: } catch (e) { michael@0: dump("settings 'handle' callback threw an exception, dropping: " + e + "\n"); michael@0: } michael@0: }, michael@0: michael@0: callAbort: function callAbort(aCallback, aMessage) { michael@0: try { michael@0: aCallback ? aCallback.handleAbort(aMessage) : null; michael@0: } catch (e) { michael@0: dump("settings 'abort' callback threw an exception, dropping: " + e + "\n"); michael@0: } michael@0: }, michael@0: michael@0: callError: function callError(aCallback, aMessage) { michael@0: try { michael@0: aCallback ? aCallback.handleError(aMessage) : null; michael@0: } catch (e) { michael@0: dump("settings 'error' callback threw an exception, dropping: " + e + "\n"); michael@0: } michael@0: }, michael@0: michael@0: process: function process() { michael@0: debug("process!"); michael@0: let lock = this; michael@0: lock._open = false; michael@0: let store = lock._transaction.objectStore(SETTINGSSTORE_NAME); michael@0: michael@0: while (!lock._requests.isEmpty()) { michael@0: if (lock._isBusy) { michael@0: return; michael@0: } michael@0: let info = lock._requests.dequeue(); michael@0: if (DEBUG) debug("info:" + info.intent); michael@0: let callback = info.callback; michael@0: let name = info.name; michael@0: switch (info.intent) { michael@0: case "set": michael@0: let value = info.value; michael@0: let message = info.message; michael@0: if(DEBUG && typeof(value) == 'object') { michael@0: debug("object name:" + name + ", val: " + JSON.stringify(value)); michael@0: } michael@0: lock._isBusy = true; michael@0: let checkKeyRequest = store.get(name); michael@0: michael@0: checkKeyRequest.onsuccess = function (event) { michael@0: let defaultValue; michael@0: if (event.target.result) { michael@0: defaultValue = event.target.result.defaultValue; michael@0: } else { michael@0: defaultValue = null; michael@0: if (DEBUG) debug("MOZSETTINGS-SET-WARNING: " + name + " is not in the database.\n"); michael@0: } michael@0: let setReq = store.put({ settingName: name, defaultValue: defaultValue, userValue: value }); michael@0: michael@0: setReq.onsuccess = function() { michael@0: lock._isBusy = false; michael@0: lock._open = true; michael@0: lock.callHandle(callback, name, value); michael@0: Services.obs.notifyObservers(lock, "mozsettings-changed", JSON.stringify({ michael@0: key: name, michael@0: value: value, michael@0: message: message michael@0: })); michael@0: lock._open = false; michael@0: lock.process(); michael@0: }; michael@0: michael@0: setReq.onerror = function(event) { michael@0: lock._isBusy = false; michael@0: lock.callError(callback, event.target.errorMessage); michael@0: lock.process(); michael@0: }; michael@0: } michael@0: michael@0: checkKeyRequest.onerror = function(event) { michael@0: lock._isBusy = false; michael@0: lock.callError(callback, event.target.errorMessage); michael@0: lock.process(); michael@0: }; michael@0: break; michael@0: case "get": michael@0: let getReq = store.mozGetAll(name); michael@0: getReq.onsuccess = function(event) { michael@0: if (DEBUG) { michael@0: debug("Request successful. Record count:" + event.target.result.length); michael@0: debug("result: " + JSON.stringify(event.target.result)); michael@0: } michael@0: this._open = true; michael@0: if (callback) { michael@0: if (event.target.result[0]) { michael@0: if (event.target.result.length > 1) { michael@0: if (DEBUG) debug("Warning: overloaded setting:" + name); michael@0: } michael@0: let result = event.target.result[0]; michael@0: let value = result.userValue !== undefined michael@0: ? result.userValue michael@0: : result.defaultValue; michael@0: lock.callHandle(callback, name, value); michael@0: } else { michael@0: lock.callHandle(callback, name, null); michael@0: } michael@0: } else { michael@0: if (DEBUG) debug("no callback defined!"); michael@0: } michael@0: this._open = false; michael@0: }.bind(lock); michael@0: getReq.onerror = function error(event) { michael@0: lock.callError(callback, event.target.errorMessage); michael@0: }; michael@0: break; michael@0: } michael@0: } michael@0: lock._open = true; michael@0: }, michael@0: michael@0: createTransactionAndProcess: function(aCallback) { michael@0: if (this._settingsService._settingsDB._db) { michael@0: let lock; michael@0: while (lock = this._settingsService._locks.dequeue()) { michael@0: if (!lock._transaction) { michael@0: lock._transaction = lock._settingsService._settingsDB._db.transaction(SETTINGSSTORE_NAME, "readwrite"); michael@0: if (aCallback) { michael@0: lock._transaction.oncomplete = aCallback.handle; michael@0: lock._transaction.onabort = function(event) { michael@0: let message = ''; michael@0: if (event.target.error) { michael@0: message = event.target.error.name + ': ' + event.target.error.message; michael@0: } michael@0: this.callAbort(aCallback, message); michael@0: }; michael@0: } michael@0: } michael@0: if (!lock._isBusy) { michael@0: lock.process(); michael@0: } else { michael@0: this._settingsService._locks.enqueue(lock); michael@0: return; michael@0: } michael@0: } michael@0: if (!this._requests.isEmpty() && !this._isBusy) { michael@0: this.process(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: get: function get(aName, aCallback) { michael@0: if (DEBUG) debug("get: " + aName + ", " + aCallback); michael@0: this._requests.enqueue({ callback: aCallback, intent:"get", name: aName }); michael@0: this.createTransactionAndProcess(); michael@0: }, michael@0: michael@0: set: function set(aName, aValue, aCallback, aMessage) { michael@0: debug("set: " + aName + ": " + JSON.stringify(aValue)); michael@0: if (aMessage === undefined) michael@0: aMessage = null; michael@0: this._requests.enqueue({ callback: aCallback, michael@0: intent: "set", michael@0: name: aName, michael@0: value: this._settingsService._settingsDB.prepareValue(aValue), michael@0: message: aMessage }); michael@0: this.createTransactionAndProcess(); michael@0: }, michael@0: michael@0: classID : SETTINGSSERVICELOCK_CID, michael@0: QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock]) michael@0: }; michael@0: michael@0: const SETTINGSSERVICE_CID = Components.ID("{f656f0c0-f776-11e1-a21f-0800200c9a66}"); michael@0: michael@0: function SettingsService() michael@0: { michael@0: debug("settingsService Constructor"); michael@0: this._locks = new Queue(); michael@0: this._settingsDB = new SettingsDB(); michael@0: this._settingsDB.init(); michael@0: } michael@0: michael@0: SettingsService.prototype = { michael@0: michael@0: nextTick: function nextTick(aCallback, thisObj) { michael@0: if (thisObj) michael@0: aCallback = aCallback.bind(thisObj); michael@0: michael@0: Services.tm.currentThread.dispatch(aCallback, Ci.nsIThread.DISPATCH_NORMAL); michael@0: }, michael@0: michael@0: createLock: function createLock(aCallback) { michael@0: var lock = new SettingsServiceLock(this); michael@0: this._locks.enqueue(lock); michael@0: this._settingsDB.ensureDB( michael@0: function() { lock.createTransactionAndProcess(aCallback); }, michael@0: function() { dump("SettingsService failed to open DB!\n"); } michael@0: ); michael@0: this.nextTick(function() { this._open = false; }, lock); michael@0: return lock; michael@0: }, michael@0: michael@0: classID : SETTINGSSERVICE_CID, michael@0: QueryInterface : XPCOMUtils.generateQI([Ci.nsISettingsService]) michael@0: } michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsService, SettingsServiceLock])