summaryrefslogtreecommitdiff
path: root/data/extensions/spyblock@gnu.org/lib/io.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/io.js')
-rw-r--r--data/extensions/spyblock@gnu.org/lib/io.js465
1 files changed, 206 insertions, 259 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/io.js b/data/extensions/spyblock@gnu.org/lib/io.js
index 5e60b54..0a22513 100644
--- a/data/extensions/spyblock@gnu.org/lib/io.js
+++ b/data/extensions/spyblock@gnu.org/lib/io.js
@@ -1,6 +1,6 @@
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
- * Copyright (C) 2006-2015 Eyeo GmbH
+ * Copyright (C) 2006-2017 eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -15,318 +15,265 @@
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
-/**
- * @fileOverview Module containing file I/O helpers.
- */
-
-let {Services} = Cu.import("resource://gre/modules/Services.jsm", null);
-let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", null);
-let {OS} = Cu.import("resource://gre/modules/osfile.jsm", null);
-let {Task} = Cu.import("resource://gre/modules/Task.jsm", null);
+"use strict";
-let {Prefs} = require("prefs");
+let {IO: LegacyIO} = require("legacyIO");
let {Utils} = require("utils");
-let firstRead = true;
-const BUFFER_SIZE = 0x80000; // 512kB
+let webextension = require("webextension");
+let messageID = 0;
+let messageCallbacks = new Map();
-let IO = exports.IO =
+webextension.then(port =>
{
- /**
- * Retrieves the platform-dependent line break string.
- */
- get lineBreak()
+ port.onMessage.addListener(message =>
{
- let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n");
- Object.defineProperty(this, "lineBreak", {value: lineBreak});
- return lineBreak;
- },
-
- /**
- * Tries to interpret a file path as an absolute path or a path relative to
- * user's profile. Returns a file or null on failure.
- */
- resolveFilePath: function(/**String*/ path) /**nsIFile*/
- {
- if (!path)
- return null;
-
- try {
- // Assume an absolute path first
- return new FileUtils.File(path);
- } catch (e) {}
-
- try {
- // Try relative path now
- return FileUtils.getFile("ProfD", path.split("/"));
- } catch (e) {}
+ let {id} = message;
+ let callbacks = messageCallbacks.get(id);
+ if (callbacks)
+ {
+ messageCallbacks.delete(id);
- return null;
- },
+ if (message.success)
+ callbacks.resolve(message.result);
+ else
+ callbacks.reject(message.result);
+ }
+ });
+});
- /**
- * Reads strings from a file asynchronously, calls listener.process() with
- * each line read and with a null parameter once the read operation is done.
- * The callback will be called when the operation is done.
- */
- readFromFile: function(/**nsIFile*/ file, /**Object*/ listener, /**Function*/ callback)
+function callWebExt(method, ...args)
+{
+ return webextension.then(port =>
{
- try
+ return new Promise((resolve, reject) =>
{
- let processing = false;
- let buffer = "";
- let loaded = false;
- let error = null;
+ let id = ++messageID;
+ messageCallbacks.set(id, {resolve, reject});
+ port.postMessage({id, method, args});
+ });
+ });
+}
- let onProgress = function(data)
- {
- let index = (processing ? -1 : Math.max(data.lastIndexOf("\n"), data.lastIndexOf("\r")));
- if (index >= 0)
- {
- // Protect against reentrance in case the listener processes events.
- processing = true;
- try
- {
- let oldBuffer = buffer;
- buffer = data.substr(index + 1);
- data = data.substr(0, index + 1);
- let lines = data.split(/[\r\n]+/);
- lines.pop();
- lines[0] = oldBuffer + lines[0];
- for (let i = 0; i < lines.length; i++)
- listener.process(lines[i]);
- }
- finally
- {
- processing = false;
- data = buffer;
- buffer = "";
- onProgress(data);
+function callLegacy(method, ...args)
+{
+ return new Promise((resolve, reject) =>
+ {
+ LegacyIO[method](...args, (error, result) =>
+ {
+ if (error)
+ reject(error);
+ else
+ resolve(result);
+ });
+ });
+}
- if (loaded)
- {
- loaded = false;
- onSuccess();
- }
+function legacyFile(fileName)
+{
+ let file = LegacyIO.resolveFilePath("adblockplus");
+ file.append(fileName);
+ return file;
+}
- if (error)
- {
- let param = error;
- error = null;
- onError(param);
- }
- }
- }
- else
- buffer += data;
- };
+function ensureDirExists(file)
+{
+ if (!file.exists())
+ {
+ ensureDirExists(file.parent);
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+}
- let onSuccess = function()
+let fallback = {
+ readFromFile(fileName, listener)
+ {
+ let wrapper = {
+ process(line)
{
- if (processing)
- {
- // Still processing data, delay processing this event.
- loaded = true;
- return;
- }
-
- if (buffer !== "")
- listener.process(buffer);
- listener.process(null);
+ if (line !== null)
+ listener(line);
+ }
+ };
+ return callLegacy("readFromFile", legacyFile(fileName), wrapper);
+ },
- callback(null);
- };
+ writeToFile(fileName, data)
+ {
+ let file = legacyFile(fileName);
+ ensureDirExists(file.parent);
+ return callLegacy("writeToFile", file, data);
+ },
- let onError = function(e)
- {
- if (processing)
- {
- // Still processing data, delay processing this event.
- error = e;
- return;
- }
+ copyFile(fromFile, toFile)
+ {
+ return callLegacy("copyFile", legacyFile(fromFile), legacyFile(toFile));
+ },
- callback(e);
- };
+ renameFile(fromFile, newName)
+ {
+ return callLegacy("renameFile", legacyFile(fromFile), newName);
+ },
- let decoder = new TextDecoder();
- Task.spawn(function()
- {
- if (firstRead && Services.vc.compare(Utils.platformVersion, "23.0a1") <= 0)
- {
- // See https://issues.adblockplus.org/ticket/530 - the first file
- // opened cannot be closed due to Gecko bug 858723. Make sure that
- // our patterns.ini file doesn't stay locked by opening a dummy file
- // first.
- try
- {
- let dummyPath = IO.resolveFilePath(Prefs.data_directory + "/dummy").path;
- let dummy = yield OS.File.open(dummyPath, {write: true, truncate: true});
- yield dummy.close();
- }
- catch (e)
- {
- // Dummy might be locked already, we don't care
- }
- }
- firstRead = false;
+ removeFile(fileName)
+ {
+ return callLegacy("removeFile", legacyFile(fileName));
+ },
- let f = yield OS.File.open(file.path, {read: true});
- while (true)
- {
- let array = yield f.read(BUFFER_SIZE);
- if (!array.length)
- break;
+ statFile(fileName)
+ {
+ return callLegacy("statFile", legacyFile(fileName));
+ }
+};
- let data = decoder.decode(array, {stream: true});
- onProgress(data);
- }
- yield f.close();
- }.bind(this)).then(onSuccess, onError);
- }
- catch (e)
- {
- callback(e);
- }
- },
+exports.IO =
+{
+ /**
+ * @callback TextSink
+ * @param {string} line
+ */
/**
- * Writes string data to a file in UTF-8 format asynchronously. The callback
- * will be called when the write operation is done.
+ * Reads text lines from a file.
+ * @param {string} fileName
+ * Name of the file to be read
+ * @param {TextSink} listener
+ * Function that will be called for each line in the file
+ * @return {Promise}
+ * Promise to be resolved or rejected once the operation is completed
*/
- writeToFile: function(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ callback)
+ readFromFile(fileName, listener)
{
- try
+ return callWebExt("readFromFile", fileName).then(contents =>
{
- let encoder = new TextEncoder();
-
- Task.spawn(function()
+ return new Promise((resolve, reject) =>
{
- // This mimics OS.File.writeAtomic() but writes in chunks.
- let tmpPath = file.path + ".tmp";
- let f = yield OS.File.open(tmpPath, {write: true, truncate: true});
+ let lineIndex = 0;
- let buf = [];
- let bufLen = 0;
- let lineBreak = this.lineBreak;
-
- function writeChunk()
- {
- let array = encoder.encode(buf.join(lineBreak) + lineBreak);
- buf = [];
- bufLen = 0;
- return f.write(array);
- }
-
- for (let line in data)
+ function processBatch()
{
- buf.push(line);
- bufLen += line.length;
- if (bufLen >= BUFFER_SIZE)
- yield writeChunk();
+ while (lineIndex < contents.length)
+ {
+ listener(contents[lineIndex++]);
+ if (lineIndex % 1000 == 0)
+ {
+ Utils.runAsync(processBatch);
+ return;
+ }
+ }
+ resolve();
}
- if (bufLen)
- yield writeChunk();
+ processBatch();
+ });
+ });
+ },
- // OS.File.flush() isn't exposed prior to Gecko 27, see bug 912457.
- if (typeof f.flush == "function")
- yield f.flush();
- yield f.close();
- yield OS.File.move(tmpPath, file.path, {noCopy: true});
- }.bind(this)).then(callback.bind(null, null), callback);
- }
- catch (e)
- {
- callback(e);
- }
+ /**
+ * Writes text lines to a file.
+ * @param {string} fileName
+ * Name of the file to be written
+ * @param {Iterable.<string>} data
+ * An array-like or iterable object containing the lines (without line
+ * endings)
+ * @return {Promise}
+ * Promise to be resolved or rejected once the operation is completed
+ */
+ writeToFile(fileName, data)
+ {
+ return callWebExt("writeToFile", fileName, Array.from(data));
},
/**
- * Copies a file asynchronously. The callback will be called when the copy
- * operation is done.
+ * Copies a file.
+ * @param {string} fromFile
+ * Name of the file to be copied
+ * @param {string} toFile
+ * Name of the file to be written, will be overwritten if exists
+ * @return {Promise}
+ * Promise to be resolved or rejected once the operation is completed
*/
- copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback)
+ copyFile(fromFile, toFile)
{
- try
- {
- let promise = OS.File.copy(fromFile.path, toFile.path);
- promise.then(callback.bind(null, null), callback);
- }
- catch (e)
- {
- callback(e);
- }
+ return callWebExt("copyFile", fromFile, toFile);
},
/**
- * Renames a file within the same directory, will call callback when done.
+ * Renames a file.
+ * @param {string} fromFile
+ * Name of the file to be renamed
+ * @param {string} newName
+ * New file name, will be overwritten if exists
+ * @return {Promise}
+ * Promise to be resolved or rejected once the operation is completed
*/
- renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback)
+ renameFile(fromFile, newName)
{
- try
- {
- toFile = fromFile.clone();
- toFile.leafName = newName;
- let promise = OS.File.move(fromFile.path, toFile.path);
- promise.then(callback.bind(null, null), callback);
- }
- catch(e)
- {
- callback(e);
- }
+ return callWebExt("renameFile", fromFile, newName);
},
/**
- * Removes a file, will call callback when done.
+ * Removes a file.
+ * @param {string} fileName
+ * Name of the file to be removed
+ * @return {Promise}
+ * Promise to be resolved or rejected once the operation is completed
*/
- removeFile: function(/**nsIFile*/ file, /**Function*/ callback)
+ removeFile(fileName)
{
- try
- {
- let promise = OS.File.remove(file.path);
- promise.then(callback.bind(null, null), callback);
- }
- catch(e)
- {
- callback(e);
- }
+ return callWebExt("removeFile", fileName);
},
/**
- * Gets file information such as whether the file exists.
+ * @typedef StatData
+ * @type {object}
+ * @property {boolean} exists
+ * true if the file exists
+ * @property {number} lastModified
+ * file modification time in milliseconds
*/
- statFile: function(/**nsIFile*/ file, /**Function*/ callback)
+
+ /**
+ * Retrieves file metadata.
+ * @param {string} fileName
+ * Name of the file to be looked up
+ * @return {Promise.<StatData>}
+ * Promise to be resolved with file metadata once the operation is
+ * completed
+ */
+ statFile(fileName)
+ {
+ return callWebExt("statFile", fileName);
+ }
+};
+
+let {application} = require("info");
+if (application != "firefox" && application != "fennec2")
+{
+ // Currently, only Firefox has a working WebExtensions implementation, other
+ // applications should just use the fallback.
+ exports.IO = fallback;
+}
+else
+{
+ // Add fallbacks to IO methods - fall back to legacy I/O if file wasn't found.
+ for (let name of Object.getOwnPropertyNames(exports.IO))
{
- try
+ // No fallback for writeToFile method, new data should always be stored to
+ // new storage only.
+ if (name == "writeToFile")
+ continue;
+
+ let method = exports.IO[name];
+ let fallbackMethod = fallback[name];
+ exports.IO[name] = (...args) =>
{
- let promise = OS.File.stat(file.path);
- promise.then(function onSuccess(info)
- {
- callback(null, {
- exists: true,
- isDirectory: info.isDir,
- isFile: !info.isDir,
- lastModified: info.lastModificationDate.getTime()
- });
- }, function onError(e)
+ return method(...args).catch(error =>
{
- if (e.becauseNoSuchFile)
- {
- callback(null, {
- exists: false,
- isDirectory: false,
- isFile: false,
- lastModified: 0
- });
- }
- else
- callback(e);
+ if (error == "NoSuchFile")
+ return fallbackMethod(...args);
+ throw error;
});
- }
- catch(e)
- {
- callback(e);
- }
+ };
}
}