src/eric7/PluginManager/PluginUninstallDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/PluginManager/PluginUninstallDialog.py	Thu Jul 07 11:23:56 2022 +0200
@@ -0,0 +1,289 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog for plugin deinstallation.
+"""
+
+import sys
+import os
+import importlib
+import shutil
+import glob
+
+from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt
+from PyQt6.QtWidgets import QWidget, QDialog, QVBoxLayout, QListWidgetItem
+
+from EricWidgets import EricMessageBox
+from EricWidgets.EricMainWindow import EricMainWindow
+from EricWidgets.EricApplication import ericApp
+
+from .Ui_PluginUninstallDialog import Ui_PluginUninstallDialog
+
+import Preferences
+import UI.PixmapCache
+
+
+class PluginUninstallWidget(QWidget, Ui_PluginUninstallDialog):
+    """
+    Class implementing a dialog for plugin deinstallation.
+    
+    @signal accepted() emitted to indicate the removal of a plug-in
+    """
+    accepted = pyqtSignal()
+    
+    def __init__(self, pluginManager, parent=None):
+        """
+        Constructor
+        
+        @param pluginManager reference to the plugin manager object
+        @param parent parent of this dialog (QWidget)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        if pluginManager is None:
+            # started as external plugin deinstaller
+            from .PluginManager import PluginManager
+            self.__pluginManager = PluginManager(doLoadPlugins=False)
+            self.__external = True
+        else:
+            self.__pluginManager = pluginManager
+            self.__external = False
+        
+        self.pluginDirectoryCombo.addItem(
+            self.tr("User plugins directory"),
+            self.__pluginManager.getPluginDir("user"))
+        
+        globalDir = self.__pluginManager.getPluginDir("global")
+        if globalDir is not None and os.access(globalDir, os.W_OK):
+            self.pluginDirectoryCombo.addItem(
+                self.tr("Global plugins directory"),
+                globalDir)
+    
+    @pyqtSlot(int)
+    def on_pluginDirectoryCombo_currentIndexChanged(self, index):
+        """
+        Private slot to populate the plugin name combo upon a change of the
+        plugin area.
+        
+        @param index index of the selected item (integer)
+        """
+        pluginDirectory = self.pluginDirectoryCombo.itemData(index)
+        pluginNames = sorted(self.__pluginManager.getPluginModules(
+            pluginDirectory))
+        
+        self.pluginsList.clear()
+        for pluginName in pluginNames:
+            fname = "{0}.py".format(os.path.join(pluginDirectory, pluginName))
+            itm = QListWidgetItem(pluginName)
+            itm.setData(Qt.ItemDataRole.UserRole, fname)
+            itm.setFlags(Qt.ItemFlag.ItemIsEnabled |
+                         Qt.ItemFlag.ItemIsUserCheckable)
+            itm.setCheckState(Qt.CheckState.Unchecked)
+            self.pluginsList.addItem(itm)
+    
+    @pyqtSlot()
+    def on_buttonBox_accepted(self):
+        """
+        Private slot to handle the accepted signal of the button box.
+        """
+        if self.__uninstallPlugins():
+            self.accepted.emit()
+    
+    def __getCheckedPlugins(self):
+        """
+        Private method to get the list of plugins to be uninstalled.
+        
+        @return list of tuples with the plugin name and plugin file name
+        @rtype list of tuples of (str, str)
+        """
+        plugins = []
+        for row in range(self.pluginsList.count()):
+            itm = self.pluginsList.item(row)
+            if itm.checkState() == Qt.CheckState.Checked:
+                plugins.append((itm.text(),
+                                itm.data(Qt.ItemDataRole.UserRole)))
+        return plugins
+    
+    def __uninstallPlugins(self):
+        """
+        Private method to uninstall the selected plugins.
+        
+        @return flag indicating success
+        @rtype bool
+        """
+        checkedPlugins = self.__getCheckedPlugins()
+        uninstallCount = 0
+        for pluginName, pluginFile in checkedPlugins:
+            if self.__uninstallPlugin(pluginName, pluginFile):
+                uninstallCount += 1
+        return uninstallCount == len(checkedPlugins)
+    
+    def __uninstallPlugin(self, pluginName, pluginFile):
+        """
+        Private method to uninstall a given plugin.
+        
+        @param pluginName name of the plugin
+        @type str
+        @param pluginFile file name of the plugin
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        pluginDirectory = self.pluginDirectoryCombo.itemData(
+            self.pluginDirectoryCombo.currentIndex())
+        
+        if not self.__pluginManager.unloadPlugin(pluginName):
+            EricMessageBox.critical(
+                self,
+                self.tr("Plugin Uninstallation"),
+                self.tr(
+                    """<p>The plugin <b>{0}</b> could not be unloaded."""
+                    """ Aborting...</p>""").format(pluginName))
+            return False
+        
+        if pluginDirectory not in sys.path:
+            sys.path.insert(2, pluginDirectory)
+        spec = importlib.util.spec_from_file_location(pluginName, pluginFile)
+        module = importlib.util.module_from_spec(spec)
+        spec.loader.exec_module(module)
+        if not hasattr(module, "packageName"):
+            EricMessageBox.critical(
+                self,
+                self.tr("Plugin Uninstallation"),
+                self.tr(
+                    """<p>The plugin <b>{0}</b> has no 'packageName'"""
+                    """ attribute. Aborting...</p>""").format(pluginName))
+            return False
+        
+        package = getattr(module, "packageName", None)
+        if package is None:
+            package = "None"
+            packageDir = ""
+        else:
+            packageDir = os.path.join(pluginDirectory, package)
+        if (
+            hasattr(module, "prepareUninstall") and
+            not self.keepConfigurationCheckBox.isChecked()
+        ):
+            module.prepareUninstall()
+        internalPackages = []
+        if hasattr(module, "internalPackages"):
+            # it is a comma separated string
+            internalPackages = [p.strip() for p in
+                                module.internalPackages.split(",")]
+        del module
+        
+        # clean sys.modules
+        self.__pluginManager.removePluginFromSysModules(
+            pluginName, package, internalPackages)
+        
+        try:
+            if packageDir and os.path.exists(packageDir):
+                shutil.rmtree(packageDir)
+            
+            fnameo = "{0}o".format(pluginFile)
+            if os.path.exists(fnameo):
+                os.remove(fnameo)
+            
+            fnamec = "{0}c".format(pluginFile)
+            if os.path.exists(fnamec):
+                os.remove(fnamec)
+            
+            pluginDirCache = os.path.join(
+                os.path.dirname(pluginFile), "__pycache__")
+            if os.path.exists(pluginDirCache):
+                pluginFileName = os.path.splitext(
+                    os.path.basename(pluginFile))[0]
+                for fnameo in glob.glob(os.path.join(
+                        pluginDirCache, "{0}*.pyo".format(pluginFileName))):
+                    os.remove(fnameo)
+                for fnamec in glob.glob(os.path.join(
+                        pluginDirCache, "{0}*.pyc".format(pluginFileName))):
+                    os.remove(fnamec)
+            
+            os.remove(pluginFile)
+        except OSError as err:
+            EricMessageBox.critical(
+                self,
+                self.tr("Plugin Uninstallation"),
+                self.tr(
+                    """<p>The plugin package <b>{0}</b> could not be"""
+                    """ removed. Aborting...</p>"""
+                    """<p>Reason: {1}</p>""").format(packageDir, str(err)))
+            return False
+        
+        if not self.__external:
+            ui = ericApp().getObject("UserInterface")
+            ui.showNotification(
+                UI.PixmapCache.getPixmap("plugin48"),
+                self.tr("Plugin Uninstallation"),
+                self.tr(
+                    """<p>The plugin <b>{0}</b> was uninstalled"""
+                    """ successfully from {1}.</p>""")
+                .format(pluginName, pluginDirectory))
+            return True
+        
+        EricMessageBox.information(
+            self,
+            self.tr("Plugin Uninstallation"),
+            self.tr(
+                """<p>The plugin <b>{0}</b> was uninstalled successfully"""
+                """ from {1}.</p>""")
+            .format(pluginName, pluginDirectory))
+        return True
+
+
+class PluginUninstallDialog(QDialog):
+    """
+    Class for the dialog variant.
+    """
+    def __init__(self, pluginManager, parent=None):
+        """
+        Constructor
+        
+        @param pluginManager reference to the plugin manager object
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.setSizeGripEnabled(True)
+        
+        self.__layout = QVBoxLayout(self)
+        self.__layout.setContentsMargins(0, 0, 0, 0)
+        self.setLayout(self.__layout)
+        
+        self.cw = PluginUninstallWidget(pluginManager, self)
+        size = self.cw.size()
+        self.__layout.addWidget(self.cw)
+        self.resize(size)
+        self.setWindowTitle(self.cw.windowTitle())
+        
+        self.cw.buttonBox.accepted.connect(self.accept)
+        self.cw.buttonBox.rejected.connect(self.reject)
+
+
+class PluginUninstallWindow(EricMainWindow):
+    """
+    Main window class for the standalone dialog.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.cw = PluginUninstallWidget(None, self)
+        size = self.cw.size()
+        self.setCentralWidget(self.cw)
+        self.resize(size)
+        self.setWindowTitle(self.cw.windowTitle())
+        
+        self.setStyle(Preferences.getUI("Style"),
+                      Preferences.getUI("StyleSheet"))
+        
+        self.cw.buttonBox.accepted.connect(self.close)
+        self.cw.buttonBox.rejected.connect(self.close)

eric ide

mercurial