dom/settings/SettingsDB.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/settings/SettingsDB.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,205 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +let Cc = Components.classes;
    1.11 +let Ci = Components.interfaces;
    1.12 +let Cu = Components.utils;
    1.13 +
    1.14 +Cu.import("resource://gre/modules/Services.jsm");
    1.15 +Cu.import("resource://gre/modules/ObjectWrapper.jsm");
    1.16 +
    1.17 +this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"];
    1.18 +
    1.19 +const DEBUG = false;
    1.20 +function debug(s) {
    1.21 +  if (DEBUG) dump("-*- SettingsDB: " + s + "\n");
    1.22 +}
    1.23 +
    1.24 +this.SETTINGSDB_NAME = "settings";
    1.25 +this.SETTINGSDB_VERSION = 3;
    1.26 +this.SETTINGSSTORE_NAME = "settings";
    1.27 +
    1.28 +Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
    1.29 +Cu.import("resource://gre/modules/FileUtils.jsm");
    1.30 +Cu.import("resource://gre/modules/NetUtil.jsm");
    1.31 +
    1.32 +this.SettingsDB = function SettingsDB() {}
    1.33 +
    1.34 +SettingsDB.prototype = {
    1.35 +
    1.36 +  __proto__: IndexedDBHelper.prototype,
    1.37 +
    1.38 +  upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
    1.39 +    let objectStore;
    1.40 +    if (aOldVersion == 0) {
    1.41 +      objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" });
    1.42 +      if (DEBUG) debug("Created object stores");
    1.43 +    } else if (aOldVersion == 1) {
    1.44 +      if (DEBUG) debug("Get object store for upgrade and remove old index");
    1.45 +      objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
    1.46 +      objectStore.deleteIndex("settingValue");
    1.47 +    } else {
    1.48 +      if (DEBUG) debug("Get object store for upgrade");
    1.49 +      objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
    1.50 +    }
    1.51 +
    1.52 +    // Loading resource://app/defaults/settings.json doesn't work because
    1.53 +    // settings.json is not in the omnijar.
    1.54 +    // So we look for the app dir instead and go from here...
    1.55 +    let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false);
    1.56 +    if (!settingsFile || (settingsFile && !settingsFile.exists())) {
    1.57 +      // On b2g desktop builds the settings.json file is moved in the
    1.58 +      // profile directory by the build system.
    1.59 +      settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false);
    1.60 +      if (!settingsFile || (settingsFile && !settingsFile.exists())) {
    1.61 +        return;
    1.62 +      }
    1.63 +    }
    1.64 +
    1.65 +    let chan = NetUtil.newChannel(settingsFile);
    1.66 +    let stream = chan.open();
    1.67 +    // Obtain a converter to read from a UTF-8 encoded input stream.
    1.68 +    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
    1.69 +                    .createInstance(Ci.nsIScriptableUnicodeConverter);
    1.70 +    converter.charset = "UTF-8";
    1.71 +    let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString(
    1.72 +                                            stream,
    1.73 +                                            stream.available()) || "");
    1.74 +    let settings;
    1.75 +    try {
    1.76 +      settings = JSON.parse(rawstr);
    1.77 +    } catch(e) {
    1.78 +      if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e);
    1.79 +      return;
    1.80 +    }
    1.81 +    stream.close();
    1.82 +
    1.83 +    objectStore.openCursor().onsuccess = function(event) {
    1.84 +      let cursor = event.target.result;
    1.85 +      if (cursor) {
    1.86 +        let value = cursor.value;
    1.87 +        if (value.settingName in settings) {
    1.88 +          if (DEBUG) debug("Upgrade " +settings[value.settingName]);
    1.89 +          value.defaultValue = this.prepareValue(settings[value.settingName]);
    1.90 +          delete settings[value.settingName];
    1.91 +          if ("settingValue" in value) {
    1.92 +            value.userValue = this.prepareValue(value.settingValue);
    1.93 +            delete value.settingValue;
    1.94 +          }
    1.95 +          cursor.update(value);
    1.96 +        } else if ("userValue" in value || "settingValue" in value) {
    1.97 +          value.defaultValue = undefined;
    1.98 +          if (aOldVersion == 1 && value.settingValue) {
    1.99 +            value.userValue = this.prepareValue(value.settingValue);
   1.100 +            delete value.settingValue;
   1.101 +          }
   1.102 +          cursor.update(value);
   1.103 +        } else {
   1.104 +          cursor.delete();
   1.105 +        }
   1.106 +        cursor.continue();
   1.107 +      } else {
   1.108 +        for (let name in settings) {
   1.109 +          let value = this.prepareValue(settings[name]);
   1.110 +          if (DEBUG) debug("Set new:" + name +", " + value);
   1.111 +          objectStore.add({ settingName: name, defaultValue: value, userValue: undefined });
   1.112 +        }
   1.113 +      }
   1.114 +    }.bind(this);
   1.115 +  },
   1.116 +
   1.117 +  // If the value is a data: uri, convert it to a Blob.
   1.118 +  convertDataURIToBlob: function(aValue) {
   1.119 +    /* base64 to ArrayBuffer decoding, from
   1.120 +       https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
   1.121 +    */
   1.122 +    function b64ToUint6 (nChr) {
   1.123 +      return nChr > 64 && nChr < 91 ?
   1.124 +          nChr - 65
   1.125 +        : nChr > 96 && nChr < 123 ?
   1.126 +          nChr - 71
   1.127 +        : nChr > 47 && nChr < 58 ?
   1.128 +          nChr + 4
   1.129 +        : nChr === 43 ?
   1.130 +          62
   1.131 +        : nChr === 47 ?
   1.132 +          63
   1.133 +        :
   1.134 +          0;
   1.135 +    }
   1.136 +
   1.137 +    function base64DecToArr(sBase64, nBlocksSize) {
   1.138 +      let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
   1.139 +          nInLen = sB64Enc.length,
   1.140 +          nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize
   1.141 +                                : nInLen * 3 + 1 >> 2,
   1.142 +          taBytes = new Uint8Array(nOutLen);
   1.143 +
   1.144 +      for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
   1.145 +        nMod4 = nInIdx & 3;
   1.146 +        nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
   1.147 +        if (nMod4 === 3 || nInLen - nInIdx === 1) {
   1.148 +          for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
   1.149 +            taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
   1.150 +          }
   1.151 +          nUint24 = 0;
   1.152 +        }
   1.153 +      }
   1.154 +      return taBytes;
   1.155 +    }
   1.156 +
   1.157 +    // Check if we have a data: uri, and if it's base64 encoded.
   1.158 +    // data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...
   1.159 +    if (typeof aValue == "string" && aValue.startsWith("data:")) {
   1.160 +      try {
   1.161 +        let uri = Services.io.newURI(aValue, null, null);
   1.162 +        // XXX: that would be nice to reuse the c++ bits of the data:
   1.163 +        // protocol handler instead.
   1.164 +        let mimeType = "application/octet-stream";
   1.165 +        let mimeDelim = aValue.indexOf(";");
   1.166 +        if (mimeDelim !== -1) {
   1.167 +          mimeType = aValue.substring(5, mimeDelim);
   1.168 +        }
   1.169 +        let start = aValue.indexOf(",") + 1;
   1.170 +        let isBase64 = ((aValue.indexOf("base64") + 7) == start);
   1.171 +        let payload = aValue.substring(start);
   1.172 +
   1.173 +        return new Blob([isBase64 ? base64DecToArr(payload) : payload],
   1.174 +                        { type: mimeType });
   1.175 +      } catch(e) {
   1.176 +        dump(e);
   1.177 +      }
   1.178 +    }
   1.179 +    return aValue
   1.180 +  },
   1.181 +
   1.182 +  // Makes sure any property that is a data: uri gets converted to a Blob.
   1.183 +  prepareValue: function(aObject) {
   1.184 +    let kind = ObjectWrapper.getObjectKind(aObject);
   1.185 +    if (kind == "array") {
   1.186 +      let res = [];
   1.187 +      aObject.forEach(function(aObj) {
   1.188 +        res.push(this.prepareValue(aObj));
   1.189 +      }, this);
   1.190 +      return res;
   1.191 +    } else if (kind == "file" || kind == "blob" || kind == "date") {
   1.192 +      return aObject;
   1.193 +    } else if (kind == "primitive") {
   1.194 +      return this.convertDataURIToBlob(aObject);
   1.195 +    }
   1.196 +
   1.197 +    // Fall-through, we now have a dictionary object.
   1.198 +    for (let prop in aObject) {
   1.199 +      aObject[prop] = this.prepareValue(aObject[prop]);
   1.200 +    }
   1.201 +    return aObject;
   1.202 +  },
   1.203 +
   1.204 +  init: function init() {
   1.205 +    this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION,
   1.206 +                      [SETTINGSSTORE_NAME]);
   1.207 +  }
   1.208 +}

mercurial