| 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 | 
|---|