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