/******************************************************************************* ηMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors Copyright (C) 2019-2020 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://libregit.org/heckyel/ematrix uMatrix Home: https://github.com/gorhill/uMatrix */ 'use strict'; /******************************************************************************/ (function () { // 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; })();