| Index: lib/io.js |
| =================================================================== |
| --- a/lib/io.js |
| +++ b/lib/io.js |
| @@ -14,24 +14,26 @@ |
| * 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. |
| */ |
| -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
| -Cu.import("resource://gre/modules/Services.jsm"); |
| -Cu.import("resource://gre/modules/FileUtils.jsm"); |
| -Cu.import("resource://gre/modules/NetUtil.jsm"); |
| +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 {TimeLine} = require("timeline"); |
| let {Utils} = require("utils"); |
| +const BUFFER_SIZE = 0x8000; // 32kB |
| + |
| let IO = exports.IO = |
| { |
| /** |
| * Retrieves the platform-dependent line break string. |
| */ |
| get lineBreak() |
| { |
| let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n"); |
| @@ -62,30 +64,24 @@ let IO = exports.IO = |
| return null; |
| }, |
| /** |
| * 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|nsIURI*/ file, /**Boolean*/ decode, /**Object*/ listener, /**Function*/ callback, /**String*/ timeLineID) |
| + readFromFile: function(/**nsIFile*/ file, /**Boolean*/ decode, /**Object*/ listener, /**Function*/ callback, /**String*/ timeLineID) |
| { |
| try |
| { |
| let processing = false; |
| let buffer = ""; |
| let loaded = false; |
| - let error = Cr.NS_OK; |
| - let uri = file instanceof Ci.nsIFile ? Services.io.newFileURI(file) : file; |
| - let request = new XMLHttpRequest(); |
| - request.mozBackgroundRequest = true; |
| - request.open("GET", uri.spec); |
| - request.responseType = "moz-chunked-text"; |
| - request.overrideMimeType("text/plain" + (decode ? "? charset=utf-8" : "")); |
| + let error = null; |
| let onProgress = function(data) |
| { |
| if (timeLineID) |
| { |
| TimeLine.asyncStart(timeLineID); |
| } |
| @@ -110,37 +106,37 @@ let IO = exports.IO = |
| processing = false; |
| data = buffer; |
| buffer = ""; |
| onProgress(data); |
| if (loaded) |
| { |
| loaded = false; |
| - onLoad(); |
| + onSuccess(); |
| } |
| - if (error != Cr.NS_OK) |
| + if (error) |
| { |
| let param = error; |
| - error = Cr.NS_OK; |
| + error = null; |
| onError(param); |
| } |
| } |
| } |
| else |
| buffer += data; |
| if (timeLineID) |
| { |
| TimeLine.asyncEnd(timeLineID); |
| } |
| }; |
| - let onLoad = function() |
| + let onSuccess = function() |
| { |
| if (processing) |
| { |
| // Still processing data, delay processing this event. |
| loaded = true; |
| return; |
| } |
| @@ -157,238 +153,192 @@ let IO = exports.IO = |
| { |
| TimeLine.asyncEnd(timeLineID); |
| TimeLine.asyncDone(timeLineID); |
| } |
| callback(null); |
| }; |
| - let onError = function(status) |
| + let onError = function(e) |
| { |
| if (processing) |
| { |
| // Still processing data, delay processing this event. |
| - error = status; |
| + error = e; |
| return; |
| } |
| - let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException); |
| - e.initialize("File read operation failed", status, null, Components.stack, file, null); |
| callback(e); |
| if (timeLineID) |
| { |
| TimeLine.asyncDone(timeLineID); |
| } |
| }; |
| - request.addEventListener("progress", function(event) |
| + let decoder = new TextDecoder(); |
| + let array = new Uint8Array(BUFFER_SIZE); |
| + Task.spawn(function() |
| { |
| - Utils.runAsync(onProgress.bind(this, event.target.response)); |
| - }, false); |
| - request.addEventListener("load", function(event) |
| - { |
| - Utils.runAsync(onLoad.bind(this)); |
| - }, false); |
| - request.addEventListener("error", function(event) |
| - { |
| - Utils.runAsync(onError.bind(this, event.target.channel.status)); |
| - }, false); |
| + let f = yield OS.File.open(file.path, {read: true}); |
| + let numBytes; |
| + do |
| + { |
| + numBytes = yield f.readTo(array); |
| + if (numBytes) |
| + { |
| + let data = decoder.decode(numBytes == BUFFER_SIZE ? |
| + array : |
| + array.subarray(0, numBytes), {stream: true}); |
| + onProgress(data); |
| + } |
| + } while (numBytes); |
| - request.send(null); |
| + yield f.close(); |
| + }.bind(this)).then(onSuccess, onError); |
| } |
| catch (e) |
| { |
| callback(e); |
| } |
| }, |
| + |
| /** |
| * Writes string data to a file asynchronously, optionally encodes it into |
| * UTF-8 first. The callback will be called when the write operation is done. |
| */ |
| writeToFile: function(/**nsIFile*/ file, /**Boolean*/ encode, /**Iterator*/ data, /**Function*/ callback, /**String*/ timeLineID) |
| { |
| try |
| { |
| - let fileStream = FileUtils.openSafeFileOutputStream(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); |
| + let encoder = new TextEncoder(); |
| - let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); |
| - pipe.init(true, true, 0, 0x8000, null); |
| + 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 outStream = pipe.outputStream; |
| - if (encode) |
| - { |
| - outStream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream); |
| - outStream.init(pipe.outputStream, "UTF-8", 0, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); |
| - } |
| - |
| - let copier = Cc["@mozilla.org/network/async-stream-copier;1"].createInstance(Ci.nsIAsyncStreamCopier); |
| - copier.init(pipe.inputStream, fileStream, null, true, false, 0x8000, true, true); |
| - copier.asyncCopy({ |
| - onStartRequest: function(request, context) {}, |
| - onStopRequest: function(request, context, result) |
| - { |
| - if (timeLineID) |
| - { |
| - TimeLine.asyncDone(timeLineID); |
| - } |
| - |
| - if (!Components.isSuccessCode(result)) |
| - { |
| - let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException); |
| - e.initialize("File write operation failed", result, null, Components.stack, file, null); |
| - callback(e); |
| - } |
| - else |
| - callback(null); |
| - } |
| - }, null); |
| - |
| - let lineBreak = this.lineBreak; |
| - let writeNextChunk = function() |
| - { |
| let buf = []; |
| let bufLen = 0; |
| - while (bufLen < 0x4000) |
| + let lineBreak = this.lineBreak; |
| + |
| + function writeChunk() |
| { |
| - try |
| - { |
| - let str = data.next(); |
| - buf.push(str); |
| - bufLen += str.length; |
| - } |
| - catch (e) |
| - { |
| - if (e instanceof StopIteration) |
| - break; |
| - else if (typeof e == "number") |
| - pipe.outputStream.closeWithStatus(e); |
| - else if (e instanceof Ci.nsIException) |
| - pipe.outputStream.closeWithStatus(e.result); |
| - else |
| - { |
| - Cu.reportError(e); |
| - pipe.outputStream.closeWithStatus(Cr.NS_ERROR_FAILURE); |
| - } |
| - return; |
| - } |
| + let array = encoder.encode(buf.join(lineBreak) + lineBreak); |
| + buf = []; |
| + bufLen = 0; |
| + return f.write(array); |
| } |
| - pipe.outputStream.asyncWait({ |
| - onOutputStreamReady: function() |
| - { |
| - if (timeLineID) |
| - { |
| - TimeLine.asyncStart(timeLineID); |
| - } |
| + for (let line in data) |
| + { |
| + buf.push(line); |
| + bufLen += line.length; |
| + if (bufLen >= BUFFER_SIZE) |
| + yield writeChunk(); |
| + } |
| - if (buf.length) |
| - { |
| - let str = buf.join(lineBreak) + lineBreak; |
| - if (encode) |
| - outStream.writeString(str); |
| - else |
| - outStream.write(str, str.length); |
| - writeNextChunk(); |
| - } |
| - else |
| - outStream.close(); |
| + if (bufLen) |
| + yield writeChunk(); |
| - if (timeLineID) |
| - { |
| - TimeLine.asyncEnd(timeLineID); |
| - } |
| - } |
| - }, 0, 0, Services.tm.currentThread); |
| - }; |
| - writeNextChunk(); |
| + // 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); |
| } |
| }, |
| /** |
| * Copies a file asynchronously. The callback will be called when the copy |
| * operation is done. |
| */ |
| copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback) |
| { |
| try |
| { |
| - let inStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); |
| - inStream.init(fromFile, FileUtils.MODE_RDONLY, 0, Ci.nsIFileInputStream.DEFER_OPEN); |
| - |
| - let outStream = FileUtils.openFileOutputStream(toFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); |
| - |
| - NetUtil.asyncCopy(inStream, outStream, function(result) |
| - { |
| - if (!Components.isSuccessCode(result)) |
| - { |
| - let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCException); |
| - e.initialize("File write operation failed", result, null, Components.stack, file, null); |
| - callback(e); |
| - } |
| - else |
| - callback(null); |
| - }); |
| + let promise = OS.File.copy(fromFile.path, toFile.path); |
| + promise.then(callback.bind(null, null), callback); |
| } |
| catch (e) |
| { |
| callback(e); |
| } |
| }, |
| /** |
| * Renames a file within the same directory, will call callback when done. |
| */ |
| renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback) |
| { |
| try |
| { |
| - fromFile.moveTo(null, newName); |
| - callback(null); |
| + 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); |
| } |
| }, |
| /** |
| * Removes a file, will call callback when done. |
| */ |
| removeFile: function(/**nsIFile*/ file, /**Function*/ callback) |
| { |
| try |
| { |
| - file.remove(false); |
| - callback(null); |
| + let promise = OS.File.remove(file.path); |
| + promise.then(callback.bind(null, null), callback); |
| } |
| catch(e) |
| { |
| callback(e); |
| } |
| }, |
| /** |
| * Gets file information such as whether the file exists. |
| */ |
| statFile: function(/**nsIFile*/ file, /**Function*/ callback) |
| { |
| try |
| { |
| - let exists = file.exists(); |
| - callback(null, { |
| - exists: exists, |
| - isDirectory: exists && file.isDirectory(), |
| - isFile: exists && file.isFile(), |
| - lastModified: exists ? file.lastModifiedTime : 0 |
| + 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 |
| + }) |
|
Felix Dahlke
2014/03/21 15:44:27
Missing semicolon.
|
| + } |
| + else |
| + callback(e); |
| }); |
| } |
| catch(e) |
| { |
| callback(e); |
| } |
| } |
| } |