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