|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the AdBlock subscription class. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt4.QtCore import * |
|
13 from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply |
|
14 from PyQt4.QtGui import QMessageBox |
|
15 |
|
16 from AdBlockRule import AdBlockRule |
|
17 |
|
18 import Helpviewer.HelpWindow |
|
19 |
|
20 import Utilities |
|
21 |
|
22 class AdBlockSubscription(QObject): |
|
23 """ |
|
24 Class implementing the AdBlock subscription. |
|
25 |
|
26 @signal changed() emitted after the subscription has changed |
|
27 @signal rulesChanged() emitted after the subscription's rules have changed |
|
28 """ |
|
29 def __init__(self, url, parent = None): |
|
30 """ |
|
31 Constructor |
|
32 |
|
33 @param url AdBlock URL for the subscription (QUrl) |
|
34 @param parent reference to the parent object (QObject) |
|
35 """ |
|
36 QObject.__init__(self, parent) |
|
37 |
|
38 self.__url = url.toEncoded() |
|
39 self.__enabled = False |
|
40 self.__downloading = None |
|
41 |
|
42 self.__title = "" |
|
43 self.__location = QByteArray() |
|
44 self.__lastUpdate = QDateTime() |
|
45 |
|
46 self.__rules = [] # list containing all AdBlock rules |
|
47 |
|
48 self.__networkExceptionRules = [] |
|
49 self.__networkBlockRules = [] |
|
50 self.__pageRules = [] |
|
51 |
|
52 self.__parseUrl(url) |
|
53 |
|
54 def __parseUrl(self, url): |
|
55 """ |
|
56 Private method to parse the AdBlock URL for the subscription. |
|
57 |
|
58 @param url AdBlock URL for the subscription (QUrl) |
|
59 """ |
|
60 if url.scheme() != "abp": |
|
61 return |
|
62 |
|
63 if url.path() != "subscribe": |
|
64 return |
|
65 |
|
66 self.__title = \ |
|
67 QUrl.fromPercentEncoding(url.encodedQueryItemValue("title")) |
|
68 self.__enabled = \ |
|
69 QUrl.fromPercentEncoding(url.encodedQueryItemValue("enabled")) != "false" |
|
70 self.__location = \ |
|
71 QByteArray(QUrl.fromPercentEncoding(url.encodedQueryItemValue("location"))) |
|
72 |
|
73 lastUpdateByteArray = url.encodedQueryItemValue("lastUpdate") |
|
74 lastUpdateString = QUrl.fromPercentEncoding(lastUpdateByteArray) |
|
75 self.__lastUpdate = QDateTime.fromString(lastUpdateString, Qt.ISODate) |
|
76 |
|
77 self.__loadRules() |
|
78 |
|
79 def url(self): |
|
80 """ |
|
81 Public method to generate the url for this subscription. |
|
82 |
|
83 @return AdBlock URL for the subscription (QUrl) |
|
84 """ |
|
85 url = QUrl() |
|
86 url.setScheme("abp") |
|
87 url.setPath("subscribe") |
|
88 |
|
89 queryItems = [] |
|
90 queryItems.append(("location", unicode(self.__location))) |
|
91 queryItems.append(("title", self.__title)) |
|
92 if self.__enabled: |
|
93 queryItems.append(("enabled", "false")) |
|
94 if self.__lastUpdate.isValid(): |
|
95 queryItems.append(("lastUpdate", |
|
96 self.__lastUpdate.toString(Qt.ISODate))) |
|
97 url.setQueryItems(queryItems) |
|
98 return url |
|
99 |
|
100 def isEnabled(self): |
|
101 """ |
|
102 Public method to check, if the subscription is enabled. |
|
103 |
|
104 @return flag indicating the enabled status (boolean) |
|
105 """ |
|
106 return self.__enabled |
|
107 |
|
108 def setEnabled(self, enabled): |
|
109 """ |
|
110 Public method to set the enabled status. |
|
111 |
|
112 @param enabled flag indicating the enabled status (boolean) |
|
113 """ |
|
114 if self.__enabled == enabled: |
|
115 return |
|
116 |
|
117 self.__enabled = enabled |
|
118 self.__populateCache() |
|
119 self.emit(SIGNAL("changed()")) |
|
120 |
|
121 def title(self): |
|
122 """ |
|
123 Public method to get the subscription title. |
|
124 |
|
125 @return subscription title (string) |
|
126 """ |
|
127 return self.__title |
|
128 |
|
129 def setTitle(self, title): |
|
130 """ |
|
131 Public method to set the subscription title. |
|
132 |
|
133 @param title subscription title (string) |
|
134 """ |
|
135 if self.__title == title: |
|
136 return |
|
137 |
|
138 self.__title = title |
|
139 self.emit(SIGNAL("changed()")) |
|
140 |
|
141 def location(self): |
|
142 """ |
|
143 Public method to get the subscription location. |
|
144 |
|
145 @return URL of the subscription location (QUrl) |
|
146 """ |
|
147 return QUrl.fromEncoded(self.__location) |
|
148 |
|
149 def setLocation(self, url): |
|
150 """ |
|
151 Public method to set the subscription location. |
|
152 |
|
153 @param url URL of the subscription location (QUrl) |
|
154 """ |
|
155 if url == self.location(): |
|
156 return |
|
157 |
|
158 self.__location = url.toEncoded() |
|
159 self.__lastUpdate = QDateTime() |
|
160 self.emit(SIGNAL("changed()")) |
|
161 |
|
162 def lastUpdate(self): |
|
163 """ |
|
164 Public method to get the date and time of the last update. |
|
165 |
|
166 @return date and time of the last update (QDateTime) |
|
167 """ |
|
168 return self.__lastUpdate |
|
169 |
|
170 def rulesFileName(self): |
|
171 """ |
|
172 Public method to get the name of the rules file. |
|
173 |
|
174 @return name of the rules file (string) |
|
175 """ |
|
176 if self.location().scheme() == "file": |
|
177 return self.location().toLocalFile() |
|
178 |
|
179 if self.__location.isEmpty(): |
|
180 return "" |
|
181 |
|
182 sha1 = QCryptographicHash.hash(self.__location, QCryptographicHash.Sha1).toHex() |
|
183 dataDir = os.path.join(Utilities.getConfigDir(), "browser", "subscriptions") |
|
184 if not os.path.exists(dataDir): |
|
185 os.makedirs(dataDir) |
|
186 fileName = os.path.join(dataDir, "adblock_subscription_%s" % sha1) |
|
187 return fileName |
|
188 |
|
189 def __loadRules(self): |
|
190 """ |
|
191 Private method to load the rules of the subscription. |
|
192 """ |
|
193 fileName = self.rulesFileName() |
|
194 f = QFile(fileName) |
|
195 if f.exists(): |
|
196 if not f.open(QIODevice.ReadOnly): |
|
197 QMessageBox.warning(None, |
|
198 self.trUtf8("Load subscription rules"), |
|
199 self.trUtf8("""Unable to open adblock file '{0}' for reading.""")\ |
|
200 .format(fileName)) |
|
201 else: |
|
202 textStream = QTextStream(f) |
|
203 header = textStream.readLine(1024) |
|
204 if not header.startswith("[Adblock"): |
|
205 QMessageBox.warning(None, |
|
206 self.trUtf8("Load subscription rules"), |
|
207 self.trUtf8("""Adblock file '{0}' does not start""" |
|
208 """ with [Adblock.""")\ |
|
209 .format(fileName)) |
|
210 f.close() |
|
211 f.remove() |
|
212 self.__lastUpdate = QDateTime() |
|
213 else: |
|
214 self.__rules = [] |
|
215 while not textStream.atEnd(): |
|
216 line = textStream.readLine() |
|
217 self.__rules.append(AdBlockRule(line)) |
|
218 self.__populateCache() |
|
219 self.emit(SIGNAL("changed()")) |
|
220 |
|
221 if not self.__lastUpdate.isValid() or \ |
|
222 self.__lastUpdate.addDays(7) < QDateTime.currentDateTime(): |
|
223 self.updateNow() |
|
224 |
|
225 def updateNow(self): |
|
226 """ |
|
227 Public method to update the subscription immediately. |
|
228 """ |
|
229 if self.__downloading is not None: |
|
230 return |
|
231 |
|
232 if not self.location().isValid(): |
|
233 return |
|
234 |
|
235 if self.location().scheme() == "file": |
|
236 self.__lastUpdate = QDateTime.currentDateTime() |
|
237 self.__loadRules() |
|
238 self.emit(SIGNAL("changed()")) |
|
239 return |
|
240 |
|
241 request = QNetworkRequest(self.location()) |
|
242 self.__downloading = \ |
|
243 Helpviewer.HelpWindow.HelpWindow.networkAccessManager().get(request) |
|
244 self.connect(self.__downloading, SIGNAL("finished()"), self.__rulesDownloaded) |
|
245 |
|
246 def __rulesDownloaded(self): |
|
247 """ |
|
248 Private slot to deal with the downloaded rules. |
|
249 """ |
|
250 reply = self.sender() |
|
251 |
|
252 response = reply.readAll() |
|
253 redirect = reply.attribute(QNetworkRequest.RedirectionTargetAttribute).toUrl() |
|
254 reply.close() |
|
255 reply.deleteLater() |
|
256 |
|
257 if reply.error() != QNetworkReply.NoError: |
|
258 QMessageBox.warning(None, |
|
259 self.trUtf8("Downloading subscription rules"), |
|
260 self.trUtf8("""<p>Subscription rules could not be downloaded.</p>""" |
|
261 """<p>Error: {0}</p>""").format(reply.errorString())) |
|
262 return |
|
263 |
|
264 if redirect.isValid(): |
|
265 request = QNetworkRequest(redirect) |
|
266 self.__downloading = \ |
|
267 Helpviewer.HelpWindow.HelpWindow.networkAccessManager().get(request) |
|
268 self.connect(self.__downloading, SIGNAL("finished()"), self.__rulesDownloaded) |
|
269 return |
|
270 |
|
271 if response.isEmpty(): |
|
272 QMessageBox.warning(None, |
|
273 self.trUtf8("Downloading subscription rules"), |
|
274 self.trUtf8("""Got empty subscription rules.""")) |
|
275 return |
|
276 |
|
277 fileName = self.rulesFileName() |
|
278 f = QFile(fileName) |
|
279 if not f.open(QIODevice.ReadWrite): |
|
280 QMessageBox.warning(None, |
|
281 self.trUtf8("Downloading subscription rules"), |
|
282 self.trUtf8("""Unable to open adblock file '{0}' for writing.""")\ |
|
283 .file(fileName)) |
|
284 return |
|
285 f.write(response) |
|
286 self.__lastUpdate = QDateTime.currentDateTime() |
|
287 self.__loadRules() |
|
288 self.emit(SIGNAL("changed()")) |
|
289 self.__downloading = None |
|
290 |
|
291 def saveRules(self): |
|
292 """ |
|
293 Public method to save the subscription rules. |
|
294 """ |
|
295 fileName = self.rulesFileName() |
|
296 if not fileName: |
|
297 return |
|
298 |
|
299 f = QFile(fileName) |
|
300 if not f.open(QIODevice.ReadWrite | QIODevice.Truncate): |
|
301 QMessageBox.warning(None, |
|
302 self.trUtf8("Saving subscription rules"), |
|
303 self.trUtf8("""Unable to open adblock file '{0}' for writing.""")\ |
|
304 .format(fileName)) |
|
305 return |
|
306 |
|
307 textStream = QTextStream(f) |
|
308 textStream << "[Adblock Plus 0.7.1]\n" |
|
309 for rule in self.__rules: |
|
310 textStream << rule.filter() << "\n" |
|
311 |
|
312 def pageRules(self): |
|
313 """ |
|
314 Public method to get the page rules of the subscription. |
|
315 |
|
316 @return list of rule objects (list of AdBlockRule) |
|
317 """ |
|
318 return self.__pageRules[:] |
|
319 |
|
320 def allow(self, urlString): |
|
321 """ |
|
322 Public method to check, if the given URL is allowed. |
|
323 |
|
324 @return reference to the rule object or None (AdBlockRule) |
|
325 """ |
|
326 for rule in self.__networkExceptionRules: |
|
327 if rule.networkMatch(urlString): |
|
328 return rule |
|
329 |
|
330 return None |
|
331 |
|
332 def block(self, urlString): |
|
333 """ |
|
334 Public method to check, if the given URL should be blocked. |
|
335 |
|
336 @return reference to the rule object or None (AdBlockRule) |
|
337 """ |
|
338 for rule in self.__networkBlockRules: |
|
339 if rule.networkMatch(urlString): |
|
340 return rule |
|
341 |
|
342 return None |
|
343 |
|
344 def allRules(self): |
|
345 """ |
|
346 Public method to get the list of rules. |
|
347 |
|
348 @return list of rules (list of AdBlockRule) |
|
349 """ |
|
350 return self.__rules[:] |
|
351 |
|
352 def addRule(self, rule): |
|
353 """ |
|
354 Public method to add a rule. |
|
355 |
|
356 @param rule reference to the rule to add (AdBlockRule) |
|
357 """ |
|
358 self.__rules.append(rule) |
|
359 self.__populateCache() |
|
360 self.emit(SIGNAL("rulesChanged()")) |
|
361 |
|
362 def removeRule(self, offset): |
|
363 """ |
|
364 Public method to remove a rule given the offset. |
|
365 |
|
366 @param offset offset of the rule to remove (integer) |
|
367 """ |
|
368 if offset < 0 or offset > len(self.__rules): |
|
369 return |
|
370 |
|
371 del self.__rules[offset] |
|
372 self.__populateCache() |
|
373 self.emit(SIGNAL("rulesChanged()")) |
|
374 |
|
375 def replaceRule(self, rule, offset): |
|
376 """ |
|
377 Public method to replace a rule given the offset. |
|
378 |
|
379 @param rule reference to the rule to set (AdBlockRule) |
|
380 @param offset offset of the rule to remove (integer) |
|
381 """ |
|
382 self.__rules[offset] = rule |
|
383 self.__populateCache() |
|
384 self.emit(SIGNAL("rulesChanged()")) |
|
385 |
|
386 def __populateCache(self): |
|
387 """ |
|
388 Private method to populate the various rule caches. |
|
389 """ |
|
390 self.__networkBlockRules = [] |
|
391 self.__networkExceptionRules = [] |
|
392 self.__pageRules = [] |
|
393 if not self.isEnabled(): |
|
394 return |
|
395 |
|
396 for rule in self.__rules: |
|
397 if not rule.isEnabled(): |
|
398 continue |
|
399 |
|
400 if rule.isCSSRule(): |
|
401 self.__pageRules.append(rule) |
|
402 elif rule.isException(): |
|
403 self.__networkExceptionRules.append(rule) |
|
404 else: |
|
405 self.__networkBlockRules.append(rule) |