|
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) |