2012-04-27 16:21:31 -07:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* 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/. */
|
|
|
|
|
2013-01-04 16:04:28 -08:00
|
|
|
"use strict";
|
|
|
|
|
2012-10-06 22:15:25 -07:00
|
|
|
let Cc = Components.classes;
|
|
|
|
let Ci = Components.interfaces;
|
|
|
|
let Cu = Components.utils;
|
|
|
|
|
2014-10-08 09:15:22 -07:00
|
|
|
Cu.importGlobalProperties(['Blob']);
|
2013-08-11 08:41:59 -07:00
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"];
|
2012-04-27 16:21:31 -07:00
|
|
|
|
2013-01-07 01:18:05 -08:00
|
|
|
const DEBUG = false;
|
2012-10-06 22:15:25 -07:00
|
|
|
function debug(s) {
|
2012-12-14 10:20:16 -08:00
|
|
|
if (DEBUG) dump("-*- SettingsDB: " + s + "\n");
|
2012-04-27 16:21:31 -07:00
|
|
|
}
|
|
|
|
|
2014-06-10 08:15:42 -07:00
|
|
|
const TYPED_ARRAY_THINGS = new Set([
|
|
|
|
"Int8Array",
|
|
|
|
"Uint8Array",
|
|
|
|
"Uint8ClampedArray",
|
|
|
|
"Int16Array",
|
|
|
|
"Uint16Array",
|
|
|
|
"Int32Array",
|
|
|
|
"Uint32Array",
|
|
|
|
"Float32Array",
|
|
|
|
"Float64Array",
|
|
|
|
]);
|
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.SETTINGSDB_NAME = "settings";
|
2014-09-02 04:40:00 -07:00
|
|
|
this.SETTINGSDB_VERSION = 5;
|
2012-10-31 09:13:28 -07:00
|
|
|
this.SETTINGSSTORE_NAME = "settings";
|
2012-04-27 16:21:31 -07:00
|
|
|
|
2012-10-06 22:15:25 -07:00
|
|
|
Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
|
|
|
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
2012-04-27 16:21:31 -07:00
|
|
|
|
2012-10-31 09:13:28 -07:00
|
|
|
this.SettingsDB = function SettingsDB() {}
|
2012-04-27 16:21:31 -07:00
|
|
|
|
|
|
|
SettingsDB.prototype = {
|
|
|
|
|
|
|
|
__proto__: IndexedDBHelper.prototype,
|
|
|
|
|
2012-05-10 21:51:48 -07:00
|
|
|
upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
|
2013-01-04 16:04:28 -08:00
|
|
|
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);
|
2012-10-06 22:15:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Loading resource://app/defaults/settings.json doesn't work because
|
|
|
|
// settings.json is not in the omnijar.
|
|
|
|
// So we look for the app dir instead and go from here...
|
|
|
|
let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false);
|
|
|
|
if (!settingsFile || (settingsFile && !settingsFile.exists())) {
|
|
|
|
// On b2g desktop builds the settings.json file is moved in the
|
|
|
|
// profile directory by the build system.
|
|
|
|
settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false);
|
|
|
|
if (!settingsFile || (settingsFile && !settingsFile.exists())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let chan = NetUtil.newChannel(settingsFile);
|
|
|
|
let stream = chan.open();
|
|
|
|
// Obtain a converter to read from a UTF-8 encoded input stream.
|
|
|
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
|
|
converter.charset = "UTF-8";
|
|
|
|
let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString(
|
|
|
|
stream,
|
|
|
|
stream.available()) || "");
|
|
|
|
let settings;
|
|
|
|
try {
|
|
|
|
settings = JSON.parse(rawstr);
|
|
|
|
} catch(e) {
|
2012-12-14 10:20:16 -08:00
|
|
|
if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e);
|
2012-10-06 22:15:25 -07:00
|
|
|
return;
|
|
|
|
}
|
2013-01-04 16:04:28 -08:00
|
|
|
stream.close();
|
2012-10-06 22:15:25 -07:00
|
|
|
|
2013-01-04 16:04:28 -08:00
|
|
|
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]);
|
2013-08-11 08:41:59 -07:00
|
|
|
value.defaultValue = this.prepareValue(settings[value.settingName]);
|
2013-01-04 16:04:28 -08:00
|
|
|
delete settings[value.settingName];
|
|
|
|
if ("settingValue" in value) {
|
2013-08-11 08:41:59 -07:00
|
|
|
value.userValue = this.prepareValue(value.settingValue);
|
2013-01-04 16:04:28 -08:00
|
|
|
delete value.settingValue;
|
|
|
|
}
|
|
|
|
cursor.update(value);
|
|
|
|
} else if ("userValue" in value || "settingValue" in value) {
|
|
|
|
value.defaultValue = undefined;
|
|
|
|
if (aOldVersion == 1 && value.settingValue) {
|
2013-08-11 08:41:59 -07:00
|
|
|
value.userValue = this.prepareValue(value.settingValue);
|
2013-01-04 16:04:28 -08:00
|
|
|
delete value.settingValue;
|
|
|
|
}
|
|
|
|
cursor.update(value);
|
|
|
|
} else {
|
|
|
|
cursor.delete();
|
|
|
|
}
|
|
|
|
cursor.continue();
|
|
|
|
} else {
|
|
|
|
for (let name in settings) {
|
2013-08-11 08:41:59 -07:00
|
|
|
let value = this.prepareValue(settings[name]);
|
|
|
|
if (DEBUG) debug("Set new:" + name +", " + value);
|
|
|
|
objectStore.add({ settingName: name, defaultValue: value, userValue: undefined });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.bind(this);
|
|
|
|
},
|
|
|
|
|
|
|
|
// If the value is a data: uri, convert it to a Blob.
|
|
|
|
convertDataURIToBlob: function(aValue) {
|
|
|
|
/* base64 to ArrayBuffer decoding, from
|
|
|
|
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
|
|
|
|
*/
|
|
|
|
function b64ToUint6 (nChr) {
|
|
|
|
return nChr > 64 && nChr < 91 ?
|
|
|
|
nChr - 65
|
|
|
|
: nChr > 96 && nChr < 123 ?
|
|
|
|
nChr - 71
|
|
|
|
: nChr > 47 && nChr < 58 ?
|
|
|
|
nChr + 4
|
|
|
|
: nChr === 43 ?
|
|
|
|
62
|
|
|
|
: nChr === 47 ?
|
|
|
|
63
|
|
|
|
:
|
|
|
|
0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function base64DecToArr(sBase64, nBlocksSize) {
|
|
|
|
let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
|
|
|
|
nInLen = sB64Enc.length,
|
|
|
|
nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize
|
|
|
|
: nInLen * 3 + 1 >> 2,
|
|
|
|
taBytes = new Uint8Array(nOutLen);
|
|
|
|
|
|
|
|
for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
|
|
|
nMod4 = nInIdx & 3;
|
|
|
|
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
|
|
|
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
|
|
|
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
|
|
|
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
|
|
|
}
|
|
|
|
nUint24 = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return taBytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we have a data: uri, and if it's base64 encoded.
|
|
|
|
// data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...
|
|
|
|
if (typeof aValue == "string" && aValue.startsWith("data:")) {
|
|
|
|
try {
|
|
|
|
let uri = Services.io.newURI(aValue, null, null);
|
|
|
|
// XXX: that would be nice to reuse the c++ bits of the data:
|
|
|
|
// protocol handler instead.
|
|
|
|
let mimeType = "application/octet-stream";
|
|
|
|
let mimeDelim = aValue.indexOf(";");
|
|
|
|
if (mimeDelim !== -1) {
|
|
|
|
mimeType = aValue.substring(5, mimeDelim);
|
2013-01-04 16:04:28 -08:00
|
|
|
}
|
2013-08-11 08:41:59 -07:00
|
|
|
let start = aValue.indexOf(",") + 1;
|
|
|
|
let isBase64 = ((aValue.indexOf("base64") + 7) == start);
|
|
|
|
let payload = aValue.substring(start);
|
|
|
|
|
|
|
|
return new Blob([isBase64 ? base64DecToArr(payload) : payload],
|
|
|
|
{ type: mimeType });
|
|
|
|
} catch(e) {
|
|
|
|
dump(e);
|
2013-01-04 16:04:28 -08:00
|
|
|
}
|
2013-08-11 08:41:59 -07:00
|
|
|
}
|
|
|
|
return aValue
|
|
|
|
},
|
|
|
|
|
2014-06-10 08:15:42 -07:00
|
|
|
getObjectKind: function(aObject) {
|
|
|
|
if (aObject === null || aObject === undefined) {
|
|
|
|
return "primitive";
|
|
|
|
} else if (Array.isArray(aObject)) {
|
|
|
|
return "array";
|
|
|
|
} else if (aObject instanceof Ci.nsIDOMFile) {
|
|
|
|
return "file";
|
|
|
|
} else if (aObject instanceof Ci.nsIDOMBlob) {
|
|
|
|
return "blob";
|
|
|
|
} else if (aObject.constructor.name == "Date") {
|
|
|
|
return "date";
|
|
|
|
} else if (TYPED_ARRAY_THINGS.has(aObject.constructor.name)) {
|
|
|
|
return aObject.constructor.name;
|
|
|
|
} else if (typeof aObject == "object") {
|
|
|
|
return "object";
|
|
|
|
} else {
|
|
|
|
return "primitive";
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-08-11 08:41:59 -07:00
|
|
|
// Makes sure any property that is a data: uri gets converted to a Blob.
|
|
|
|
prepareValue: function(aObject) {
|
2014-06-10 08:15:42 -07:00
|
|
|
let kind = this.getObjectKind(aObject);
|
2013-08-11 08:41:59 -07:00
|
|
|
if (kind == "array") {
|
|
|
|
let res = [];
|
|
|
|
aObject.forEach(function(aObj) {
|
|
|
|
res.push(this.prepareValue(aObj));
|
|
|
|
}, this);
|
|
|
|
return res;
|
|
|
|
} else if (kind == "file" || kind == "blob" || kind == "date") {
|
|
|
|
return aObject;
|
|
|
|
} else if (kind == "primitive") {
|
|
|
|
return this.convertDataURIToBlob(aObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall-through, we now have a dictionary object.
|
2014-05-28 11:14:26 -07:00
|
|
|
let res = {};
|
2013-08-11 08:41:59 -07:00
|
|
|
for (let prop in aObject) {
|
2014-05-28 11:14:26 -07:00
|
|
|
res[prop] = this.prepareValue(aObject[prop]);
|
2013-08-11 08:41:59 -07:00
|
|
|
}
|
2014-05-28 11:14:26 -07:00
|
|
|
return res;
|
2012-04-27 16:21:31 -07:00
|
|
|
},
|
|
|
|
|
2013-09-28 04:25:46 -07:00
|
|
|
init: function init() {
|
2012-10-06 22:15:25 -07:00
|
|
|
this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION,
|
2013-09-28 04:25:46 -07:00
|
|
|
[SETTINGSSTORE_NAME]);
|
2012-04-27 16:21:31 -07:00
|
|
|
}
|
|
|
|
}
|