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

Delta Between Two Patch Sets: lib/filterStorage.js

Issue 29899601: Issue 7015 - Serialize subscriptions for *exportData in one loop (Closed)
Left Patch Set: Rebase Created Oct. 3, 2018, 4:28 p.m.
Right Patch Set: Rebase Created Dec. 9, 2018, 4:37 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
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-present eyeo GmbH 3 * Copyright (C) 2006-present 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 "use strict"; 18 "use strict";
19 19
20 /** 20 /**
21 * @fileOverview FilterStorage class responsible for managing user's 21 * @fileOverview <code>filterStorage</code> object responsible for managing the
22 * subscriptions and filters. 22 * user's subscriptions and filters.
23 */ 23 */
24 24
25 const {IO} = require("io"); 25 const {IO} = require("io");
26 const {Prefs} = require("prefs"); 26 const {Prefs} = require("prefs");
27 const {Filter, ActiveFilter} = require("./filterClasses"); 27 const {Filter, ActiveFilter} = require("./filterClasses");
28 const {Subscription, SpecialSubscription, 28 const {Subscription, SpecialSubscription,
29 ExternalSubscription} = require("./subscriptionClasses"); 29 ExternalSubscription} = require("./subscriptionClasses");
30 const {filterNotifier} = require("./filterNotifier"); 30 const {filterNotifier} = require("./filterNotifier");
31 const {INIParser} = require("./iniParser"); 31 const {INIParser} = require("./iniParser");
32 32
33 /** 33 /**
34 * Version number of the filter storage file format. 34 * Version number of the filter storage file format.
35 * @type {number} 35 * @type {number}
36 */ 36 */
37 let formatVersion = 5; 37 const FORMAT_VERSION = 5;
38 38
39 /** 39 /**
40 * This class reads user's filters from disk, manages them in memory 40 * {@link filterStorage} implementation.
41 * and writes them back.
42 * @class
43 */ 41 */
44 let FilterStorage = exports.FilterStorage = 42 class FilterStorage
45 { 43 {
46 /** 44 /**
47 * Will be set to true after the initial loadFromDisk() call completes. 45 * @hideconstructor
48 * @type {boolean} 46 */
49 */ 47 constructor()
50 initialized: false, 48 {
51 49 /**
52 /** 50 * Will be set to true after the initial {@link FilterStorage#loadFromDisk}
53 * Version number of the patterns.ini format used. 51 * call completes.
52 * @type {boolean}
53 */
54 this.initialized = false;
55
56 /**
57 * Will be set to <code>true</code> if no <code>patterns.ini</code> file
58 * exists.
59 * @type {boolean}
60 */
61 this.firstRun = false;
62
63 /**
64 * Map of properties listed in the filter storage file before the sections
65 * start. Right now this should be only the format version.
66 * @type {object}
67 */
68 this.fileProperties = Object.create(null);
69
70 /**
71 * Map of subscriptions already on the list, by their URL/identifier.
72 * @type {Map.<string,Subscription>}
73 */
74 this.knownSubscriptions = new Map();
75
76 /**
77 * Will be set to true if {@link FilterStorage#saveToDisk} is running
78 * (reentrance protection).
79 * @type {boolean}
80 * @private
81 */
82 this._saving = false;
83
84 /**
85 * Will be set to true if a {@link FilterStorage#saveToDisk} call arrives
86 * while {@link FilterStorage#saveToDisk} is already running (delayed
87 * execution).
88 * @type {boolean}
89 * @private
90 */
91 this._needsSave = false;
92 }
93
94 /**
95 * The version number of the <code>patterns.ini</code> format used.
54 * @type {number} 96 * @type {number}
55 */ 97 */
56 get formatVersion() 98 get formatVersion()
57 { 99 {
58 return formatVersion; 100 return FORMAT_VERSION;
59 }, 101 }
60 102
61 /** 103 /**
62 * File containing the filter list 104 * The file containing the subscriptions.
63 * @type {string} 105 * @type {string}
64 */ 106 */
65 get sourceFile() 107 get sourceFile()
66 { 108 {
67 return "patterns.ini"; 109 return "patterns.ini";
68 }, 110 }
69 111
70 /** 112 /**
71 * Will be set to true if no patterns.ini file exists. 113 * Yields all subscriptions in the storage.
72 * @type {boolean}
73 */
74 firstRun: false,
75
76 /**
77 * Map of properties listed in the filter storage file before the sections
78 * start. Right now this should be only the format version.
79 */
80 fileProperties: Object.create(null),
81
82 /**
83 * Yields subscriptions containing all filters
84 * @yields {Subscription} 114 * @yields {Subscription}
85 */ 115 */
86 *subscriptions() 116 *subscriptions()
87 { 117 {
88 yield* this.knownSubscriptions.values(); 118 yield* this.knownSubscriptions.values();
89 }, 119 }
90 120
91 /** 121 /**
92 * Number of known subscriptions. 122 * The number of subscriptions in the storage.
93 * @type {number} 123 * @type {number}
94 */ 124 */
95 get subscriptionCount() 125 get subscriptionCount()
96 { 126 {
97 return this.knownSubscriptions.size; 127 return this.knownSubscriptions.size;
98 }, 128 }
99
100 /**
101 * Map of subscriptions already on the list, by their URL/identifier
102 * @type {Map.<string,Subscription>}
103 */
104 knownSubscriptions: new Map(),
105 129
106 /** 130 /**
107 * Finds the filter group that a filter should be added to by default. Will 131 * Finds the filter group that a filter should be added to by default. Will
108 * return null if this group doesn't exist yet. 132 * return <code>null</code> if this group doesn't exist yet.
109 * @param {Filter} filter 133 * @param {Filter} filter
110 * @return {?SpecialSubscription} 134 * @returns {?SpecialSubscription}
111 */ 135 */
112 getGroupForFilter(filter) 136 getGroupForFilter(filter)
113 { 137 {
114 let generalSubscription = null; 138 let generalSubscription = null;
115 for (let subscription of FilterStorage.knownSubscriptions.values()) 139 for (let subscription of this.knownSubscriptions.values())
116 { 140 {
117 if (subscription instanceof SpecialSubscription && !subscription.disabled) 141 if (subscription instanceof SpecialSubscription && !subscription.disabled)
118 { 142 {
119 // Always prefer specialized subscriptions 143 // Always prefer specialized subscriptions
120 if (subscription.isDefaultFor(filter)) 144 if (subscription.isDefaultFor(filter))
121 return subscription; 145 return subscription;
122 146
123 // If this is a general subscription - store it as fallback 147 // If this is a general subscription - store it as fallback
124 if (!generalSubscription && 148 if (!generalSubscription &&
125 (!subscription.defaults || !subscription.defaults.length)) 149 (!subscription.defaults || !subscription.defaults.length))
126 { 150 {
127 generalSubscription = subscription; 151 generalSubscription = subscription;
128 } 152 }
129 } 153 }
130 } 154 }
131 return generalSubscription; 155 return generalSubscription;
132 }, 156 }
133 157
134 /** 158 /**
135 * Adds a filter subscription to the list 159 * Adds a subscription to the storage.
136 * @param {Subscription} subscription filter subscription to be added 160 * @param {Subscription} subscription The subscription to be added.
137 */ 161 */
138 addSubscription(subscription) 162 addSubscription(subscription)
139 { 163 {
140 if (FilterStorage.knownSubscriptions.has(subscription.url)) 164 if (this.knownSubscriptions.has(subscription.url))
141 return; 165 return;
142 166
143 FilterStorage.knownSubscriptions.set(subscription.url, subscription); 167 this.knownSubscriptions.set(subscription.url, subscription);
144 addSubscriptionFilters(subscription); 168 connectSubscriptionFilters(subscription);
145 169
146 filterNotifier.emit("subscription.added", subscription); 170 filterNotifier.emit("subscription.added", subscription);
147 }, 171 }
148 172
149 /** 173 /**
150 * Removes a filter subscription from the list 174 * Removes a subscription from the storage.
151 * @param {Subscription} subscription filter subscription to be removed 175 * @param {Subscription} subscription The subscription to be removed.
152 */ 176 */
153 removeSubscription(subscription) 177 removeSubscription(subscription)
154 { 178 {
155 if (!FilterStorage.knownSubscriptions.has(subscription.url)) 179 if (!this.knownSubscriptions.has(subscription.url))
156 return; 180 return;
157 181
158 removeSubscriptionFilters(subscription); 182 disconnectSubscriptionFilters(subscription);
159 183
160 FilterStorage.knownSubscriptions.delete(subscription.url); 184 this.knownSubscriptions.delete(subscription.url);
161 185
162 // This should be the last remaining reference to the Subscription 186 // This should be the last remaining reference to the Subscription
163 // object. 187 // object.
164 Subscription.knownSubscriptions.delete(subscription.url); 188 Subscription.knownSubscriptions.delete(subscription.url);
165 189
166 filterNotifier.emit("subscription.removed", subscription); 190 filterNotifier.emit("subscription.removed", subscription);
167 }, 191 }
168 192
169 /** 193 /**
170 * Replaces the list of filters in a subscription by a new list 194 * Replaces the list of filters in a subscription with a new list.
171 * @param {Subscription} subscription filter subscription to be updated 195 * @param {Subscription} subscription The subscription to be updated.
172 * @param {Filter[]} filters new filter list 196 * @param {Array.<Filter>} filters The new list of filters.
173 */ 197 */
174 updateSubscriptionFilters(subscription, filters) 198 updateSubscriptionFilters(subscription, filters)
175 { 199 {
176 removeSubscriptionFilters(subscription); 200 let oldFilters = [...subscription.filters()];
177 let oldFilters = subscription.filters; 201 disconnectSubscriptionFilters(subscription, oldFilters);
178 subscription.filters = filters; 202 subscription.clearFilters();
179 addSubscriptionFilters(subscription); 203
204 for (let filter of filters)
205 subscription.addFilter(filter);
206
207 connectSubscriptionFilters(subscription, filters);
208
180 filterNotifier.emit("subscription.updated", subscription, oldFilters); 209 filterNotifier.emit("subscription.updated", subscription, oldFilters);
181 }, 210 }
182 211
183 /** 212 /**
184 * Adds a user-defined filter to the list 213 * Adds a user-defined filter to the storage.
185 * @param {Filter} filter 214 * @param {Filter} filter
186 * @param {SpecialSubscription} [subscription] 215 * @param {?SpecialSubscription} [subscription] The subscription that the
187 * particular group that the filter should be added to 216 * filter should be added to.
188 * @param {number} [position] 217 * @param {number} [position] The position within the subscription at which
189 * position within the subscription at which the filter should be added 218 * the filter should be added. If not specified, the filter is added at the
219 * end of the subscription.
190 */ 220 */
191 addFilter(filter, subscription, position) 221 addFilter(filter, subscription, position)
192 { 222 {
193 if (!subscription) 223 if (!subscription)
194 { 224 {
195 for (let currentSubscription of filter.subscriptions()) 225 for (let currentSubscription of filter.subscriptions())
196 { 226 {
197 if (currentSubscription instanceof SpecialSubscription && 227 if (currentSubscription instanceof SpecialSubscription &&
198 !currentSubscription.disabled) 228 !currentSubscription.disabled)
199 { 229 {
200 return; // No need to add 230 return; // No need to add
201 } 231 }
202 } 232 }
203 subscription = FilterStorage.getGroupForFilter(filter); 233 subscription = this.getGroupForFilter(filter);
204 } 234 }
205 if (!subscription) 235 if (!subscription)
206 { 236 {
207 // No group for this filter exists, create one 237 // No group for this filter exists, create one
208 subscription = SpecialSubscription.createForFilter(filter); 238 subscription = SpecialSubscription.createForFilter(filter);
209 this.addSubscription(subscription); 239 this.addSubscription(subscription);
210 return; 240 return;
211 } 241 }
212 242
213 if (typeof position == "undefined") 243 if (typeof position == "undefined")
214 position = subscription.filters.length; 244 position = subscription.filterCount;
215 245
216 filter.addSubscription(subscription); 246 filter.addSubscription(subscription);
217 subscription.filters.splice(position, 0, filter); 247 subscription.insertFilterAt(filter, position);
218 filterNotifier.emit("filter.added", filter, subscription, position); 248 filterNotifier.emit("filter.added", filter, subscription, position);
219 }, 249 }
220 250
221 /** 251 /**
222 * Removes a user-defined filter from the list 252 * Removes a user-defined filter from the storage.
223 * @param {Filter} filter 253 * @param {Filter} filter
224 * @param {SpecialSubscription} [subscription] a particular filter group that 254 * @param {?SpecialSubscription} [subscription] The subscription that the
225 * the filter should be removed from (if ommited will be removed from all 255 * filter should be removed from. If not specified, the filter will be
226 * subscriptions) 256 * removed from all subscriptions.
227 * @param {number} [position] position inside the filter group at which the 257 * @param {number} [position] The position within the subscription at which
228 * filter should be removed (if ommited all instances will be removed) 258 * the filter should be removed. If not specified, all instances of the
259 * filter will be removed.
229 */ 260 */
230 removeFilter(filter, subscription, position) 261 removeFilter(filter, subscription, position)
231 { 262 {
232 let subscriptions = ( 263 let subscriptions = (
233 subscription ? [subscription] : filter.subscriptions() 264 subscription ? [subscription] : filter.subscriptions()
234 ); 265 );
235 for (let currentSubscription of subscriptions) 266 for (let currentSubscription of subscriptions)
236 { 267 {
237 if (currentSubscription instanceof SpecialSubscription) 268 if (currentSubscription instanceof SpecialSubscription)
238 { 269 {
239 let positions = []; 270 let positions = [];
240 if (typeof position == "undefined") 271 if (typeof position == "undefined")
241 { 272 {
242 let index = -1; 273 let index = -1;
243 do 274 do
244 { 275 {
245 index = currentSubscription.filters.indexOf(filter, index + 1); 276 index = currentSubscription.searchFilter(filter, index + 1);
246 if (index >= 0) 277 if (index >= 0)
247 positions.push(index); 278 positions.push(index);
248 } while (index >= 0); 279 } while (index >= 0);
249 } 280 }
250 else 281 else
251 positions.push(position); 282 positions.push(position);
252 283
253 for (let j = positions.length - 1; j >= 0; j--) 284 for (let j = positions.length - 1; j >= 0; j--)
254 { 285 {
255 let currentPosition = positions[j]; 286 let currentPosition = positions[j];
256 if (currentSubscription.filters[currentPosition] == filter) 287 let currentFilter = currentSubscription.filterAt(currentPosition);
288 if (currentFilter && currentFilter.text == filter.text)
257 { 289 {
258 currentSubscription.filters.splice(currentPosition, 1); 290 currentSubscription.deleteFilterAt(currentPosition);
259 if (currentSubscription.filters.indexOf(filter) < 0) 291 if (currentSubscription.searchFilter(filter) < 0)
260 filter.removeSubscription(currentSubscription); 292 filter.removeSubscription(currentSubscription);
261 filterNotifier.emit("filter.removed", filter, currentSubscription, 293 filterNotifier.emit("filter.removed", filter, currentSubscription,
262 currentPosition); 294 currentPosition);
263 } 295 }
264 } 296 }
265 } 297 }
266 } 298 }
267 }, 299 }
268 300
269 /** 301 /**
270 * Moves a user-defined filter to a new position 302 * Moves a user-defined filter to a new position.
271 * @param {Filter} filter 303 * @param {Filter} filter
272 * @param {SpecialSubscription} subscription filter group where the filter is 304 * @param {SpecialSubscription} subscription The subscription where the
273 * located 305 * filter is located.
274 * @param {number} oldPosition current position of the filter 306 * @param {number} oldPosition The current position of the filter.
275 * @param {number} newPosition new position of the filter 307 * @param {number} newPosition The new position of the filter.
276 */ 308 */
277 moveFilter(filter, subscription, oldPosition, newPosition) 309 moveFilter(filter, subscription, oldPosition, newPosition)
278 { 310 {
279 if (!(subscription instanceof SpecialSubscription) || 311 if (!(subscription instanceof SpecialSubscription))
280 subscription.filters[oldPosition] != filter) 312 return;
281 { 313
282 return; 314 let currentFilter = subscription.filterAt(oldPosition);
283 } 315 if (!currentFilter || currentFilter.text != filter.text)
316 return;
284 317
285 newPosition = Math.min(Math.max(newPosition, 0), 318 newPosition = Math.min(Math.max(newPosition, 0),
286 subscription.filters.length - 1); 319 subscription.filterCount - 1);
287 if (oldPosition == newPosition) 320 if (oldPosition == newPosition)
288 return; 321 return;
289 322
290 subscription.filters.splice(oldPosition, 1); 323 subscription.deleteFilterAt(oldPosition);
291 subscription.filters.splice(newPosition, 0, filter); 324 subscription.insertFilterAt(filter, newPosition);
292 filterNotifier.emit("filter.moved", filter, subscription, oldPosition, 325 filterNotifier.emit("filter.moved", filter, subscription, oldPosition,
293 newPosition); 326 newPosition);
294 }, 327 }
295 328
296 /** 329 /**
297 * Increases the hit count for a filter by one 330 * Increases the hit count for a filter by one.
298 * @param {Filter} filter 331 * @param {Filter} filter
299 */ 332 */
300 increaseHitCount(filter) 333 increaseHitCount(filter)
301 { 334 {
302 if (!Prefs.savestats || !(filter instanceof ActiveFilter)) 335 if (!Prefs.savestats || !(filter instanceof ActiveFilter))
303 return; 336 return;
304 337
305 filter.hitCount++; 338 filter.hitCount++;
306 filter.lastHit = Date.now(); 339 filter.lastHit = Date.now();
307 }, 340 }
308 341
309 /** 342 /**
310 * Resets hit count for some filters 343 * Resets hit count for some filters.
311 * @param {Filter[]} filters filters to be reset, if null all filters will 344 * @param {?Array.<Filter>} [filters] The filters to be reset. If not
312 * be reset 345 * specified, all filters will be reset.
313 */ 346 */
314 resetHitCounts(filters) 347 resetHitCounts(filters)
315 { 348 {
316 if (!filters) 349 if (!filters)
317 filters = Filter.knownFilters.values(); 350 filters = Filter.knownFilters.values();
318 for (let filter of filters) 351 for (let filter of filters)
319 { 352 {
320 filter.hitCount = 0; 353 filter.hitCount = 0;
321 filter.lastHit = 0; 354 filter.lastHit = 0;
322 } 355 }
323 }, 356 }
324 357
325 /** 358 /**
326 * @callback TextSink 359 * @callback TextSink
327 * @param {string?} line 360 * @param {string?} line
328 */ 361 */
329 362
330 /** 363 /**
331 * Allows importing previously serialized filter data. 364 * Allows importing previously serialized filter data.
332 * @param {boolean} silent 365 * @param {boolean} silent If <code>true</code>, no "load" notification will
333 * If true, no "load" notification will be sent out. 366 * be sent out.
334 * @return {TextSink} 367 * @returns {TextSink} The function to be called for each line of data.
335 * Function to be called for each line of data. Calling it with null as 368 * Calling it with <code>null</code> as the argument finalizes the import
336 * parameter finalizes the import and replaces existing data. No changes 369 * and replaces existing data. No changes will be applied before
337 * will be applied before finalization, so import can be "aborted" by 370 * finalization, so import can be "aborted" by forgetting this callback.
338 * forgetting this callback.
339 */ 371 */
340 importData(silent) 372 importData(silent)
341 { 373 {
342 let parser = new INIParser(); 374 let parser = new INIParser();
343 return line => 375 return line =>
344 { 376 {
345 parser.process(line); 377 parser.process(line);
346 if (line === null) 378 if (line === null)
347 { 379 {
348 let knownSubscriptions = new Map(); 380 let knownSubscriptions = new Map();
349 for (let subscription of parser.subscriptions) 381 for (let subscription of parser.subscriptions)
350 knownSubscriptions.set(subscription.url, subscription); 382 knownSubscriptions.set(subscription.url, subscription);
351 383
352 this.fileProperties = parser.fileProperties; 384 this.fileProperties = parser.fileProperties;
353 this.knownSubscriptions = knownSubscriptions; 385 this.knownSubscriptions = knownSubscriptions;
354 Filter.knownFilters = parser.knownFilters; 386 Filter.knownFilters = parser.knownFilters;
355 Subscription.knownSubscriptions = parser.knownSubscriptions; 387 Subscription.knownSubscriptions = parser.knownSubscriptions;
356 388
357 if (!silent) 389 if (!silent)
358 filterNotifier.emit("load"); 390 filterNotifier.emit("load");
359 } 391 }
360 }; 392 };
361 }, 393 }
362 394
363 /** 395 /**
364 * Loads all subscriptions from the disk. 396 * Loads all subscriptions from disk.
365 * @return {Promise} promise resolved or rejected when loading is complete 397 * @returns {Promise} A promise resolved or rejected when loading is complete.
366 */ 398 */
367 loadFromDisk() 399 loadFromDisk()
368 { 400 {
369 let tryBackup = backupIndex => 401 let tryBackup = backupIndex =>
370 { 402 {
371 return this.restoreBackup(backupIndex, true).then(() => 403 return this.restoreBackup(backupIndex, true).then(() =>
372 { 404 {
373 if (this.knownSubscriptions.size == 0) 405 if (this.knownSubscriptions.size == 0)
374 return tryBackup(backupIndex + 1); 406 return tryBackup(backupIndex + 1);
375 }).catch(error => 407 }).catch(error =>
(...skipping 22 matching lines...) Expand all
398 }); 430 });
399 }).catch(error => 431 }).catch(error =>
400 { 432 {
401 Cu.reportError(error); 433 Cu.reportError(error);
402 return tryBackup(1); 434 return tryBackup(1);
403 }).then(() => 435 }).then(() =>
404 { 436 {
405 this.initialized = true; 437 this.initialized = true;
406 filterNotifier.emit("load"); 438 filterNotifier.emit("load");
407 }); 439 });
408 }, 440 }
409 441
410 /** 442 /**
411 * Constructs the file name for a patterns.ini backup. 443 * Constructs the file name for a <code>patterns.ini</code> backup.
412 * @param {number} backupIndex 444 * @param {number} backupIndex Number of the backup file (1 being the most
413 * number of the backup file (1 being the most recent) 445 * recent).
414 * @return {string} backup file name 446 * @returns {string} Backup file name.
415 */ 447 */
416 getBackupName(backupIndex) 448 getBackupName(backupIndex)
417 { 449 {
418 let [name, extension] = this.sourceFile.split(".", 2); 450 let [name, extension] = this.sourceFile.split(".", 2);
419 return (name + "-backup" + backupIndex + "." + extension); 451 return (name + "-backup" + backupIndex + "." + extension);
420 }, 452 }
421 453
422 /** 454 /**
423 * Restores an automatically created backup. 455 * Restores an automatically created backup.
424 * @param {number} backupIndex 456 * @param {number} backupIndex Number of the backup to restore (1 being the
425 * number of the backup to restore (1 being the most recent) 457 * most recent).
426 * @param {boolean} silent 458 * @param {boolean} silent If <code>true</code>, no "load" notification will
427 * If true, no "load" notification will be sent out. 459 * be sent out.
428 * @return {Promise} promise resolved or rejected when restoring is complete 460 * @returns {Promise} A promise resolved or rejected when restoration is
461 * complete.
429 */ 462 */
430 restoreBackup(backupIndex, silent) 463 restoreBackup(backupIndex, silent)
431 { 464 {
432 let backupFile = this.getBackupName(backupIndex); 465 let backupFile = this.getBackupName(backupIndex);
433 let parser = this.importData(silent); 466 let parser = this.importData(silent);
434 return IO.readFromFile(backupFile, parser).then(() => 467 return IO.readFromFile(backupFile, parser).then(() =>
435 { 468 {
436 parser(null); 469 parser(null);
437 return this.saveToDisk(); 470 return this.saveToDisk();
438 }); 471 });
439 }, 472 }
440 473
441 /** 474 /**
442 * Generator serializing filter data and yielding it line by line. 475 * Generator serializing filter data and yielding it line by line.
476 * @yields {string}
443 */ 477 */
444 *exportData() 478 *exportData()
445 { 479 {
446 yield "# Adblock Plus preferences"; 480 yield "# Adblock Plus preferences";
447 yield "version=" + formatVersion; 481 yield "version=" + this.formatVersion;
448 482
449 let saved = new Set(); 483 let saved = new Set();
450 let buf = [];
451 484
452 // Save subscriptions 485 // Save subscriptions
453 for (let subscription of this.subscriptions()) 486 for (let subscription of this.subscriptions())
454 { 487 {
455 // Do not persist external subscriptions, special subscriptions 488 // Do not persist external subscriptions
456 // and subscriptions with no filters. 489 if (!(subscription instanceof ExternalSubscription) &&
457 if (subscription instanceof ExternalSubscription || 490 !(subscription instanceof SpecialSubscription &&
458 subscription instanceof SpecialSubscription && 491 subscription.filters.length == 0))
459 subscription.filters.length == 0) 492 {
460 { 493 yield* subscription.serialize();
461 continue; 494 yield* subscription.serializeFilters();
462 } 495 }
463 496
464 yield ""; 497 // Save filter data
465 subscription.serialize(buf); 498 for (let subscription of subscriptions)
466 499 {
467 if (subscription.filters.length) 500 for (let filter of subscription.filters())
468 { 501 {
469 buf.push("", "[Subscription filters]"); 502 // Save filter data
470 subscription.serializeFilters(buf);
471 }
472
473 for (let line of buf)
474 yield line;
475 buf.splice(0);
476
477 // Save filter data
478 for (let filter of subscription.filters)
479 {
480 if (!saved.has(filter.text)) 503 if (!saved.has(filter.text))
481 { 504 {
482 filter.serialize(buf); 505 yield* filter.serialize();
483 saved.add(filter.text); 506 saved.add(filter.text);
484 for (let line of buf)
485 yield line;
486 buf.splice(0);
487 } 507 }
488 } 508 }
489 } 509 }
490 }, 510 }
491 511
492 /** 512 /**
493 * Will be set to true if saveToDisk() is running (reentrance protection). 513 * Saves all subscriptions back to disk.
494 * @type {boolean} 514 * @returns {Promise} A promise resolved or rejected when saving is complete.
495 */
496 _saving: false,
497
498 /**
499 * Will be set to true if a saveToDisk() call arrives while saveToDisk() is
500 * already running (delayed execution).
501 * @type {boolean}
502 */
503 _needsSave: false,
504
505 /**
506 * Saves all subscriptions back to disk
507 * @return {Promise} promise resolved or rejected when saving is complete
508 */ 515 */
509 saveToDisk() 516 saveToDisk()
510 { 517 {
511 if (this._saving) 518 if (this._saving)
512 { 519 {
513 this._needsSave = true; 520 this._needsSave = true;
514 return; 521 return;
515 } 522 }
516 523
517 this._saving = true; 524 this._saving = true;
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
581 Cu.reportError(error); 588 Cu.reportError(error);
582 }).then(() => 589 }).then(() =>
583 { 590 {
584 this._saving = false; 591 this._saving = false;
585 if (this._needsSave) 592 if (this._needsSave)
586 { 593 {
587 this._needsSave = false; 594 this._needsSave = false;
588 this.saveToDisk(); 595 this.saveToDisk();
589 } 596 }
590 }); 597 });
591 }, 598 }
592 599
593 /** 600 /**
594 * @typedef FileInfo 601 * @typedef FileInfo
595 * @type {object} 602 * @type {object}
596 * @property {number} index 603 * @property {number} index
597 * @property {number} lastModified 604 * @property {number} lastModified
598 */ 605 */
599 606
600 /** 607 /**
601 * Returns a promise resolving in a list of existing backup files. 608 * Returns a promise resolving in a list of existing backup files.
602 * @return {Promise.<FileInfo[]>} 609 * @returns {Promise.<Array.<FileInfo>>}
603 */ 610 */
604 getBackupFiles() 611 getBackupFiles()
605 { 612 {
606 let backups = []; 613 let backups = [];
607 614
608 let checkBackupFile = index => 615 let checkBackupFile = index =>
609 { 616 {
610 return IO.statFile(this.getBackupName(index)).then(statData => 617 return IO.statFile(this.getBackupName(index)).then(statData =>
611 { 618 {
612 if (!statData.exists) 619 if (!statData.exists)
613 return backups; 620 return backups;
614 621
615 backups.push({ 622 backups.push({
616 index, 623 index,
617 lastModified: statData.lastModified 624 lastModified: statData.lastModified
618 }); 625 });
619 return checkBackupFile(index + 1); 626 return checkBackupFile(index + 1);
620 }).catch(error => 627 }).catch(error =>
621 { 628 {
622 // Something went wrong, return whatever data we got so far. 629 // Something went wrong, return whatever data we got so far.
623 Cu.reportError(error); 630 Cu.reportError(error);
624 return backups; 631 return backups;
625 }); 632 });
626 }; 633 };
627 634
628 return checkBackupFile(1); 635 return checkBackupFile(1);
629 } 636 }
630 }; 637 }
631 638
632 /** 639 /**
633 * Joins subscription's filters to the subscription without any notifications. 640 * Reads the user's filters from disk, manages them in memory, and writes them
634 * @param {Subscription} subscription 641 * back to disk.
635 * filter subscription that should be connected to its filters
636 */ 642 */
637 function addSubscriptionFilters(subscription) 643 let filterStorage = new FilterStorage();
644
645 exports.filterStorage = filterStorage;
646
647 /**
648 * Connects a subscription to its filters without any notifications.
649 * @param {Subscription} subscription The subscription that should be
650 * connected to its filters.
651 * @param {?Array.<Filter>} [filters] A list of filters to which the
652 * subscription should be connected. If this is not given, the subscription
653 * is connected to its own filters.
654 */
655 function connectSubscriptionFilters(subscription, filters)
638 { 656 {
639 if (!FilterStorage.knownSubscriptions.has(subscription.url)) 657 if (!filterStorage.knownSubscriptions.has(subscription.url))
640 return; 658 return;
641 659
642 for (let filter of subscription.filters) 660 for (let filter of filters || subscription.filters())
643 filter.addSubscription(subscription); 661 filter.addSubscription(subscription);
644 } 662 }
645 663
646 /** 664 /**
647 * Removes subscription's filters from the subscription without any 665 * Disconnects a subscription from its filters without any notifications.
648 * notifications. 666 * @param {Subscription} subscription The subscription that should be
649 * @param {Subscription} subscription filter subscription to be removed 667 * disconnected from its filters.
668 * @param {?Array.<Filter>} [filters] A list of filters from which the
669 * subscription should be disconnected. If this is not given, the
670 * subscription is disconnected from its own filters.
650 */ 671 */
651 function removeSubscriptionFilters(subscription) 672 function disconnectSubscriptionFilters(subscription, filters)
652 { 673 {
653 if (!FilterStorage.knownSubscriptions.has(subscription.url)) 674 if (!filterStorage.knownSubscriptions.has(subscription.url))
654 return; 675 return;
655 676
656 for (let filter of subscription.filters) 677 for (let filter of filters || subscription.filters())
657 filter.removeSubscription(subscription); 678 filter.removeSubscription(subscription);
658 } 679 }
LEFTRIGHT
« no previous file | no next file » | Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Toggle Comments ('s')

Powered by Google App Engine
This is Rietveld