| OLD | NEW |
| 1 /* | 1 /* |
| 2 * This file is part of Adblock Plus <http://adblockplus.org/>, | 2 * This file is part of Adblock Plus <http://adblockplus.org/>, |
| 3 * Copyright (C) 2006-2014 Eyeo GmbH | 3 * Copyright (C) 2006-2014 Eyeo GmbH |
| 4 * | 4 * |
| 5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
| 6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
| 7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
| 8 * | 8 * |
| 9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
| 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 * GNU General Public License for more details. | 12 * GNU General Public License for more details. |
| 13 * | 13 * |
| 14 * You should have received a copy of the GNU General Public License | 14 * You should have received a copy of the GNU General Public License |
| 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| 16 */ | 16 */ |
| 17 | 17 |
| 18 /** | 18 /** |
| 19 * @fileOverview Module containing file I/O helpers. | 19 * @fileOverview Module containing file I/O helpers. |
| 20 */ | 20 */ |
| 21 | 21 |
| 22 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); | 22 let {Services} = Cu.import("resource://gre/modules/Services.jsm", null); |
| 23 Cu.import("resource://gre/modules/Services.jsm"); | 23 let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", null); |
| 24 Cu.import("resource://gre/modules/FileUtils.jsm"); | 24 let {OS} = Cu.import("resource://gre/modules/osfile.jsm", null); |
| 25 Cu.import("resource://gre/modules/NetUtil.jsm"); | 25 let {Task} = Cu.import("resource://gre/modules/Task.jsm", null); |
| 26 | 26 |
| 27 let {TimeLine} = require("timeline"); | 27 let {TimeLine} = require("timeline"); |
| 28 let {Utils} = require("utils"); | 28 let {Utils} = require("utils"); |
| 29 | 29 |
| 30 const BUFFER_SIZE = 0x8000; // 32kB |
| 31 |
| 30 let IO = exports.IO = | 32 let IO = exports.IO = |
| 31 { | 33 { |
| 32 /** | 34 /** |
| 33 * Retrieves the platform-dependent line break string. | 35 * Retrieves the platform-dependent line break string. |
| 34 */ | 36 */ |
| 35 get lineBreak() | 37 get lineBreak() |
| 36 { | 38 { |
| 37 let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n"); | 39 let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n"); |
| 38 delete IO.lineBreak; | 40 delete IO.lineBreak; |
| 39 IO.__defineGetter__("lineBreak", function() lineBreak); | 41 IO.__defineGetter__("lineBreak", function() lineBreak); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 60 } catch (e) {} | 62 } catch (e) {} |
| 61 | 63 |
| 62 return null; | 64 return null; |
| 63 }, | 65 }, |
| 64 | 66 |
| 65 /** | 67 /** |
| 66 * Reads strings from a file asynchronously, calls listener.process() with | 68 * Reads strings from a file asynchronously, calls listener.process() with |
| 67 * each line read and with a null parameter once the read operation is done. | 69 * each line read and with a null parameter once the read operation is done. |
| 68 * The callback will be called when the operation is done. | 70 * The callback will be called when the operation is done. |
| 69 */ | 71 */ |
| 70 readFromFile: function(/**nsIFile|nsIURI*/ file, /**Boolean*/ decode, /**Objec
t*/ listener, /**Function*/ callback, /**String*/ timeLineID) | 72 readFromFile: function(/**nsIFile*/ file, /**Boolean*/ decode, /**Object*/ lis
tener, /**Function*/ callback, /**String*/ timeLineID) |
| 71 { | 73 { |
| 72 try | 74 try |
| 73 { | 75 { |
| 74 let processing = false; | 76 let processing = false; |
| 75 let buffer = ""; | 77 let buffer = ""; |
| 76 let loaded = false; | 78 let loaded = false; |
| 77 let error = Cr.NS_OK; | 79 let error = null; |
| 78 let uri = file instanceof Ci.nsIFile ? Services.io.newFileURI(file) : file
; | |
| 79 let request = new XMLHttpRequest(); | |
| 80 request.mozBackgroundRequest = true; | |
| 81 request.open("GET", uri.spec); | |
| 82 request.responseType = "moz-chunked-text"; | |
| 83 request.overrideMimeType("text/plain" + (decode ? "? charset=utf-8" : ""))
; | |
| 84 | 80 |
| 85 let onProgress = function(data) | 81 let onProgress = function(data) |
| 86 { | 82 { |
| 87 if (timeLineID) | 83 if (timeLineID) |
| 88 { | 84 { |
| 89 TimeLine.asyncStart(timeLineID); | 85 TimeLine.asyncStart(timeLineID); |
| 90 } | 86 } |
| 91 | 87 |
| 92 let index = (processing ? -1 : Math.max(data.lastIndexOf("\n"), data.las
tIndexOf("\r"))); | 88 let index = (processing ? -1 : Math.max(data.lastIndexOf("\n"), data.las
tIndexOf("\r"))); |
| 93 if (index >= 0) | 89 if (index >= 0) |
| (...skipping 14 matching lines...) Expand all Loading... |
| 108 finally | 104 finally |
| 109 { | 105 { |
| 110 processing = false; | 106 processing = false; |
| 111 data = buffer; | 107 data = buffer; |
| 112 buffer = ""; | 108 buffer = ""; |
| 113 onProgress(data); | 109 onProgress(data); |
| 114 | 110 |
| 115 if (loaded) | 111 if (loaded) |
| 116 { | 112 { |
| 117 loaded = false; | 113 loaded = false; |
| 118 onLoad(); | 114 onSuccess(); |
| 119 } | 115 } |
| 120 | 116 |
| 121 if (error != Cr.NS_OK) | 117 if (error) |
| 122 { | 118 { |
| 123 let param = error; | 119 let param = error; |
| 124 error = Cr.NS_OK; | 120 error = null; |
| 125 onError(param); | 121 onError(param); |
| 126 } | 122 } |
| 127 } | 123 } |
| 128 } | 124 } |
| 129 else | 125 else |
| 130 buffer += data; | 126 buffer += data; |
| 131 | 127 |
| 132 if (timeLineID) | 128 if (timeLineID) |
| 133 { | 129 { |
| 134 TimeLine.asyncEnd(timeLineID); | 130 TimeLine.asyncEnd(timeLineID); |
| 135 } | 131 } |
| 136 }; | 132 }; |
| 137 | 133 |
| 138 let onLoad = function() | 134 let onSuccess = function() |
| 139 { | 135 { |
| 140 if (processing) | 136 if (processing) |
| 141 { | 137 { |
| 142 // Still processing data, delay processing this event. | 138 // Still processing data, delay processing this event. |
| 143 loaded = true; | 139 loaded = true; |
| 144 return; | 140 return; |
| 145 } | 141 } |
| 146 | 142 |
| 147 if (timeLineID) | 143 if (timeLineID) |
| 148 { | 144 { |
| 149 TimeLine.asyncStart(timeLineID); | 145 TimeLine.asyncStart(timeLineID); |
| 150 } | 146 } |
| 151 | 147 |
| 152 if (buffer !== "") | 148 if (buffer !== "") |
| 153 listener.process(buffer); | 149 listener.process(buffer); |
| 154 listener.process(null); | 150 listener.process(null); |
| 155 | 151 |
| 156 if (timeLineID) | 152 if (timeLineID) |
| 157 { | 153 { |
| 158 TimeLine.asyncEnd(timeLineID); | 154 TimeLine.asyncEnd(timeLineID); |
| 159 TimeLine.asyncDone(timeLineID); | 155 TimeLine.asyncDone(timeLineID); |
| 160 } | 156 } |
| 161 | 157 |
| 162 callback(null); | 158 callback(null); |
| 163 }; | 159 }; |
| 164 | 160 |
| 165 let onError = function(status) | 161 let onError = function(e) |
| 166 { | 162 { |
| 167 if (processing) | 163 if (processing) |
| 168 { | 164 { |
| 169 // Still processing data, delay processing this event. | 165 // Still processing data, delay processing this event. |
| 170 error = status; | 166 error = e; |
| 171 return; | 167 return; |
| 172 } | 168 } |
| 173 | 169 |
| 174 let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPCEx
ception); | |
| 175 e.initialize("File read operation failed", status, null, Components.stac
k, file, null); | |
| 176 callback(e); | 170 callback(e); |
| 177 | 171 |
| 178 if (timeLineID) | 172 if (timeLineID) |
| 179 { | 173 { |
| 180 TimeLine.asyncDone(timeLineID); | 174 TimeLine.asyncDone(timeLineID); |
| 181 } | 175 } |
| 182 }; | 176 }; |
| 183 | 177 |
| 184 request.addEventListener("progress", function(event) | 178 let decoder = new TextDecoder(); |
| 179 let array = new Uint8Array(BUFFER_SIZE); |
| 180 Task.spawn(function() |
| 185 { | 181 { |
| 186 Utils.runAsync(onProgress.bind(this, event.target.response)); | 182 let f = yield OS.File.open(file.path, {read: true}); |
| 187 }, false); | 183 let numBytes; |
| 188 request.addEventListener("load", function(event) | 184 do |
| 189 { | 185 { |
| 190 Utils.runAsync(onLoad.bind(this)); | 186 numBytes = yield f.readTo(array); |
| 191 }, false); | 187 if (numBytes) |
| 192 request.addEventListener("error", function(event) | 188 { |
| 193 { | 189 let data = decoder.decode(numBytes == BUFFER_SIZE ? |
| 194 Utils.runAsync(onError.bind(this, event.target.channel.status)); | 190 array : |
| 195 }, false); | 191 array.subarray(0, numBytes), {stream: true
}); |
| 192 onProgress(data); |
| 193 } |
| 194 } while (numBytes); |
| 196 | 195 |
| 197 request.send(null); | 196 yield f.close(); |
| 197 }.bind(this)).then(onSuccess, onError); |
| 198 } | 198 } |
| 199 catch (e) | 199 catch (e) |
| 200 { | 200 { |
| 201 callback(e); | 201 callback(e); |
| 202 } | 202 } |
| 203 }, | 203 }, |
| 204 |
| 204 /** | 205 /** |
| 205 * Writes string data to a file asynchronously, optionally encodes it into | 206 * Writes string data to a file asynchronously, optionally encodes it into |
| 206 * UTF-8 first. The callback will be called when the write operation is done. | 207 * UTF-8 first. The callback will be called when the write operation is done. |
| 207 */ | 208 */ |
| 208 writeToFile: function(/**nsIFile*/ file, /**Boolean*/ encode, /**Iterator*/ da
ta, /**Function*/ callback, /**String*/ timeLineID) | 209 writeToFile: function(/**nsIFile*/ file, /**Boolean*/ encode, /**Iterator*/ da
ta, /**Function*/ callback, /**String*/ timeLineID) |
| 209 { | 210 { |
| 210 try | 211 try |
| 211 { | 212 { |
| 212 let fileStream = FileUtils.openSafeFileOutputStream(file, FileUtils.MODE_W
RONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); | 213 let encoder = new TextEncoder(); |
| 213 | 214 |
| 214 let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); | 215 Task.spawn(function() |
| 215 pipe.init(true, true, 0, 0x8000, null); | 216 { |
| 217 // This mimics OS.File.writeAtomic() but writes in chunks. |
| 218 let tmpPath = file.path + ".tmp"; |
| 219 let f = yield OS.File.open(tmpPath, {write: true, truncate: true}); |
| 216 | 220 |
| 217 let outStream = pipe.outputStream; | |
| 218 if (encode) | |
| 219 { | |
| 220 outStream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInst
ance(Ci.nsIConverterOutputStream); | |
| 221 outStream.init(pipe.outputStream, "UTF-8", 0, Ci.nsIConverterInputStream
.DEFAULT_REPLACEMENT_CHARACTER); | |
| 222 } | |
| 223 | |
| 224 let copier = Cc["@mozilla.org/network/async-stream-copier;1"].createInstan
ce(Ci.nsIAsyncStreamCopier); | |
| 225 copier.init(pipe.inputStream, fileStream, null, true, false, 0x8000, true,
true); | |
| 226 copier.asyncCopy({ | |
| 227 onStartRequest: function(request, context) {}, | |
| 228 onStopRequest: function(request, context, result) | |
| 229 { | |
| 230 if (timeLineID) | |
| 231 { | |
| 232 TimeLine.asyncDone(timeLineID); | |
| 233 } | |
| 234 | |
| 235 if (!Components.isSuccessCode(result)) | |
| 236 { | |
| 237 let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIX
PCException); | |
| 238 e.initialize("File write operation failed", result, null, Components
.stack, file, null); | |
| 239 callback(e); | |
| 240 } | |
| 241 else | |
| 242 callback(null); | |
| 243 } | |
| 244 }, null); | |
| 245 | |
| 246 let lineBreak = this.lineBreak; | |
| 247 let writeNextChunk = function() | |
| 248 { | |
| 249 let buf = []; | 221 let buf = []; |
| 250 let bufLen = 0; | 222 let bufLen = 0; |
| 251 while (bufLen < 0x4000) | 223 let lineBreak = this.lineBreak; |
| 224 |
| 225 function writeChunk() |
| 252 { | 226 { |
| 253 try | 227 let array = encoder.encode(buf.join(lineBreak) + lineBreak); |
| 254 { | 228 buf = []; |
| 255 let str = data.next(); | 229 bufLen = 0; |
| 256 buf.push(str); | 230 return f.write(array); |
| 257 bufLen += str.length; | |
| 258 } | |
| 259 catch (e) | |
| 260 { | |
| 261 if (e instanceof StopIteration) | |
| 262 break; | |
| 263 else if (typeof e == "number") | |
| 264 pipe.outputStream.closeWithStatus(e); | |
| 265 else if (e instanceof Ci.nsIException) | |
| 266 pipe.outputStream.closeWithStatus(e.result); | |
| 267 else | |
| 268 { | |
| 269 Cu.reportError(e); | |
| 270 pipe.outputStream.closeWithStatus(Cr.NS_ERROR_FAILURE); | |
| 271 } | |
| 272 return; | |
| 273 } | |
| 274 } | 231 } |
| 275 | 232 |
| 276 pipe.outputStream.asyncWait({ | 233 for (let line in data) |
| 277 onOutputStreamReady: function() | 234 { |
| 278 { | 235 buf.push(line); |
| 279 if (timeLineID) | 236 bufLen += line.length; |
| 280 { | 237 if (bufLen >= BUFFER_SIZE) |
| 281 TimeLine.asyncStart(timeLineID); | 238 yield writeChunk(); |
| 282 } | 239 } |
| 283 | 240 |
| 284 if (buf.length) | 241 if (bufLen) |
| 285 { | 242 yield writeChunk(); |
| 286 let str = buf.join(lineBreak) + lineBreak; | |
| 287 if (encode) | |
| 288 outStream.writeString(str); | |
| 289 else | |
| 290 outStream.write(str, str.length); | |
| 291 writeNextChunk(); | |
| 292 } | |
| 293 else | |
| 294 outStream.close(); | |
| 295 | 243 |
| 296 if (timeLineID) | 244 // OS.File.flush() isn't exposed prior to Gecko 27, see bug 912457. |
| 297 { | 245 if (typeof f.flush == "function") |
| 298 TimeLine.asyncEnd(timeLineID); | 246 yield f.flush(); |
| 299 } | 247 yield f.close(); |
| 300 } | 248 yield OS.File.move(tmpPath, file.path, {noCopy: true}); |
| 301 }, 0, 0, Services.tm.currentThread); | 249 }.bind(this)).then(callback.bind(null, null), callback); |
| 302 }; | |
| 303 writeNextChunk(); | |
| 304 } | 250 } |
| 305 catch (e) | 251 catch (e) |
| 306 { | 252 { |
| 307 callback(e); | 253 callback(e); |
| 308 } | 254 } |
| 309 }, | 255 }, |
| 310 | 256 |
| 311 /** | 257 /** |
| 312 * Copies a file asynchronously. The callback will be called when the copy | 258 * Copies a file asynchronously. The callback will be called when the copy |
| 313 * operation is done. | 259 * operation is done. |
| 314 */ | 260 */ |
| 315 copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ c
allback) | 261 copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ c
allback) |
| 316 { | 262 { |
| 317 try | 263 try |
| 318 { | 264 { |
| 319 let inStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstan
ce(Ci.nsIFileInputStream); | 265 let promise = OS.File.copy(fromFile.path, toFile.path); |
| 320 inStream.init(fromFile, FileUtils.MODE_RDONLY, 0, Ci.nsIFileInputStream.DE
FER_OPEN); | 266 promise.then(callback.bind(null, null), callback); |
| 321 | |
| 322 let outStream = FileUtils.openFileOutputStream(toFile, FileUtils.MODE_WRON
LY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); | |
| 323 | |
| 324 NetUtil.asyncCopy(inStream, outStream, function(result) | |
| 325 { | |
| 326 if (!Components.isSuccessCode(result)) | |
| 327 { | |
| 328 let e = Cc["@mozilla.org/js/xpc/Exception;1"].createInstance(Ci.nsIXPC
Exception); | |
| 329 e.initialize("File write operation failed", result, null, Components.s
tack, file, null); | |
| 330 callback(e); | |
| 331 } | |
| 332 else | |
| 333 callback(null); | |
| 334 }); | |
| 335 } | 267 } |
| 336 catch (e) | 268 catch (e) |
| 337 { | 269 { |
| 338 callback(e); | 270 callback(e); |
| 339 } | 271 } |
| 340 }, | 272 }, |
| 341 | 273 |
| 342 /** | 274 /** |
| 343 * Renames a file within the same directory, will call callback when done. | 275 * Renames a file within the same directory, will call callback when done. |
| 344 */ | 276 */ |
| 345 renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/
callback) | 277 renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/
callback) |
| 346 { | 278 { |
| 347 try | 279 try |
| 348 { | 280 { |
| 349 fromFile.moveTo(null, newName); | 281 toFile = fromFile.clone(); |
| 350 callback(null); | 282 toFile.leafName = newName; |
| 283 let promise = OS.File.move(fromFile.path, toFile.path); |
| 284 promise.then(callback.bind(null, null), callback); |
| 351 } | 285 } |
| 352 catch(e) | 286 catch(e) |
| 353 { | 287 { |
| 354 callback(e); | 288 callback(e); |
| 355 } | 289 } |
| 356 }, | 290 }, |
| 357 | 291 |
| 358 /** | 292 /** |
| 359 * Removes a file, will call callback when done. | 293 * Removes a file, will call callback when done. |
| 360 */ | 294 */ |
| 361 removeFile: function(/**nsIFile*/ file, /**Function*/ callback) | 295 removeFile: function(/**nsIFile*/ file, /**Function*/ callback) |
| 362 { | 296 { |
| 363 try | 297 try |
| 364 { | 298 { |
| 365 file.remove(false); | 299 let promise = OS.File.remove(file.path); |
| 366 callback(null); | 300 promise.then(callback.bind(null, null), callback); |
| 367 } | 301 } |
| 368 catch(e) | 302 catch(e) |
| 369 { | 303 { |
| 370 callback(e); | 304 callback(e); |
| 371 } | 305 } |
| 372 }, | 306 }, |
| 373 | 307 |
| 374 /** | 308 /** |
| 375 * Gets file information such as whether the file exists. | 309 * Gets file information such as whether the file exists. |
| 376 */ | 310 */ |
| 377 statFile: function(/**nsIFile*/ file, /**Function*/ callback) | 311 statFile: function(/**nsIFile*/ file, /**Function*/ callback) |
| 378 { | 312 { |
| 379 try | 313 try |
| 380 { | 314 { |
| 381 let exists = file.exists(); | 315 let promise = OS.File.stat(file.path); |
| 382 callback(null, { | 316 promise.then(function onSuccess(info) |
| 383 exists: exists, | 317 { |
| 384 isDirectory: exists && file.isDirectory(), | 318 callback(null, { |
| 385 isFile: exists && file.isFile(), | 319 exists: true, |
| 386 lastModified: exists ? file.lastModifiedTime : 0 | 320 isDirectory: info.isDir, |
| 321 isFile: !info.isDir, |
| 322 lastModified: info.lastModificationDate.getTime() |
| 323 }); |
| 324 }, function onError(e) |
| 325 { |
| 326 if (e.becauseNoSuchFile) |
| 327 { |
| 328 callback(null, { |
| 329 exists: false, |
| 330 isDirectory: false, |
| 331 isFile: false, |
| 332 lastModified: 0 |
| 333 }) |
| 334 } |
| 335 else |
| 336 callback(e); |
| 387 }); | 337 }); |
| 388 } | 338 } |
| 389 catch(e) | 339 catch(e) |
| 390 { | 340 { |
| 391 callback(e); | 341 callback(e); |
| 392 } | 342 } |
| 393 } | 343 } |
| 394 } | 344 } |
| OLD | NEW |