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 |