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) |
+ ); |
} |
-} |
+}; |