Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/io.js

Issue 6726956523454464: Issue 153 - Reimplement io.js based on OS.File API (Closed)
Patch Set: Created March 21, 2014, 1:10 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/filterStorage.js ('k') | metadata.gecko » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 })
Felix Dahlke 2014/03/21 15:44:27 Missing semicolon.
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 }
OLDNEW
« no previous file with comments | « lib/filterStorage.js ('k') | metadata.gecko » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld