|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Plugin Manager. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import sys |
|
12 import zipfile |
|
13 import types |
|
14 import importlib |
|
15 import contextlib |
|
16 import datetime |
|
17 import pathlib |
|
18 |
|
19 from PyQt6.QtCore import pyqtSignal, QObject, QFile, QUrl, QIODevice |
|
20 from PyQt6.QtGui import QPixmap |
|
21 from PyQt6.QtNetwork import ( |
|
22 QNetworkAccessManager, QNetworkRequest, QNetworkReply |
|
23 ) |
|
24 |
|
25 from EricWidgets import EricMessageBox |
|
26 from EricWidgets.EricApplication import ericApp |
|
27 |
|
28 from EricNetwork.EricNetworkProxyFactory import proxyAuthenticationRequired |
|
29 try: |
|
30 from EricNetwork.EricSslErrorHandler import ( |
|
31 EricSslErrorHandler, EricSslErrorState |
|
32 ) |
|
33 SSL_AVAILABLE = True |
|
34 except ImportError: |
|
35 SSL_AVAILABLE = False |
|
36 |
|
37 from .PluginExceptions import ( |
|
38 PluginPathError, PluginModulesError, PluginLoadError, |
|
39 PluginActivationError, PluginModuleFormatError, PluginClassFormatError |
|
40 ) |
|
41 |
|
42 import UI.PixmapCache |
|
43 |
|
44 import Globals |
|
45 import Utilities |
|
46 import Preferences |
|
47 |
|
48 from eric7config import getConfig |
|
49 |
|
50 |
|
51 class PluginManager(QObject): |
|
52 """ |
|
53 Class implementing the Plugin Manager. |
|
54 |
|
55 @signal shutdown() emitted at shutdown of the IDE |
|
56 @signal pluginAboutToBeActivated(modulName, pluginObject) emitted just |
|
57 before a plugin is activated |
|
58 @signal pluginActivated(moduleName, pluginObject) emitted just after |
|
59 a plugin was activated |
|
60 @signal allPlugginsActivated() emitted at startup after all plugins have |
|
61 been activated |
|
62 @signal pluginAboutToBeDeactivated(moduleName, pluginObject) emitted just |
|
63 before a plugin is deactivated |
|
64 @signal pluginDeactivated(moduleName, pluginObject) emitted just after |
|
65 a plugin was deactivated |
|
66 @signal pluginRepositoryFileDownloaded() emitted to indicate a completed |
|
67 download of the plugin repository file |
|
68 """ |
|
69 shutdown = pyqtSignal() |
|
70 pluginAboutToBeActivated = pyqtSignal(str, object) |
|
71 pluginActivated = pyqtSignal(str, object) |
|
72 allPlugginsActivated = pyqtSignal() |
|
73 pluginAboutToBeDeactivated = pyqtSignal(str, object) |
|
74 pluginDeactivated = pyqtSignal(str, object) |
|
75 pluginRepositoryFileDownloaded = pyqtSignal() |
|
76 |
|
77 def __init__(self, parent=None, disabledPlugins=None, doLoadPlugins=True, |
|
78 develPlugin=None): |
|
79 """ |
|
80 Constructor |
|
81 |
|
82 The Plugin Manager deals with three different plugin directories. |
|
83 The first is the one, that is part of eric7 (eric7/Plugins). The |
|
84 second one is the global plugin directory called 'eric7plugins', |
|
85 which is located inside the site-packages directory. The last one |
|
86 is the user plugin directory located inside the .eric7 directory |
|
87 of the users home directory. |
|
88 |
|
89 @param parent reference to the parent object |
|
90 @type QObject |
|
91 @param disabledPlugins list of plug-ins that have been disabled via |
|
92 the command line parameters '--disable-plugin=' |
|
93 @type list of str |
|
94 @param doLoadPlugins flag indicating, that plug-ins should |
|
95 be loaded |
|
96 @type bool |
|
97 @param develPlugin filename of a plug-in to be loaded for |
|
98 development |
|
99 @type str |
|
100 @exception PluginPathError raised to indicate an invalid plug-in path |
|
101 @exception PluginModulesError raised to indicate the absence of |
|
102 plug-in modules |
|
103 """ |
|
104 super().__init__(parent) |
|
105 |
|
106 self.__ui = parent |
|
107 self.__develPluginFile = develPlugin |
|
108 self.__develPluginName = None |
|
109 if disabledPlugins is not None: |
|
110 self.__disabledPlugins = disabledPlugins[:] |
|
111 else: |
|
112 self.__disabledPlugins = [] |
|
113 |
|
114 self.__inactivePluginsKey = "PluginManager/InactivePlugins" |
|
115 |
|
116 self.pluginDirs = { |
|
117 "eric7": os.path.join(getConfig('ericDir'), "Plugins"), |
|
118 "global": os.path.join(Utilities.getPythonLibraryDirectory(), |
|
119 "eric7plugins"), |
|
120 "user": os.path.join(Utilities.getConfigDir(), "eric7plugins"), |
|
121 } |
|
122 self.__priorityOrder = ["eric7", "global", "user"] |
|
123 |
|
124 self.__defaultDownloadDir = os.path.join( |
|
125 Utilities.getConfigDir(), "Downloads") |
|
126 |
|
127 self.__activePlugins = {} |
|
128 self.__inactivePlugins = {} |
|
129 self.__onDemandActivePlugins = {} |
|
130 self.__onDemandInactivePlugins = {} |
|
131 self.__activeModules = {} |
|
132 self.__inactiveModules = {} |
|
133 self.__onDemandActiveModules = {} |
|
134 self.__onDemandInactiveModules = {} |
|
135 self.__failedModules = {} |
|
136 |
|
137 self.__foundCoreModules = [] |
|
138 self.__foundGlobalModules = [] |
|
139 self.__foundUserModules = [] |
|
140 |
|
141 self.__modulesCount = 0 |
|
142 |
|
143 pdirsExist, msg = self.__pluginDirectoriesExist() |
|
144 if not pdirsExist: |
|
145 raise PluginPathError(msg) |
|
146 |
|
147 if doLoadPlugins: |
|
148 if not self.__pluginModulesExist(): |
|
149 raise PluginModulesError |
|
150 |
|
151 self.__insertPluginsPaths() |
|
152 |
|
153 self.__loadPlugins() |
|
154 |
|
155 self.__checkPluginsDownloadDirectory() |
|
156 |
|
157 self.pluginRepositoryFile = os.path.join(Utilities.getConfigDir(), |
|
158 "PluginRepository") |
|
159 |
|
160 # attributes for the network objects |
|
161 self.__networkManager = QNetworkAccessManager(self) |
|
162 self.__networkManager.proxyAuthenticationRequired.connect( |
|
163 proxyAuthenticationRequired) |
|
164 if SSL_AVAILABLE: |
|
165 self.__sslErrorHandler = EricSslErrorHandler(self) |
|
166 self.__networkManager.sslErrors.connect(self.__sslErrors) |
|
167 self.__replies = [] |
|
168 |
|
169 def finalizeSetup(self): |
|
170 """ |
|
171 Public method to finalize the setup of the plugin manager. |
|
172 """ |
|
173 for module in ( |
|
174 list(self.__onDemandInactiveModules.values()) + |
|
175 list(self.__onDemandActiveModules.values()) |
|
176 ): |
|
177 if hasattr(module, "moduleSetup"): |
|
178 module.moduleSetup() |
|
179 |
|
180 def getPluginDir(self, key): |
|
181 """ |
|
182 Public method to get the path of a plugin directory. |
|
183 |
|
184 @param key key of the plug-in directory (string) |
|
185 @return path of the requested plugin directory (string) |
|
186 """ |
|
187 if key not in ["global", "user"]: |
|
188 return None |
|
189 else: |
|
190 try: |
|
191 return self.pluginDirs[key] |
|
192 except KeyError: |
|
193 return None |
|
194 |
|
195 def __pluginDirectoriesExist(self): |
|
196 """ |
|
197 Private method to check, if the plugin folders exist. |
|
198 |
|
199 If the plugin folders don't exist, they are created (if possible). |
|
200 |
|
201 @return tuple of a flag indicating existence of any of the plugin |
|
202 directories (boolean) and a message (string) |
|
203 """ |
|
204 if self.__develPluginFile: |
|
205 path = Utilities.splitPath(self.__develPluginFile)[0] |
|
206 fname = os.path.join(path, "__init__.py") |
|
207 if not os.path.exists(fname): |
|
208 try: |
|
209 with open(fname, "w"): |
|
210 pass |
|
211 except OSError: |
|
212 return ( |
|
213 False, |
|
214 self.tr("Could not create a package for {0}.") |
|
215 .format(self.__develPluginFile)) |
|
216 |
|
217 fname = os.path.join(self.pluginDirs["user"], "__init__.py") |
|
218 if not os.path.exists(fname): |
|
219 if not os.path.exists(self.pluginDirs["user"]): |
|
220 os.mkdir(self.pluginDirs["user"], 0o755) |
|
221 try: |
|
222 with open(fname, "w"): |
|
223 pass |
|
224 except OSError: |
|
225 del self.pluginDirs["user"] |
|
226 |
|
227 if not os.path.exists(self.pluginDirs["global"]): |
|
228 try: |
|
229 # create the global plugins directory |
|
230 os.mkdir(self.pluginDirs["global"], 0o755) |
|
231 fname = os.path.join(self.pluginDirs["global"], "__init__.py") |
|
232 with open(fname, "w", encoding="utf-8") as f: |
|
233 f.write('# -*- coding: utf-8 -*-' + "\n") |
|
234 f.write("\n") |
|
235 f.write('"""' + "\n") |
|
236 f.write('Package containing the global plugins.' + "\n") |
|
237 f.write('"""' + "\n") |
|
238 except OSError: |
|
239 del self.pluginDirs["global"] |
|
240 |
|
241 if not os.path.exists(self.pluginDirs["eric7"]): |
|
242 return ( |
|
243 False, |
|
244 self.tr( |
|
245 "The internal plugin directory <b>{0}</b>" |
|
246 " does not exits.").format(self.pluginDirs["eric7"])) |
|
247 |
|
248 return (True, "") |
|
249 |
|
250 def __pluginModulesExist(self): |
|
251 """ |
|
252 Private method to check, if there are plugins available. |
|
253 |
|
254 @return flag indicating the availability of plugins (boolean) |
|
255 """ |
|
256 if ( |
|
257 self.__develPluginFile and |
|
258 not os.path.exists(self.__develPluginFile) |
|
259 ): |
|
260 return False |
|
261 |
|
262 self.__foundCoreModules = self.getPluginModules( |
|
263 self.pluginDirs["eric7"]) |
|
264 if Preferences.getPluginManager("ActivateExternal"): |
|
265 if "global" in self.pluginDirs: |
|
266 self.__foundGlobalModules = self.getPluginModules( |
|
267 self.pluginDirs["global"]) |
|
268 if "user" in self.pluginDirs: |
|
269 self.__foundUserModules = self.getPluginModules( |
|
270 self.pluginDirs["user"]) |
|
271 |
|
272 return len(self.__foundCoreModules + self.__foundGlobalModules + |
|
273 self.__foundUserModules) > 0 |
|
274 |
|
275 def getPluginModules(self, pluginPath): |
|
276 """ |
|
277 Public method to get a list of plugin modules. |
|
278 |
|
279 @param pluginPath name of the path to search (string) |
|
280 @return list of plugin module names (list of string) |
|
281 """ |
|
282 pluginFiles = [f[:-3] for f in os.listdir(pluginPath) |
|
283 if self.isValidPluginName(f)] |
|
284 return pluginFiles[:] |
|
285 |
|
286 def isValidPluginName(self, pluginName): |
|
287 """ |
|
288 Public method to check, if a file name is a valid plugin name. |
|
289 |
|
290 Plugin modules must start with "Plugin" and have the extension ".py". |
|
291 |
|
292 @param pluginName name of the file to be checked (string) |
|
293 @return flag indicating a valid plugin name (boolean) |
|
294 """ |
|
295 return pluginName.startswith("Plugin") and pluginName.endswith(".py") |
|
296 |
|
297 def __insertPluginsPaths(self): |
|
298 """ |
|
299 Private method to insert the valid plugin paths intos the search path. |
|
300 """ |
|
301 for key in self.__priorityOrder: |
|
302 if key in self.pluginDirs: |
|
303 if self.pluginDirs[key] not in sys.path: |
|
304 sys.path.insert(2, self.pluginDirs[key]) |
|
305 UI.PixmapCache.addSearchPath(self.pluginDirs[key]) |
|
306 |
|
307 if self.__develPluginFile: |
|
308 path = Utilities.splitPath(self.__develPluginFile)[0] |
|
309 if path not in sys.path: |
|
310 sys.path.insert(2, path) |
|
311 UI.PixmapCache.addSearchPath(path) |
|
312 |
|
313 def __loadPlugins(self): |
|
314 """ |
|
315 Private method to load the plugins found. |
|
316 """ |
|
317 develPluginName = "" |
|
318 if self.__develPluginFile: |
|
319 develPluginPath, develPluginName = Utilities.splitPath( |
|
320 self.__develPluginFile) |
|
321 if self.isValidPluginName(develPluginName): |
|
322 develPluginName = develPluginName[:-3] |
|
323 |
|
324 for pluginName in self.__foundGlobalModules: |
|
325 # user and core plug-ins have priority |
|
326 if ( |
|
327 pluginName not in self.__foundUserModules and |
|
328 pluginName not in self.__foundCoreModules and |
|
329 pluginName != develPluginName |
|
330 ): |
|
331 self.loadPlugin(pluginName, self.pluginDirs["global"]) |
|
332 |
|
333 for pluginName in self.__foundUserModules: |
|
334 # core plug-ins have priority |
|
335 if ( |
|
336 pluginName not in self.__foundCoreModules and |
|
337 pluginName != develPluginName |
|
338 ): |
|
339 self.loadPlugin(pluginName, self.pluginDirs["user"]) |
|
340 |
|
341 for pluginName in self.__foundCoreModules: |
|
342 # plug-in under development has priority |
|
343 if pluginName != develPluginName: |
|
344 self.loadPlugin(pluginName, self.pluginDirs["eric7"]) |
|
345 |
|
346 if develPluginName: |
|
347 self.loadPlugin(develPluginName, develPluginPath) |
|
348 self.__develPluginName = develPluginName |
|
349 |
|
350 def loadDocumentationSetPlugins(self): |
|
351 """ |
|
352 Public method to load just the documentation sets plugins. |
|
353 |
|
354 @exception PluginModulesError raised to indicate the absence of |
|
355 plug-in modules |
|
356 """ |
|
357 if not self.__pluginModulesExist(): |
|
358 raise PluginModulesError |
|
359 |
|
360 self.__insertPluginsPaths() |
|
361 |
|
362 for pluginName in self.__foundGlobalModules: |
|
363 # user and core plug-ins have priority |
|
364 if ( |
|
365 pluginName not in self.__foundUserModules and |
|
366 pluginName not in self.__foundCoreModules and |
|
367 pluginName.startswith("PluginDocumentationSets") |
|
368 ): |
|
369 self.loadPlugin(pluginName, self.pluginDirs["global"]) |
|
370 |
|
371 for pluginName in self.__foundUserModules: |
|
372 # core plug-ins have priority |
|
373 if ( |
|
374 pluginName not in self.__foundCoreModules and |
|
375 pluginName.startswith("PluginDocumentationSets") |
|
376 ): |
|
377 self.loadPlugin(pluginName, self.pluginDirs["user"]) |
|
378 |
|
379 for pluginName in self.__foundCoreModules: |
|
380 # plug-in under development has priority |
|
381 if pluginName.startswith("PluginDocumentationSets"): |
|
382 self.loadPlugin(pluginName, self.pluginDirs["eric7"]) |
|
383 |
|
384 def loadPlugin(self, name, directory, reload_=False, install=False): |
|
385 """ |
|
386 Public method to load a plugin module. |
|
387 |
|
388 Initially all modules are inactive. Modules that are requested on |
|
389 demand are sorted out and are added to the on demand list. Some |
|
390 basic validity checks are performed as well. Modules failing these |
|
391 checks are added to the failed modules list. |
|
392 |
|
393 @param name name of the module to be loaded |
|
394 @type str |
|
395 @param directory name of the plugin directory |
|
396 @type str |
|
397 @param reload_ flag indicating to reload the module |
|
398 @type bool |
|
399 @param install flag indicating a load operation as part of an |
|
400 installation process |
|
401 @type bool |
|
402 @exception PluginLoadError raised to indicate an issue loading |
|
403 the plug-in |
|
404 """ |
|
405 try: |
|
406 fname = "{0}.py".format(os.path.join(directory, name)) |
|
407 spec = importlib.util.spec_from_file_location(name, fname) |
|
408 module = importlib.util.module_from_spec(spec) |
|
409 sys.modules[module.__name__] = module |
|
410 spec.loader.exec_module(module) |
|
411 if not hasattr(module, "autoactivate"): |
|
412 module.error = self.tr( |
|
413 "Module is missing the 'autoactivate' attribute.") |
|
414 self.__failedModules[name] = module |
|
415 raise PluginLoadError(name) |
|
416 if getattr(module, "autoactivate", False): |
|
417 self.__inactiveModules[name] = module |
|
418 else: |
|
419 if ( |
|
420 not hasattr(module, "pluginType") or |
|
421 not hasattr(module, "pluginTypename") |
|
422 ): |
|
423 module.error = self.tr( |
|
424 "Module is missing the 'pluginType' " |
|
425 "and/or 'pluginTypename' attributes." |
|
426 ) |
|
427 self.__failedModules[name] = module |
|
428 raise PluginLoadError(name) |
|
429 else: |
|
430 self.__onDemandInactiveModules[name] = module |
|
431 module.eric7PluginModuleName = name |
|
432 module.eric7PluginModuleFilename = fname |
|
433 if ( |
|
434 (install or |
|
435 Preferences.getPluginManager("AutoInstallDependencies")) and |
|
436 hasattr(module, "installDependencies") |
|
437 ): |
|
438 # ask the module to install its dependencies |
|
439 module.installDependencies(self.pipInstall) |
|
440 self.__modulesCount += 1 |
|
441 if reload_: |
|
442 importlib.reload(module) |
|
443 self.initOnDemandPlugin(name) |
|
444 with contextlib.suppress(KeyError, AttributeError): |
|
445 pluginObject = self.__onDemandInactivePlugins[name] |
|
446 pluginObject.initToolbar( |
|
447 self.__ui, ericApp().getObject("ToolbarManager")) |
|
448 except PluginLoadError: |
|
449 print("Error loading plug-in module:", name) |
|
450 except Exception as err: |
|
451 module = types.ModuleType(name) |
|
452 module.error = self.tr( |
|
453 "Module failed to load. Error: {0}").format(str(err)) |
|
454 self.__failedModules[name] = module |
|
455 print("Error loading plug-in module:", name) |
|
456 print(str(err)) |
|
457 |
|
458 def unloadPlugin(self, name): |
|
459 """ |
|
460 Public method to unload a plugin module. |
|
461 |
|
462 @param name name of the module to be unloaded (string) |
|
463 @return flag indicating success (boolean) |
|
464 """ |
|
465 if name in self.__onDemandActiveModules: |
|
466 # cannot unload an ondemand plugin, that is in use |
|
467 return False |
|
468 |
|
469 if name in self.__activeModules: |
|
470 self.deactivatePlugin(name) |
|
471 |
|
472 if name in self.__inactiveModules: |
|
473 with contextlib.suppress(KeyError): |
|
474 pluginObject = self.__inactivePlugins[name] |
|
475 with contextlib.suppress(AttributeError): |
|
476 pluginObject.prepareUnload() |
|
477 del self.__inactivePlugins[name] |
|
478 del self.__inactiveModules[name] |
|
479 elif name in self.__onDemandInactiveModules: |
|
480 with contextlib.suppress(KeyError): |
|
481 pluginObject = self.__onDemandInactivePlugins[name] |
|
482 with contextlib.suppress(AttributeError): |
|
483 pluginObject.prepareUnload() |
|
484 del self.__onDemandInactivePlugins[name] |
|
485 del self.__onDemandInactiveModules[name] |
|
486 elif name in self.__failedModules: |
|
487 del self.__failedModules[name] |
|
488 |
|
489 self.__modulesCount -= 1 |
|
490 return True |
|
491 |
|
492 def removePluginFromSysModules(self, pluginName, package, |
|
493 internalPackages): |
|
494 """ |
|
495 Public method to remove a plugin and all related modules from |
|
496 sys.modules. |
|
497 |
|
498 @param pluginName name of the plugin module (string) |
|
499 @param package name of the plugin package (string) |
|
500 @param internalPackages list of intenal packages (list of string) |
|
501 @return flag indicating the plugin module was found in sys.modules |
|
502 (boolean) |
|
503 """ |
|
504 packages = [package] + internalPackages |
|
505 found = False |
|
506 if not package: |
|
507 package = "__None__" |
|
508 for moduleName in list(sys.modules.keys())[:]: |
|
509 if ( |
|
510 moduleName == pluginName or |
|
511 moduleName.split(".")[0] in packages |
|
512 ): |
|
513 found = True |
|
514 del sys.modules[moduleName] |
|
515 return found |
|
516 |
|
517 def initOnDemandPlugins(self): |
|
518 """ |
|
519 Public method to create plugin objects for all on demand plugins. |
|
520 |
|
521 Note: The plugins are not activated. |
|
522 """ |
|
523 names = sorted(self.__onDemandInactiveModules.keys()) |
|
524 for name in names: |
|
525 self.initOnDemandPlugin(name) |
|
526 |
|
527 def initOnDemandPlugin(self, name): |
|
528 """ |
|
529 Public method to create a plugin object for the named on demand plugin. |
|
530 |
|
531 Note: The plug-in is not activated. |
|
532 |
|
533 @param name name of the plug-in (string) |
|
534 @exception PluginActivationError raised to indicate an issue during the |
|
535 plug-in activation |
|
536 """ |
|
537 try: |
|
538 try: |
|
539 module = self.__onDemandInactiveModules[name] |
|
540 except KeyError: |
|
541 return |
|
542 |
|
543 if not self.__canActivatePlugin(module): |
|
544 raise PluginActivationError(module.eric7PluginModuleName) |
|
545 version = getattr(module, "version", "0.0.0") |
|
546 className = getattr(module, "className", "") |
|
547 pluginClass = getattr(module, className) |
|
548 pluginObject = None |
|
549 if name not in self.__onDemandInactivePlugins: |
|
550 pluginObject = pluginClass(self.__ui) |
|
551 pluginObject.eric7PluginModule = module |
|
552 pluginObject.eric7PluginName = className |
|
553 pluginObject.eric7PluginVersion = version |
|
554 self.__onDemandInactivePlugins[name] = pluginObject |
|
555 except PluginActivationError: |
|
556 return |
|
557 |
|
558 def initPluginToolbars(self, toolbarManager): |
|
559 """ |
|
560 Public method to initialize plug-in toolbars. |
|
561 |
|
562 @param toolbarManager reference to the toolbar manager object |
|
563 (EricToolBarManager) |
|
564 """ |
|
565 self.initOnDemandPlugins() |
|
566 for pluginObject in self.__onDemandInactivePlugins.values(): |
|
567 with contextlib.suppress(AttributeError): |
|
568 pluginObject.initToolbar(self.__ui, toolbarManager) |
|
569 |
|
570 def activatePlugins(self): |
|
571 """ |
|
572 Public method to activate all plugins having the "autoactivate" |
|
573 attribute set to True. |
|
574 """ |
|
575 savedInactiveList = Preferences.getSettings().value( |
|
576 self.__inactivePluginsKey) |
|
577 inactiveList = self.__disabledPlugins[:] |
|
578 if savedInactiveList is not None: |
|
579 inactiveList += [p for p in savedInactiveList |
|
580 if p not in self.__disabledPlugins] |
|
581 if ( |
|
582 self.__develPluginName is not None and |
|
583 self.__develPluginName in inactiveList |
|
584 ): |
|
585 inactiveList.remove(self.__develPluginName) |
|
586 names = sorted(self.__inactiveModules.keys()) |
|
587 for name in names: |
|
588 if name not in inactiveList: |
|
589 self.activatePlugin(name) |
|
590 self.allPlugginsActivated.emit() |
|
591 |
|
592 def activatePlugin(self, name, onDemand=False): |
|
593 """ |
|
594 Public method to activate a plugin. |
|
595 |
|
596 @param name name of the module to be activated |
|
597 @param onDemand flag indicating activation of an |
|
598 on demand plugin (boolean) |
|
599 @return reference to the initialized plugin object |
|
600 @exception PluginActivationError raised to indicate an issue during the |
|
601 plug-in activation |
|
602 """ |
|
603 try: |
|
604 try: |
|
605 module = ( |
|
606 self.__onDemandInactiveModules[name] |
|
607 if onDemand else |
|
608 self.__inactiveModules[name] |
|
609 ) |
|
610 except KeyError: |
|
611 return None |
|
612 |
|
613 if not self.__canActivatePlugin(module): |
|
614 raise PluginActivationError(module.eric7PluginModuleName) |
|
615 version = getattr(module, "version", "0.0.0") |
|
616 className = getattr(module, "className", "") |
|
617 pluginClass = getattr(module, className) |
|
618 pluginObject = None |
|
619 if onDemand and name in self.__onDemandInactivePlugins: |
|
620 pluginObject = self.__onDemandInactivePlugins[name] |
|
621 elif not onDemand and name in self.__inactivePlugins: |
|
622 pluginObject = self.__inactivePlugins[name] |
|
623 else: |
|
624 pluginObject = pluginClass(self.__ui) |
|
625 self.pluginAboutToBeActivated.emit(name, pluginObject) |
|
626 try: |
|
627 obj, ok = pluginObject.activate() |
|
628 except TypeError: |
|
629 module.error = self.tr( |
|
630 "Incompatible plugin activation method.") |
|
631 obj = None |
|
632 ok = True |
|
633 except Exception as err: |
|
634 module.error = str(err) |
|
635 obj = None |
|
636 ok = False |
|
637 if not ok: |
|
638 return None |
|
639 |
|
640 self.pluginActivated.emit(name, pluginObject) |
|
641 pluginObject.eric7PluginModule = module |
|
642 pluginObject.eric7PluginName = className |
|
643 pluginObject.eric7PluginVersion = version |
|
644 |
|
645 if onDemand: |
|
646 self.__onDemandInactiveModules.pop(name) |
|
647 with contextlib.suppress(KeyError): |
|
648 self.__onDemandInactivePlugins.pop(name) |
|
649 self.__onDemandActivePlugins[name] = pluginObject |
|
650 self.__onDemandActiveModules[name] = module |
|
651 else: |
|
652 self.__inactiveModules.pop(name) |
|
653 with contextlib.suppress(KeyError): |
|
654 self.__inactivePlugins.pop(name) |
|
655 self.__activePlugins[name] = pluginObject |
|
656 self.__activeModules[name] = module |
|
657 return obj |
|
658 except PluginActivationError: |
|
659 return None |
|
660 |
|
661 def __canActivatePlugin(self, module): |
|
662 """ |
|
663 Private method to check, if a plugin can be activated. |
|
664 |
|
665 @param module reference to the module to be activated |
|
666 @return flag indicating, if the module satisfies all requirements |
|
667 for being activated (boolean) |
|
668 @exception PluginModuleFormatError raised to indicate an invalid |
|
669 plug-in module format |
|
670 @exception PluginClassFormatError raised to indicate an invalid |
|
671 plug-in class format |
|
672 """ |
|
673 try: |
|
674 if not hasattr(module, "version"): |
|
675 raise PluginModuleFormatError( |
|
676 module.eric7PluginModuleName, "version") |
|
677 if not hasattr(module, "className"): |
|
678 raise PluginModuleFormatError( |
|
679 module.eric7PluginModuleName, "className") |
|
680 className = getattr(module, "className", "") |
|
681 if not className or not hasattr(module, className): |
|
682 raise PluginModuleFormatError( |
|
683 module.eric7PluginModuleName, className) |
|
684 pluginClass = getattr(module, className) |
|
685 if not hasattr(pluginClass, "__init__"): |
|
686 raise PluginClassFormatError( |
|
687 module.eric7PluginModuleName, |
|
688 className, "__init__") |
|
689 if not hasattr(pluginClass, "activate"): |
|
690 raise PluginClassFormatError( |
|
691 module.eric7PluginModuleName, |
|
692 className, "activate") |
|
693 if not hasattr(pluginClass, "deactivate"): |
|
694 raise PluginClassFormatError( |
|
695 module.eric7PluginModuleName, |
|
696 className, "deactivate") |
|
697 return True |
|
698 except PluginModuleFormatError as e: |
|
699 print(repr(e)) |
|
700 return False |
|
701 except PluginClassFormatError as e: |
|
702 print(repr(e)) |
|
703 return False |
|
704 |
|
705 def deactivatePlugin(self, name, onDemand=False): |
|
706 """ |
|
707 Public method to deactivate a plugin. |
|
708 |
|
709 @param name name of the module to be deactivated |
|
710 @param onDemand flag indicating deactivation of an |
|
711 on demand plugin (boolean) |
|
712 """ |
|
713 try: |
|
714 module = ( |
|
715 self.__onDemandActiveModules[name] |
|
716 if onDemand else |
|
717 self.__activeModules[name] |
|
718 ) |
|
719 except KeyError: |
|
720 return |
|
721 |
|
722 if self.__canDeactivatePlugin(module): |
|
723 pluginObject = None |
|
724 if onDemand and name in self.__onDemandActivePlugins: |
|
725 pluginObject = self.__onDemandActivePlugins[name] |
|
726 elif not onDemand and name in self.__activePlugins: |
|
727 pluginObject = self.__activePlugins[name] |
|
728 if pluginObject: |
|
729 self.pluginAboutToBeDeactivated.emit(name, pluginObject) |
|
730 pluginObject.deactivate() |
|
731 self.pluginDeactivated.emit(name, pluginObject) |
|
732 |
|
733 if onDemand: |
|
734 self.__onDemandActiveModules.pop(name) |
|
735 self.__onDemandActivePlugins.pop(name) |
|
736 self.__onDemandInactivePlugins[name] = pluginObject |
|
737 self.__onDemandInactiveModules[name] = module |
|
738 else: |
|
739 self.__activeModules.pop(name) |
|
740 with contextlib.suppress(KeyError): |
|
741 self.__activePlugins.pop(name) |
|
742 self.__inactivePlugins[name] = pluginObject |
|
743 self.__inactiveModules[name] = module |
|
744 |
|
745 def __canDeactivatePlugin(self, module): |
|
746 """ |
|
747 Private method to check, if a plugin can be deactivated. |
|
748 |
|
749 @param module reference to the module to be deactivated |
|
750 @return flag indicating, if the module satisfies all requirements |
|
751 for being deactivated (boolean) |
|
752 """ |
|
753 return getattr(module, "deactivateable", True) |
|
754 |
|
755 def getPluginObject(self, type_, typename, maybeActive=False): |
|
756 """ |
|
757 Public method to activate an ondemand plugin given by type and |
|
758 typename. |
|
759 |
|
760 @param type_ type of the plugin to be activated (string) |
|
761 @param typename name of the plugin within the type category (string) |
|
762 @param maybeActive flag indicating, that the plugin may be active |
|
763 already (boolean) |
|
764 @return reference to the initialized plugin object |
|
765 """ |
|
766 for name, module in list(self.__onDemandInactiveModules.items()): |
|
767 if ( |
|
768 getattr(module, "pluginType", "") == type_ and |
|
769 getattr(module, "pluginTypename", "") == typename |
|
770 ): |
|
771 return self.activatePlugin(name, onDemand=True) |
|
772 |
|
773 if maybeActive: |
|
774 for name, module in list(self.__onDemandActiveModules.items()): |
|
775 if ( |
|
776 getattr(module, "pluginType", "") == type_ and |
|
777 getattr(module, "pluginTypename", "") == typename |
|
778 ): |
|
779 self.deactivatePlugin(name, onDemand=True) |
|
780 return self.activatePlugin(name, onDemand=True) |
|
781 |
|
782 return None |
|
783 |
|
784 def getPluginInfos(self): |
|
785 """ |
|
786 Public method to get infos about all loaded plug-ins. |
|
787 |
|
788 @return list of dictionaries with keys "module_name", "plugin_name", |
|
789 "version", "auto_activate", "active", "short_desc", "error" |
|
790 @rtype list of dict ("module_name": str, "plugin_name": str, |
|
791 "version": str, "auto_activate": bool, "active": bool, |
|
792 "short_desc": str, "error": bool) |
|
793 """ |
|
794 infos = [] |
|
795 |
|
796 # 1. active, non-on-demand modules |
|
797 for name in list(self.__activeModules.keys()): |
|
798 info = self.__getShortInfo(self.__activeModules[name]) |
|
799 info.update({ |
|
800 "module_name": name, |
|
801 "auto_activate": True, |
|
802 "active": True, |
|
803 }) |
|
804 infos.append(info) |
|
805 |
|
806 # 2. inactive, non-on-demand modules |
|
807 for name in list(self.__inactiveModules.keys()): |
|
808 info = self.__getShortInfo(self.__inactiveModules[name]) |
|
809 info.update({ |
|
810 "module_name": name, |
|
811 "auto_activate": True, |
|
812 "active": False, |
|
813 }) |
|
814 infos.append(info) |
|
815 |
|
816 # 3. active, on-demand modules |
|
817 for name in list(self.__onDemandActiveModules.keys()): |
|
818 info = self.__getShortInfo(self.__onDemandActiveModules[name]) |
|
819 info.update({ |
|
820 "module_name": name, |
|
821 "auto_activate": False, |
|
822 "active": True, |
|
823 }) |
|
824 infos.append(info) |
|
825 |
|
826 # 4. inactive, non-on-demand modules |
|
827 for name in list(self.__onDemandInactiveModules.keys()): |
|
828 info = self.__getShortInfo(self.__onDemandInactiveModules[name]) |
|
829 info.update({ |
|
830 "module_name": name, |
|
831 "auto_activate": False, |
|
832 "active": False, |
|
833 }) |
|
834 infos.append(info) |
|
835 |
|
836 # 5. failed modules |
|
837 for name in list(self.__failedModules.keys()): |
|
838 info = self.__getShortInfo(self.__failedModules[name]) |
|
839 info.update({ |
|
840 "module_name": name, |
|
841 "auto_activate": False, |
|
842 "active": False, |
|
843 }) |
|
844 infos.append(info) |
|
845 |
|
846 return infos |
|
847 |
|
848 def __getShortInfo(self, module): |
|
849 """ |
|
850 Private method to extract the short info from a module. |
|
851 |
|
852 @param module module to extract short info from |
|
853 @return dictionay containing plug-in data |
|
854 @rtype dict ("plugin_name": str, "version": str, "short_desc": str, |
|
855 "error": bool) |
|
856 """ |
|
857 return { |
|
858 "plugin_name": getattr(module, "name", ""), |
|
859 "version": getattr(module, "version", ""), |
|
860 "short_desc": getattr(module, "shortDescription", ""), |
|
861 "error": bool(getattr(module, "error", "")), |
|
862 } |
|
863 |
|
864 def getPluginDetails(self, name): |
|
865 """ |
|
866 Public method to get detailed information about a plugin. |
|
867 |
|
868 @param name name of the module to get detailed infos about (string) |
|
869 @return details of the plugin as a dictionary |
|
870 """ |
|
871 details = {} |
|
872 |
|
873 autoactivate = True |
|
874 active = True |
|
875 |
|
876 if name in self.__activeModules: |
|
877 module = self.__activeModules[name] |
|
878 elif name in self.__inactiveModules: |
|
879 module = self.__inactiveModules[name] |
|
880 active = False |
|
881 elif name in self.__onDemandActiveModules: |
|
882 module = self.__onDemandActiveModules[name] |
|
883 autoactivate = False |
|
884 elif name in self.__onDemandInactiveModules: |
|
885 module = self.__onDemandInactiveModules[name] |
|
886 autoactivate = False |
|
887 active = False |
|
888 elif name in self.__failedModules: |
|
889 module = self.__failedModules[name] |
|
890 autoactivate = False |
|
891 active = False |
|
892 elif "_" in name: |
|
893 # try stripping of a postfix |
|
894 return self.getPluginDetails(name.rsplit("_", 1)[0]) |
|
895 else: |
|
896 # should not happen |
|
897 return None |
|
898 |
|
899 details["moduleName"] = name |
|
900 details["moduleFileName"] = getattr( |
|
901 module, "eric7PluginModuleFilename", "") |
|
902 details["pluginName"] = getattr(module, "name", "") |
|
903 details["version"] = getattr(module, "version", "") |
|
904 details["author"] = getattr(module, "author", "") |
|
905 details["description"] = getattr(module, "longDescription", "") |
|
906 details["autoactivate"] = autoactivate |
|
907 details["active"] = active |
|
908 details["error"] = getattr(module, "error", "") |
|
909 |
|
910 return details |
|
911 |
|
912 def doShutdown(self): |
|
913 """ |
|
914 Public method called to perform actions upon shutdown of the IDE. |
|
915 """ |
|
916 names = [] |
|
917 for name in list(self.__inactiveModules.keys()): |
|
918 names.append(name) |
|
919 Preferences.getSettings().setValue(self.__inactivePluginsKey, names) |
|
920 |
|
921 self.shutdown.emit() |
|
922 |
|
923 def getPluginDisplayStrings(self, type_): |
|
924 """ |
|
925 Public method to get the display strings of all plugins of a specific |
|
926 type. |
|
927 |
|
928 @param type_ type of the plugins (string) |
|
929 @return dictionary with name as key and display string as value |
|
930 (dictionary of string) |
|
931 """ |
|
932 pluginDict = {} |
|
933 |
|
934 for module in ( |
|
935 list(self.__onDemandActiveModules.values()) + |
|
936 list(self.__onDemandInactiveModules.values()) |
|
937 ): |
|
938 if ( |
|
939 getattr(module, "pluginType", "") == type_ and |
|
940 getattr(module, "error", "") == "" |
|
941 ): |
|
942 plugin_name = getattr(module, "pluginTypename", "") |
|
943 if plugin_name: |
|
944 if hasattr(module, "displayString"): |
|
945 try: |
|
946 disp = module.displayString() |
|
947 except TypeError: |
|
948 disp = getattr(module, "displayString", "") |
|
949 if disp != "": |
|
950 pluginDict[plugin_name] = disp |
|
951 else: |
|
952 pluginDict[plugin_name] = plugin_name |
|
953 |
|
954 return pluginDict |
|
955 |
|
956 def getPluginPreviewPixmap(self, type_, name): |
|
957 """ |
|
958 Public method to get a preview pixmap of a plugin of a specific type. |
|
959 |
|
960 @param type_ type of the plugin (string) |
|
961 @param name name of the plugin type (string) |
|
962 @return preview pixmap (QPixmap) |
|
963 """ |
|
964 for module in ( |
|
965 list(self.__onDemandActiveModules.values()) + |
|
966 list(self.__onDemandInactiveModules.values()) |
|
967 ): |
|
968 if ( |
|
969 getattr(module, "pluginType", "") == type_ and |
|
970 getattr(module, "pluginTypename", "") == name |
|
971 ): |
|
972 if hasattr(module, "previewPix"): |
|
973 return module.previewPix() |
|
974 else: |
|
975 return QPixmap() |
|
976 |
|
977 return QPixmap() |
|
978 |
|
979 def getPluginApiFiles(self, language): |
|
980 """ |
|
981 Public method to get the list of API files installed by a plugin. |
|
982 |
|
983 @param language language of the requested API files (string) |
|
984 @return list of API filenames (list of string) |
|
985 """ |
|
986 apis = [] |
|
987 |
|
988 for module in ( |
|
989 list(self.__activeModules.values()) + |
|
990 list(self.__onDemandActiveModules.values()) |
|
991 ): |
|
992 if hasattr(module, "apiFiles"): |
|
993 apis.extend(module.apiFiles(language)) |
|
994 |
|
995 return apis |
|
996 |
|
997 def getPluginQtHelpFiles(self): |
|
998 """ |
|
999 Public method to get the list of QtHelp documentation files provided |
|
1000 by a plug-in. |
|
1001 |
|
1002 @return dictionary with documentation type as key and list of files |
|
1003 as value |
|
1004 @rtype dict (key: str, value: list of str) |
|
1005 """ |
|
1006 helpFiles = {} |
|
1007 for module in ( |
|
1008 list(self.__activeModules.values()) + |
|
1009 list(self.__onDemandActiveModules.values()) |
|
1010 ): |
|
1011 if hasattr(module, "helpFiles"): |
|
1012 helpFiles.update(module.helpFiles()) |
|
1013 |
|
1014 return helpFiles |
|
1015 |
|
1016 def getPluginExeDisplayData(self): |
|
1017 """ |
|
1018 Public method to get data to display information about a plugins |
|
1019 external tool. |
|
1020 |
|
1021 @return list of dictionaries containing the data. Each dictionary must |
|
1022 either contain data for the determination or the data to be |
|
1023 displayed.<br /> |
|
1024 A dictionary of the first form must have the following entries: |
|
1025 <ul> |
|
1026 <li>programEntry - indicator for this dictionary form |
|
1027 (boolean), always True</li> |
|
1028 <li>header - string to be diplayed as a header (string)</li> |
|
1029 <li>exe - the executable (string)</li> |
|
1030 <li>versionCommand - commandline parameter for the exe |
|
1031 (string)</li> |
|
1032 <li>versionStartsWith - indicator for the output line |
|
1033 containing the version (string)</li> |
|
1034 <li>versionPosition - number of element containing the |
|
1035 version (integer)</li> |
|
1036 <li>version - version to be used as default (string)</li> |
|
1037 <li>versionCleanup - tuple of two integers giving string |
|
1038 positions start and stop for the version string |
|
1039 (tuple of integers)</li> |
|
1040 </ul> |
|
1041 A dictionary of the second form must have the following entries: |
|
1042 <ul> |
|
1043 <li>programEntry - indicator for this dictionary form |
|
1044 (boolean), always False</li> |
|
1045 <li>header - string to be diplayed as a header (string)</li> |
|
1046 <li>text - entry text to be shown (string)</li> |
|
1047 <li>version - version text to be shown (string)</li> |
|
1048 </ul> |
|
1049 """ |
|
1050 infos = [] |
|
1051 |
|
1052 for module in ( |
|
1053 list(self.__activeModules.values()) + |
|
1054 list(self.__inactiveModules.values()) |
|
1055 ): |
|
1056 if hasattr(module, "exeDisplayDataList"): |
|
1057 infos.extend(module.exeDisplayDataList()) |
|
1058 elif hasattr(module, "exeDisplayData"): |
|
1059 infos.append(module.exeDisplayData()) |
|
1060 for module in ( |
|
1061 list(self.__onDemandActiveModules.values()) + |
|
1062 list(self.__onDemandInactiveModules.values()) |
|
1063 ): |
|
1064 if hasattr(module, "exeDisplayDataList"): |
|
1065 infos.extend(module.exeDisplayDataList()) |
|
1066 elif hasattr(module, "exeDisplayData"): |
|
1067 infos.append(module.exeDisplayData()) |
|
1068 |
|
1069 return infos |
|
1070 |
|
1071 def getPluginConfigData(self): |
|
1072 """ |
|
1073 Public method to get the config data of all active, non on-demand |
|
1074 plugins used by the configuration dialog. |
|
1075 |
|
1076 Plugins supporting this functionality must provide the plugin module |
|
1077 function 'getConfigData' returning a dictionary with unique keys |
|
1078 of lists with the following list contents: |
|
1079 <dl> |
|
1080 <dt>display string</dt> |
|
1081 <dd>string shown in the selection area of the configuration page. |
|
1082 This should be a localized string</dd> |
|
1083 <dt>pixmap name</dt> |
|
1084 <dd>filename of the pixmap to be shown next to the display |
|
1085 string</dd> |
|
1086 <dt>page creation function</dt> |
|
1087 <dd>plugin module function to be called to create the configuration |
|
1088 page. The page must be subclasses from |
|
1089 Preferences.ConfigurationPages.ConfigurationPageBase and must |
|
1090 implement a method called 'save' to save the settings. A parent |
|
1091 entry will be created in the selection list, if this value is |
|
1092 None.</dd> |
|
1093 <dt>parent key</dt> |
|
1094 <dd>dictionary key of the parent entry or None, if this defines a |
|
1095 toplevel entry.</dd> |
|
1096 <dt>reference to configuration page</dt> |
|
1097 <dd>This will be used by the configuration dialog and must always |
|
1098 be None</dd> |
|
1099 </dl> |
|
1100 |
|
1101 @return plug-in configuration data |
|
1102 """ |
|
1103 configData = {} |
|
1104 for module in ( |
|
1105 list(self.__activeModules.values()) + |
|
1106 list(self.__onDemandActiveModules.values()) + |
|
1107 list(self.__onDemandInactiveModules.values()) |
|
1108 ): |
|
1109 if hasattr(module, 'getConfigData'): |
|
1110 configData.update(module.getConfigData()) |
|
1111 return configData |
|
1112 |
|
1113 def isPluginLoaded(self, pluginName): |
|
1114 """ |
|
1115 Public method to check, if a certain plugin is loaded. |
|
1116 |
|
1117 @param pluginName name of the plugin to check for (string) |
|
1118 @return flag indicating, if the plugin is loaded (boolean) |
|
1119 """ |
|
1120 return ( |
|
1121 pluginName in self.__activeModules or |
|
1122 pluginName in self.__inactiveModules or |
|
1123 pluginName in self.__onDemandActiveModules or |
|
1124 pluginName in self.__onDemandInactiveModules |
|
1125 ) |
|
1126 |
|
1127 def isPluginActive(self, pluginName): |
|
1128 """ |
|
1129 Public method to check, if a certain plugin is active. |
|
1130 |
|
1131 @param pluginName name of the plugin to check for (string) |
|
1132 @return flag indicating, if the plugin is active (boolean) |
|
1133 """ |
|
1134 return ( |
|
1135 pluginName in self.__activeModules or |
|
1136 pluginName in self.__onDemandActiveModules |
|
1137 ) |
|
1138 |
|
1139 ########################################################################### |
|
1140 ## Specialized plug-in module handling methods below |
|
1141 ########################################################################### |
|
1142 |
|
1143 ########################################################################### |
|
1144 ## VCS related methods below |
|
1145 ########################################################################### |
|
1146 |
|
1147 def getVcsSystemIndicators(self): |
|
1148 """ |
|
1149 Public method to get the Vcs System indicators. |
|
1150 |
|
1151 Plugins supporting this functionality must support the module function |
|
1152 getVcsSystemIndicator returning a dictionary with indicator as key and |
|
1153 a tuple with the vcs name (string) and vcs display string (string). |
|
1154 |
|
1155 @return dictionary with indicator as key and a list of tuples as |
|
1156 values. Each tuple contains the vcs name (string) and vcs display |
|
1157 string (string). |
|
1158 """ |
|
1159 vcsDict = {} |
|
1160 |
|
1161 for module in ( |
|
1162 list(self.__onDemandActiveModules.values()) + |
|
1163 list(self.__onDemandInactiveModules.values()) |
|
1164 ): |
|
1165 if ( |
|
1166 getattr(module, "pluginType", "") == "version_control" and |
|
1167 hasattr(module, "getVcsSystemIndicator") |
|
1168 ): |
|
1169 res = module.getVcsSystemIndicator() |
|
1170 for indicator, vcsData in list(res.items()): |
|
1171 if indicator in vcsDict: |
|
1172 vcsDict[indicator].append(vcsData) |
|
1173 else: |
|
1174 vcsDict[indicator] = [vcsData] |
|
1175 |
|
1176 return vcsDict |
|
1177 |
|
1178 def deactivateVcsPlugins(self): |
|
1179 """ |
|
1180 Public method to deactivated all activated VCS plugins. |
|
1181 """ |
|
1182 for name, module in list(self.__onDemandActiveModules.items()): |
|
1183 if getattr(module, "pluginType", "") == "version_control": |
|
1184 self.deactivatePlugin(name, True) |
|
1185 |
|
1186 ######################################################################## |
|
1187 ## Methods for the creation of the plug-ins download directory |
|
1188 ######################################################################## |
|
1189 |
|
1190 def __checkPluginsDownloadDirectory(self): |
|
1191 """ |
|
1192 Private slot to check for the existence of the plugins download |
|
1193 directory. |
|
1194 """ |
|
1195 downloadDir = Preferences.getPluginManager("DownloadPath") |
|
1196 if not downloadDir: |
|
1197 downloadDir = self.__defaultDownloadDir |
|
1198 |
|
1199 if not os.path.exists(downloadDir): |
|
1200 try: |
|
1201 os.mkdir(downloadDir, 0o755) |
|
1202 except OSError: |
|
1203 # try again with (possibly) new default |
|
1204 downloadDir = self.__defaultDownloadDir |
|
1205 if not os.path.exists(downloadDir): |
|
1206 try: |
|
1207 os.mkdir(downloadDir, 0o755) |
|
1208 except OSError as err: |
|
1209 EricMessageBox.critical( |
|
1210 self.__ui, |
|
1211 self.tr("Plugin Manager Error"), |
|
1212 self.tr( |
|
1213 """<p>The plugin download directory""" |
|
1214 """ <b>{0}</b> could not be created. Please""" |
|
1215 """ configure it via the configuration""" |
|
1216 """ dialog.</p><p>Reason: {1}</p>""") |
|
1217 .format(downloadDir, str(err))) |
|
1218 downloadDir = "" |
|
1219 |
|
1220 Preferences.setPluginManager("DownloadPath", downloadDir) |
|
1221 |
|
1222 def preferencesChanged(self): |
|
1223 """ |
|
1224 Public slot to react to changes in configuration. |
|
1225 """ |
|
1226 self.__checkPluginsDownloadDirectory() |
|
1227 |
|
1228 ######################################################################## |
|
1229 ## Methods for automatic plug-in update check below |
|
1230 ######################################################################## |
|
1231 |
|
1232 def checkPluginUpdatesAvailable(self): |
|
1233 """ |
|
1234 Public method to check the availability of updates of plug-ins. |
|
1235 """ |
|
1236 period = Preferences.getPluginManager("UpdatesCheckInterval") |
|
1237 # 0 = off |
|
1238 # 1 = daily |
|
1239 # 2 = weekly |
|
1240 # 3 = monthly |
|
1241 # 4 = always |
|
1242 |
|
1243 if ( |
|
1244 period == 0 or |
|
1245 (self.__ui is not None and not self.__ui.isOnline()) |
|
1246 ): |
|
1247 return |
|
1248 |
|
1249 elif period in [1, 2, 3] and pathlib.Path(self.pluginRepositoryFile).exists(): |
|
1250 lastModified = datetime.datetime.fromtimestamp( |
|
1251 pathlib.Path(self.pluginRepositoryFile).stat().st_mtime |
|
1252 ) |
|
1253 now = datetime.datetime.now() |
|
1254 delta = now - lastModified |
|
1255 if ( |
|
1256 (period == 1 and lastModified.date().day == now.date().day) or |
|
1257 (period == 2 and delta.days < 7) or |
|
1258 (period == 3 and delta.days < 30) |
|
1259 ): |
|
1260 # daily, weekly, monthly |
|
1261 return |
|
1262 |
|
1263 self.downLoadRepositoryFile() |
|
1264 |
|
1265 def downLoadRepositoryFile(self, url=None): |
|
1266 """ |
|
1267 Public method to download the plugin repository file. |
|
1268 |
|
1269 @param url URL to get the plugin repository file from |
|
1270 (defaults to None) |
|
1271 @type QUrl or str (optional) |
|
1272 """ |
|
1273 self.__updateAvailable = False |
|
1274 |
|
1275 if url is None: |
|
1276 url = Preferences.getUI("PluginRepositoryUrl7") |
|
1277 request = QNetworkRequest(QUrl(url)) |
|
1278 request.setAttribute( |
|
1279 QNetworkRequest.Attribute.CacheLoadControlAttribute, |
|
1280 QNetworkRequest.CacheLoadControl.AlwaysNetwork) |
|
1281 reply = self.__networkManager.get(request) |
|
1282 reply.finished.connect( |
|
1283 lambda: self.__downloadRepositoryFileDone(reply)) |
|
1284 self.__replies.append(reply) |
|
1285 |
|
1286 def __downloadRepositoryFileDone(self, reply): |
|
1287 """ |
|
1288 Private method called after the repository file was downloaded. |
|
1289 |
|
1290 @param reply reference to the reply object of the download |
|
1291 @type QNetworkReply |
|
1292 """ |
|
1293 if reply in self.__replies: |
|
1294 self.__replies.remove(reply) |
|
1295 |
|
1296 if reply.error() != QNetworkReply.NetworkError.NoError: |
|
1297 EricMessageBox.warning( |
|
1298 None, |
|
1299 self.tr("Error downloading file"), |
|
1300 self.tr( |
|
1301 """<p>Could not download the requested file""" |
|
1302 """ from {0}.</p><p>Error: {1}</p>""" |
|
1303 ).format(Preferences.getUI("PluginRepositoryUrl7"), |
|
1304 reply.errorString()) |
|
1305 ) |
|
1306 reply.deleteLater() |
|
1307 return |
|
1308 |
|
1309 ioDevice = QFile(self.pluginRepositoryFile + ".tmp") |
|
1310 ioDevice.open(QIODevice.OpenModeFlag.WriteOnly) |
|
1311 ioDevice.write(reply.readAll()) |
|
1312 ioDevice.close() |
|
1313 if QFile.exists(self.pluginRepositoryFile): |
|
1314 QFile.remove(self.pluginRepositoryFile) |
|
1315 ioDevice.rename(self.pluginRepositoryFile) |
|
1316 reply.deleteLater() |
|
1317 |
|
1318 if os.path.exists(self.pluginRepositoryFile): |
|
1319 f = QFile(self.pluginRepositoryFile) |
|
1320 if f.open(QIODevice.OpenModeFlag.ReadOnly): |
|
1321 # save current URL |
|
1322 url = Preferences.getUI("PluginRepositoryUrl7") |
|
1323 |
|
1324 # read the repository file |
|
1325 from EricXML.PluginRepositoryReader import ( |
|
1326 PluginRepositoryReader |
|
1327 ) |
|
1328 reader = PluginRepositoryReader(f, self.checkPluginEntry) |
|
1329 reader.readXML() |
|
1330 if url != Preferences.getUI("PluginRepositoryUrl7"): |
|
1331 # redo if it is a redirect |
|
1332 self.checkPluginUpdatesAvailable() |
|
1333 return |
|
1334 |
|
1335 if self.__updateAvailable: |
|
1336 self.__ui and self.__ui.activatePluginRepositoryViewer() |
|
1337 else: |
|
1338 self.pluginRepositoryFileDownloaded.emit() |
|
1339 |
|
1340 def checkPluginEntry(self, name, short, description, url, author, version, |
|
1341 filename, status): |
|
1342 """ |
|
1343 Public method to check a plug-in's data for an update. |
|
1344 |
|
1345 @param name data for the name field (string) |
|
1346 @param short data for the short field (string) |
|
1347 @param description data for the description field (list of strings) |
|
1348 @param url data for the url field (string) |
|
1349 @param author data for the author field (string) |
|
1350 @param version data for the version field (string) |
|
1351 @param filename data for the filename field (string) |
|
1352 @param status status of the plugin (string [stable, unstable, unknown]) |
|
1353 """ |
|
1354 # ignore hidden plug-ins |
|
1355 pluginName = os.path.splitext(url.rsplit("/", 1)[1])[0] |
|
1356 if pluginName in Preferences.getPluginManager("HiddenPlugins"): |
|
1357 return |
|
1358 |
|
1359 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), |
|
1360 filename) |
|
1361 |
|
1362 # Check against installed/loaded plug-ins |
|
1363 pluginDetails = self.getPluginDetails(pluginName) |
|
1364 if pluginDetails is None: |
|
1365 if not Preferences.getPluginManager("CheckInstalledOnly"): |
|
1366 self.__updateAvailable = True |
|
1367 return |
|
1368 |
|
1369 versionTuple = Globals.versionToTuple(version)[:3] |
|
1370 pluginVersionTuple = Globals.versionToTuple( |
|
1371 pluginDetails["version"])[:3] |
|
1372 |
|
1373 if pluginVersionTuple < versionTuple: |
|
1374 self.__updateAvailable = True |
|
1375 return |
|
1376 |
|
1377 if not Preferences.getPluginManager("CheckInstalledOnly"): |
|
1378 # Check against downloaded plugin archives |
|
1379 # 1. Check, if the archive file exists |
|
1380 if not os.path.exists(archive): |
|
1381 if pluginDetails["moduleName"] != pluginName: |
|
1382 self.__updateAvailable = True |
|
1383 return |
|
1384 |
|
1385 # 2. Check, if the archive is a valid zip file |
|
1386 if not zipfile.is_zipfile(archive): |
|
1387 self.__updateAvailable = True |
|
1388 return |
|
1389 |
|
1390 # 3. Check the version of the archive file |
|
1391 zipFile = zipfile.ZipFile(archive, "r") |
|
1392 try: |
|
1393 aversion = zipFile.read("VERSION").decode("utf-8") |
|
1394 except KeyError: |
|
1395 aversion = "0.0.0" |
|
1396 zipFile.close() |
|
1397 |
|
1398 aversionTuple = Globals.versionToTuple(aversion)[:3] |
|
1399 if aversionTuple != versionTuple: |
|
1400 self.__updateAvailable = True |
|
1401 |
|
1402 def __sslErrors(self, reply, errors): |
|
1403 """ |
|
1404 Private slot to handle SSL errors. |
|
1405 |
|
1406 @param reply reference to the reply object (QNetworkReply) |
|
1407 @param errors list of SSL errors (list of QSslError) |
|
1408 """ |
|
1409 ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] |
|
1410 if ignored == EricSslErrorState.NOT_IGNORED: |
|
1411 self.__downloadCancelled = True |
|
1412 |
|
1413 ######################################################################## |
|
1414 ## Methods to clear private data of plug-ins below |
|
1415 ######################################################################## |
|
1416 |
|
1417 def clearPluginsPrivateData(self, type_): |
|
1418 """ |
|
1419 Public method to clear the private data of plug-ins of a specified |
|
1420 type. |
|
1421 |
|
1422 Plugins supporting this functionality must support the module function |
|
1423 clearPrivateData() and have the module level attribute pluginType. |
|
1424 |
|
1425 @param type_ type of the plugin to clear private data for (string) |
|
1426 """ |
|
1427 for module in ( |
|
1428 list(self.__onDemandActiveModules.values()) + |
|
1429 list(self.__onDemandInactiveModules.values()) + |
|
1430 list(self.__activeModules.values()) + |
|
1431 list(self.__inactiveModules.values()) |
|
1432 ): |
|
1433 if ( |
|
1434 getattr(module, "pluginType", "") == type_ and |
|
1435 hasattr(module, "clearPrivateData") |
|
1436 ): |
|
1437 module.clearPrivateData() |
|
1438 |
|
1439 ######################################################################## |
|
1440 ## Methods to install a plug-in module dependency via pip |
|
1441 ######################################################################## |
|
1442 |
|
1443 def pipInstall(self, packages): |
|
1444 """ |
|
1445 Public method to install the given package via pip. |
|
1446 |
|
1447 @param packages list of packages to install |
|
1448 @type list of str |
|
1449 """ |
|
1450 try: |
|
1451 pip = ericApp().getObject("Pip") |
|
1452 except KeyError: |
|
1453 # Installation is performed via the plug-in installation script. |
|
1454 from PipInterface.Pip import Pip |
|
1455 pip = Pip(self) |
|
1456 pip.installPackages(packages, |
|
1457 interpreter=Globals.getPythonExecutable()) |
|
1458 |
|
1459 # |
|
1460 # eflag: noqa = M801 |