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

Side by Side Diff: lib/io.js

Issue 29408710: Issue 5050 - Make legacy extension use WebExtensions I/O (Closed) Base URL: https://hg.adblockplus.org/adblockplus
Patch Set: Created April 10, 2017, 11:23 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2017 eyeo GmbH 3 * Copyright (C) 2006-2017 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 "use strict";
19 * @fileOverview Module containing file I/O helpers.
20 */
21 19
22 let {Services} = Cu.import("resource://gre/modules/Services.jsm", null); 20 let {IO: LegacyIO} = require("legacyIO");
23 let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", null);
24 let {OS} = Cu.import("resource://gre/modules/osfile.jsm", null);
25 let {Task} = Cu.import("resource://gre/modules/Task.jsm", null);
26
27 let {Prefs} = require("prefs");
28 let {Utils} = require("utils"); 21 let {Utils} = require("utils");
29 22
30 let firstRead = true; 23 let webextension = require("webextension");
31 const BUFFER_SIZE = 0x80000; // 512kB 24 let messageID = 0;
25 let messageCallbacks = new Map();
32 26
33 let IO = exports.IO = 27 webextension.then(port =>
34 { 28 {
35 /** 29 port.onMessage.addListener(message =>
36 * Retrieves the platform-dependent line break string.
37 */
38 get lineBreak()
39 { 30 {
40 let lineBreak = (Services.appinfo.OS == "WINNT" ? "\r\n" : "\n"); 31 let {id} = message;
41 Object.defineProperty(this, "lineBreak", {value: lineBreak}); 32 let callbacks = messageCallbacks.get(id);
42 return lineBreak; 33 if (callbacks)
43 }, 34 {
35 messageCallbacks.delete(id);
44 36
45 /** 37 if (message.success)
46 * Tries to interpret a file path as an absolute path or a path relative to 38 callbacks.resolve(message.result);
47 * user's profile. Returns a file or null on failure. 39 else
48 */ 40 callbacks.reject(message.result);
49 resolveFilePath: function(/**String*/ path) /**nsIFile*/ 41 }
42 });
43 });
44
45 function callWebExt(method, ...args)
46 {
47 return webextension.then(port =>
50 { 48 {
51 if (!path) 49 return new Promise((resolve, reject) =>
52 return null; 50 {
51 let id = ++messageID;
52 messageCallbacks.set(id, {resolve, reject});
53 port.postMessage({id, method, args});
54 });
55 });
56 }
53 57
54 try { 58 function attachCallback(promise, callback, fallback)
55 // Assume an absolute path first 59 {
56 return new FileUtils.File(path); 60 promise.then(result =>
57 } catch (e) {} 61 {
62 callback(null, result);
63 }).catch(error =>
64 {
65 if (fallback && error == "NoSuchFile")
66 fallback();
67 else
68 callback(error);
69 });
70 }
58 71
59 try { 72 exports.IO =
60 // Try relative path now 73 {
61 return FileUtils.getFile("ProfD", path.split("/")); 74 resolveFilePath: LegacyIO.resolveFilePath,
62 } catch (e) {}
63
64 return null;
65 },
66 75
67 /** 76 /**
68 * Reads strings from a file asynchronously, calls listener.process() with 77 * Reads strings from a file asynchronously, calls listener.process() with
69 * each line read and with a null parameter once the read operation is done. 78 * each line read and with a null parameter once the read operation is done.
70 * The callback will be called when the operation is done. 79 * The callback will be called when the operation is done.
71 */ 80 */
72 readFromFile: function(/**nsIFile*/ file, /**Object*/ listener, /**Function*/ callback) 81 readFromFile(/**nsIFile*/ file, /**Object*/ listener, /**Function*/ callback)
73 { 82 {
74 try 83 attachCallback(
75 { 84 callWebExt("readFromFile", file.leafName).then(contents =>
76 let processing = false; 85 {
77 let buffer = ""; 86 return new Promise((resolve, reject) =>
78 let loaded = false; 87 {
79 let error = null; 88 let lineIndex = 0;
80 89
81 let onProgress = function*(data) 90 function processBatch()
82 {
83 let index = (processing ? -1 : Math.max(data.lastIndexOf("\n"), data.las tIndexOf("\r")));
84 if (index >= 0)
85 {
86 // Protect against reentrance in case the listener processes events.
87 processing = true;
88 try
89 { 91 {
90 let oldBuffer = buffer; 92 while (lineIndex < contents.length)
91 buffer = data.substr(index + 1);
92 data = data.substr(0, index + 1);
93 let lines = data.split(/[\r\n]+/);
94 lines.pop();
95 lines[0] = oldBuffer + lines[0];
96 for (let i = 0; i < lines.length; i++)
97 { 93 {
98 let promise = listener.process(lines[i]); 94 listener.process(contents[lineIndex++]);
99 if (promise) 95 if (lineIndex % 1000 == 0)
100 yield promise; 96 {
101 } 97 Utils.runAsync(processBatch);
102 } 98 return;
103 finally 99 }
104 {
105 processing = false;
106 data = buffer;
107 buffer = "";
108 yield* onProgress(data);
109
110 if (loaded)
111 {
112 loaded = false;
113 onSuccess();
114 } 100 }
115 101
116 if (error) 102 listener.process(null);
117 { 103 resolve();
118 let param = error;
119 error = null;
120 onError(param);
121 }
122 } 104 }
123 }
124 else
125 buffer += data;
126 };
127 105
128 let onSuccess = function() 106 processBatch();
Wladimir Palant 2017/04/10 11:35:17 The files are processed in batches of 1000 lines i
129 { 107 });
130 if (processing) 108 }),
131 { 109 callback,
132 // Still processing data, delay processing this event. 110 () => LegacyIO.readFromFile(file, listener, callback)
133 loaded = true; 111 );
134 return;
135 }
136
137 // We are ignoring return value of listener.process() here because
138 // turning this callback into a generator would be complicated, and
139 // delaying isn't really necessary for the last two calls.
140 if (buffer !== "")
141 listener.process(buffer);
142 listener.process(null);
143
144 callback(null);
145 };
146
147 let onError = function(e)
148 {
149 if (processing)
150 {
151 // Still processing data, delay processing this event.
152 error = e;
153 return;
154 }
155
156 callback(e);
157 };
158
159 let decoder = new TextDecoder();
160 Task.spawn(function*()
161 {
162 if (firstRead && Services.vc.compare(Utils.platformVersion, "23.0a1") <= 0)
163 {
164 // See https://issues.adblockplus.org/ticket/530 - the first file
165 // opened cannot be closed due to Gecko bug 858723. Make sure that
166 // our patterns.ini file doesn't stay locked by opening a dummy file
167 // first.
168 try
169 {
170 let dummyPath = IO.resolveFilePath(Prefs.data_directory + "/dummy"). path;
171 let dummy = yield OS.File.open(dummyPath, {write: true, truncate: tr ue});
172 yield dummy.close();
173 }
174 catch (e)
175 {
176 // Dummy might be locked already, we don't care
177 }
178 }
179 firstRead = false;
180
181 let f = yield OS.File.open(file.path, {read: true});
182 while (true)
183 {
184 let array = yield f.read(BUFFER_SIZE);
185 if (!array.length)
186 break;
187
188 let data = decoder.decode(array, {stream: true});
189 yield* onProgress(data);
190 }
191 yield f.close();
192 }.bind(this)).then(onSuccess, onError);
193 }
194 catch (e)
195 {
196 callback(e);
197 }
198 }, 112 },
199 113
200 /** 114 /**
201 * Writes string data to a file in UTF-8 format asynchronously. The callback 115 * Writes string data to a file in UTF-8 format asynchronously. The callback
202 * will be called when the write operation is done. 116 * will be called when the write operation is done.
203 */ 117 */
204 writeToFile: function(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ cal lback) 118 writeToFile(/**nsIFile*/ file, /**Iterator*/ data, /**Function*/ callback)
205 { 119 {
206 try 120 attachCallback(
207 { 121 callWebExt("writeToFile", file.leafName, Array.from(data)),
208 let encoder = new TextEncoder(); 122 callback
209 123 );
210 Task.spawn(function*()
211 {
212 // This mimics OS.File.writeAtomic() but writes in chunks.
213 let tmpPath = file.path + ".tmp";
214 let f = yield OS.File.open(tmpPath, {write: true, truncate: true});
215
216 let buf = [];
217 let bufLen = 0;
218 let lineBreak = this.lineBreak;
219
220 function writeChunk()
221 {
222 let array = encoder.encode(buf.join(lineBreak) + lineBreak);
223 buf = [];
224 bufLen = 0;
225 return f.write(array);
226 }
227
228 for (let line of data)
229 {
230 buf.push(line);
231 bufLen += line.length;
232 if (bufLen >= BUFFER_SIZE)
233 yield writeChunk();
234 }
235
236 if (bufLen)
237 yield writeChunk();
238
239 // OS.File.flush() isn't exposed prior to Gecko 27, see bug 912457.
240 if (typeof f.flush == "function")
241 yield f.flush();
242 yield f.close();
243 yield OS.File.move(tmpPath, file.path, {noCopy: true});
244 }.bind(this)).then(callback.bind(null, null), callback);
245 }
246 catch (e)
247 {
248 callback(e);
249 }
250 }, 124 },
251 125
252 /** 126 /**
253 * Copies a file asynchronously. The callback will be called when the copy 127 * Copies a file asynchronously. The callback will be called when the copy
254 * operation is done. 128 * operation is done.
255 */ 129 */
256 copyFile: function(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ c allback) 130 copyFile(/**nsIFile*/ fromFile, /**nsIFile*/ toFile, /**Function*/ callback)
257 { 131 {
258 try 132 attachCallback(
259 { 133 callWebExt("copyFile", fromFile.leafName, toFile.leafName),
260 let promise = OS.File.copy(fromFile.path, toFile.path); 134 callback,
261 promise.then(callback.bind(null, null), callback); 135 () => LegacyIO.copyFile(fromFile, toFile, callback)
262 } 136 );
263 catch (e)
264 {
265 callback(e);
266 }
267 }, 137 },
268 138
269 /** 139 /**
270 * Renames a file within the same directory, will call callback when done. 140 * Renames a file within the same directory, will call callback when done.
271 */ 141 */
272 renameFile: function(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback) 142 renameFile(/**nsIFile*/ fromFile, /**String*/ newName, /**Function*/ callback)
273 { 143 {
274 try 144 attachCallback(
275 { 145 callWebExt("renameFile", fromFile.leafName, newName),
276 let toFile = fromFile.clone(); 146 callback,
277 toFile.leafName = newName; 147 () => LegacyIO.renameFile(fromFile, newName, callback)
278 let promise = OS.File.move(fromFile.path, toFile.path); 148 );
279 promise.then(callback.bind(null, null), callback);
280 }
281 catch(e)
282 {
283 callback(e);
284 }
285 }, 149 },
286 150
287 /** 151 /**
288 * Removes a file, will call callback when done. 152 * Removes a file, will call callback when done.
289 */ 153 */
290 removeFile: function(/**nsIFile*/ file, /**Function*/ callback) 154 removeFile(/**nsIFile*/ file, /**Function*/ callback)
291 { 155 {
292 try 156 attachCallback(
293 { 157 callWebExt("removeFile", file.leafName),
294 let promise = OS.File.remove(file.path); 158 callback,
295 promise.then(callback.bind(null, null), callback); 159 () => LegacyIO.removeFile(file, callback)
296 } 160 );
297 catch(e)
298 {
299 callback(e);
300 }
301 }, 161 },
302 162
303 /** 163 /**
304 * Gets file information such as whether the file exists. 164 * Gets file information such as whether the file exists.
305 */ 165 */
306 statFile: function(/**nsIFile*/ file, /**Function*/ callback) 166 statFile(/**nsIFile*/ file, /**Function*/ callback)
307 { 167 {
308 try 168 attachCallback(
309 { 169 callWebExt("statFile", file.leafName),
310 let promise = OS.File.stat(file.path); 170 callback,
311 promise.then(function onSuccess(info) 171 () => LegacyIO.statFile(file, callback)
312 { 172 );
313 callback(null, {
314 exists: true,
315 isDirectory: info.isDir,
316 isFile: !info.isDir,
317 lastModified: info.lastModificationDate.getTime()
318 });
319 }, function onError(e)
320 {
321 if (e.becauseNoSuchFile)
322 {
323 callback(null, {
324 exists: false,
325 isDirectory: false,
326 isFile: false,
327 lastModified: 0
328 });
329 }
330 else
331 callback(e);
332 });
333 }
334 catch(e)
335 {
336 callback(e);
337 }
338 } 173 }
339 } 174 };
OLDNEW

Powered by Google App Engine
This is Rietveld