eric7/PluginManager/PluginUninstallDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
diff -r 4e8b98454baa -r 800c432b34c8 eric7/PluginManager/PluginUninstallDialog.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/PluginManager/PluginUninstallDialog.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2007 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog for plugin deinstallation.
+"""
+
+import sys
+import os
+import importlib
+import shutil
+import glob
+
+from PyQt5.QtCore import pyqtSlot, pyqtSignal
+from PyQt5.QtWidgets import QWidget, QDialog, QDialogButtonBox, QVBoxLayout
+
+from E5Gui import E5MessageBox
+from E5Gui.E5MainWindow import E5MainWindow
+from E5Gui.E5Application import e5App
+
+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)
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    @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.pluginNameCombo.clear()
+        for pluginName in pluginNames:
+            fname = "{0}.py".format(os.path.join(pluginDirectory, pluginName))
+            self.pluginNameCombo.addItem(pluginName, fname)
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+            self.pluginNameCombo.currentText() != "")
+    
+    @pyqtSlot()
+    def on_buttonBox_accepted(self):
+        """
+        Private slot to handle the accepted signal of the button box.
+        """
+        if self.__uninstallPlugin():
+            self.accepted.emit()
+    
+    def __uninstallPlugin(self):
+        """
+        Private slot to uninstall the selected plugin.
+        
+        @return flag indicating success (boolean)
+        """
+        pluginDirectory = self.pluginDirectoryCombo.itemData(
+            self.pluginDirectoryCombo.currentIndex())
+        pluginName = self.pluginNameCombo.currentText()
+        pluginFile = self.pluginNameCombo.itemData(
+            self.pluginNameCombo.currentIndex())
+        
+        if not self.__pluginManager.unloadPlugin(pluginName):
+            E5MessageBox.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"):
+            E5MessageBox.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:
+            E5MessageBox.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 = e5App().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
+        
+        E5MessageBox.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(E5MainWindow):
+    """
+    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