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

Unified Diff: lib/filterStorage.js

Issue 29426559: Issue 5137 - [emscripten] Added basic filter storage implementation (Closed) Base URL: https://hg.adblockplus.org/adblockpluscore
Patch Set: Fixed bogus assert Created Aug. 31, 2017, 12:44 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « compiled/subscription/UserDefinedSubscription.cpp ('k') | lib/subscriptionClasses.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
« no previous file with comments | « compiled/subscription/UserDefinedSubscription.cpp ('k') | lib/subscriptionClasses.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld