aboutsummaryrefslogtreecommitdiffstats
path: root/js/vapi-storage.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/vapi-storage.js')
-rw-r--r--js/vapi-storage.js339
1 files changed, 339 insertions, 0 deletions
diff --git a/js/vapi-storage.js b/js/vapi-storage.js
new file mode 100644
index 0000000..768958c
--- /dev/null
+++ b/js/vapi-storage.js
@@ -0,0 +1,339 @@
+/*******************************************************************************
+
+ ηMatrix - a browser extension to black/white list requests.
+ Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
+ Copyright (C) 2019 Alessio Vanni
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://gitlab.com/vannilla/ematrix
+ uMatrix Home: https://github.com/gorhill/uMatrix
+*/
+
+/* global self, Components */
+
+// For background page (tabs management)
+
+'use strict';
+
+/******************************************************************************/
+
+(function () {
+ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+ const {Services} = Cu.import('resource://gre/modules/Services.jsm', null);
+
+ let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js
+
+ // API matches that of chrome.storage.local:
+ // https://developer.chrome.com/extensions/storage
+ vAPI.storage = (function () {
+ let db = null;
+ let vacuumTimer = null;
+
+ let close = function () {
+ if (vacuumTimer !== null) {
+ clearTimeout(vacuumTimer);
+ vacuumTimer = null;
+ }
+
+ if (db === null) {
+ return;
+ }
+
+ db.asyncClose();
+ db = null;
+ };
+
+ let open = function () {
+ if (db !== null) {
+ return db;
+ }
+
+ // Create path
+ let path = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ path.append('ematrix-data');
+ if (!path.exists()) {
+ path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8));
+ }
+ if (!path.isDirectory()) {
+ throw Error('Should be a directory...');
+ }
+
+ let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ path2.append('extension-data');
+ path2.append(location.host + '.sqlite');
+ if (path2.exists()) {
+ path2.moveTo(path, location.host+'.sqlite');
+ }
+
+ path.append(location.host + '.sqlite');
+
+ // Open database
+ try {
+ db = Services.storage.openDatabase(path);
+ if (db.connectionReady === false) {
+ db.asyncClose();
+ db = null;
+ }
+ } catch (ex) {
+ // Ignore
+ }
+
+ if (db === null) {
+ return null;
+ }
+
+ // Database was opened, register cleanup task
+ vAPI.addCleanUpTask(close);
+
+ // Setup database
+ db.createAsyncStatement('CREATE TABLE IF NOT EXISTS '
+ +'"settings" ("name" '
+ +'TEXT PRIMARY KEY NOT NULL, '
+ +'"value" TEXT);')
+ .executeAsync();
+
+ if (vacuum !== null) {
+ vacuumTimer = vAPI.setTimeout(vacuum, 60000);
+ }
+
+ return db;
+ };
+
+ // Vacuum only once, and only while idle
+ let vacuum = function () {
+ vacuumTimer = null;
+ if (db === null) {
+ return;
+ }
+ let idleSvc =
+ Cc['@mozilla.org/widget/idleservice;1']
+ .getService(Ci.nsIIdleService);
+
+ if (idleSvc.idleTime < 60000) {
+ vacuumTimer = vAPI.setTimeout(vacuum, 60000);
+ return;
+ }
+
+ db.createAsyncStatement('VACUUM').executeAsync();
+ vacuum = null;
+ };
+
+ // Execute a query
+ let runStatement = function (stmt, callback) {
+ let result = {};
+
+ stmt.executeAsync({
+ handleResult: function (rows) {
+ if (!rows || typeof callback !== 'function') {
+ return;
+ }
+
+ let row;
+ while ((row = rows.getNextRow())) {
+ // we assume that there will be two columns, since we're
+ // using it only for preferences
+ // eMatrix: the above comment is obsolete
+ // (it's not used just for preferences
+ // anymore), but we still expect two columns.
+ let res = row.getResultByIndex(0);
+ result[res] = row.getResultByIndex(1);
+ }
+ },
+ handleCompletion: function (reason) {
+ if (typeof callback === 'function' && reason === 0) {
+ callback(result);
+ }
+ },
+ handleError: function (error) {
+ console.error('SQLite error ', error.result, error.message);
+
+ // Caller expects an answer regardless of failure.
+ if (typeof callback === 'function' ) {
+ callback(null);
+ }
+ },
+ });
+ };
+
+ let bindNames = function (stmt, names) {
+ if (Array.isArray(names) === false || names.length === 0) {
+ return;
+ }
+
+ let params = stmt.newBindingParamsArray();
+
+ for (let i=names.length-1; i>=0; --i) {
+ let bp = params.newBindingParams();
+ bp.bindByName('name', names[i]);
+ params.addParams(bp);
+ }
+
+ stmt.bindParameters(params);
+ };
+
+ let clear = function (callback) {
+ if (open() === null) {
+ if (typeof callback === 'function') {
+ callback();
+ }
+ return;
+ }
+
+ runStatement(db.createAsyncStatement('DELETE FROM "settings";'),
+ callback);
+ };
+
+ let getBytesInUse = function (keys, callback) {
+ if (typeof callback !== 'function') {
+ return;
+ }
+
+ if (open() === null) {
+ callback(0);
+ return;
+ }
+
+ let stmt;
+ if (Array.isArray(keys)) {
+ stmt = db.createAsyncStatement('SELECT "size" AS "size", '
+ +'SUM(LENGTH("value")) '
+ +'FROM "settings" WHERE '
+ +'"name" = :name');
+ bindNames(keys);
+ } else {
+ stmt = db.createAsyncStatement('SELECT "size" AS "size", '
+ +'SUM(LENGTH("value")) '
+ +'FROM "settings"');
+ }
+
+ runStatement(stmt, function (result) {
+ callback(result.size);
+ });
+ };
+
+ let read = function (details, callback) {
+ if (typeof callback !== 'function') {
+ return;
+ }
+
+ let prepareResult = function (result) {
+ for (let key in result) {
+ if (result.hasOwnProperty(key) === false) {
+ continue;
+ }
+
+ result[key] = JSON.parse(result[key]);
+ }
+
+ if (typeof details === 'object' && details !== null) {
+ for (let key in details) {
+ if (result.hasOwnProperty(key) === false) {
+ result[key] = details[key];
+ }
+ }
+ }
+
+ callback(result);
+ };
+
+ if (open() === null) {
+ prepareResult({});
+ return;
+ }
+
+ let names = [];
+ if (details !== null) {
+ if (Array.isArray(details)) {
+ names = details;
+ } else if (typeof details === 'object') {
+ names = Object.keys(details);
+ } else {
+ names = [details.toString()];
+ }
+ }
+
+ let stmt;
+ if (names.length === 0) {
+ stmt = db.createAsyncStatement('SELECT * FROM "settings"');
+ } else {
+ stmt = db.createAsyncStatement('SELECT * FROM "settings" '
+ +'WHERE "name" = :name');
+ bindNames(stmt, names);
+ }
+
+ runStatement(stmt, prepareResult);
+ };
+
+ let remove = function (keys, callback) {
+ if (open() === null) {
+ if (typeof callback === 'function') {
+ callback();
+ }
+ return;
+ }
+
+ var stmt = db.createAsyncStatement('DELETE FROM "settings" '
+ +'WHERE "name" = :name');
+ bindNames(stmt, typeof keys === 'string' ? [keys] : keys);
+ runStatement(stmt, callback);
+ };
+
+ let write = function (details, callback) {
+ if (open() === null) {
+ if (typeof callback === 'function') {
+ callback();
+ }
+ return;
+ }
+
+ let stmt = db.createAsyncStatement('INSERT OR REPLACE INTO '
+ +'"settings" ("name", "value") '
+ +'VALUES(:name, :value)');
+ let params = stmt.newBindingParamsArray();
+
+ for (let key in details) {
+ if (details.hasOwnProperty(key) === false) {
+ continue;
+ }
+
+ let bp = params.newBindingParams();
+ bp.bindByName('name', key);
+ bp.bindByName('value', JSON.stringify(details[key]));
+ params.addParams(bp);
+ }
+
+ if (params.length === 0) {
+ return;
+ }
+
+ stmt.bindParameters(params);
+ runStatement(stmt, callback);
+ };
+
+ // Export API
+ var api = {
+ QUOTA_BYTES: 100 * 1024 * 1024,
+ clear: clear,
+ get: read,
+ getBytesInUse: getBytesInUse,
+ remove: remove,
+ set: write
+ };
+
+ return api;
+ })();
+
+ vAPI.cacheStorage = vAPI.storage;
+})();