eric7/PluginManager/PluginUninstallDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog for plugin deinstallation.
8 """
9
10 import sys
11 import os
12 import importlib
13 import shutil
14 import glob
15
16 from PyQt5.QtCore import pyqtSlot, pyqtSignal
17 from PyQt5.QtWidgets import QWidget, QDialog, QDialogButtonBox, QVBoxLayout
18
19 from E5Gui import E5MessageBox
20 from E5Gui.E5MainWindow import E5MainWindow
21 from E5Gui.E5Application import e5App
22
23 from .Ui_PluginUninstallDialog import Ui_PluginUninstallDialog
24
25 import Preferences
26 import UI.PixmapCache
27
28
29 class PluginUninstallWidget(QWidget, Ui_PluginUninstallDialog):
30 """
31 Class implementing a dialog for plugin deinstallation.
32
33 @signal accepted() emitted to indicate the removal of a plug-in
34 """
35 accepted = pyqtSignal()
36
37 def __init__(self, pluginManager, parent=None):
38 """
39 Constructor
40
41 @param pluginManager reference to the plugin manager object
42 @param parent parent of this dialog (QWidget)
43 """
44 super().__init__(parent)
45 self.setupUi(self)
46
47 if pluginManager is None:
48 # started as external plugin deinstaller
49 from .PluginManager import PluginManager
50 self.__pluginManager = PluginManager(doLoadPlugins=False)
51 self.__external = True
52 else:
53 self.__pluginManager = pluginManager
54 self.__external = False
55
56 self.pluginDirectoryCombo.addItem(
57 self.tr("User plugins directory"),
58 self.__pluginManager.getPluginDir("user"))
59
60 globalDir = self.__pluginManager.getPluginDir("global")
61 if globalDir is not None and os.access(globalDir, os.W_OK):
62 self.pluginDirectoryCombo.addItem(
63 self.tr("Global plugins directory"),
64 globalDir)
65
66 msh = self.minimumSizeHint()
67 self.resize(max(self.width(), msh.width()), msh.height())
68
69 @pyqtSlot(int)
70 def on_pluginDirectoryCombo_currentIndexChanged(self, index):
71 """
72 Private slot to populate the plugin name combo upon a change of the
73 plugin area.
74
75 @param index index of the selected item (integer)
76 """
77 pluginDirectory = self.pluginDirectoryCombo.itemData(index)
78 pluginNames = sorted(self.__pluginManager.getPluginModules(
79 pluginDirectory))
80 self.pluginNameCombo.clear()
81 for pluginName in pluginNames:
82 fname = "{0}.py".format(os.path.join(pluginDirectory, pluginName))
83 self.pluginNameCombo.addItem(pluginName, fname)
84 self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
85 self.pluginNameCombo.currentText() != "")
86
87 @pyqtSlot()
88 def on_buttonBox_accepted(self):
89 """
90 Private slot to handle the accepted signal of the button box.
91 """
92 if self.__uninstallPlugin():
93 self.accepted.emit()
94
95 def __uninstallPlugin(self):
96 """
97 Private slot to uninstall the selected plugin.
98
99 @return flag indicating success (boolean)
100 """
101 pluginDirectory = self.pluginDirectoryCombo.itemData(
102 self.pluginDirectoryCombo.currentIndex())
103 pluginName = self.pluginNameCombo.currentText()
104 pluginFile = self.pluginNameCombo.itemData(
105 self.pluginNameCombo.currentIndex())
106
107 if not self.__pluginManager.unloadPlugin(pluginName):
108 E5MessageBox.critical(
109 self,
110 self.tr("Plugin Uninstallation"),
111 self.tr(
112 """<p>The plugin <b>{0}</b> could not be unloaded."""
113 """ Aborting...</p>""").format(pluginName))
114 return False
115
116 if pluginDirectory not in sys.path:
117 sys.path.insert(2, pluginDirectory)
118 spec = importlib.util.spec_from_file_location(pluginName, pluginFile)
119 module = importlib.util.module_from_spec(spec)
120 spec.loader.exec_module(module)
121 if not hasattr(module, "packageName"):
122 E5MessageBox.critical(
123 self,
124 self.tr("Plugin Uninstallation"),
125 self.tr(
126 """<p>The plugin <b>{0}</b> has no 'packageName'"""
127 """ attribute. Aborting...</p>""").format(pluginName))
128 return False
129
130 package = getattr(module, "packageName", None)
131 if package is None:
132 package = "None"
133 packageDir = ""
134 else:
135 packageDir = os.path.join(pluginDirectory, package)
136 if (
137 hasattr(module, "prepareUninstall") and
138 not self.keepConfigurationCheckBox.isChecked()
139 ):
140 module.prepareUninstall()
141 internalPackages = []
142 if hasattr(module, "internalPackages"):
143 # it is a comma separated string
144 internalPackages = [p.strip() for p in
145 module.internalPackages.split(",")]
146 del module
147
148 # clean sys.modules
149 self.__pluginManager.removePluginFromSysModules(
150 pluginName, package, internalPackages)
151
152 try:
153 if packageDir and os.path.exists(packageDir):
154 shutil.rmtree(packageDir)
155
156 fnameo = "{0}o".format(pluginFile)
157 if os.path.exists(fnameo):
158 os.remove(fnameo)
159
160 fnamec = "{0}c".format(pluginFile)
161 if os.path.exists(fnamec):
162 os.remove(fnamec)
163
164 pluginDirCache = os.path.join(
165 os.path.dirname(pluginFile), "__pycache__")
166 if os.path.exists(pluginDirCache):
167 pluginFileName = os.path.splitext(
168 os.path.basename(pluginFile))[0]
169 for fnameo in glob.glob(os.path.join(
170 pluginDirCache, "{0}*.pyo".format(pluginFileName))):
171 os.remove(fnameo)
172 for fnamec in glob.glob(os.path.join(
173 pluginDirCache, "{0}*.pyc".format(pluginFileName))):
174 os.remove(fnamec)
175
176 os.remove(pluginFile)
177 except OSError as err:
178 E5MessageBox.critical(
179 self,
180 self.tr("Plugin Uninstallation"),
181 self.tr(
182 """<p>The plugin package <b>{0}</b> could not be"""
183 """ removed. Aborting...</p>"""
184 """<p>Reason: {1}</p>""").format(packageDir, str(err)))
185 return False
186
187 if not self.__external:
188 ui = e5App().getObject("UserInterface")
189 ui.showNotification(
190 UI.PixmapCache.getPixmap("plugin48"),
191 self.tr("Plugin Uninstallation"),
192 self.tr(
193 """<p>The plugin <b>{0}</b> was uninstalled"""
194 """ successfully from {1}.</p>""")
195 .format(pluginName, pluginDirectory))
196 return True
197
198 E5MessageBox.information(
199 self,
200 self.tr("Plugin Uninstallation"),
201 self.tr(
202 """<p>The plugin <b>{0}</b> was uninstalled successfully"""
203 """ from {1}.</p>""")
204 .format(pluginName, pluginDirectory))
205 return True
206
207
208 class PluginUninstallDialog(QDialog):
209 """
210 Class for the dialog variant.
211 """
212 def __init__(self, pluginManager, parent=None):
213 """
214 Constructor
215
216 @param pluginManager reference to the plugin manager object
217 @param parent reference to the parent widget (QWidget)
218 """
219 super().__init__(parent)
220 self.setSizeGripEnabled(True)
221
222 self.__layout = QVBoxLayout(self)
223 self.__layout.setContentsMargins(0, 0, 0, 0)
224 self.setLayout(self.__layout)
225
226 self.cw = PluginUninstallWidget(pluginManager, self)
227 size = self.cw.size()
228 self.__layout.addWidget(self.cw)
229 self.resize(size)
230 self.setWindowTitle(self.cw.windowTitle())
231
232 self.cw.buttonBox.accepted.connect(self.accept)
233 self.cw.buttonBox.rejected.connect(self.reject)
234
235
236 class PluginUninstallWindow(E5MainWindow):
237 """
238 Main window class for the standalone dialog.
239 """
240 def __init__(self, parent=None):
241 """
242 Constructor
243
244 @param parent reference to the parent widget (QWidget)
245 """
246 super().__init__(parent)
247 self.cw = PluginUninstallWidget(None, self)
248 size = self.cw.size()
249 self.setCentralWidget(self.cw)
250 self.resize(size)
251 self.setWindowTitle(self.cw.windowTitle())
252
253 self.setStyle(Preferences.getUI("Style"),
254 Preferences.getUI("StyleSheet"))
255
256 self.cw.buttonBox.accepted.connect(self.close)
257 self.cw.buttonBox.rejected.connect(self.close)

eric ide

mercurial