OLD | NEW |
| (Empty) |
1 /* | |
2 * This file is part of Adblock Plus <https://adblockplus.org/>, | |
3 * Copyright (C) 2006-2016 Eyeo GmbH | |
4 * | |
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 | |
7 * published by the Free Software Foundation. | |
8 * | |
9 * Adblock Plus is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
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/>. | |
16 */ | |
17 | |
18 /** | |
19 * @fileOverview FilterStorage class responsible for managing user's subscriptio
ns and filters. | |
20 */ | |
21 | |
22 Cu.import("resource://gre/modules/Services.jsm"); | |
23 Cu.import("resource://gre/modules/FileUtils.jsm"); | |
24 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); | |
25 | |
26 let {IO} = require("io"); | |
27 let {Prefs} = require("prefs"); | |
28 let {Filter, ActiveFilter} = require("filterClasses"); | |
29 let {Subscription, SpecialSubscription, ExternalSubscription} = require("subscri
ptionClasses"); | |
30 let {FilterNotifier} = require("filterNotifier"); | |
31 let {Utils} = require("utils"); | |
32 | |
33 /** | |
34 * Version number of the filter storage file format. | |
35 * @type Integer | |
36 */ | |
37 let formatVersion = 4; | |
38 | |
39 /** | |
40 * This class reads user's filters from disk, manages them in memory and writes
them back. | |
41 * @class | |
42 */ | |
43 let FilterStorage = exports.FilterStorage = | |
44 { | |
45 /** | |
46 * Version number of the patterns.ini format used. | |
47 * @type Integer | |
48 */ | |
49 get formatVersion() | |
50 { | |
51 return formatVersion; | |
52 }, | |
53 | |
54 /** | |
55 * File that the filter list has been loaded from and should be saved to | |
56 * @type nsIFile | |
57 */ | |
58 get sourceFile() | |
59 { | |
60 let file = null; | |
61 if (Prefs.patternsfile) | |
62 { | |
63 // Override in place, use it instead of placing the file in the regular da
ta dir | |
64 file = IO.resolveFilePath(Prefs.patternsfile); | |
65 } | |
66 if (!file) | |
67 { | |
68 // Place the file in the data dir | |
69 file = IO.resolveFilePath(Prefs.data_directory); | |
70 if (file) | |
71 file.append("patterns.ini"); | |
72 } | |
73 if (!file) | |
74 { | |
75 // Data directory pref misconfigured? Try the default value | |
76 try | |
77 { | |
78 file = IO.resolveFilePath(Services.prefs.getDefaultBranch("extensions.ad
blockplus.").getCharPref("data_directory")); | |
79 if (file) | |
80 file.append("patterns.ini"); | |
81 } catch(e) {} | |
82 } | |
83 | |
84 if (!file) | |
85 Cu.reportError("Adblock Plus: Failed to resolve filter file location from
extensions.adblockplus.patternsfile preference"); | |
86 | |
87 // Property is configurable because of the test suite. | |
88 Object.defineProperty(this, "sourceFile", {value: file, configurable: true})
; | |
89 return file; | |
90 }, | |
91 | |
92 /** | |
93 * Will be set to true if no patterns.ini file exists. | |
94 * @type Boolean | |
95 */ | |
96 firstRun: false, | |
97 | |
98 /** | |
99 * Map of properties listed in the filter storage file before the sections | |
100 * start. Right now this should be only the format version. | |
101 */ | |
102 fileProperties: Object.create(null), | |
103 | |
104 /** | |
105 * List of filter subscriptions containing all filters | |
106 * @type Subscription[] | |
107 */ | |
108 subscriptions: [], | |
109 | |
110 /** | |
111 * Map of subscriptions already on the list, by their URL/identifier | |
112 * @type Object | |
113 */ | |
114 knownSubscriptions: Object.create(null), | |
115 | |
116 /** | |
117 * Finds the filter group that a filter should be added to by default. Will | |
118 * return null if this group doesn't exist yet. | |
119 */ | |
120 getGroupForFilter: function(/**Filter*/ filter) /**SpecialSubscription*/ | |
121 { | |
122 let generalSubscription = null; | |
123 for (let subscription of FilterStorage.subscriptions) | |
124 { | |
125 if (subscription instanceof SpecialSubscription && !subscription.disabled) | |
126 { | |
127 // Always prefer specialized subscriptions | |
128 if (subscription.isDefaultFor(filter)) | |
129 return subscription; | |
130 | |
131 // If this is a general subscription - store it as fallback | |
132 if (!generalSubscription && (!subscription.defaults || !subscription.def
aults.length)) | |
133 generalSubscription = subscription; | |
134 } | |
135 } | |
136 return generalSubscription; | |
137 }, | |
138 | |
139 /** | |
140 * Adds a filter subscription to the list | |
141 * @param {Subscription} subscription filter subscription to be added | |
142 * @param {Boolean} silent if true, no listeners will be triggered (to be use
d when filter list is reloaded) | |
143 */ | |
144 addSubscription: function(subscription, silent) | |
145 { | |
146 if (subscription.url in FilterStorage.knownSubscriptions) | |
147 return; | |
148 | |
149 FilterStorage.subscriptions.push(subscription); | |
150 FilterStorage.knownSubscriptions[subscription.url] = subscription; | |
151 addSubscriptionFilters(subscription); | |
152 | |
153 if (!silent) | |
154 FilterNotifier.triggerListeners("subscription.added", subscription); | |
155 }, | |
156 | |
157 /** | |
158 * Removes a filter subscription from the list | |
159 * @param {Subscription} subscription filter subscription to be removed | |
160 * @param {Boolean} silent if true, no listeners will be triggered (to be use
d when filter list is reloaded) | |
161 */ | |
162 removeSubscription: function(subscription, silent) | |
163 { | |
164 for (let i = 0; i < FilterStorage.subscriptions.length; i++) | |
165 { | |
166 if (FilterStorage.subscriptions[i].url == subscription.url) | |
167 { | |
168 removeSubscriptionFilters(subscription); | |
169 | |
170 FilterStorage.subscriptions.splice(i--, 1); | |
171 delete FilterStorage.knownSubscriptions[subscription.url]; | |
172 if (!silent) | |
173 FilterNotifier.triggerListeners("subscription.removed", subscription); | |
174 return; | |
175 } | |
176 } | |
177 }, | |
178 | |
179 /** | |
180 * Moves a subscription in the list to a new position. | |
181 * @param {Subscription} subscription filter subscription to be moved | |
182 * @param {Subscription} [insertBefore] filter subscription to insert before | |
183 * (if omitted the subscription will be put at the end of the list) | |
184 */ | |
185 moveSubscription: function(subscription, insertBefore) | |
186 { | |
187 let currentPos = FilterStorage.subscriptions.indexOf(subscription); | |
188 if (currentPos < 0) | |
189 return; | |
190 | |
191 let newPos = insertBefore ? FilterStorage.subscriptions.indexOf(insertBefore
) : -1; | |
192 if (newPos < 0) | |
193 newPos = FilterStorage.subscriptions.length; | |
194 | |
195 if (currentPos < newPos) | |
196 newPos--; | |
197 if (currentPos == newPos) | |
198 return; | |
199 | |
200 FilterStorage.subscriptions.splice(currentPos, 1); | |
201 FilterStorage.subscriptions.splice(newPos, 0, subscription); | |
202 FilterNotifier.triggerListeners("subscription.moved", subscription); | |
203 }, | |
204 | |
205 /** | |
206 * Replaces the list of filters in a subscription by a new list | |
207 * @param {Subscription} subscription filter subscription to be updated | |
208 * @param {Filter[]} filters new filter list | |
209 */ | |
210 updateSubscriptionFilters: function(subscription, filters) | |
211 { | |
212 removeSubscriptionFilters(subscription); | |
213 subscription.oldFilters = subscription.filters; | |
214 subscription.filters = filters; | |
215 addSubscriptionFilters(subscription); | |
216 FilterNotifier.triggerListeners("subscription.updated", subscription); | |
217 delete subscription.oldFilters; | |
218 }, | |
219 | |
220 /** | |
221 * Adds a user-defined filter to the list | |
222 * @param {Filter} filter | |
223 * @param {SpecialSubscription} [subscription] particular group that the filte
r should be added to | |
224 * @param {Integer} [position] position within the subscription at which the f
ilter should be added | |
225 * @param {Boolean} silent if true, no listeners will be triggered (to be use
d when filter list is reloaded) | |
226 */ | |
227 addFilter: function(filter, subscription, position, silent) | |
228 { | |
229 if (!subscription) | |
230 { | |
231 if (filter.subscriptions.some(s => s instanceof SpecialSubscription && !s.
disabled)) | |
232 return; // No need to add | |
233 subscription = FilterStorage.getGroupForFilter(filter); | |
234 } | |
235 if (!subscription) | |
236 { | |
237 // No group for this filter exists, create one | |
238 subscription = SpecialSubscription.createForFilter(filter); | |
239 this.addSubscription(subscription); | |
240 return; | |
241 } | |
242 | |
243 if (typeof position == "undefined") | |
244 position = subscription.filters.length; | |
245 | |
246 if (filter.subscriptions.indexOf(subscription) < 0) | |
247 filter.subscriptions.push(subscription); | |
248 subscription.filters.splice(position, 0, filter); | |
249 if (!silent) | |
250 FilterNotifier.triggerListeners("filter.added", filter, subscription, posi
tion); | |
251 }, | |
252 | |
253 /** | |
254 * Removes a user-defined filter from the list | |
255 * @param {Filter} filter | |
256 * @param {SpecialSubscription} [subscription] a particular filter group that | |
257 * the filter should be removed from (if ommited will be removed from all
subscriptions) | |
258 * @param {Integer} [position] position inside the filter group at which the | |
259 * filter should be removed (if ommited all instances will be removed) | |
260 */ | |
261 removeFilter: function(filter, subscription, position) | |
262 { | |
263 let subscriptions = (subscription ? [subscription] : filter.subscriptions.sl
ice()); | |
264 for (let i = 0; i < subscriptions.length; i++) | |
265 { | |
266 let subscription = subscriptions[i]; | |
267 if (subscription instanceof SpecialSubscription) | |
268 { | |
269 let positions = []; | |
270 if (typeof position == "undefined") | |
271 { | |
272 let index = -1; | |
273 do | |
274 { | |
275 index = subscription.filters.indexOf(filter, index + 1); | |
276 if (index >= 0) | |
277 positions.push(index); | |
278 } while (index >= 0); | |
279 } | |
280 else | |
281 positions.push(position); | |
282 | |
283 for (let j = positions.length - 1; j >= 0; j--) | |
284 { | |
285 let position = positions[j]; | |
286 if (subscription.filters[position] == filter) | |
287 { | |
288 subscription.filters.splice(position, 1); | |
289 if (subscription.filters.indexOf(filter) < 0) | |
290 { | |
291 let index = filter.subscriptions.indexOf(subscription); | |
292 if (index >= 0) | |
293 filter.subscriptions.splice(index, 1); | |
294 } | |
295 FilterNotifier.triggerListeners("filter.removed", filter, subscripti
on, position); | |
296 } | |
297 } | |
298 } | |
299 } | |
300 }, | |
301 | |
302 /** | |
303 * Moves a user-defined filter to a new position | |
304 * @param {Filter} filter | |
305 * @param {SpecialSubscription} subscription filter group where the filter is
located | |
306 * @param {Integer} oldPosition current position of the filter | |
307 * @param {Integer} newPosition new position of the filter | |
308 */ | |
309 moveFilter: function(filter, subscription, oldPosition, newPosition) | |
310 { | |
311 if (!(subscription instanceof SpecialSubscription) || subscription.filters[o
ldPosition] != filter) | |
312 return; | |
313 | |
314 newPosition = Math.min(Math.max(newPosition, 0), subscription.filters.length
- 1); | |
315 if (oldPosition == newPosition) | |
316 return; | |
317 | |
318 subscription.filters.splice(oldPosition, 1); | |
319 subscription.filters.splice(newPosition, 0, filter); | |
320 FilterNotifier.triggerListeners("filter.moved", filter, subscription, oldPos
ition, newPosition); | |
321 }, | |
322 | |
323 /** | |
324 * Increases the hit count for a filter by one | |
325 * @param {Filter} filter | |
326 */ | |
327 increaseHitCount: function(filter) | |
328 { | |
329 if (!Prefs.savestats || !(filter instanceof ActiveFilter)) | |
330 return; | |
331 | |
332 filter.hitCount++; | |
333 filter.lastHit = Date.now(); | |
334 }, | |
335 | |
336 /** | |
337 * Resets hit count for some filters | |
338 * @param {Filter[]} filters filters to be reset, if null all filters will be
reset | |
339 */ | |
340 resetHitCounts: function(filters) | |
341 { | |
342 if (!filters) | |
343 { | |
344 filters = []; | |
345 for (let text in Filter.knownFilters) | |
346 filters.push(Filter.knownFilters[text]); | |
347 } | |
348 for (let filter of filters) | |
349 { | |
350 filter.hitCount = 0; | |
351 filter.lastHit = 0; | |
352 } | |
353 }, | |
354 | |
355 _loading: false, | |
356 | |
357 /** | |
358 * Loads all subscriptions from the disk | |
359 * @param {nsIFile} [sourceFile] File to read from | |
360 */ | |
361 loadFromDisk: function(sourceFile) | |
362 { | |
363 if (this._loading) | |
364 return; | |
365 | |
366 this._loading = true; | |
367 | |
368 let readFile = function(sourceFile, backupIndex) | |
369 { | |
370 let parser = new INIParser(); | |
371 IO.readFromFile(sourceFile, parser, function(e) | |
372 { | |
373 if (!e && parser.subscriptions.length == 0) | |
374 { | |
375 // No filter subscriptions in the file, this isn't right. | |
376 e = new Error("No data in the file"); | |
377 } | |
378 | |
379 if (e) | |
380 Cu.reportError(e); | |
381 | |
382 if (e && !explicitFile) | |
383 { | |
384 // Attempt to load a backup | |
385 sourceFile = this.sourceFile; | |
386 if (sourceFile) | |
387 { | |
388 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(sourceFile.leafName) ||
[null, sourceFile.leafName, ""]; | |
389 | |
390 sourceFile = sourceFile.clone(); | |
391 sourceFile.leafName = part1 + "-backup" + (++backupIndex) + part2; | |
392 | |
393 IO.statFile(sourceFile, function(e, statData) | |
394 { | |
395 if (!e && statData.exists) | |
396 readFile(sourceFile, backupIndex); | |
397 else | |
398 doneReading(parser); | |
399 }); | |
400 return; | |
401 } | |
402 } | |
403 doneReading(parser); | |
404 }.bind(this)); | |
405 }.bind(this); | |
406 | |
407 var doneReading = function(parser) | |
408 { | |
409 // Old special groups might have been converted, remove them if they are e
mpty | |
410 let specialMap = {"~il~": true, "~wl~": true, "~fl~": true, "~eh~": true}; | |
411 let knownSubscriptions = Object.create(null); | |
412 for (let i = 0; i < parser.subscriptions.length; i++) | |
413 { | |
414 let subscription = parser.subscriptions[i]; | |
415 if (subscription instanceof SpecialSubscription && subscription.filters.
length == 0 && subscription.url in specialMap) | |
416 parser.subscriptions.splice(i--, 1); | |
417 else | |
418 knownSubscriptions[subscription.url] = subscription; | |
419 } | |
420 | |
421 this.fileProperties = parser.fileProperties; | |
422 this.subscriptions = parser.subscriptions; | |
423 this.knownSubscriptions = knownSubscriptions; | |
424 Filter.knownFilters = parser.knownFilters; | |
425 Subscription.knownSubscriptions = parser.knownSubscriptions; | |
426 | |
427 if (parser.userFilters) | |
428 { | |
429 for (let i = 0; i < parser.userFilters.length; i++) | |
430 { | |
431 let filter = Filter.fromText(parser.userFilters[i]); | |
432 this.addFilter(filter, null, undefined, true); | |
433 } | |
434 } | |
435 | |
436 this._loading = false; | |
437 FilterNotifier.triggerListeners("load"); | |
438 | |
439 if (sourceFile != this.sourceFile) | |
440 this.saveToDisk(); | |
441 | |
442 }.bind(this); | |
443 | |
444 let explicitFile; | |
445 if (sourceFile) | |
446 { | |
447 explicitFile = true; | |
448 readFile(sourceFile, 0); | |
449 } | |
450 else | |
451 { | |
452 explicitFile = false; | |
453 sourceFile = FilterStorage.sourceFile; | |
454 | |
455 let callback = function(e, statData) | |
456 { | |
457 if (e || !statData.exists) | |
458 { | |
459 this.firstRun = true; | |
460 this._loading = false; | |
461 FilterNotifier.triggerListeners("load"); | |
462 } | |
463 else | |
464 readFile(sourceFile, 0); | |
465 }.bind(this); | |
466 | |
467 if (sourceFile) | |
468 IO.statFile(sourceFile, callback); | |
469 else | |
470 callback(true); | |
471 } | |
472 }, | |
473 | |
474 _generateFilterData: function*(subscriptions) | |
475 { | |
476 yield "# Adblock Plus preferences"; | |
477 yield "version=" + formatVersion; | |
478 | |
479 let saved = Object.create(null); | |
480 let buf = []; | |
481 | |
482 // Save filter data | |
483 for (let i = 0; i < subscriptions.length; i++) | |
484 { | |
485 let subscription = subscriptions[i]; | |
486 for (let j = 0; j < subscription.filters.length; j++) | |
487 { | |
488 let filter = subscription.filters[j]; | |
489 if (!(filter.text in saved)) | |
490 { | |
491 filter.serialize(buf); | |
492 saved[filter.text] = filter; | |
493 for (let k = 0; k < buf.length; k++) | |
494 yield buf[k]; | |
495 buf.splice(0); | |
496 } | |
497 } | |
498 } | |
499 | |
500 // Save subscriptions | |
501 for (let i = 0; i < subscriptions.length; i++) | |
502 { | |
503 let subscription = subscriptions[i]; | |
504 | |
505 yield ""; | |
506 | |
507 subscription.serialize(buf); | |
508 if (subscription.filters.length) | |
509 { | |
510 buf.push("", "[Subscription filters]") | |
511 subscription.serializeFilters(buf); | |
512 } | |
513 for (let k = 0; k < buf.length; k++) | |
514 yield buf[k]; | |
515 buf.splice(0); | |
516 } | |
517 }, | |
518 | |
519 /** | |
520 * Will be set to true if saveToDisk() is running (reentrance protection). | |
521 * @type Boolean | |
522 */ | |
523 _saving: false, | |
524 | |
525 /** | |
526 * Will be set to true if a saveToDisk() call arrives while saveToDisk() is | |
527 * already running (delayed execution). | |
528 * @type Boolean | |
529 */ | |
530 _needsSave: false, | |
531 | |
532 /** | |
533 * Saves all subscriptions back to disk | |
534 * @param {nsIFile} [targetFile] File to be written | |
535 */ | |
536 saveToDisk: function(targetFile) | |
537 { | |
538 let explicitFile = true; | |
539 if (!targetFile) | |
540 { | |
541 targetFile = FilterStorage.sourceFile; | |
542 explicitFile = false; | |
543 } | |
544 if (!targetFile) | |
545 return; | |
546 | |
547 if (!explicitFile && this._saving) | |
548 { | |
549 this._needsSave = true; | |
550 return; | |
551 } | |
552 | |
553 // Make sure the file's parent directory exists | |
554 try { | |
555 targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECT
ORY); | |
556 } catch (e) {} | |
557 | |
558 let writeFilters = function() | |
559 { | |
560 IO.writeToFile(targetFile, this._generateFilterData(subscriptions), functi
on(e) | |
561 { | |
562 if (!explicitFile) | |
563 this._saving = false; | |
564 | |
565 if (e) | |
566 Cu.reportError(e); | |
567 | |
568 if (!explicitFile && this._needsSave) | |
569 { | |
570 this._needsSave = false; | |
571 this.saveToDisk(); | |
572 } | |
573 else | |
574 FilterNotifier.triggerListeners("save"); | |
575 }.bind(this)); | |
576 }.bind(this); | |
577 | |
578 let checkBackupRequired = function(callbackNotRequired, callbackRequired) | |
579 { | |
580 if (explicitFile || Prefs.patternsbackups <= 0) | |
581 callbackNotRequired(); | |
582 else | |
583 { | |
584 IO.statFile(targetFile, function(e, statData) | |
585 { | |
586 if (e || !statData.exists) | |
587 callbackNotRequired(); | |
588 else | |
589 { | |
590 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(targetFile.leafName) ||
[null, targetFile.leafName, ""]; | |
591 let newestBackup = targetFile.clone(); | |
592 newestBackup.leafName = part1 + "-backup1" + part2; | |
593 IO.statFile(newestBackup, function(e, statData) | |
594 { | |
595 if (!e && (!statData.exists || (Date.now() - statData.lastModified
) / 3600000 >= Prefs.patternsbackupinterval)) | |
596 callbackRequired(part1, part2) | |
597 else | |
598 callbackNotRequired(); | |
599 }); | |
600 } | |
601 }); | |
602 } | |
603 }.bind(this); | |
604 | |
605 let removeLastBackup = function(part1, part2) | |
606 { | |
607 let file = targetFile.clone(); | |
608 file.leafName = part1 + "-backup" + Prefs.patternsbackups + part2; | |
609 IO.removeFile(file, (e) => renameBackup(part1, part2, Prefs.patternsbackup
s - 1)); | |
610 }.bind(this); | |
611 | |
612 let renameBackup = function(part1, part2, index) | |
613 { | |
614 if (index > 0) | |
615 { | |
616 let fromFile = targetFile.clone(); | |
617 fromFile.leafName = part1 + "-backup" + index + part2; | |
618 | |
619 let toName = part1 + "-backup" + (index + 1) + part2; | |
620 | |
621 IO.renameFile(fromFile, toName, (e) => renameBackup(part1, part2, index
- 1)); | |
622 } | |
623 else | |
624 { | |
625 let toFile = targetFile.clone(); | |
626 toFile.leafName = part1 + "-backup" + (index + 1) + part2; | |
627 | |
628 IO.copyFile(targetFile, toFile, writeFilters); | |
629 } | |
630 }.bind(this); | |
631 | |
632 // Do not persist external subscriptions | |
633 let subscriptions = this.subscriptions.filter((s) => !(s instanceof External
Subscription)); | |
634 if (!explicitFile) | |
635 this._saving = true; | |
636 | |
637 checkBackupRequired(writeFilters, removeLastBackup); | |
638 }, | |
639 | |
640 /** | |
641 * Returns the list of existing backup files. | |
642 */ | |
643 getBackupFiles: function() /**nsIFile[]*/ | |
644 { | |
645 // TODO: This method should be asynchronous | |
646 let result = []; | |
647 | |
648 let [, part1, part2] = /^(.*)(\.\w+)$/.exec(FilterStorage.sourceFile.leafNam
e) || [null, FilterStorage.sourceFile.leafName, ""]; | |
649 for (let i = 1; ; i++) | |
650 { | |
651 let file = FilterStorage.sourceFile.clone(); | |
652 file.leafName = part1 + "-backup" + i + part2; | |
653 if (file.exists()) | |
654 result.push(file); | |
655 else | |
656 break; | |
657 } | |
658 return result; | |
659 } | |
660 }; | |
661 | |
662 /** | |
663 * Joins subscription's filters to the subscription without any notifications. | |
664 * @param {Subscription} subscription filter subscription that should be connect
ed to its filters | |
665 */ | |
666 function addSubscriptionFilters(subscription) | |
667 { | |
668 if (!(subscription.url in FilterStorage.knownSubscriptions)) | |
669 return; | |
670 | |
671 for (let filter of subscription.filters) | |
672 filter.subscriptions.push(subscription); | |
673 } | |
674 | |
675 /** | |
676 * Removes subscription's filters from the subscription without any notification
s. | |
677 * @param {Subscription} subscription filter subscription to be removed | |
678 */ | |
679 function removeSubscriptionFilters(subscription) | |
680 { | |
681 if (!(subscription.url in FilterStorage.knownSubscriptions)) | |
682 return; | |
683 | |
684 for (let filter of subscription.filters) | |
685 { | |
686 let i = filter.subscriptions.indexOf(subscription); | |
687 if (i >= 0) | |
688 filter.subscriptions.splice(i, 1); | |
689 } | |
690 } | |
691 | |
692 /** | |
693 * IO.readFromFile() listener to parse filter data. | |
694 * @constructor | |
695 */ | |
696 function INIParser() | |
697 { | |
698 this.fileProperties = this.curObj = {}; | |
699 this.subscriptions = []; | |
700 this.knownFilters = Object.create(null); | |
701 this.knownSubscriptions = Object.create(null); | |
702 } | |
703 INIParser.prototype = | |
704 { | |
705 linesProcessed: 0, | |
706 subscriptions: null, | |
707 knownFilters: null, | |
708 knownSubscriptions : null, | |
709 wantObj: true, | |
710 fileProperties: null, | |
711 curObj: null, | |
712 curSection: null, | |
713 userFilters: null, | |
714 | |
715 process: function(val) | |
716 { | |
717 let origKnownFilters = Filter.knownFilters; | |
718 Filter.knownFilters = this.knownFilters; | |
719 let origKnownSubscriptions = Subscription.knownSubscriptions; | |
720 Subscription.knownSubscriptions = this.knownSubscriptions; | |
721 let match; | |
722 try | |
723 { | |
724 if (this.wantObj === true && (match = /^(\w+)=(.*)$/.exec(val))) | |
725 this.curObj[match[1]] = match[2]; | |
726 else if (val === null || (match = /^\s*\[(.+)\]\s*$/.exec(val))) | |
727 { | |
728 if (this.curObj) | |
729 { | |
730 // Process current object before going to next section | |
731 switch (this.curSection) | |
732 { | |
733 case "filter": | |
734 case "pattern": | |
735 if ("text" in this.curObj) | |
736 Filter.fromObject(this.curObj); | |
737 break; | |
738 case "subscription": | |
739 let subscription = Subscription.fromObject(this.curObj); | |
740 if (subscription) | |
741 this.subscriptions.push(subscription); | |
742 break; | |
743 case "subscription filters": | |
744 case "subscription patterns": | |
745 if (this.subscriptions.length) | |
746 { | |
747 let subscription = this.subscriptions[this.subscriptions.length
- 1]; | |
748 for (let text of this.curObj) | |
749 { | |
750 let filter = Filter.fromText(text); | |
751 subscription.filters.push(filter); | |
752 filter.subscriptions.push(subscription); | |
753 } | |
754 } | |
755 break; | |
756 case "user patterns": | |
757 this.userFilters = this.curObj; | |
758 break; | |
759 } | |
760 } | |
761 | |
762 if (val === null) | |
763 return; | |
764 | |
765 this.curSection = match[1].toLowerCase(); | |
766 switch (this.curSection) | |
767 { | |
768 case "filter": | |
769 case "pattern": | |
770 case "subscription": | |
771 this.wantObj = true; | |
772 this.curObj = {}; | |
773 break; | |
774 case "subscription filters": | |
775 case "subscription patterns": | |
776 case "user patterns": | |
777 this.wantObj = false; | |
778 this.curObj = []; | |
779 break; | |
780 default: | |
781 this.wantObj = undefined; | |
782 this.curObj = null; | |
783 } | |
784 } | |
785 else if (this.wantObj === false && val) | |
786 this.curObj.push(val.replace(/\\\[/g, "[")); | |
787 } | |
788 finally | |
789 { | |
790 Filter.knownFilters = origKnownFilters; | |
791 Subscription.knownSubscriptions = origKnownSubscriptions; | |
792 } | |
793 | |
794 // Allow events to be processed every now and then. | |
795 // Note: IO.readFromFile() will deal with the potential reentrance here. | |
796 this.linesProcessed++; | |
797 if (this.linesProcessed % 1000 == 0) | |
798 Utils.yield(); | |
799 } | |
800 }; | |
OLD | NEW |