|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the AdBlock manager. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 |
|
14 from PyQt5.QtCore import pyqtSignal, QObject, QUrl, QFile |
|
15 |
|
16 from .AdBlockSubscription import AdBlockSubscription |
|
17 |
|
18 from Utilities.AutoSaver import AutoSaver |
|
19 import Utilities |
|
20 import Preferences |
|
21 |
|
22 |
|
23 class AdBlockManager(QObject): |
|
24 """ |
|
25 Class implementing the AdBlock manager. |
|
26 |
|
27 @signal rulesChanged() emitted after some rule has changed |
|
28 """ |
|
29 rulesChanged = pyqtSignal() |
|
30 requiredSubscriptionLoaded = pyqtSignal(AdBlockSubscription) |
|
31 |
|
32 def __init__(self, parent=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param parent reference to the parent object (QObject) |
|
37 """ |
|
38 super(AdBlockManager, self).__init__(parent) |
|
39 |
|
40 self.__loaded = False |
|
41 self.__subscriptionsLoaded = False |
|
42 self.__enabled = False |
|
43 self.__adBlockDialog = None |
|
44 self.__adBlockExceptionsDialog = None |
|
45 self.__adBlockNetwork = None |
|
46 self.__adBlockPage = None |
|
47 self.__subscriptions = [] |
|
48 self.__exceptedHosts = Preferences.getWebBrowser("AdBlockExceptions") |
|
49 self.__saveTimer = AutoSaver(self, self.save) |
|
50 |
|
51 self.__defaultSubscriptionUrlString = \ |
|
52 "abp:subscribe?location=" \ |
|
53 "https://easylist-downloads.adblockplus.org/easylist.txt&"\ |
|
54 "title=EasyList" |
|
55 self.__customSubscriptionUrlString = \ |
|
56 bytes(self.__customSubscriptionUrl().toEncoded()).decode() |
|
57 |
|
58 self.rulesChanged.connect(self.__saveTimer.changeOccurred) |
|
59 |
|
60 def close(self): |
|
61 """ |
|
62 Public method to close the open search engines manager. |
|
63 """ |
|
64 self.__adBlockDialog and self.__adBlockDialog.close() |
|
65 self.__adBlockExceptionsDialog and \ |
|
66 self.__adBlockExceptionsDialog.close() |
|
67 |
|
68 self.__saveTimer.saveIfNeccessary() |
|
69 |
|
70 def isEnabled(self): |
|
71 """ |
|
72 Public method to check, if blocking ads is enabled. |
|
73 |
|
74 @return flag indicating the enabled state (boolean) |
|
75 """ |
|
76 if not self.__loaded: |
|
77 self.load() |
|
78 |
|
79 return self.__enabled |
|
80 |
|
81 def setEnabled(self, enabled): |
|
82 """ |
|
83 Public slot to set the enabled state. |
|
84 |
|
85 @param enabled flag indicating the enabled state (boolean) |
|
86 """ |
|
87 if self.isEnabled() == enabled: |
|
88 return |
|
89 |
|
90 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
|
91 self.__enabled = enabled |
|
92 for mainWindow in WebBrowserWindow.mainWindows(): |
|
93 mainWindow.adBlockIcon().setEnabled(enabled) |
|
94 if enabled: |
|
95 self.__loadSubscriptions() |
|
96 self.rulesChanged.emit() |
|
97 ## |
|
98 ## def network(self): |
|
99 ## """ |
|
100 ## Public method to get a reference to the network block object. |
|
101 ## |
|
102 ## @return reference to the network block object (AdBlockNetwork) |
|
103 ## """ |
|
104 ## if self.__adBlockNetwork is None: |
|
105 ## from .AdBlockNetwork import AdBlockNetwork |
|
106 ## self.__adBlockNetwork = AdBlockNetwork(self) |
|
107 ## return self.__adBlockNetwork |
|
108 |
|
109 def page(self): |
|
110 """ |
|
111 Public method to get a reference to the page block object. |
|
112 |
|
113 @return reference to the page block object (AdBlockPage) |
|
114 """ |
|
115 if self.__adBlockPage is None: |
|
116 from .AdBlockPage import AdBlockPage |
|
117 self.__adBlockPage = AdBlockPage(self) |
|
118 return self.__adBlockPage |
|
119 |
|
120 def __customSubscriptionLocation(self): |
|
121 """ |
|
122 Private method to generate the path for custom subscriptions. |
|
123 |
|
124 @return URL for custom subscriptions (QUrl) |
|
125 """ |
|
126 dataDir = os.path.join(Utilities.getConfigDir(), "web_browser", |
|
127 "subscriptions") |
|
128 if not os.path.exists(dataDir): |
|
129 os.makedirs(dataDir) |
|
130 fileName = os.path.join(dataDir, "adblock_subscription_custom") |
|
131 return QUrl.fromLocalFile(fileName) |
|
132 |
|
133 def __customSubscriptionUrl(self): |
|
134 """ |
|
135 Private method to generate the URL for custom subscriptions. |
|
136 |
|
137 @return URL for custom subscriptions (QUrl) |
|
138 """ |
|
139 location = self.__customSubscriptionLocation() |
|
140 encodedUrl = bytes(location.toEncoded()).decode() |
|
141 url = QUrl("abp:subscribe?location={0}&title={1}".format( |
|
142 encodedUrl, self.tr("Custom Rules"))) |
|
143 return url |
|
144 |
|
145 def customRules(self): |
|
146 """ |
|
147 Public method to get a subscription for custom rules. |
|
148 |
|
149 @return subscription object for custom rules (AdBlockSubscription) |
|
150 """ |
|
151 location = self.__customSubscriptionLocation() |
|
152 for subscription in self.__subscriptions: |
|
153 if subscription.location() == location: |
|
154 return subscription |
|
155 |
|
156 url = self.__customSubscriptionUrl() |
|
157 customAdBlockSubscription = AdBlockSubscription(url, True, self) |
|
158 self.addSubscription(customAdBlockSubscription) |
|
159 return customAdBlockSubscription |
|
160 |
|
161 def subscriptions(self): |
|
162 """ |
|
163 Public method to get all subscriptions. |
|
164 |
|
165 @return list of subscriptions (list of AdBlockSubscription) |
|
166 """ |
|
167 if not self.__loaded: |
|
168 self.load() |
|
169 |
|
170 return self.__subscriptions[:] |
|
171 |
|
172 def subscription(self, location): |
|
173 """ |
|
174 Public method to get a subscription based on its location. |
|
175 |
|
176 @param location location of the subscription to search for (string) |
|
177 @return subscription or None (AdBlockSubscription) |
|
178 """ |
|
179 if location != "": |
|
180 for subscription in self.__subscriptions: |
|
181 if subscription.location().toString() == location: |
|
182 return subscription |
|
183 |
|
184 return None |
|
185 |
|
186 def updateAllSubscriptions(self): |
|
187 """ |
|
188 Public method to update all subscriptions. |
|
189 """ |
|
190 for subscription in self.__subscriptions: |
|
191 subscription.updateNow() |
|
192 |
|
193 def removeSubscription(self, subscription, emitSignal=True): |
|
194 """ |
|
195 Public method to remove an AdBlock subscription. |
|
196 |
|
197 @param subscription AdBlock subscription to be removed |
|
198 (AdBlockSubscription) |
|
199 @param emitSignal flag indicating to send a signal (boolean) |
|
200 """ |
|
201 if subscription is None: |
|
202 return |
|
203 |
|
204 if subscription.url().toString().startswith( |
|
205 (self.__defaultSubscriptionUrlString, |
|
206 self.__customSubscriptionUrlString)): |
|
207 return |
|
208 |
|
209 try: |
|
210 self.__subscriptions.remove(subscription) |
|
211 rulesFileName = subscription.rulesFileName() |
|
212 QFile.remove(rulesFileName) |
|
213 requiresSubscriptions = self.getRequiresSubscriptions(subscription) |
|
214 for requiresSubscription in requiresSubscriptions: |
|
215 self.removeSubscription(requiresSubscription, False) |
|
216 if emitSignal: |
|
217 self.rulesChanged.emit() |
|
218 except ValueError: |
|
219 pass |
|
220 |
|
221 def addSubscription(self, subscription): |
|
222 """ |
|
223 Public method to add an AdBlock subscription. |
|
224 |
|
225 @param subscription AdBlock subscription to be added |
|
226 (AdBlockSubscription) |
|
227 """ |
|
228 if subscription is None: |
|
229 return |
|
230 |
|
231 self.__subscriptions.insert(-1, subscription) |
|
232 |
|
233 subscription.rulesChanged.connect(self.rulesChanged) |
|
234 subscription.changed.connect(self.rulesChanged) |
|
235 subscription.enabledChanged.connect(self.rulesChanged) |
|
236 |
|
237 self.rulesChanged.emit() |
|
238 |
|
239 def save(self): |
|
240 """ |
|
241 Public method to save the AdBlock subscriptions. |
|
242 """ |
|
243 if not self.__loaded: |
|
244 return |
|
245 |
|
246 Preferences.setWebBrowser("AdBlockEnabled", self.__enabled) |
|
247 if self.__subscriptionsLoaded: |
|
248 subscriptions = [] |
|
249 requiresSubscriptions = [] |
|
250 # intermediate store for subscription requiring others |
|
251 for subscription in self.__subscriptions: |
|
252 if subscription is None: |
|
253 continue |
|
254 urlString = bytes(subscription.url().toEncoded()).decode() |
|
255 if "requiresLocation" in urlString: |
|
256 requiresSubscriptions.append(urlString) |
|
257 else: |
|
258 subscriptions.append(urlString) |
|
259 subscription.saveRules() |
|
260 for subscription in requiresSubscriptions: |
|
261 subscriptions.insert(-1, subscription) # custom should be last |
|
262 Preferences.setWebBrowser("AdBlockSubscriptions", subscriptions) |
|
263 |
|
264 def load(self): |
|
265 """ |
|
266 Public method to load the AdBlock subscriptions. |
|
267 """ |
|
268 if self.__loaded: |
|
269 return |
|
270 |
|
271 self.__loaded = True |
|
272 |
|
273 self.__enabled = Preferences.getWebBrowser("AdBlockEnabled") |
|
274 if self.__enabled: |
|
275 self.__loadSubscriptions() |
|
276 |
|
277 def __loadSubscriptions(self): |
|
278 """ |
|
279 Private method to load the set of subscriptions. |
|
280 """ |
|
281 if self.__subscriptionsLoaded: |
|
282 return |
|
283 |
|
284 subscriptions = Preferences.getWebBrowser("AdBlockSubscriptions") |
|
285 if subscriptions: |
|
286 for subscription in subscriptions: |
|
287 if subscription.startswith( |
|
288 self.__defaultSubscriptionUrlString): |
|
289 break |
|
290 else: |
|
291 subscriptions.insert(0, self.__defaultSubscriptionUrlString) |
|
292 for subscription in subscriptions: |
|
293 if subscription.startswith(self.__customSubscriptionUrlString): |
|
294 break |
|
295 else: |
|
296 subscriptions.append(self.__customSubscriptionUrlString) |
|
297 else: |
|
298 subscriptions = [self.__defaultSubscriptionUrlString, |
|
299 self.__customSubscriptionUrlString] |
|
300 for subscription in subscriptions: |
|
301 url = QUrl.fromEncoded(subscription.encode("utf-8")) |
|
302 adBlockSubscription = AdBlockSubscription( |
|
303 url, |
|
304 subscription.startswith(self.__customSubscriptionUrlString), |
|
305 self, |
|
306 subscription.startswith(self.__defaultSubscriptionUrlString)) |
|
307 adBlockSubscription.rulesChanged.connect(self.rulesChanged) |
|
308 adBlockSubscription.changed.connect(self.rulesChanged) |
|
309 adBlockSubscription.enabledChanged.connect(self.rulesChanged) |
|
310 self.__subscriptions.append(adBlockSubscription) |
|
311 |
|
312 self.__subscriptionsLoaded = True |
|
313 |
|
314 def loadRequiredSubscription(self, location, title): |
|
315 """ |
|
316 Public method to load a subscription required by another one. |
|
317 |
|
318 @param location location of the required subscription (string) |
|
319 @param title title of the required subscription (string) |
|
320 """ |
|
321 # Step 1: check, if the subscription is in the list of subscriptions |
|
322 urlString = "abp:subscribe?location={0}&title={1}".format( |
|
323 location, title) |
|
324 for subscription in self.__subscriptions: |
|
325 if subscription.url().toString().startswith(urlString): |
|
326 # We found it! |
|
327 return |
|
328 |
|
329 # Step 2: if it is not, get it |
|
330 url = QUrl.fromEncoded(urlString.encode("utf-8")) |
|
331 adBlockSubscription = AdBlockSubscription(url, False, self) |
|
332 self.addSubscription(adBlockSubscription) |
|
333 self.requiredSubscriptionLoaded.emit(adBlockSubscription) |
|
334 |
|
335 def getRequiresSubscriptions(self, subscription): |
|
336 """ |
|
337 Public method to get a list of subscriptions, that require the given |
|
338 one. |
|
339 |
|
340 @param subscription subscription to check for (AdBlockSubscription) |
|
341 @return list of subscription requiring the given one (list of |
|
342 AdBlockSubscription) |
|
343 """ |
|
344 subscriptions = [] |
|
345 location = subscription.location().toString() |
|
346 for subscription in self.__subscriptions: |
|
347 if subscription.requiresLocation() == location: |
|
348 subscriptions.append(subscription) |
|
349 |
|
350 return subscriptions |
|
351 |
|
352 def showDialog(self): |
|
353 """ |
|
354 Public slot to show the AdBlock subscription management dialog. |
|
355 |
|
356 @return reference to the dialog (AdBlockDialog) |
|
357 """ |
|
358 if self.__adBlockDialog is None: |
|
359 from .AdBlockDialog import AdBlockDialog |
|
360 self.__adBlockDialog = AdBlockDialog() |
|
361 |
|
362 self.__adBlockDialog.show() |
|
363 return self.__adBlockDialog |
|
364 |
|
365 def showRule(self): |
|
366 """ |
|
367 Public slot to show an AdBlock rule. |
|
368 """ |
|
369 act = self.sender() |
|
370 if act is not None: |
|
371 rule = act.data() |
|
372 if rule: |
|
373 self.showDialog().showRule(rule) |
|
374 |
|
375 def elementHidingRules(self): |
|
376 """ |
|
377 Public method to get the element hiding rules. |
|
378 |
|
379 @return element hiding rules (string) |
|
380 """ |
|
381 if not self.__enabled: |
|
382 return "" |
|
383 |
|
384 rules = "" |
|
385 |
|
386 for subscription in self.__subscriptions: |
|
387 rules += subscription.elementHidingRules() |
|
388 |
|
389 if rules: |
|
390 # remove last ", |
|
391 rules = rules[:-1] |
|
392 |
|
393 return rules |
|
394 |
|
395 def elementHidingRulesForDomain(self, url): |
|
396 """ |
|
397 Public method to get the element hiding rules for a domain. |
|
398 |
|
399 @param url URL to get hiding rules for (QUrl) |
|
400 @return element hiding rules (string) |
|
401 """ |
|
402 if not self.__enabled: |
|
403 return "" |
|
404 |
|
405 rules = "" |
|
406 |
|
407 for subscription in self.__subscriptions: |
|
408 if subscription.elemHideDisabledForUrl(url): |
|
409 continue |
|
410 |
|
411 rules += subscription.elementHidingRulesForDomain(url.host()) |
|
412 |
|
413 if rules: |
|
414 # remove last "," |
|
415 rules = rules[:-1] |
|
416 |
|
417 rules += "{display:none !important;}\n" |
|
418 |
|
419 return rules |
|
420 |
|
421 def exceptions(self): |
|
422 """ |
|
423 Public method to get a list of excepted hosts. |
|
424 |
|
425 @return list of excepted hosts (list of string) |
|
426 """ |
|
427 return self.__exceptedHosts |
|
428 |
|
429 def setExceptions(self, hosts): |
|
430 """ |
|
431 Public method to set the list of excepted hosts. |
|
432 |
|
433 @param hosts list of excepted hosts (list of string) |
|
434 """ |
|
435 self.__exceptedHosts = hosts[:] |
|
436 Preferences.setWebBrowser("AdBlockExceptions", self.__exceptedHosts) |
|
437 |
|
438 def addException(self, host): |
|
439 """ |
|
440 Public method to add an exception. |
|
441 |
|
442 @param host to be excepted (string) |
|
443 """ |
|
444 if host and host not in self.__exceptedHosts: |
|
445 self.__exceptedHosts.append(host) |
|
446 Preferences.setWebBrowser( |
|
447 "AdBlockExceptions", self.__exceptedHosts) |
|
448 |
|
449 def removeException(self, host): |
|
450 """ |
|
451 Public method to remove an exception. |
|
452 |
|
453 @param host to be removed from the list of exceptions (string) |
|
454 """ |
|
455 if host in self.__exceptedHosts: |
|
456 self.__exceptedHosts.remove(host) |
|
457 Preferences.setWebBrowser( |
|
458 "AdBlockExceptions", self.__exceptedHosts) |
|
459 |
|
460 def isHostExcepted(self, host): |
|
461 """ |
|
462 Public slot to check, if a host is excepted. |
|
463 |
|
464 @param host host to check (string) |
|
465 @return flag indicating an exception (boolean) |
|
466 """ |
|
467 return host in self.__exceptedHosts |
|
468 |
|
469 def showExceptionsDialog(self): |
|
470 """ |
|
471 Public method to show the AdBlock Exceptions dialog. |
|
472 |
|
473 @return reference to the exceptions dialog (AdBlockExceptionsDialog) |
|
474 """ |
|
475 if self.__adBlockExceptionsDialog is None: |
|
476 from .AdBlockExceptionsDialog import AdBlockExceptionsDialog |
|
477 self.__adBlockExceptionsDialog = AdBlockExceptionsDialog() |
|
478 |
|
479 self.__adBlockExceptionsDialog.load(self.__exceptedHosts) |
|
480 self.__adBlockExceptionsDialog.show() |
|
481 return self.__adBlockExceptionsDialog |