| 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 | 
|---|