--- 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)