|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the manager for GreaseMonkey scripts. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import contextlib |
|
12 |
|
13 from PyQt5.QtCore import ( |
|
14 pyqtSignal, pyqtSlot, Qt, QObject, QTimer, QFile, QFileInfo, QDir, |
|
15 QSettings, QMetaObject, QUrl, Q_ARG, QCoreApplication |
|
16 ) |
|
17 from PyQt5.QtWidgets import QDialog |
|
18 |
|
19 from E5Gui import E5MessageBox |
|
20 |
|
21 import Utilities |
|
22 import Preferences |
|
23 |
|
24 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
|
25 from WebBrowser.JavaScript.ExternalJsObject import ExternalJsObject |
|
26 |
|
27 from .GreaseMonkeyJsObject import GreaseMonkeyJsObject |
|
28 |
|
29 |
|
30 class GreaseMonkeyManager(QObject): |
|
31 """ |
|
32 Class implementing the manager for GreaseMonkey scripts. |
|
33 |
|
34 @signal scriptsChanged() emitted to indicate a change of scripts |
|
35 """ |
|
36 scriptsChanged = pyqtSignal() |
|
37 |
|
38 def __init__(self, parent=None): |
|
39 """ |
|
40 Constructor |
|
41 |
|
42 @param parent reference to the parent object (QObject) |
|
43 """ |
|
44 super().__init__(parent) |
|
45 |
|
46 self.__disabledScripts = [] |
|
47 self.__scripts = [] |
|
48 self.__downloaders = [] |
|
49 |
|
50 self.__jsObject = GreaseMonkeyJsObject(self) |
|
51 |
|
52 QTimer.singleShot(0, self.__load) |
|
53 |
|
54 def showConfigurationDialog(self, parent=None): |
|
55 """ |
|
56 Public method to show the configuration dialog. |
|
57 |
|
58 @param parent reference to the parent widget (QWidget) |
|
59 """ |
|
60 from .GreaseMonkeyConfiguration import GreaseMonkeyConfigurationDialog |
|
61 self.__configDiaolg = ( |
|
62 GreaseMonkeyConfigurationDialog.GreaseMonkeyConfigurationDialog( |
|
63 self, parent) |
|
64 ) |
|
65 self.__configDiaolg.show() |
|
66 |
|
67 def downloadScript(self, url): |
|
68 """ |
|
69 Public method to download a GreaseMonkey script. |
|
70 |
|
71 @param url URL to download script from |
|
72 @type QUrl |
|
73 """ |
|
74 QMetaObject.invokeMethod( |
|
75 self, "doDownloadScript", Qt.ConnectionType.QueuedConnection, |
|
76 Q_ARG(QUrl, url)) |
|
77 |
|
78 @pyqtSlot(QUrl) |
|
79 def doDownloadScript(self, url): |
|
80 """ |
|
81 Public slot to download a GreaseMonkey script. |
|
82 |
|
83 Note: The download needed to be separated in the invoking part |
|
84 (s.a.) and the one doing the real download because the invoking |
|
85 part runs in a different thread (i.e. the web engine thread). |
|
86 |
|
87 @param url URL to download script from |
|
88 @type QUrl |
|
89 """ |
|
90 from .GreaseMonkeyDownloader import GreaseMonkeyDownloader |
|
91 downloader = GreaseMonkeyDownloader( |
|
92 url, self, GreaseMonkeyDownloader.DownloadMainScript) |
|
93 downloader.finished.connect( |
|
94 lambda f: self.__downloaderFinished(f, downloader)) |
|
95 self.__downloaders.append(downloader) |
|
96 |
|
97 def __downloaderFinished(self, fileName, downloader): |
|
98 """ |
|
99 Private slot to handle the completion of a script download. |
|
100 |
|
101 @param fileName name of the downloaded script |
|
102 @type str |
|
103 @param downloader reference to the downloader object |
|
104 @type GreaseMonkeyDownloader |
|
105 """ |
|
106 if downloader in self.__downloaders: |
|
107 self.__downloaders.remove(downloader) |
|
108 |
|
109 deleteScript = True |
|
110 from .GreaseMonkeyScript import GreaseMonkeyScript |
|
111 script = GreaseMonkeyScript(self, fileName) |
|
112 if script.isValid(): |
|
113 if not self.containsScript(script.fullName()): |
|
114 from .GreaseMonkeyAddScriptDialog import ( |
|
115 GreaseMonkeyAddScriptDialog |
|
116 ) |
|
117 dlg = GreaseMonkeyAddScriptDialog(self, script) |
|
118 deleteScript = dlg.exec() != QDialog.DialogCode.Accepted |
|
119 else: |
|
120 E5MessageBox.information( |
|
121 None, |
|
122 QCoreApplication.translate( |
|
123 "GreaseMonkeyManager", |
|
124 "Install GreaseMonkey Script"), |
|
125 QCoreApplication.translate( |
|
126 "GreaseMonkeyManager", |
|
127 """'{0}' is already installed.""").format( |
|
128 script.fullName()) |
|
129 ) |
|
130 |
|
131 if deleteScript: |
|
132 with contextlib.suppress(OSError): |
|
133 os.remove(fileName) |
|
134 |
|
135 def scriptsDirectory(self): |
|
136 """ |
|
137 Public method to get the path of the scripts directory. |
|
138 |
|
139 @return path of the scripts directory (string) |
|
140 """ |
|
141 return os.path.join( |
|
142 Utilities.getConfigDir(), "web_browser", "greasemonkey") |
|
143 |
|
144 def requireScriptsDirectory(self): |
|
145 """ |
|
146 Public method to get the path of the scripts directory. |
|
147 |
|
148 @return path of the scripts directory (string) |
|
149 """ |
|
150 return os.path.join(self.scriptsDirectory(), "requires") |
|
151 |
|
152 def requireScripts(self, urlList): |
|
153 """ |
|
154 Public method to get the sources of all required scripts. |
|
155 |
|
156 @param urlList list of URLs (list of string) |
|
157 @return sources of all required scripts (string) |
|
158 """ |
|
159 requiresDir = QDir(self.requireScriptsDirectory()) |
|
160 if not requiresDir.exists() or len(urlList) == 0: |
|
161 return "" |
|
162 |
|
163 script = "" |
|
164 |
|
165 settings = QSettings( |
|
166 os.path.join(self.requireScriptsDirectory(), "requires.ini"), |
|
167 QSettings.Format.IniFormat) |
|
168 settings.beginGroup("Files") |
|
169 for url in urlList: |
|
170 if settings.contains(url): |
|
171 fileName = settings.value(url) |
|
172 if not QFileInfo(fileName).isAbsolute(): |
|
173 fileName = os.path.join(self.requireScriptsDirectory(), |
|
174 fileName) |
|
175 try: |
|
176 with open(fileName, "r", encoding="utf-8") as f: |
|
177 source = f.read().strip() |
|
178 except OSError: |
|
179 source = "" |
|
180 if source: |
|
181 script += source + "\n" |
|
182 |
|
183 return script |
|
184 |
|
185 def saveConfiguration(self): |
|
186 """ |
|
187 Public method to save the configuration. |
|
188 """ |
|
189 Preferences.setWebBrowser("GreaseMonkeyDisabledScripts", |
|
190 self.__disabledScripts) |
|
191 |
|
192 def allScripts(self): |
|
193 """ |
|
194 Public method to get a list of all scripts. |
|
195 |
|
196 @return list of all scripts (list of GreaseMonkeyScript) |
|
197 """ |
|
198 return self.__scripts[:] |
|
199 |
|
200 def containsScript(self, fullName): |
|
201 """ |
|
202 Public method to check, if the given script exists. |
|
203 |
|
204 @param fullName full name of the script (string) |
|
205 @return flag indicating the existence (boolean) |
|
206 """ |
|
207 return any(script.fullName() == fullName for script in self.__scripts) |
|
208 |
|
209 def enableScript(self, script): |
|
210 """ |
|
211 Public method to enable the given script. |
|
212 |
|
213 @param script script to be enabled (GreaseMonkeyScript) |
|
214 """ |
|
215 script.setEnabled(True) |
|
216 fullName = script.fullName() |
|
217 if fullName in self.__disabledScripts: |
|
218 self.__disabledScripts.remove(fullName) |
|
219 |
|
220 collection = WebBrowserWindow.webProfile().scripts() |
|
221 collection.insert(script.webScript()) |
|
222 |
|
223 def disableScript(self, script): |
|
224 """ |
|
225 Public method to disable the given script. |
|
226 |
|
227 @param script script to be disabled (GreaseMonkeyScript) |
|
228 """ |
|
229 script.setEnabled(False) |
|
230 fullName = script.fullName() |
|
231 if fullName not in self.__disabledScripts: |
|
232 self.__disabledScripts.append(fullName) |
|
233 |
|
234 collection = WebBrowserWindow.webProfile().scripts() |
|
235 collection.remove(collection.findScript(fullName)) |
|
236 |
|
237 def addScript(self, script): |
|
238 """ |
|
239 Public method to add a script. |
|
240 |
|
241 @param script script to be added (GreaseMonkeyScript) |
|
242 @return flag indicating success (boolean) |
|
243 """ |
|
244 if not script or not script.isValid(): |
|
245 return False |
|
246 |
|
247 self.__scripts.append(script) |
|
248 script.scriptChanged.connect(lambda: self.__scriptChanged(script)) |
|
249 |
|
250 collection = WebBrowserWindow.webProfile().scripts() |
|
251 collection.insert(script.webScript()) |
|
252 |
|
253 self.scriptsChanged.emit() |
|
254 return True |
|
255 |
|
256 def removeScript(self, script, removeFile=True): |
|
257 """ |
|
258 Public method to remove a script. |
|
259 |
|
260 @param script script to be removed (GreaseMonkeyScript) |
|
261 @param removeFile flag indicating to remove the script file as well |
|
262 (bool) |
|
263 @return flag indicating success (boolean) |
|
264 """ |
|
265 if not script: |
|
266 return False |
|
267 |
|
268 with contextlib.suppress(ValueError): |
|
269 self.__scripts.remove(script) |
|
270 |
|
271 fullName = script.fullName() |
|
272 collection = WebBrowserWindow.webProfile().scripts() |
|
273 collection.remove(collection.findScript(fullName)) |
|
274 |
|
275 if fullName in self.__disabledScripts: |
|
276 self.__disabledScripts.remove(fullName) |
|
277 |
|
278 if removeFile: |
|
279 QFile.remove(script.fileName()) |
|
280 del script |
|
281 |
|
282 self.scriptsChanged.emit() |
|
283 return True |
|
284 |
|
285 def canRunOnScheme(self, scheme): |
|
286 """ |
|
287 Public method to check, if scripts can be run on a scheme. |
|
288 |
|
289 @param scheme scheme to check (string) |
|
290 @return flag indicating, that scripts can be run (boolean) |
|
291 """ |
|
292 return scheme in ["http", "https", "data", "ftp"] |
|
293 |
|
294 def __load(self): |
|
295 """ |
|
296 Private slot to load the available scripts into the manager. |
|
297 """ |
|
298 scriptsDir = QDir(self.scriptsDirectory()) |
|
299 if not scriptsDir.exists(): |
|
300 scriptsDir.mkpath(self.scriptsDirectory()) |
|
301 |
|
302 if not scriptsDir.exists("requires"): |
|
303 scriptsDir.mkdir("requires") |
|
304 |
|
305 self.__disabledScripts = Preferences.getWebBrowser( |
|
306 "GreaseMonkeyDisabledScripts") |
|
307 |
|
308 from .GreaseMonkeyScript import GreaseMonkeyScript |
|
309 for fileName in scriptsDir.entryList(["*.js"], QDir.Filter.Files): |
|
310 absolutePath = scriptsDir.absoluteFilePath(fileName) |
|
311 script = GreaseMonkeyScript(self, absolutePath) |
|
312 |
|
313 if not script.isValid(): |
|
314 del script |
|
315 continue |
|
316 |
|
317 self.__scripts.append(script) |
|
318 |
|
319 if script.fullName() in self.__disabledScripts: |
|
320 script.setEnabled(False) |
|
321 else: |
|
322 collection = WebBrowserWindow.webProfile().scripts() |
|
323 collection.insert(script.webScript()) |
|
324 |
|
325 self.__jsObject.setSettingsFile(os.path.join( |
|
326 Utilities.getConfigDir(), "web_browser", |
|
327 "greasemonkey_values.ini")) |
|
328 ExternalJsObject.registerExtraObject("GreaseMonkey", self.__jsObject) |
|
329 |
|
330 def __scriptChanged(self, script): |
|
331 """ |
|
332 Private slot handling a changed script. |
|
333 |
|
334 @param script reference to the changed script |
|
335 @type GreaseMonkeyScript |
|
336 """ |
|
337 fullName = script.fullName() |
|
338 collection = WebBrowserWindow.webProfile().scripts() |
|
339 collection.remove(collection.findScript(fullName)) |
|
340 collection.insert(script.webScript()) |