src/eric7/PluginManager/PluginUninstallDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2022 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 PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt
17 from PyQt6.QtWidgets import QWidget, QDialog, QVBoxLayout, QListWidgetItem
18
19 from EricWidgets import EricMessageBox
20 from EricWidgets.EricMainWindow import EricMainWindow
21 from EricWidgets.EricApplication import ericApp
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 @pyqtSlot(int)
67 def on_pluginDirectoryCombo_currentIndexChanged(self, index):
68 """
69 Private slot to populate the plugin name combo upon a change of the
70 plugin area.
71
72 @param index index of the selected item (integer)
73 """
74 pluginDirectory = self.pluginDirectoryCombo.itemData(index)
75 pluginNames = sorted(self.__pluginManager.getPluginModules(
76 pluginDirectory))
77
78 self.pluginsList.clear()
79 for pluginName in pluginNames:
80 fname = "{0}.py".format(os.path.join(pluginDirectory, pluginName))
81 itm = QListWidgetItem(pluginName)
82 itm.setData(Qt.ItemDataRole.UserRole, fname)
83 itm.setFlags(Qt.ItemFlag.ItemIsEnabled |
84 Qt.ItemFlag.ItemIsUserCheckable)
85 itm.setCheckState(Qt.CheckState.Unchecked)
86 self.pluginsList.addItem(itm)
87
88 @pyqtSlot()
89 def on_buttonBox_accepted(self):
90 """
91 Private slot to handle the accepted signal of the button box.
92 """
93 if self.__uninstallPlugins():
94 self.accepted.emit()
95
96 def __getCheckedPlugins(self):
97 """
98 Private method to get the list of plugins to be uninstalled.
99
100 @return list of tuples with the plugin name and plugin file name
101 @rtype list of tuples of (str, str)
102 """
103 plugins = []
104 for row in range(self.pluginsList.count()):
105 itm = self.pluginsList.item(row)
106 if itm.checkState() == Qt.CheckState.Checked:
107 plugins.append((itm.text(),
108 itm.data(Qt.ItemDataRole.UserRole)))
109 return plugins
110
111 def __uninstallPlugins(self):
112 """
113 Private method to uninstall the selected plugins.
114
115 @return flag indicating success
116 @rtype bool
117 """
118 checkedPlugins = self.__getCheckedPlugins()
119 uninstallCount = 0
120 for pluginName, pluginFile in checkedPlugins:
121 if self.__uninstallPlugin(pluginName, pluginFile):
122 uninstallCount += 1
123 return uninstallCount == len(checkedPlugins)
124
125 def __uninstallPlugin(self, pluginName, pluginFile):
126 """
127 Private method to uninstall a given plugin.
128
129 @param pluginName name of the plugin
130 @type str
131 @param pluginFile file name of the plugin
132 @type str
133 @return flag indicating success
134 @rtype bool
135 """
136 pluginDirectory = self.pluginDirectoryCombo.itemData(
137 self.pluginDirectoryCombo.currentIndex())
138
139 if not self.__pluginManager.unloadPlugin(pluginName):
140 EricMessageBox.critical(
141 self,
142 self.tr("Plugin Uninstallation"),
143 self.tr(
144 """<p>The plugin <b>{0}</b> could not be unloaded."""
145 """ Aborting...</p>""").format(pluginName))
146 return False
147
148 if pluginDirectory not in sys.path:
149 sys.path.insert(2, pluginDirectory)
150 spec = importlib.util.spec_from_file_location(pluginName, pluginFile)
151 module = importlib.util.module_from_spec(spec)
152 spec.loader.exec_module(module)
153 if not hasattr(module, "packageName"):
154 EricMessageBox.critical(
155 self,
156 self.tr("Plugin Uninstallation"),
157 self.tr(
158 """<p>The plugin <b>{0}</b> has no 'packageName'"""
159 """ attribute. Aborting...</p>""").format(pluginName))
160 return False
161
162 package = getattr(module, "packageName", None)
163 if package is None:
164 package = "None"
165 packageDir = ""
166 else:
167 packageDir = os.path.join(pluginDirectory, package)
168 if (
169 hasattr(module, "prepareUninstall") and
170 not self.keepConfigurationCheckBox.isChecked()
171 ):
172 module.prepareUninstall()
173 internalPackages = []
174 if hasattr(module, "internalPackages"):
175 # it is a comma separated string
176 internalPackages = [p.strip() for p in
177 module.internalPackages.split(",")]
178 del module
179
180 # clean sys.modules
181 self.__pluginManager.removePluginFromSysModules(
182 pluginName, package, internalPackages)
183
184 try:
185 if packageDir and os.path.exists(packageDir):
186 shutil.rmtree(packageDir)
187
188 fnameo = "{0}o".format(pluginFile)
189 if os.path.exists(fnameo):
190 os.remove(fnameo)
191
192 fnamec = "{0}c".format(pluginFile)
193 if os.path.exists(fnamec):
194 os.remove(fnamec)
195
196 pluginDirCache = os.path.join(
197 os.path.dirname(pluginFile), "__pycache__")
198 if os.path.exists(pluginDirCache):
199 pluginFileName = os.path.splitext(
200 os.path.basename(pluginFile))[0]
201 for fnameo in glob.glob(os.path.join(
202 pluginDirCache, "{0}*.pyo".format(pluginFileName))):
203 os.remove(fnameo)
204 for fnamec in glob.glob(os.path.join(
205 pluginDirCache, "{0}*.pyc".format(pluginFileName))):
206 os.remove(fnamec)
207
208 os.remove(pluginFile)
209 except OSError as err:
210 EricMessageBox.critical(
211 self,
212 self.tr("Plugin Uninstallation"),
213 self.tr(
214 """<p>The plugin package <b>{0}</b> could not be"""
215 """ removed. Aborting...</p>"""
216 """<p>Reason: {1}</p>""").format(packageDir, str(err)))
217 return False
218
219 if not self.__external:
220 ui = ericApp().getObject("UserInterface")
221 ui.showNotification(
222 UI.PixmapCache.getPixmap("plugin48"),
223 self.tr("Plugin Uninstallation"),
224 self.tr(
225 """<p>The plugin <b>{0}</b> was uninstalled"""
226 """ successfully from {1}.</p>""")
227 .format(pluginName, pluginDirectory))
228 return True
229
230 EricMessageBox.information(
231 self,
232 self.tr("Plugin Uninstallation"),
233 self.tr(
234 """<p>The plugin <b>{0}</b> was uninstalled successfully"""
235 """ from {1}.</p>""")
236 .format(pluginName, pluginDirectory))
237 return True
238
239
240 class PluginUninstallDialog(QDialog):
241 """
242 Class for the dialog variant.
243 """
244 def __init__(self, pluginManager, parent=None):
245 """
246 Constructor
247
248 @param pluginManager reference to the plugin manager object
249 @param parent reference to the parent widget (QWidget)
250 """
251 super().__init__(parent)
252 self.setSizeGripEnabled(True)
253
254 self.__layout = QVBoxLayout(self)
255 self.__layout.setContentsMargins(0, 0, 0, 0)
256 self.setLayout(self.__layout)
257
258 self.cw = PluginUninstallWidget(pluginManager, self)
259 size = self.cw.size()
260 self.__layout.addWidget(self.cw)
261 self.resize(size)
262 self.setWindowTitle(self.cw.windowTitle())
263
264 self.cw.buttonBox.accepted.connect(self.accept)
265 self.cw.buttonBox.rejected.connect(self.reject)
266
267
268 class PluginUninstallWindow(EricMainWindow):
269 """
270 Main window class for the standalone dialog.
271 """
272 def __init__(self, parent=None):
273 """
274 Constructor
275
276 @param parent reference to the parent widget (QWidget)
277 """
278 super().__init__(parent)
279 self.cw = PluginUninstallWidget(None, self)
280 size = self.cw.size()
281 self.setCentralWidget(self.cw)
282 self.resize(size)
283 self.setWindowTitle(self.cw.windowTitle())
284
285 self.setStyle(Preferences.getUI("Style"),
286 Preferences.getUI("StyleSheet"))
287
288 self.cw.buttonBox.accepted.connect(self.close)
289 self.cw.buttonBox.rejected.connect(self.close)

eric ide

mercurial