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