| Index: lib/filterStorage.js |
| =================================================================== |
| --- a/lib/filterStorage.js |
| +++ b/lib/filterStorage.js |
| @@ -13,224 +13,250 @@ |
| * |
| * 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. |
| + * @fileOverview <code>filterStorage</code> object responsible for managing the |
| + * user's subscriptions and filters. |
| */ |
| const {IO} = require("io"); |
| const {Prefs} = require("prefs"); |
| const {Filter, ActiveFilter} = require("./filterClasses"); |
| const {Subscription, SpecialSubscription, |
| ExternalSubscription} = require("./subscriptionClasses"); |
| const {filterNotifier} = require("./filterNotifier"); |
| const {INIParser} = require("./iniParser"); |
| /** |
| * Version number of the filter storage file format. |
| * @type {number} |
| */ |
| -let formatVersion = 5; |
| +const FORMAT_VERSION = 5; |
|
Manish Jethani
2018/10/16 21:59:32
Using `const` and accordingly using ALL_CAPS
|
| /** |
| - * This class reads user's filters from disk, manages them in memory |
| - * and writes them back. |
| - * @class |
| + * {@link filterStorage} implementation. |
| */ |
| -let FilterStorage = exports.FilterStorage = |
| +class FilterStorage |
| { |
| /** |
| - * Will be set to true after the initial loadFromDisk() call completes. |
| - * @type {boolean} |
| + * @hideconstructor |
|
Manish Jethani
2018/10/16 21:59:31
Singleton, so hide constructor.
|
| */ |
| - initialized: false, |
| + constructor() |
| + { |
| + /** |
| + * Will be set to true after the initial {@link FilterStorage#loadFromDisk} |
| + * call completes. |
| + * @type {boolean} |
| + */ |
| + this.initialized = false; |
| + |
| + /** |
| + * Will be set to <code>true</code> if no <code>patterns.ini</code> file |
| + * exists. |
| + * @type {boolean} |
| + */ |
| + this.firstRun = false; |
| + |
| + /** |
| + * Map of properties listed in the filter storage file before the sections |
| + * start. Right now this should be only the format version. |
| + * @type {object} |
| + */ |
| + this.fileProperties = Object.create(null); |
| + |
| + /** |
| + * Map of subscriptions already on the list, by their URL/identifier. |
| + * @type {Map.<string,Subscription>} |
| + */ |
| + this.knownSubscriptions = new Map(); |
| + |
| + /** |
| + * Will be set to true if {@link FilterStorage#saveToDisk} is running |
| + * (reentrance protection). |
| + * @type {boolean} |
| + * @private |
|
Manish Jethani
2018/10/16 21:59:32
Marked @private
|
| + */ |
| + this._saving = false; |
| + |
| + /** |
| + * Will be set to true if a {@link FilterStorage#saveToDisk} call arrives |
| + * while {@link FilterStorage#saveToDisk} is already running (delayed |
| + * execution). |
| + * @type {boolean} |
| + * @private |
| + */ |
| + this._needsSave = false; |
| + } |
| /** |
| - * Version number of the patterns.ini format used. |
| + * The version number of the <code>patterns.ini</code> format used. |
| * @type {number} |
| */ |
| get formatVersion() |
| { |
| - return formatVersion; |
| - }, |
| + return FORMAT_VERSION; |
| + } |
| /** |
| - * File containing the filter list |
| + * The file containing the subscriptions. |
|
Manish Jethani
2018/10/16 21:59:32
Fixed some inconsistencies in the documentation.
|
| * @type {string} |
| */ |
| get sourceFile() |
| { |
| 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), |
| - |
| - /** |
| - * Yields subscriptions containing all filters |
| + * Yields all subscriptions in the storage. |
| * @yields {Subscription} |
| */ |
| *subscriptions() |
| { |
| yield* this.knownSubscriptions.values(); |
| - }, |
| + } |
| /** |
| - * Number of known subscriptions. |
| + * The number of subscriptions in the storage. |
| * @type {number} |
| */ |
| get subscriptionCount() |
| { |
| return this.knownSubscriptions.size; |
| - }, |
| - |
| - /** |
| - * Map of subscriptions already on the list, by their URL/identifier |
| - * @type {Map.<string,Subscription>} |
| - */ |
| - knownSubscriptions: new Map(), |
| + } |
| /** |
| * Finds the filter group that a filter should be added to by default. Will |
| - * return null if this group doesn't exist yet. |
| + * return <code>null</code> if this group doesn't exist yet. |
| * @param {Filter} filter |
| - * @return {?SpecialSubscription} |
| + * @returns {?SpecialSubscription} |
|
Manish Jethani
2018/10/16 21:59:32
s/@return/@returns/
|
| */ |
| getGroupForFilter(filter) |
| { |
| let generalSubscription = null; |
| - for (let subscription of FilterStorage.knownSubscriptions.values()) |
| + for (let subscription of this.knownSubscriptions.values()) |
| { |
| if (subscription instanceof SpecialSubscription && !subscription.disabled) |
| { |
| // 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 |
| + * Adds a subscription to the storage. |
| + * @param {Subscription} subscription The subscription to be added. |
| */ |
| addSubscription(subscription) |
| { |
| - if (FilterStorage.knownSubscriptions.has(subscription.url)) |
| + if (this.knownSubscriptions.has(subscription.url)) |
| return; |
| - FilterStorage.knownSubscriptions.set(subscription.url, subscription); |
| + this.knownSubscriptions.set(subscription.url, subscription); |
| addSubscriptionFilters(subscription); |
| filterNotifier.emit("subscription.added", subscription); |
| - }, |
| + } |
| /** |
| - * Removes a filter subscription from the list |
| - * @param {Subscription} subscription filter subscription to be removed |
| + * Removes a subscription from the storage. |
| + * @param {Subscription} subscription The subscription to be removed. |
| */ |
| removeSubscription(subscription) |
| { |
| - if (!FilterStorage.knownSubscriptions.has(subscription.url)) |
| + if (!this.knownSubscriptions.has(subscription.url)) |
| return; |
| removeSubscriptionFilters(subscription); |
| - FilterStorage.knownSubscriptions.delete(subscription.url); |
| + this.knownSubscriptions.delete(subscription.url); |
| // This should be the last remaining reference to the Subscription |
| // object. |
| Subscription.knownSubscriptions.delete(subscription.url); |
| filterNotifier.emit("subscription.removed", 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 |
| + * Replaces the list of filters in a subscription with a new list. |
| + * @param {Subscription} subscription The subscription to be updated. |
| + * @param {Array.<Filter>} filters The new list of filters. |
|
Manish Jethani
2018/10/16 21:59:32
s/Filter[]/Array.<Filter>/
|
| */ |
| updateSubscriptionFilters(subscription, filters) |
| { |
| removeSubscriptionFilters(subscription); |
| let oldFilters = subscription.filters; |
| subscription.filters = filters; |
| addSubscriptionFilters(subscription); |
| filterNotifier.emit("subscription.updated", subscription, oldFilters); |
| - }, |
| + } |
| /** |
| - * Adds a user-defined filter to the list |
| + * Adds a user-defined filter to the storage. |
| * @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 |
| + * @param {?SpecialSubscription} [subscription] The subscription that the |
| + * filter should be added to. |
| + * @param {number} [position] The position within the subscription at which |
| + * the filter should be added. If not specified, the filter is added at the |
| + * end of the subscription. |
| */ |
| addFilter(filter, subscription, position) |
| { |
| if (!subscription) |
| { |
| for (let currentSubscription of filter.subscriptions()) |
| { |
| if (currentSubscription instanceof SpecialSubscription && |
| !currentSubscription.disabled) |
| { |
| return; // No need to add |
| } |
| } |
| - subscription = FilterStorage.getGroupForFilter(filter); |
| + subscription = this.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; |
| filter.addSubscription(subscription); |
| subscription.filters.splice(position, 0, filter); |
| filterNotifier.emit("filter.added", filter, subscription, position); |
| - }, |
| + } |
| /** |
| - * Removes a user-defined filter from the list |
| + * Removes a user-defined filter from the storage. |
| * @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) |
| + * @param {?SpecialSubscription} [subscription] The subscription that the |
| + * filter should be removed from. If not specified, the filter will be |
| + * removed from all subscriptions. |
| + * @param {number} [position] The position within the subscription at which |
| + * the filter should be removed. If not specified, all instances of the |
| + * filter will be removed. |
| */ |
| removeFilter(filter, subscription, position) |
| { |
| let subscriptions = ( |
| subscription ? [subscription] : filter.subscriptions() |
| ); |
| for (let currentSubscription of subscriptions) |
| { |
| @@ -259,25 +285,25 @@ |
| if (currentSubscription.filters.indexOf(filter) < 0) |
| filter.removeSubscription(currentSubscription); |
| filterNotifier.emit("filter.removed", filter, currentSubscription, |
| currentPosition); |
| } |
| } |
| } |
| } |
| - }, |
| + } |
| /** |
| - * Moves a user-defined filter to a new position |
| + * 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 |
| + * @param {SpecialSubscription} subscription The subscription where the |
| + * filter is located. |
| + * @param {number} oldPosition The current position of the filter. |
| + * @param {number} newPosition The new position of the filter. |
| */ |
| moveFilter(filter, subscription, oldPosition, newPosition) |
| { |
| if (!(subscription instanceof SpecialSubscription) || |
| subscription.filters[oldPosition] != filter) |
| { |
| return; |
| } |
| @@ -286,61 +312,60 @@ |
| subscription.filters.length - 1); |
| if (oldPosition == newPosition) |
| return; |
| subscription.filters.splice(oldPosition, 1); |
| subscription.filters.splice(newPosition, 0, filter); |
| filterNotifier.emit("filter.moved", filter, subscription, oldPosition, |
| newPosition); |
| - }, |
| + } |
| /** |
| - * Increases the hit count for a filter by one |
| + * 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 |
| + * Resets hit count for some filters. |
| + * @param {?Array.<Filter>} filters The filters to be reset. If |
|
Manish Jethani
2018/10/16 21:59:32
Added ? before the type to indicate that the value
|
| + * <code>null</code>, all filters will be reset. |
| */ |
| resetHitCounts(filters) |
| { |
| if (!filters) |
| filters = Filter.knownFilters.values(); |
| 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. |
| + * @param {boolean} silent If <code>true</code>, no "load" notification will |
| + * be sent out. |
| + * @returns {TextSink} The function to be called for each line of data. |
| + * Calling it with <code>null</code> as the argument 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) |
| @@ -353,21 +378,21 @@ |
| this.knownSubscriptions = knownSubscriptions; |
| Filter.knownFilters = parser.knownFilters; |
| Subscription.knownSubscriptions = parser.knownSubscriptions; |
| if (!silent) |
| filterNotifier.emit("load"); |
| } |
| }; |
| - }, |
| + } |
| /** |
| - * Loads all subscriptions from the disk. |
| - * @return {Promise} promise resolved or rejected when loading is complete |
| + * Loads all subscriptions from disk. |
| + * @returns {Promise} A promise resolved or rejected when loading is complete. |
| */ |
| loadFromDisk() |
| { |
| let tryBackup = backupIndex => |
| { |
| return this.restoreBackup(backupIndex, true).then(() => |
| { |
| if (this.knownSubscriptions.size == 0) |
| @@ -400,68 +425,70 @@ |
| { |
| Cu.reportError(error); |
| return tryBackup(1); |
| }).then(() => |
| { |
| this.initialized = true; |
| filterNotifier.emit("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 |
| + * Constructs the file name for a <code>patterns.ini</code> backup. |
| + * @param {number} backupIndex Number of the backup file (1 being the most |
| + * recent). |
| + * @returns {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 |
| + * @param {number} backupIndex Number of the backup to restore (1 being the |
| + * most recent). |
| + * @param {boolean} silent If <code>true</code>, no "load" notification will |
| + * be sent out. |
| + * @returns {Promise} A promise resolved or rejected when restoration 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. |
| + * @yields {string} |
| */ |
| *exportData() |
| { |
| // Do not persist external subscriptions |
| let subscriptions = []; |
| for (let subscription of this.subscriptions()) |
| { |
| if (!(subscription instanceof ExternalSubscription) && |
| !(subscription instanceof SpecialSubscription && |
| subscription.filters.length == 0)) |
| { |
| subscriptions.push(subscription); |
| } |
| } |
| yield "# Adblock Plus preferences"; |
| - yield "version=" + formatVersion; |
| + yield "version=" + this.formatVersion; |
| let saved = new Set(); |
| let buf = []; |
| // Save subscriptions |
| for (let subscription of subscriptions) |
| { |
| yield ""; |
| @@ -487,34 +514,21 @@ |
| 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 |
| + * Saves all subscriptions back to disk. |
| + * @returns {Promise} A promise resolved or rejected when saving is complete. |
| */ |
| saveToDisk() |
| { |
| if (this._saving) |
| { |
| this._needsSave = true; |
| return; |
| } |
| @@ -588,28 +602,28 @@ |
| { |
| this._saving = false; |
| if (this._needsSave) |
| { |
| this._needsSave = false; |
| this.saveToDisk(); |
| } |
| }); |
| - }, |
| + } |
| /** |
| * @typedef FileInfo |
| * @type {object} |
| * @property {number} index |
| * @property {number} lastModified |
| */ |
| /** |
| * Returns a promise resolving in a list of existing backup files. |
| - * @return {Promise.<FileInfo[]>} |
| + * @returns {Promise.<Array.<FileInfo>>} |
| */ |
| getBackupFiles() |
| { |
| let backups = []; |
| let checkBackupFile = index => |
| { |
| return IO.statFile(this.getBackupName(index)).then(statData => |
| @@ -627,37 +641,45 @@ |
| // 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 |
| + * Reads the user's filters from disk, manages them in memory, and writes them |
| + * back to disk. |
| + */ |
| +let filterStorage = new FilterStorage(); |
| + |
| +exports.filterStorage = filterStorage; |
| + |
| +/** |
| + * Connects a subscription to its filters without any notifications. |
|
Manish Jethani
2018/10/16 21:59:31
s/joins/connects/
|
| + * @param {Subscription} subscription The subscription that should be |
| + * connected to its filters. |
| */ |
| function addSubscriptionFilters(subscription) |
| { |
| - if (!FilterStorage.knownSubscriptions.has(subscription.url)) |
| + if (!filterStorage.knownSubscriptions.has(subscription.url)) |
| return; |
| for (let filter of subscription.filters) |
| filter.addSubscription(subscription); |
| } |
| /** |
| - * Removes subscription's filters from the subscription without any |
| - * notifications. |
| - * @param {Subscription} subscription filter subscription to be removed |
| + * Disconnects a subscription from its filters without any notifications. |
|
Manish Jethani
2018/10/16 21:59:32
s/removes/disconnects/
|
| + * @param {Subscription} subscription The subscription that should be |
| + * disconnected from its filters. |
| */ |
| function removeSubscriptionFilters(subscription) |
| { |
| - if (!FilterStorage.knownSubscriptions.has(subscription.url)) |
| + if (!filterStorage.knownSubscriptions.has(subscription.url)) |
| return; |
| for (let filter of subscription.filters) |
| filter.removeSubscription(subscription); |
| } |