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