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: let Cc = Components.classes; michael@0: let Ci = Components.interfaces; michael@0: let Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/ObjectWrapper.jsm"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"]; michael@0: michael@0: const DEBUG = false; michael@0: function debug(s) { michael@0: if (DEBUG) dump("-*- SettingsDB: " + s + "\n"); michael@0: } michael@0: michael@0: this.SETTINGSDB_NAME = "settings"; michael@0: this.SETTINGSDB_VERSION = 3; michael@0: this.SETTINGSSTORE_NAME = "settings"; michael@0: michael@0: Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: this.SettingsDB = function SettingsDB() {} michael@0: michael@0: SettingsDB.prototype = { michael@0: michael@0: __proto__: IndexedDBHelper.prototype, michael@0: michael@0: upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) { michael@0: let objectStore; michael@0: if (aOldVersion == 0) { michael@0: objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" }); michael@0: if (DEBUG) debug("Created object stores"); michael@0: } else if (aOldVersion == 1) { michael@0: if (DEBUG) debug("Get object store for upgrade and remove old index"); michael@0: objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME); michael@0: objectStore.deleteIndex("settingValue"); michael@0: } else { michael@0: if (DEBUG) debug("Get object store for upgrade"); michael@0: objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME); michael@0: } michael@0: michael@0: // Loading resource://app/defaults/settings.json doesn't work because michael@0: // settings.json is not in the omnijar. michael@0: // So we look for the app dir instead and go from here... michael@0: let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false); michael@0: if (!settingsFile || (settingsFile && !settingsFile.exists())) { michael@0: // On b2g desktop builds the settings.json file is moved in the michael@0: // profile directory by the build system. michael@0: settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false); michael@0: if (!settingsFile || (settingsFile && !settingsFile.exists())) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: let chan = NetUtil.newChannel(settingsFile); michael@0: let stream = chan.open(); michael@0: // Obtain a converter to read from a UTF-8 encoded input stream. michael@0: let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString( michael@0: stream, michael@0: stream.available()) || ""); michael@0: let settings; michael@0: try { michael@0: settings = JSON.parse(rawstr); michael@0: } catch(e) { michael@0: if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e); michael@0: return; michael@0: } michael@0: stream.close(); michael@0: michael@0: objectStore.openCursor().onsuccess = function(event) { michael@0: let cursor = event.target.result; michael@0: if (cursor) { michael@0: let value = cursor.value; michael@0: if (value.settingName in settings) { michael@0: if (DEBUG) debug("Upgrade " +settings[value.settingName]); michael@0: value.defaultValue = this.prepareValue(settings[value.settingName]); michael@0: delete settings[value.settingName]; michael@0: if ("settingValue" in value) { michael@0: value.userValue = this.prepareValue(value.settingValue); michael@0: delete value.settingValue; michael@0: } michael@0: cursor.update(value); michael@0: } else if ("userValue" in value || "settingValue" in value) { michael@0: value.defaultValue = undefined; michael@0: if (aOldVersion == 1 && value.settingValue) { michael@0: value.userValue = this.prepareValue(value.settingValue); michael@0: delete value.settingValue; michael@0: } michael@0: cursor.update(value); michael@0: } else { michael@0: cursor.delete(); michael@0: } michael@0: cursor.continue(); michael@0: } else { michael@0: for (let name in settings) { michael@0: let value = this.prepareValue(settings[name]); michael@0: if (DEBUG) debug("Set new:" + name +", " + value); michael@0: objectStore.add({ settingName: name, defaultValue: value, userValue: undefined }); michael@0: } michael@0: } michael@0: }.bind(this); michael@0: }, michael@0: michael@0: // If the value is a data: uri, convert it to a Blob. michael@0: convertDataURIToBlob: function(aValue) { michael@0: /* base64 to ArrayBuffer decoding, from michael@0: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding michael@0: */ michael@0: function b64ToUint6 (nChr) { michael@0: return nChr > 64 && nChr < 91 ? michael@0: nChr - 65 michael@0: : nChr > 96 && nChr < 123 ? michael@0: nChr - 71 michael@0: : nChr > 47 && nChr < 58 ? michael@0: nChr + 4 michael@0: : nChr === 43 ? michael@0: 62 michael@0: : nChr === 47 ? michael@0: 63 michael@0: : michael@0: 0; michael@0: } michael@0: michael@0: function base64DecToArr(sBase64, nBlocksSize) { michael@0: let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), michael@0: nInLen = sB64Enc.length, michael@0: nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize michael@0: : nInLen * 3 + 1 >> 2, michael@0: taBytes = new Uint8Array(nOutLen); michael@0: michael@0: for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { michael@0: nMod4 = nInIdx & 3; michael@0: nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; michael@0: if (nMod4 === 3 || nInLen - nInIdx === 1) { michael@0: for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { michael@0: taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; michael@0: } michael@0: nUint24 = 0; michael@0: } michael@0: } michael@0: return taBytes; michael@0: } michael@0: michael@0: // Check if we have a data: uri, and if it's base64 encoded. michael@0: // data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA... michael@0: if (typeof aValue == "string" && aValue.startsWith("data:")) { michael@0: try { michael@0: let uri = Services.io.newURI(aValue, null, null); michael@0: // XXX: that would be nice to reuse the c++ bits of the data: michael@0: // protocol handler instead. michael@0: let mimeType = "application/octet-stream"; michael@0: let mimeDelim = aValue.indexOf(";"); michael@0: if (mimeDelim !== -1) { michael@0: mimeType = aValue.substring(5, mimeDelim); michael@0: } michael@0: let start = aValue.indexOf(",") + 1; michael@0: let isBase64 = ((aValue.indexOf("base64") + 7) == start); michael@0: let payload = aValue.substring(start); michael@0: michael@0: return new Blob([isBase64 ? base64DecToArr(payload) : payload], michael@0: { type: mimeType }); michael@0: } catch(e) { michael@0: dump(e); michael@0: } michael@0: } michael@0: return aValue michael@0: }, michael@0: michael@0: // Makes sure any property that is a data: uri gets converted to a Blob. michael@0: prepareValue: function(aObject) { michael@0: let kind = ObjectWrapper.getObjectKind(aObject); michael@0: if (kind == "array") { michael@0: let res = []; michael@0: aObject.forEach(function(aObj) { michael@0: res.push(this.prepareValue(aObj)); michael@0: }, this); michael@0: return res; michael@0: } else if (kind == "file" || kind == "blob" || kind == "date") { michael@0: return aObject; michael@0: } else if (kind == "primitive") { michael@0: return this.convertDataURIToBlob(aObject); michael@0: } michael@0: michael@0: // Fall-through, we now have a dictionary object. michael@0: for (let prop in aObject) { michael@0: aObject[prop] = this.prepareValue(aObject[prop]); michael@0: } michael@0: return aObject; michael@0: }, michael@0: michael@0: init: function init() { michael@0: this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION, michael@0: [SETTINGSSTORE_NAME]); michael@0: } michael@0: }