diff options
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/io.js')
-rw-r--r-- | data/extensions/spyblock@gnu.org/lib/io.js | 465 |
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); - } + }; } } |