Changed the upgrade actions to first check for running applications of packages to be upgraded because this situation causes issues during the upgrade.

Sat, 27 Jul 2024 19:39:32 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 27 Jul 2024 19:39:32 +0200
changeset 31
17e37d4ebe42
parent 30
1b6adcd21c6c
child 32
b7a3ae7519ba

Changed the upgrade actions to first check for running applications of packages to be upgraded because this situation causes issues during the upgrade.

PipxInterface/PipxWidget.py file | annotate | diff | comparison | revisions
PluginPipxInterface.py file | annotate | diff | comparison | revisions
--- a/PipxInterface/PipxWidget.py	Sat Jul 27 17:39:00 2024 +0200
+++ b/PipxInterface/PipxWidget.py	Sat Jul 27 19:39:32 2024 +0200
@@ -9,6 +9,8 @@
 
 import os
 
+import psutil
+
 from PyQt6.QtCore import Qt, pyqtSlot
 from PyQt6.QtWidgets import QDialog, QMenu, QTreeWidgetItem, QWidget
 
@@ -339,6 +341,7 @@
                 latestVersion = self.__pipx.checkPackageOutdated(package)
                 if latestVersion is not None:
                     self.__markPackageOutdated(itm, latestVersion)
+                # TODO: check outdated dependencies (configurable)
         self.__resizePackagesColumns()
 
     @pyqtSlot()
@@ -346,17 +349,42 @@
         """
         Private slot to upgrade the selected package.
         """
-        package = self.__selectedPackages()[0]
-        self.__pipx.upgradePackage(package)
-        self.on_refreshButton_clicked()
+        packageItem = self.__selectedPackageItems()[0]
+        runningApps = self.__getRunningApps(self.__packageApps(packageItem))
+        if runningApps:
+            EricMessageBox.warning(
+                self,
+                self.tr("Upgrade Selected Package"),
+                self.tr(
+                    "<p>The selected package cannot be upgraded because some of its"
+                    " apps are running.</p><ul><li>{0}</li></ul><p>Stop these apps"
+                    " and try again.</p>"
+                ).format("</li><li>".join(runningApps)),
+            )
+        else:
+            package = packageItem.text(PipxWidget.PackageColumn)
+            self.__pipx.upgradePackage(package)
+            self.on_refreshButton_clicked()
 
     @pyqtSlot()
     def __upgradeAllPackages(self):
         """
         Private slot to upgrade all packages.
         """
-        self.__pipx.upgradeAllPackages()
-        self.on_refreshButton_clicked()
+        runningApps = self.__getAllRunningApps()
+        if runningApps:
+            EricMessageBox.warning(
+                self,
+                self.tr("Upgrade All Packages"),
+                self.tr(
+                    "<p>The packages cannot be upgraded because some of their apps are"
+                    " running.</p><ul><li>{0}</li></ul><p>Stop these apps and try"
+                    " again.</p>"
+                ).format("</li><li>".join(runningApps)),
+            )
+        else:
+            self.__pipx.upgradeAllPackages()
+            self.on_refreshButton_clicked()
 
     @pyqtSlot()
     def __upgradeSharedLibs(self):
@@ -459,6 +487,7 @@
                 version, latestVersion
             ),
         )
+        item.setIcon(PipxWidget.VersionColumn, EricPixmapCache.getIcon("upgrade"))
 
     def __populatePackages(self):
         """
@@ -548,7 +577,7 @@
         Private method to determine the list of selected packages.
 
         @return list of selected packages
-        @rtype list of QTreeWidgetItem
+        @rtype list of str
         """
         packages = []
 
@@ -558,3 +587,67 @@
                 packages.append(itm.text(PipxWidget.PackageColumn))
 
         return packages
+
+    def __selectedPackageItems(self):
+        """
+        Private method to determine the list of selected package items.
+
+        @return list of selected package items
+        @rtype list of QTreeWidgetItem
+        """
+        packageItems = []
+
+        for row in range(self.packagesList.topLevelItemCount()):
+            itm = self.packagesList.topLevelItem(row)
+            if itm.isSelected():
+                packageItems.append(itm)
+
+        return packageItems
+
+    def __packageApps(self, packageItem):
+        """
+        Private method to determine the apps belonging to a package item.
+
+        @param packageItem reference to the package item
+        @type QTreeWidgetItem
+        @return list of app names
+        @rtype list of str
+        """
+        apps = []
+        
+        for row in range(packageItem.childCount()):
+            apps.append(packageItem.child(row).text(0))
+        
+        return apps
+
+    def __getRunningApps(self, apps):
+        """
+        Private method to determine, which app of the given list of apps is running.
+
+        @param apps list of apps to check
+        @type str
+        @return set of running apps
+        @rtype set of str
+        """
+        runningApps = set()
+        
+        for proc in psutil.process_iter(["name"]):
+            if proc.info["name"] in apps:
+                runningApps.add(proc.info["name"])
+
+        return runningApps
+
+    def __getAllRunningApps(self):
+        """
+        Private method to determine all running pipx managed apps.
+        
+        @return set of running apps
+        @rtype set of str
+        """
+        allApps = []
+
+        for topRow in range(self.packagesList.topLevelItemCount()):
+            topItm = self.packagesList.topLevelItem(topRow)
+            allApps.extend(self.__packageApps(topItm))
+
+        return self.__getRunningApps(allApps)
--- a/PluginPipxInterface.py	Sat Jul 27 17:39:00 2024 +0200
+++ b/PluginPipxInterface.py	Sat Jul 27 19:39:32 2024 +0200
@@ -7,6 +7,7 @@
 Module implementing the pipx Interface plug-in.
 """
 
+import importlib.util
 import os
 import sysconfig
 
@@ -148,6 +149,8 @@
         self.__ui = ui
         self.__initialize()
 
+        # TODO: add option to check outdated dependencies when checking for
+        #       outdated status
         self.__defaults = {
             "RecentAppWorkdirs": [],
             "MaxRecentAppWorkdirs": 20,
@@ -303,10 +306,14 @@
     @param pipInstall function to be called with a list of package names.
     @type function
     """
-    try:
-        import pipx  # __IGNORE_WARNING__
-    except ImportError:
-        pipInstall(["pipx>=1.5.0"])
+    packagesToInstall = []
+    if importlib.util.find_spec("pipx") is None:
+        packagesToInstall.append("pipx>=1.5.0")
+    if importlib.util.find_spec("psutil") is None:
+        packagesToInstall.append("psutil")
+
+    if packagesToInstall:
+        pipInstall(packagesToInstall)
 
 
 #

eric ide

mercurial