| Index: lib/filterHits.js | 
| =================================================================== | 
| new file mode 100644 | 
| --- /dev/null | 
| +++ b/lib/filterHits.js | 
| @@ -0,0 +1,236 @@ | 
| +/* | 
| + * This file is part of Adblock Plus <http://adblockplus.org/>, | 
| + * Copyright (C) 2006-2014 Eyeo GmbH | 
| + * | 
| + * Adblock Plus is free software: you can redistribute it and/or modify | 
| + * it under the terms of the GNU General Public License version 3 as | 
| + * published by the Free Software Foundation. | 
| + * | 
| + * Adblock Plus is distributed in the hope that it will be useful, | 
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
| + * 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/>. | 
| + */ | 
| + | 
| +let {IO} = require("io"); | 
| +let {Utils} = require("utils"); | 
| +let {Prefs} = require("prefs"); | 
| +let {TimeLine} = require("timeline"); | 
| +let {Filter} = require("filterClasses"); | 
| + | 
| +/** | 
| + * This class reads filter hits statistics from disk, manages them in memory and writes them back.W | 
| + * @class | 
| + */ | 
| +let FilterHits = exports.FilterHits = | 
| +{ | 
| + statistics: {}, | 
| + | 
| + _loading: false, | 
| + _saving: false, | 
| + _needsSave: false, | 
| + | 
| + /** | 
| + * File that the filter hits statistics has been loaded from and should be saved to | 
| + * @type nsIFile | 
| + */ | 
| + get filterHitsFile() | 
| + { | 
| + let file = null; | 
| + if (!file) | 
| + { | 
| + // Place the file in the data dir | 
| + file = IO.resolveFilePath(Prefs.data_directory); | 
| + if (file) | 
| + file.append("filter-hits.ini"); | 
| + } | 
| + if (!file) | 
| + { | 
| + // Data directory pref misconfigured? Try the default value | 
| + try | 
| + { | 
| + file = IO.resolveFilePath(Services.prefs.getDefaultBranch("extensions.adblockplus.").getCharPref("data_directory")); | 
| + if (file) | 
| + file.append("filter-hits.ini"); | 
| + } catch(e) {} | 
| + } | 
| + | 
| + if (!file) | 
| + Cu.reportError("Adblock Plus: Failed to resolve filter-hits file"); | 
| + | 
| + this.__defineGetter__("filterHitsFile", function() file); | 
| + return this.filterHitsFile; | 
| + }, | 
| + | 
| + /** | 
| + * Increases the filter hit count by one | 
| + * @param {Filter} filter | 
| + * @param {Window} window Window that the match originated in (required to get host name) | 
| + */ | 
| + increaseFilterHits: function(filter, wnd) | 
| + { | 
| + if(!filter.text) | 
| + return; | 
| + | 
| + if(filter.text in this.statistics) | 
| + this.statistics[filter.text].statsHitCount++; | 
| + else | 
| + this.statistics[filter.text] = {statsHitCount:1, lastHit: 0, domainsCount:{}}; | 
| + | 
| + if(filter.lastHit) | 
| + this.statistics[filter.text].lastHit = filter.lastHit; | 
| + | 
| + // Get hostname from window object | 
| + let wndLocation = Utils.getOriginWindow(wnd).location.href; | 
| + let host = Utils.unwrapURL(wndLocation).host; | 
| + | 
| + this._increaseDomainHit(this.statistics[filter.text], host); | 
| + }, | 
| + | 
| + /** | 
| + * Increases Domain hit count by one | 
| + * @param {statisticFilterObj} statisticFilterObj statistic's filter object | 
| + * @param {host} host String | 
| + */ | 
| + _increaseDomainHit: function(statisticFilterObj, host) | 
| + { | 
| + if(host in statisticFilterObj.domainsCount) | 
| + statisticFilterObj.domainsCount[host]++; | 
| + else | 
| + statisticFilterObj.domainsCount[host] = 1; | 
| + }, | 
| + | 
| + resetFilterHits: function() | 
| + { | 
| + this.statistics = {}; | 
| + }, | 
| + | 
| + /** | 
| + * send filter hits statistics to ABP sever and reset filter hits count | 
| + */ | 
| + sendFilterHitsToServer: function() | 
| + { | 
| + //TODO implement ajax request to server sending this.statistics | 
| + //append user subscriptions and browser info before sending | 
| + //and reset filterhits on 200 response | 
| + this.resetFilterHits(); | 
| + this.saveFilterHitsToDisk(); | 
| + }, | 
| + | 
| + /** | 
| + * Loads Filter hit statistics from the disk | 
| + */ | 
| + loadFilterHitsFromDisk: function() | 
| + { | 
| + if (this._loading) | 
| + return; | 
| + | 
| + let sourceFile = FilterHits.filterHitsFile; | 
| + if(!sourceFile) | 
| + return; | 
| + | 
| + TimeLine.enter("Entered FilterHits.loadFilterHitsFromDisk()"); | 
| + this._loading = true; | 
| + | 
| + let readFile = function() | 
| + { | 
| + TimeLine.enter("FilterHits.loadFilterHitsFromDisk() -> readFile()"); | 
| + let parser = new FilterHitsParser(); | 
| + IO.readFromFile(sourceFile, true, parser, function(e) | 
| + { | 
| + TimeLine.enter("FilterHits.loadFilterHitsFromDisk() read callback"); | 
| + if (e) | 
| + Cu.reportError(e); | 
| + | 
| + doneReading(parser); | 
| + }.bind(this), "FilterHitsRead"); | 
| + | 
| + TimeLine.leave("FilterHits.loadFilterHitsFromDisk() <- readFile()", "FilterHitsRead"); | 
| + }.bind(this); | 
| + | 
| + var doneReading = function(parser) | 
| + { | 
| + FilterHits.statistics = parser.statistics; | 
| + this._loading = false; | 
| + TimeLine.leave("FilterHits.loadFilterHitsFromDisk() read callback done"); | 
| + }.bind(this); | 
| + | 
| + readFile(sourceFile); | 
| + TimeLine.leave("FilterHits.loadFilterHitsFromDisk() done"); | 
| + }, | 
| + | 
| + /** | 
| + * Stringify filter hits statistics object | 
| + */ | 
| + _generateFilterHitData: function() | 
| + { | 
| + yield JSON.stringify(FilterHits.statistics); | 
| + }, | 
| + | 
| + /** | 
| + * Save Filters hit statistics to the disk | 
| + */ | 
| + saveFilterHitsToDisk: function() | 
| + { | 
| + let targetFile = FilterHits.filterHitsFile; | 
| + if (!targetFile) | 
| + return; | 
| + | 
| + if (this._saving) | 
| + { | 
| + this._needsSave = true; | 
| + return; | 
| + } | 
| + TimeLine.enter("Entered FilterHits.saveFilterHitsToDisk()"); | 
| + // Make sure the file's parent directory exists | 
| + try { | 
| + targetFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); | 
| + } catch (e) {} | 
| + | 
| + let writeStats = function() | 
| + { | 
| + TimeLine.enter("FilterHits.saveFilterHitsToDisk() -> writeStats()"); | 
| + IO.writeToFile(targetFile, true, this._generateFilterHitData(), function(e) | 
| + { | 
| + TimeLine.enter("FilterHits.saveFilterHitsToDisk() write callback"); | 
| + this._saving = false; | 
| + | 
| + if (e) | 
| + Cu.reportError(e); | 
| + | 
| + if (this._needsSave) | 
| + { | 
| + this._needsSave = false; | 
| + this.saveFilterHitsToDisk(); | 
| + } | 
| + TimeLine.leave("FilterHits.saveFilterHitsToDisk() write callback done"); | 
| + }.bind(this), "FilterHitsWriteStats"); | 
| + TimeLine.leave("FilterHits.saveFilterHitsToDisk() -> writeStats()", "FilterStorageWriteStats"); | 
| + }.bind(this); | 
| + | 
| + this._saving = true; | 
| + writeStats(); | 
| + TimeLine.leave("FilterHits.saveFilterHitsToDisk() done"); | 
| + } | 
| +}; | 
| + | 
| +/** | 
| + * IO.readFromFile() listener to parse filter-hits data. | 
| + * @constructor | 
| + */ | 
| +function FilterHitsParser() | 
| +{ | 
| + this.statistics = {__proto__: null}; | 
| +} | 
| +FilterHitsParser.prototype = | 
| +{ | 
| + process: function(val) | 
| + { | 
| + if (val === null) | 
| + return; | 
| + this.statistics = JSON.parse(val); | 
| + } | 
| +}; |