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