diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 059dd9bbef..d93ed87ba0 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -1,7 +1,9 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, Dict, Generator, List, Optional, TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING +from operator import attrgetter + from PyQt5.QtCore import pyqtSlot, QObject if TYPE_CHECKING: @@ -21,16 +23,16 @@ catalog = i18nCatalog("cura") class LocalPackageList(PackageList): - PACKAGE_SECTION_HEADER = { + PACKAGE_CATEGORIES = { "installed": { - "plugin": catalog.i18nc("@label:property", "Installed Plugins"), - "material": catalog.i18nc("@label:property", "Installed Materials") + "plugin": catalog.i18nc("@label", "Installed Plugins"), + "material": catalog.i18nc("@label", "Installed Materials") }, "bundled": { - "plugin": catalog.i18nc("@label:property", "Bundled Plugins"), - "material": catalog.i18nc("@label:property", "Bundled Materials") + "plugin": catalog.i18nc("@label", "Bundled Plugins"), + "material": catalog.i18nc("@label", "Bundled Materials") } } # The section headers to be used for the different package categories @@ -47,57 +49,24 @@ class LocalPackageList(PackageList): """ self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) - self._getLocalPackages() + + # Obtain and sort the local packages + self.setItems([{"package": p} for p in [self._makePackageModel(p) for p in self._manager.locally_installed_packages]]) + self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True) + self.checkForUpdates(self._manager.locally_installed_packages) + self.setIsLoading(False) self.setHasMore(False) # All packages should have been loaded at this time - def _getLocalPackages(self) -> None: - """ Obtain the local packages. - - The list is sorted per category as in the order of the PACKAGE_SECTION_HEADER dictionary, whereas the packages - for the sections are sorted alphabetically on the display name. These sorted sections are then added to the items - """ - package_info = list(self._allPackageInfo()) - self.checkForUpdates(package_info) - sorted_sections: List[Dict[str, PackageModel]] = [] - for section in self._getSections(): - packages = filter(lambda p: p.sectionTitle == section, package_info) - sorted_sections.extend([{"package": p} for p in sorted(packages, key = lambda p: p.displayName)]) - self.setItems(sorted_sections) - - def _getSections(self) -> Generator[str, None, None]: - """ Flatten and order the PACKAGE_SECTION_HEADER such that it can be used in obtaining the packages in the - correct order""" - for package_type in self.PACKAGE_SECTION_HEADER.values(): - for section in package_type.values(): - yield section - - def _allPackageInfo(self) -> Generator[PackageModel, None, None]: - """ A generator which returns a unordered list of all the PackageModels""" - - # Get all the installed packages, add a section_title depending on package_type and user installed - for packages in self._manager.getAllInstalledPackagesInfo().values(): - for package_info in packages: - yield self._makePackageModel(package_info) - - # Get all to be removed package_info's. These packages are still used in the current session so the user might - # still want to interact with these. - for package_data in self._manager.getPackagesToRemove().values(): - yield self._makePackageModel(package_data["package_info"]) - - # Get all to be installed package_info's. Since the user might want to interact with these - for package_data in self._manager.getPackagesToInstall().values(): - yield self._makePackageModel(package_data["package_info"]) - def _makePackageModel(self, package_info: Dict[str, Any]) -> PackageModel: """ Create a PackageModel from the package_info and determine its section_title""" bundled_or_installed = "installed" if self._manager.isUserInstalledPackage(package_info["package_id"]) else "bundled" package_type = package_info["package_type"] - section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] + section_title = self.PACKAGE_CATEGORIES[bundled_or_installed][package_type] return PackageModel(package_info, section_title = section_title, parent = self) - def checkForUpdates(self, packages: List[PackageModel]): - installed_packages = "installed_packages=".join([f"{package.packageId}:{package.packageVersion}&" for package in packages]) + def checkForUpdates(self, packages: List[Dict[str, Any]]): + installed_packages = "installed_packages=".join([f"{package['package_id']}:{package['package_version']}&" for package in packages]) request_url = f"{Marketplace.PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" self._ongoing_request = HttpRequestManager.getInstance().get( @@ -105,7 +74,6 @@ class LocalPackageList(PackageList): scope = self._scope, callback = self._parseResponse ) - return [] def _parseResponse(self, reply: "QNetworkReply") -> None: """ @@ -115,10 +83,14 @@ class LocalPackageList(PackageList): """ response_data = HttpRequestManager.readJSON(reply) if "data" not in response_data: - Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") - self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) + Logger.error( + f"Could not interpret the server's response. Missing 'data' from response data. Keys in response: {response_data.keys()}") + return + if len(response_data["data"]) == 0: return for package_data in response_data["data"]: index = self.find("package", package_data["package_id"]) self.getItem(index)["package"].canUpdate = True + + self.sort(attrgetter("sectionTitle", "canUpdate", "displayName"), key = "package", reverse = True)