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

Side by Side Diff: lib/filterHits.js

Issue 6337686776315904: Issue 394 - hit statistics tool data collection (Closed)
Patch Set: Rebase to changeset: 4095 Created Dec. 15, 2015, 6:09 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2015 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 let {Services} = Cu.import("resource://gre/modules/Services.jsm", null);
19 let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", null);
20
21 let {Prefs} = require("prefs");
22 let {Utils} = require("utils");
23 let {MILLIS_IN_DAY} = require("downloader");
24 let {FilterNotifier} = require("filterNotifier");
25 let {DownloadableSubscription} = require("subscriptionClasses");
26
27 /**
28 * This class reads filter hits statistics from SQLite database,
29 * manages them in memory and writes them back.
30 * @class
31 */
32 let FilterHits = exports.FilterHits =
33 {
34 /**
35 * Data that shoud be sent to the server
36 * @type Object
37 */
38 filters: Object.create(null),
39
40 /**
41 * Time since last push
42 * @type Number
43 */
44 _lastPush: 0,
45
46 /**
47 * Indicates the timeframe between pushes
48 * @type Number
49 */
50 _pushInterval: MILLIS_IN_DAY * 7,
51
52 /**
53 * Indicates whether the data is being loaded from storage
54 * @type Boolean
55 */
56 _loading: false,
57
58 /**
59 * Indicates whether the data is being saved to storage
60 * @type Boolean
61 */
62 _saving: false,
63
64 /**
65 * Indicates whether the data is being sent to the server
66 * @type Boolean
67 */
68 _sending: false,
69
70 /**
71 * Increases the filter hit count
72 * @param {Filter} filter
73 * @param {String} host
74 */
75 increaseFilterHits: function(filter, host)
76 {
77 let subscriptions = filter.subscriptions;
78 let inDownloadableSubscription = false;
79 for (let i = 0; i < subscriptions.length; i++)
80 {
81 if (subscriptions[i] instanceof DownloadableSubscription)
82 {
83 inDownloadableSubscription = true;
84 break;
85 }
86 }
87
88 if (!inDownloadableSubscription)
89 return;
90
91 if (!(filter.text in this.filters))
92 this.filters[filter.text] = Object.create(null);
93
94 let statFilter = this.filters[filter.text];
95 let filterType = filter.thirdParty ? "thirdParty" : "firstParty";
96
97 if (!(filterType in statFilter))
98 statFilter[filterType] = Object.create(null);
99
100 if (!("subscriptions" in statFilter))
101 statFilter.subscriptions = [];
102
103 for (let i = 0; i < subscriptions.length; i++)
104 {
105 if (subscriptions[i] instanceof DownloadableSubscription
106 && statFilter.subscriptions.indexOf(subscriptions[i].url) == -1)
107 statFilter.subscriptions.push(subscriptions[i].url);
108 }
109
110 if (!(host in statFilter[filterType]))
111 {
112 statFilter[filterType][host] = {hits: 1, latest: filter.lastHit};
113 }
114 else
115 {
116 statFilter[filterType][host].hits++;
117 statFilter[filterType][host].latest = filter.lastHit;
118 }
119 },
120
121 resetFilterHits: function()
122 {
123 this.filters = Object.create(null);
124 this.saveFilterHitsToDatabase();
125 },
126
127 sendFilterHitsToServer: function()
128 {
129 if (!Prefs.sendstats)
130 return;
131
132 let request = new XMLHttpRequest();
133 request.open("POST", Prefs.sendstats_url);
134 request.setRequestHeader("Content-Type", "application/json");
135 request.addEventListener("load", function(event)
136 {
137 FilterHits._sending = false;
138 if (request.status >= 200 && request.status < 300)
139 {
140 FilterHits._lastPush = new Date().getTime();
141 FilterHits.resetFilterHits();
142 }
143 else
144 {
145 Cu.reportError("Could not send filter hit statistics to Adblock Plus ser ver.");
146 }
147 }, false);
148
149 let {addonName, addonVersion, application,
150 applicationVersion, platform, platformVersion} = require("info");
151 let data = {
152 version: 1,
153 timeSincePush: this._lastPush,
154 addonName: addonName,
155 addonVersion: addonVersion,
156 application: application,
157 applicationVersion: applicationVersion,
158 platform: platform,
159 platformVersion: platformVersion,
160 filters: this.filters
161 };
162
163 this._sending = true;
164 request.send(JSON.stringify(data));
165 },
166
167 getStorageFile: function()
168 {
169 return FileUtils.getFile("ProfD", ["adblockplus.sqlite"]);
170 },
171
172 checkCreateTable: function(connection)
173 {
174 if (!connection.tableExists("filterhits"))
175 connection.executeSimpleSQL("CREATE TABLE filterhits (id INTEGER PRIMARY K EY, filters TEXT, date INTEGER)");
176 },
177
178 /**
179 * Load Filter hits from database
180 */
181 loadFilterHitsFromDatabase: function()
182 {
183 let storageFile = this.getStorageFile();
184 if (!storageFile)
185 return;
186
187 let connection = Services.storage.openDatabase(storageFile);
188 this.checkCreateTable(connection);
189
190 let statement = connection.createStatement("SELECT * FROM filterhits");
Wladimir Palant 2016/02/15 12:25:40 That was not really the idea. We use the database
saroyanm 2016/02/19 17:38:11 Fare enough, makes more sense, not sure why initia
191 if (!this._loading)
192 {
193 this._loading = true;
194 statement.executeAsync(
195 {
196 handleResult: function(results)
197 {
198 let row = results.getNextRow();
199 if (row)
200 {
201 let filters = row.getResultByName("filters");
202 let lastDate = row.getResultByName("date");
203 FilterHits.filters = JSON.parse(filters);
204 FilterHits._lastPush = lastDate;
205 }
206 },
207
208 handleError: function(error)
209 {
210 Cu.reportError(error.message);
211 },
212
213 handleCompletion: function(reason)
214 {
215 if (reason != Ci.mozIStorageStatementCallback.REASON_FINISHED)
216 Cu.reportError("Loading of filter hits canceled or aborted.");
217 FilterHits._loading = false;
218 }
219 });
220 }
221
222 connection.asyncClose();
223 },
224
225 /**
226 * Save Filter hits to database
227 */
228 saveFilterHitsToDatabase: function()
229 {
230 let now = new Date().getTime();
231 if (!this._lastPush)
232 this._lastPush = now;
233
234 if (!this._sending && now - this._lastPush > this._pushInterval)
235 {
236 this.sendFilterHitsToServer();
237 return;
238 }
239
240 let storageFile = this.getStorageFile();
241 if (!storageFile)
242 return;
243
244 let connection = Services.storage.openDatabase(storageFile);
245 this.checkCreateTable(connection);
246
247 let statement = connection.createStatement("INSERT OR REPLACE INTO filterhit s (id, filters, date) VALUES(0, :filters, :date)");
248 statement.params.filters = JSON.stringify(this.filters);
249 statement.params.date = this._lastPush;
250 if (!this._saving)
251 {
252 this._saving = true;
253 statement.executeAsync(
254 {
255 handleError: function(aError)
256 {
257 Cu.reportError(aError.message);
258 },
259
260 handleCompletion: function(aReason)
261 {
262 if (aReason != Components.interfaces.mozIStorageStatementCallback.REAS ON_FINISHED)
263 Cu.reportError("Writing of filter hits canceled or aborted.");
264 FilterHits._saving = false;
265 }
266 });
267 }
268
269 connection.asyncClose();
270 }
271 };
272
273 FilterNotifier.addListener(function(action)
274 {
275 if (action == "load" && Prefs.sendstats)
276 FilterHits.loadFilterHitsFromDatabase();
277 });
OLDNEW

Powered by Google App Engine
This is Rietveld