| Index: lib/filterStorage.js |
| =================================================================== |
| --- a/lib/filterStorage.js |
| +++ b/lib/filterStorage.js |
| @@ -12,775 +12,94 @@ |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| "use strict"; |
| -/** |
| - * @fileOverview FilterStorage class responsible for managing user's |
| - * subscriptions and filters. |
| - */ |
| +const {FilterStorage} = require("compiled"); |
| +const {Subscription, SpecialSubscription} = require("subscriptionClasses"); |
| -const {IO} = require("io"); |
| -const {Prefs} = require("prefs"); |
| -const {Filter, ActiveFilter} = require("filterClasses"); |
| -const {Subscription, SpecialSubscription, |
| - ExternalSubscription} = require("subscriptionClasses"); |
| -const {FilterNotifier} = require("filterNotifier"); |
| - |
| -/** |
| - * Version number of the filter storage file format. |
| - * @type {number} |
| - */ |
| -let formatVersion = 5; |
| +// Backwards compatibility |
| +FilterStorage.getGroupForFilter = FilterStorage.getSubscriptionForFilter; |
| /** |
| - * This class reads user's filters from disk, manages them in memory |
| - * and writes them back. |
| - * @class |
| + * This property allows iterating over the list of subscriptions. It will delete |
| + * references automatically at the end of the current loop iteration. If you |
| + * need persistent references or element access by position you should use |
| + * FilterStorage.subscriptionAt() instead. |
| + * @type {Iterable} |
| */ |
| -let FilterStorage = exports.FilterStorage = |
| -{ |
| - /** |
| - * Will be set to true after the initial loadFromDisk() call completes. |
| - * @type {boolean} |
| - */ |
| - initialized: false, |
| - |
| - /** |
| - * Version number of the patterns.ini format used. |
| - * @type {number} |
| - */ |
| - get formatVersion() |
| - { |
| - return formatVersion; |
| - }, |
| - |
| - /** |
| - * File containing the filter list |
| - * @type {string} |
| - */ |
| - get sourceFile() |
| +FilterStorage.subscriptions = { |
| + *[Symbol.iterator]() |
| { |
| - return "patterns.ini"; |
| - }, |
| - |
| - /** |
| - * Will be set to true if no patterns.ini file exists. |
| - * @type {boolean} |
| - */ |
| - firstRun: false, |
| - |
| - /** |
| - * Map of properties listed in the filter storage file before the sections |
| - * start. Right now this should be only the format version. |
| - */ |
| - fileProperties: Object.create(null), |
| - |
| - /** |
| - * List of filter subscriptions containing all filters |
| - * @type {Subscription[]} |
| - */ |
| - subscriptions: [], |
| - |
| - /** |
| - * Map of subscriptions already on the list, by their URL/identifier |
| - * @type {Object} |
| - */ |
| - knownSubscriptions: Object.create(null), |
| - |
| - /** |
| - * Finds the filter group that a filter should be added to by default. Will |
| - * return null if this group doesn't exist yet. |
| - * @param {Filter} filter |
| - * @return {?SpecialSubscription} |
| - */ |
| - getGroupForFilter(filter) |
| - { |
| - let generalSubscription = null; |
| - for (let subscription of FilterStorage.subscriptions) |
| + for (let i = 0, l = FilterStorage.subscriptionCount; i < l; i++) |
| { |
| - if (subscription instanceof SpecialSubscription && !subscription.disabled) |
| + let subscription = FilterStorage.subscriptionAt(i); |
| + try |
| { |
| - // Always prefer specialized subscriptions |
| - if (subscription.isDefaultFor(filter)) |
| - return subscription; |
| - |
| - // If this is a general subscription - store it as fallback |
| - if (!generalSubscription && |
| - (!subscription.defaults || !subscription.defaults.length)) |
| - { |
| - generalSubscription = subscription; |
| - } |
| - } |
| - } |
| - return generalSubscription; |
| - }, |
| - |
| - /** |
| - * Adds a filter subscription to the list |
| - * @param {Subscription} subscription filter subscription to be added |
| - */ |
| - addSubscription(subscription) |
| - { |
| - if (subscription.url in FilterStorage.knownSubscriptions) |
| - return; |
| - |
| - FilterStorage.subscriptions.push(subscription); |
| - FilterStorage.knownSubscriptions[subscription.url] = subscription; |
| - addSubscriptionFilters(subscription); |
| - |
| - FilterNotifier.triggerListeners("subscription.added", subscription); |
| - }, |
| - |
| - /** |
| - * Removes a filter subscription from the list |
| - * @param {Subscription} subscription filter subscription to be removed |
| - */ |
| - removeSubscription(subscription) |
| - { |
| - for (let i = 0; i < FilterStorage.subscriptions.length; i++) |
| - { |
| - if (FilterStorage.subscriptions[i].url == subscription.url) |
| - { |
| - removeSubscriptionFilters(subscription); |
| - |
| - FilterStorage.subscriptions.splice(i--, 1); |
| - delete FilterStorage.knownSubscriptions[subscription.url]; |
| - FilterNotifier.triggerListeners("subscription.removed", subscription); |
| - return; |
| + yield subscription; |
| } |
| - } |
| - }, |
| - |
| - /** |
| - * Moves a subscription in the list to a new position. |
| - * @param {Subscription} subscription filter subscription to be moved |
| - * @param {Subscription} [insertBefore] filter subscription to insert before |
| - * (if omitted the subscription will be put at the end of the list) |
| - */ |
| - moveSubscription(subscription, insertBefore) |
| - { |
| - let currentPos = FilterStorage.subscriptions.indexOf(subscription); |
| - if (currentPos < 0) |
| - return; |
| - |
| - let newPos = -1; |
| - if (insertBefore) |
| - newPos = FilterStorage.subscriptions.indexOf(insertBefore); |
| - |
| - if (newPos < 0) |
| - newPos = FilterStorage.subscriptions.length; |
| - |
| - if (currentPos < newPos) |
| - newPos--; |
| - if (currentPos == newPos) |
| - return; |
| - |
| - FilterStorage.subscriptions.splice(currentPos, 1); |
| - FilterStorage.subscriptions.splice(newPos, 0, subscription); |
| - FilterNotifier.triggerListeners("subscription.moved", subscription); |
| - }, |
| - |
| - /** |
| - * Replaces the list of filters in a subscription by a new list |
| - * @param {Subscription} subscription filter subscription to be updated |
| - * @param {Filter[]} filters new filter list |
| - */ |
| - updateSubscriptionFilters(subscription, filters) |
| - { |
| - removeSubscriptionFilters(subscription); |
| - subscription.oldFilters = subscription.filters; |
| - subscription.filters = filters; |
| - addSubscriptionFilters(subscription); |
| - FilterNotifier.triggerListeners("subscription.updated", subscription); |
| - delete subscription.oldFilters; |
| - }, |
| - |
| - /** |
| - * Adds a user-defined filter to the list |
| - * @param {Filter} filter |
| - * @param {SpecialSubscription} [subscription] |
| - * particular group that the filter should be added to |
| - * @param {number} [position] |
| - * position within the subscription at which the filter should be added |
| - */ |
| - addFilter(filter, subscription, position) |
| - { |
| - if (!subscription) |
| - { |
| - if (filter.subscriptions.some(s => s instanceof SpecialSubscription && |
| - !s.disabled)) |
| + finally |
| { |
| - return; // No need to add |
| - } |
| - subscription = FilterStorage.getGroupForFilter(filter); |
| - } |
| - if (!subscription) |
| - { |
| - // No group for this filter exists, create one |
| - subscription = SpecialSubscription.createForFilter(filter); |
| - this.addSubscription(subscription); |
| - return; |
| - } |
| - |
| - if (typeof position == "undefined") |
| - position = subscription.filters.length; |
| - |
| - if (filter.subscriptions.indexOf(subscription) < 0) |
| - filter.subscriptions.push(subscription); |
| - subscription.filters.splice(position, 0, filter); |
| - FilterNotifier.triggerListeners("filter.added", filter, subscription, |
| - position); |
| - }, |
| - |
| - /** |
| - * Removes a user-defined filter from the list |
| - * @param {Filter} filter |
| - * @param {SpecialSubscription} [subscription] a particular filter group that |
| - * the filter should be removed from (if ommited will be removed from all |
| - * subscriptions) |
| - * @param {number} [position] position inside the filter group at which the |
| - * filter should be removed (if ommited all instances will be removed) |
| - */ |
| - removeFilter(filter, subscription, position) |
| - { |
| - let subscriptions = ( |
| - subscription ? [subscription] : filter.subscriptions.slice() |
| - ); |
| - for (let i = 0; i < subscriptions.length; i++) |
| - { |
| - let currentSubscription = subscriptions[i]; |
| - if (currentSubscription instanceof SpecialSubscription) |
| - { |
| - let positions = []; |
| - if (typeof position == "undefined") |
| - { |
| - let index = -1; |
| - do |
| - { |
| - index = currentSubscription.filters.indexOf(filter, index + 1); |
| - if (index >= 0) |
| - positions.push(index); |
| - } while (index >= 0); |
| - } |
| - else |
| - positions.push(position); |
| - |
| - for (let j = positions.length - 1; j >= 0; j--) |
| - { |
| - let currentPosition = positions[j]; |
| - if (currentSubscription.filters[currentPosition] == filter) |
| - { |
| - currentSubscription.filters.splice(currentPosition, 1); |
| - if (currentSubscription.filters.indexOf(filter) < 0) |
| - { |
| - let index = filter.subscriptions.indexOf(currentSubscription); |
| - if (index >= 0) |
| - filter.subscriptions.splice(index, 1); |
| - } |
| - FilterNotifier.triggerListeners( |
| - "filter.removed", filter, currentSubscription, currentPosition |
| - ); |
| - } |
| - } |
| + subscription.delete(); |
| } |
| } |
| - }, |
| - |
| - /** |
| - * Moves a user-defined filter to a new position |
| - * @param {Filter} filter |
| - * @param {SpecialSubscription} subscription filter group where the filter is |
| - * located |
| - * @param {number} oldPosition current position of the filter |
| - * @param {number} newPosition new position of the filter |
| - */ |
| - moveFilter(filter, subscription, oldPosition, newPosition) |
| - { |
| - if (!(subscription instanceof SpecialSubscription) || |
| - subscription.filters[oldPosition] != filter) |
| - { |
| - return; |
| - } |
| - |
| - newPosition = Math.min(Math.max(newPosition, 0), |
| - subscription.filters.length - 1); |
| - if (oldPosition == newPosition) |
| - return; |
| - |
| - subscription.filters.splice(oldPosition, 1); |
| - subscription.filters.splice(newPosition, 0, filter); |
| - FilterNotifier.triggerListeners("filter.moved", filter, subscription, |
| - oldPosition, newPosition); |
| - }, |
| - |
| - /** |
| - * Increases the hit count for a filter by one |
| - * @param {Filter} filter |
| - */ |
| - increaseHitCount(filter) |
| - { |
| - if (!Prefs.savestats || !(filter instanceof ActiveFilter)) |
| - return; |
| - |
| - filter.hitCount++; |
| - filter.lastHit = Date.now(); |
| - }, |
| - |
| - /** |
| - * Resets hit count for some filters |
| - * @param {Filter[]} filters filters to be reset, if null all filters will |
| - * be reset |
| - */ |
| - resetHitCounts(filters) |
| - { |
| - if (!filters) |
| - { |
| - filters = []; |
| - for (let text in Filter.knownFilters) |
| - filters.push(Filter.knownFilters[text]); |
| - } |
| - for (let filter of filters) |
| - { |
| - filter.hitCount = 0; |
| - filter.lastHit = 0; |
| - } |
| - }, |
| - |
| - /** |
| - * @callback TextSink |
| - * @param {string?} line |
| - */ |
| - |
| - /** |
| - * Allows importing previously serialized filter data. |
| - * @param {boolean} silent |
| - * If true, no "load" notification will be sent out. |
| - * @return {TextSink} |
| - * Function to be called for each line of data. Calling it with null as |
| - * parameter finalizes the import and replaces existing data. No changes |
| - * will be applied before finalization, so import can be "aborted" by |
| - * forgetting this callback. |
| - */ |
| - importData(silent) |
| - { |
| - let parser = new INIParser(); |
| - return line => |
| - { |
| - parser.process(line); |
| - if (line === null) |
| - { |
| - let knownSubscriptions = Object.create(null); |
| - for (let subscription of parser.subscriptions) |
| - knownSubscriptions[subscription.url] = subscription; |
| - |
| - this.fileProperties = parser.fileProperties; |
| - this.subscriptions = parser.subscriptions; |
| - this.knownSubscriptions = knownSubscriptions; |
| - Filter.knownFilters = parser.knownFilters; |
| - Subscription.knownSubscriptions = parser.knownSubscriptions; |
| - |
| - if (!silent) |
| - FilterNotifier.triggerListeners("load"); |
| - } |
| - }; |
| - }, |
| - |
| - /** |
| - * Loads all subscriptions from the disk. |
| - * @return {Promise} promise resolved or rejected when loading is complete |
| - */ |
| - loadFromDisk() |
| - { |
| - let tryBackup = backupIndex => |
| - { |
| - return this.restoreBackup(backupIndex, true).then(() => |
| - { |
| - if (this.subscriptions.length == 0) |
| - return tryBackup(backupIndex + 1); |
| - }).catch(error => |
| - { |
| - // Give up |
| - }); |
| - }; |
| - |
| - return IO.statFile(this.sourceFile).then(statData => |
| - { |
| - if (!statData.exists) |
| - { |
| - this.firstRun = true; |
| - return; |
| - } |
| - |
| - let parser = this.importData(true); |
| - return IO.readFromFile(this.sourceFile, parser).then(() => |
| - { |
| - parser(null); |
| - if (this.subscriptions.length == 0) |
| - { |
| - // No filter subscriptions in the file, this isn't right. |
| - throw new Error("No data in the file"); |
| - } |
| - }); |
| - }).catch(error => |
| - { |
| - Cu.reportError(error); |
| - return tryBackup(1); |
| - }).then(() => |
| - { |
| - this.initialized = true; |
| - FilterNotifier.triggerListeners("load"); |
| - }); |
| - }, |
| - |
| - /** |
| - * Constructs the file name for a patterns.ini backup. |
| - * @param {number} backupIndex |
| - * number of the backup file (1 being the most recent) |
| - * @return {string} backup file name |
| - */ |
| - getBackupName(backupIndex) |
| - { |
| - let [name, extension] = this.sourceFile.split(".", 2); |
| - return (name + "-backup" + backupIndex + "." + extension); |
| - }, |
| - |
| - /** |
| - * Restores an automatically created backup. |
| - * @param {number} backupIndex |
| - * number of the backup to restore (1 being the most recent) |
| - * @param {boolean} silent |
| - * If true, no "load" notification will be sent out. |
| - * @return {Promise} promise resolved or rejected when restoring is complete |
| - */ |
| - restoreBackup(backupIndex, silent) |
| - { |
| - let backupFile = this.getBackupName(backupIndex); |
| - let parser = this.importData(silent); |
| - return IO.readFromFile(backupFile, parser).then(() => |
| - { |
| - parser(null); |
| - return this.saveToDisk(); |
| - }); |
| - }, |
| - |
| - /** |
| - * Generator serializing filter data and yielding it line by line. |
| - */ |
| - *exportData() |
| - { |
| - // Do not persist external subscriptions |
| - let subscriptions = this.subscriptions.filter( |
| - s => !(s instanceof ExternalSubscription) |
| - ); |
| - |
| - yield "# Adblock Plus preferences"; |
| - yield "version=" + formatVersion; |
| - |
| - let saved = new Set(); |
| - let buf = []; |
| - |
| - // Save subscriptions |
| - for (let subscription of subscriptions) |
| - { |
| - yield ""; |
| - |
| - subscription.serialize(buf); |
| - if (subscription.filters.length) |
| - { |
| - buf.push("", "[Subscription filters]"); |
| - subscription.serializeFilters(buf); |
| - } |
| - for (let line of buf) |
| - yield line; |
| - buf.splice(0); |
| - } |
| - |
| - // Save filter data |
| - for (let subscription of subscriptions) |
| - { |
| - for (let filter of subscription.filters) |
| - { |
| - if (!saved.has(filter.text)) |
| - { |
| - filter.serialize(buf); |
| - saved.add(filter.text); |
| - for (let line of buf) |
| - yield line; |
| - buf.splice(0); |
| - } |
| - } |
| - } |
| - }, |
| - |
| - /** |
| - * Will be set to true if saveToDisk() is running (reentrance protection). |
| - * @type {boolean} |
| - */ |
| - _saving: false, |
| - |
| - /** |
| - * Will be set to true if a saveToDisk() call arrives while saveToDisk() is |
| - * already running (delayed execution). |
| - * @type {boolean} |
| - */ |
| - _needsSave: false, |
| - |
| - /** |
| - * Saves all subscriptions back to disk |
| - * @return {Promise} promise resolved or rejected when saving is complete |
| - */ |
| - saveToDisk() |
| - { |
| - if (this._saving) |
| - { |
| - this._needsSave = true; |
| - return; |
| - } |
| - |
| - this._saving = true; |
| - |
| - return Promise.resolve().then(() => |
| - { |
| - // First check whether we need to create a backup |
| - if (Prefs.patternsbackups <= 0) |
| - return false; |
| - |
| - return IO.statFile(this.sourceFile).then(statData => |
| - { |
| - if (!statData.exists) |
| - return false; |
| - |
| - return IO.statFile(this.getBackupName(1)).then(backupStatData => |
| - { |
| - if (backupStatData.exists && |
| - (Date.now() - backupStatData.lastModified) / 3600000 < |
| - Prefs.patternsbackupinterval) |
| - { |
| - return false; |
| - } |
| - return true; |
| - }); |
| - }); |
| - }).then(backupRequired => |
| - { |
| - if (!backupRequired) |
| - return; |
| - |
| - let ignoreErrors = error => |
| - { |
| - // Expected error, backup file doesn't exist. |
| - }; |
| - |
| - let renameBackup = index => |
| - { |
| - if (index > 0) |
| - { |
| - return IO.renameFile(this.getBackupName(index), |
| - this.getBackupName(index + 1)) |
| - .catch(ignoreErrors) |
| - .then(() => renameBackup(index - 1)); |
| - } |
| - |
| - return IO.renameFile(this.sourceFile, this.getBackupName(1)) |
| - .catch(ignoreErrors); |
| - }; |
| - |
| - // Rename existing files |
| - return renameBackup(Prefs.patternsbackups - 1); |
| - }).catch(error => |
| - { |
| - // Errors during backup creation shouldn't prevent writing filters. |
| - Cu.reportError(error); |
| - }).then(() => |
| - { |
| - return IO.writeToFile(this.sourceFile, this.exportData()); |
| - }).then(() => |
| - { |
| - FilterNotifier.triggerListeners("save"); |
| - }).catch(error => |
| - { |
| - // If saving failed, report error but continue - we still have to process |
| - // flags. |
| - Cu.reportError(error); |
| - }).then(() => |
| - { |
| - this._saving = false; |
| - if (this._needsSave) |
| - { |
| - this._needsSave = false; |
| - this.saveToDisk(); |
| - } |
| - }); |
| - }, |
| - |
| - /** |
| - * @typedef FileInfo |
| - * @type {object} |
| - * @property {nsIFile} file |
| - * @property {number} lastModified |
| - */ |
| - |
| - /** |
| - * Returns a promise resolving in a list of existing backup files. |
| - * @return {Promise.<FileInfo[]>} |
| - */ |
| - getBackupFiles() |
| - { |
| - let backups = []; |
| - |
| - let checkBackupFile = index => |
| - { |
| - return IO.statFile(this.getBackupName(index)).then(statData => |
| - { |
| - if (!statData.exists) |
| - return backups; |
| - |
| - backups.push({ |
| - index, |
| - lastModified: statData.lastModified |
| - }); |
| - return checkBackupFile(index + 1); |
| - }).catch(error => |
| - { |
| - // Something went wrong, return whatever data we got so far. |
| - Cu.reportError(error); |
| - return backups; |
| - }); |
| - }; |
| - |
| - return checkBackupFile(1); |
| } |
| }; |
| /** |
| - * Joins subscription's filters to the subscription without any notifications. |
| - * @param {Subscription} subscription |
| - * filter subscription that should be connected to its filters |
| + * Adds a user-defined filter to the most suitable subscription in the list, |
| + * creates one if none found. |
| + * @param {Filter} filter |
| + * @returns {boolean} |
| + * false if the filter was already in the list and no adding was performed |
| */ |
| -function addSubscriptionFilters(subscription) |
| +FilterStorage.addFilter = function(filter) |
| { |
| - if (!(subscription.url in FilterStorage.knownSubscriptions)) |
| - return; |
| - |
| - for (let filter of subscription.filters) |
| - filter.subscriptions.push(subscription); |
| -} |
| + for (let subscription of this.subscriptions) |
| + if (!subscription.disabled && subscription.indexOfFilter(filter) >= 0) |
| + return false; |
| -/** |
| - * Removes subscription's filters from the subscription without any |
| - * notifications. |
| - * @param {Subscription} subscription filter subscription to be removed |
| - */ |
| -function removeSubscriptionFilters(subscription) |
| -{ |
| - if (!(subscription.url in FilterStorage.knownSubscriptions)) |
| - return; |
| - |
| - for (let filter of subscription.filters) |
| + let subscription = this.getSubscriptionForFilter(filter); |
| + try |
| { |
| - let i = filter.subscriptions.indexOf(subscription); |
| - if (i >= 0) |
| - filter.subscriptions.splice(i, 1); |
| + if (!subscription) |
| + { |
| + subscription = Subscription.fromURL(null); |
| + subscription.makeDefaultFor(filter); |
| + this.addSubscription(subscription); |
| + } |
| + subscription.insertFilterAt(filter, subscription.filterCount); |
| } |
| -} |
| + finally |
| + { |
| + if (subscription) |
| + subscription.delete(); |
| + } |
| + return true; |
| +}; |
| /** |
| - * Listener returned by FilterStorage.importData(), parses filter data. |
| - * @constructor |
| + * Removes a user-defined filter from the list |
| + * @param {Filter} filter |
| */ |
| -function INIParser() |
| -{ |
| - this.fileProperties = this.curObj = {}; |
| - this.subscriptions = []; |
| - this.knownFilters = Object.create(null); |
| - this.knownSubscriptions = Object.create(null); |
| -} |
| -INIParser.prototype = |
| +FilterStorage.removeFilter = function(filter) |
| { |
| - linesProcessed: 0, |
| - subscriptions: null, |
| - knownFilters: null, |
| - knownSubscriptions: null, |
| - wantObj: true, |
| - fileProperties: null, |
| - curObj: null, |
| - curSection: null, |
| - |
| - process(val) |
| + for (let subscription of this.subscriptions) |
| { |
| - let origKnownFilters = Filter.knownFilters; |
| - Filter.knownFilters = this.knownFilters; |
| - let origKnownSubscriptions = Subscription.knownSubscriptions; |
| - Subscription.knownSubscriptions = this.knownSubscriptions; |
| - let match; |
| - try |
| + if (subscription instanceof SpecialSubscription) |
| { |
| - if (this.wantObj === true && (match = /^(\w+)=(.*)$/.exec(val))) |
| - this.curObj[match[1]] = match[2]; |
| - else if (val === null || (match = /^\s*\[(.+)\]\s*$/.exec(val))) |
| + while (true) |
| { |
| - if (this.curObj) |
| - { |
| - // Process current object before going to next section |
| - switch (this.curSection) |
| - { |
| - case "filter": |
| - if ("text" in this.curObj) |
| - Filter.fromObject(this.curObj); |
| - break; |
| - case "subscription": { |
| - let subscription = Subscription.fromObject(this.curObj); |
| - if (subscription) |
| - this.subscriptions.push(subscription); |
| - break; |
| - } |
| - case "subscription filters": |
| - if (this.subscriptions.length) |
| - { |
| - let subscription = this.subscriptions[ |
| - this.subscriptions.length - 1 |
| - ]; |
| - for (let text of this.curObj) |
| - { |
| - let filter = Filter.fromText(text); |
| - subscription.filters.push(filter); |
| - filter.subscriptions.push(subscription); |
| - } |
| - } |
| - break; |
| - } |
| - } |
| - |
| - if (val === null) |
| - return; |
| - |
| - this.curSection = match[1].toLowerCase(); |
| - switch (this.curSection) |
| - { |
| - case "filter": |
| - case "subscription": |
| - this.wantObj = true; |
| - this.curObj = {}; |
| - break; |
| - case "subscription filters": |
| - this.wantObj = false; |
| - this.curObj = []; |
| - break; |
| - default: |
| - this.wantObj = undefined; |
| - this.curObj = null; |
| - } |
| + let index = subscription.indexOfFilter(filter); |
| + if (index >= 0) |
| + subscription.removeFilterAt(index); |
| + else |
| + break; |
| } |
| - else if (this.wantObj === false && val) |
| - this.curObj.push(val.replace(/\\\[/g, "[")); |
| - } |
| - finally |
| - { |
| - Filter.knownFilters = origKnownFilters; |
| - Subscription.knownSubscriptions = origKnownSubscriptions; |
| } |
| } |
| }; |
| + |
| +exports.FilterStorage = FilterStorage; |