| Index: lib/io.js |
| =================================================================== |
| --- a/lib/io.js |
| +++ b/lib/io.js |
| @@ -10,330 +10,165 @@ |
| * 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 Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| -/** |
| - * @fileOverview Module containing file I/O helpers. |
| - */ |
| +"use strict"; |
| -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); |
| - |
| -let {Prefs} = require("prefs"); |
| +let {IO: LegacyIO} = require("legacyIO"); |
| let {Utils} = require("utils"); |
| -let firstRead = true; |
| -const BUFFER_SIZE = 0x80000; // 512kB |
| - |
| -let IO = exports.IO = |
| -{ |
| - /** |
| - * Retrieves the platform-dependent line break string. |
| - */ |
| - get lineBreak() |
| - { |
| - let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n"); |
| - Object.defineProperty(this, "lineBreak", {value: lineBreak}); |
| - return lineBreak; |
| - }, |
| +let webextension = require("webextension"); |
| +let messageID = 0; |
| +let messageCallbacks = new Map(); |
| - /** |
| - * 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*/ |
| +webextension.then(port => |
| +{ |
| + port.onMessage.addListener(message => |
| { |
| - if (!path) |
| - return null; |
| + let {id} = message; |
| + let callbacks = messageCallbacks.get(id); |
| + if (callbacks) |
| + { |
| + messageCallbacks.delete(id); |
| - try { |
| - // Assume an absolute path first |
| - return new FileUtils.File(path); |
| - } catch (e) {} |
| + if (message.success) |
| + callbacks.resolve(message.result); |
| + else |
| + callbacks.reject(message.result); |
| + } |
| + }); |
| +}); |
| - try { |
| - // Try relative path now |
| - return FileUtils.getFile("ProfD", path.split("/")); |
| - } catch (e) {} |
| +function callWebExt(method, ...args) |
| +{ |
| + return webextension.then(port => |
| + { |
| + return new Promise((resolve, reject) => |
| + { |
| + let id = ++messageID; |
| + messageCallbacks.set(id, {resolve, reject}); |
| + port.postMessage({id, method, args}); |
| + }); |
| + }); |
| +} |
| - return null; |
| - }, |
| +function attachCallback(promise, callback, fallback) |
| +{ |
| + promise.then(result => |
| + { |
| + callback(null, result); |
| + }).catch(error => |
| + { |
| + if (fallback && error == "NoSuchFile") |
| + fallback(); |
| + else |
| + callback(error); |
| + }); |
| +} |
| + |
| +exports.IO = |
| +{ |
| + resolveFilePath: LegacyIO.resolveFilePath, |
| /** |
| * 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) |
| + readFromFile(/**nsIFile*/ file, /**Object*/ listener, /**Function*/ callback) |
| { |
| - try |
| - { |
| - let processing = false; |
| - let buffer = ""; |
| - let loaded = false; |
| - let error = null; |
| + attachCallback( |
| + callWebExt("readFromFile", file.leafName).then(contents => |
| + { |
| + return new Promise((resolve, reject) => |
| + { |
| + let lineIndex = 0; |
| - 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 |
| + function processBatch() |
| { |
| - 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++) |
| + while (lineIndex < contents.length) |
| { |
| - let promise = listener.process(lines[i]); |
| - if (promise) |
| - yield promise; |
| - } |
| - } |
| - finally |
| - { |
| - processing = false; |
| - data = buffer; |
| - buffer = ""; |
| - yield* onProgress(data); |
| - |
| - if (loaded) |
| - { |
| - loaded = false; |
| - onSuccess(); |
| + listener.process(contents[lineIndex++]); |
| + if (lineIndex % 1000 == 0) |
| + { |
| + Utils.runAsync(processBatch); |
| + return; |
| + } |
| } |
| - if (error) |
| - { |
| - let param = error; |
| - error = null; |
| - onError(param); |
| - } |
| + listener.process(null); |
| + resolve(); |
| } |
| - } |
| - else |
| - buffer += data; |
| - }; |
| - |
| - let onSuccess = function() |
| - { |
| - if (processing) |
| - { |
| - // Still processing data, delay processing this event. |
| - loaded = true; |
| - return; |
| - } |
| - |
| - // We are ignoring return value of listener.process() here because |
| - // turning this callback into a generator would be complicated, and |
| - // delaying isn't really necessary for the last two calls. |
| - if (buffer !== "") |
| - listener.process(buffer); |
| - listener.process(null); |
| - |
| - callback(null); |
| - }; |
| - |
| - let onError = function(e) |
| - { |
| - if (processing) |
| - { |
| - // Still processing data, delay processing this event. |
| - error = e; |
| - return; |
| - } |
| - callback(e); |
| - }; |
| - |
| - 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; |
| - |
| - let f = yield OS.File.open(file.path, {read: true}); |
| - while (true) |
| - { |
| - let array = yield f.read(BUFFER_SIZE); |
| - if (!array.length) |
| - break; |
| - |
| - let data = decoder.decode(array, {stream: true}); |
| - yield* onProgress(data); |
| - } |
| - yield f.close(); |
| - }.bind(this)).then(onSuccess, onError); |
| - } |
| - catch (e) |
| - { |
| - callback(e); |
| - } |
| + processBatch(); |
|
Wladimir Palant
2017/04/10 11:35:17
The files are processed in batches of 1000 lines i
|
| + }); |
| + }), |
| + callback, |
| + () => LegacyIO.readFromFile(file, listener, callback) |
| + ); |
| }, |
| /** |
| * Writes string data to a file in UTF-8 format asynchronously. The callback |
| * will be called when the write operation is done. |
| */ |
| - writeToFile: function(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ callback) |
| + writeToFile(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ callback) |
| { |
| - try |
| - { |
| - let encoder = new TextEncoder(); |
| - |
| - Task.spawn(function*() |
| - { |
| - // 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 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 of data) |
| - { |
| - buf.push(line); |
| - bufLen += line.length; |
| - if (bufLen >= BUFFER_SIZE) |
| - yield writeChunk(); |
| - } |
| - |
| - if (bufLen) |
| - yield writeChunk(); |
| - |
| - // 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); |
| - } |
| + attachCallback( |
| + callWebExt("writeToFile", file.leafName, Array.from(data)), |
| + callback |
| + ); |
| }, |
| /** |
| * Copies a file asynchronously. The callback will be called when the copy |
| * operation is done. |
| */ |
| - copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback) |
| + copyFile(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback) |
| { |
| - try |
| - { |
| - let promise = OS.File.copy(fromFile.path, toFile.path); |
| - promise.then(callback.bind(null, null), callback); |
| - } |
| - catch (e) |
| - { |
| - callback(e); |
| - } |
| + attachCallback( |
| + callWebExt("copyFile", fromFile.leafName, toFile.leafName), |
| + callback, |
| + () => LegacyIO.copyFile(fromFile, toFile, callback) |
| + ); |
| }, |
| /** |
| * Renames a file within the same directory, will call callback when done. |
| */ |
| - renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback) |
| + renameFile(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback) |
| { |
| - try |
| - { |
| - let 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); |
| - } |
| + attachCallback( |
| + callWebExt("renameFile", fromFile.leafName, newName), |
| + callback, |
| + () => LegacyIO.renameFile(fromFile, newName, callback) |
| + ); |
| }, |
| /** |
| * Removes a file, will call callback when done. |
| */ |
| - removeFile: function(/**nsIFile*/ file, /**Function*/ callback) |
| + removeFile(/**nsIFile*/ file, /**Function*/ callback) |
| { |
| - try |
| - { |
| - let promise = OS.File.remove(file.path); |
| - promise.then(callback.bind(null, null), callback); |
| - } |
| - catch(e) |
| - { |
| - callback(e); |
| - } |
| + attachCallback( |
| + callWebExt("removeFile", file.leafName), |
| + callback, |
| + () => LegacyIO.removeFile(file, callback) |
| + ); |
| }, |
| /** |
| * Gets file information such as whether the file exists. |
| */ |
| - statFile: function(/**nsIFile*/ file, /**Function*/ callback) |
| + statFile(/**nsIFile*/ file, /**Function*/ callback) |
| { |
| - try |
| - { |
| - 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) |
| - { |
| - if (e.becauseNoSuchFile) |
| - { |
| - callback(null, { |
| - exists: false, |
| - isDirectory: false, |
| - isFile: false, |
| - lastModified: 0 |
| - }); |
| - } |
| - else |
| - callback(e); |
| - }); |
| - } |
| - catch(e) |
| - { |
| - callback(e); |
| - } |
| + attachCallback( |
| + callWebExt("statFile", file.leafName), |
| + callback, |
| + () => LegacyIO.statFile(file, callback) |
| + ); |
| } |
| -} |
| +}; |