diff --git a/plugins/Marketplace/LocalPackageList.py b/plugins/Marketplace/LocalPackageList.py index 6acbaa8500..059dd9bbef 100644 --- a/plugins/Marketplace/LocalPackageList.py +++ b/plugins/Marketplace/LocalPackageList.py @@ -8,11 +8,14 @@ if TYPE_CHECKING: from PyQt5.QtCore import QObject from UM.i18n import i18nCatalog +from UM.TaskManagement.HttpRequestManager import HttpRequestManager +from UM.Logger import Logger from cura.CuraApplication import CuraApplication from .PackageList import PackageList -from .PackageModel import PackageModel # The contents of this list. +from .PackageModel import PackageModel +from . import Marketplace catalog = i18nCatalog("cura") @@ -55,6 +58,7 @@ class LocalPackageList(PackageList): 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) @@ -91,3 +95,30 @@ class LocalPackageList(PackageList): package_type = package_info["package_type"] section_title = self.PACKAGE_SECTION_HEADER[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]) + request_url = f"{Marketplace.PACKAGE_UPDATES_URL}?installed_packages={installed_packages[:-1]}" + + self._ongoing_request = HttpRequestManager.getInstance().get( + request_url, + scope = self._scope, + callback = self._parseResponse + ) + return [] + + def _parseResponse(self, reply: "QNetworkReply") -> None: + """ + Parse the response from the package list API request which can update. + + :param reply: A reply containing information about a number of packages. + """ + 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.")) + return + + for package_data in response_data["data"]: + index = self.find("package", package_data["package_id"]) + self.getItem(index)["package"].canUpdate = True diff --git a/plugins/Marketplace/Marketplace.py b/plugins/Marketplace/Marketplace.py index 18d80d6e68..1b98503969 100644 --- a/plugins/Marketplace/Marketplace.py +++ b/plugins/Marketplace/Marketplace.py @@ -21,6 +21,7 @@ if TYPE_CHECKING: ROOT_URL = f"{UltimakerCloudConstants.CuraCloudAPIRoot}/cura-packages/v{UltimakerCloudConstants.CuraCloudAPIVersion}/cura/v{CuraSDKVersion}" # Root of all Marketplace API requests. PACKAGES_URL = f"{ROOT_URL}/packages" # URL to use for requesting the list of packages. +PACKAGE_UPDATES_URL = f"{PACKAGES_URL}/package-updates" # URL to use for requesting the list of packages that can be updated. class Marketplace(Extension): diff --git a/plugins/Marketplace/PackageList.py b/plugins/Marketplace/PackageList.py index 8171d168f2..d3da8c826a 100644 --- a/plugins/Marketplace/PackageList.py +++ b/plugins/Marketplace/PackageList.py @@ -6,6 +6,11 @@ from typing import Optional, TYPE_CHECKING from UM.i18n import i18nCatalog from UM.Qt.ListModel import ListModel +from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. +from UM.TaskManagement.HttpRequestManager import HttpRequestData # To request the package list from the API. + +from cura.CuraApplication import CuraApplication +from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. if TYPE_CHECKING: from PyQt5.QtCore import QObject @@ -27,6 +32,9 @@ class PackageList(ListModel): self._has_more = False self._has_footer = True + self._ongoing_request: Optional[HttpRequestData] = None + self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) + @pyqtSlot() def updatePackages(self) -> None: """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" diff --git a/plugins/Marketplace/RemotePackageList.py b/plugins/Marketplace/RemotePackageList.py index e7df498fbf..4fedde9ff0 100644 --- a/plugins/Marketplace/RemotePackageList.py +++ b/plugins/Marketplace/RemotePackageList.py @@ -5,12 +5,9 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot from PyQt5.QtNetwork import QNetworkReply from typing import Optional, TYPE_CHECKING -from cura.CuraApplication import CuraApplication -from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope # To make requests to the Ultimaker API with correct authorization. from UM.i18n import i18nCatalog from UM.Logger import Logger -from UM.TaskManagement.HttpRequestManager import HttpRequestManager, HttpRequestData # To request the package list from the API. -from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API. +from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API. from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports. from .PackageList import PackageList @@ -28,9 +25,6 @@ class RemotePackageList(PackageList): def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) - self._ongoing_request: Optional[HttpRequestData] = None - self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance())) - self._package_type_filter = "" self._requested_search_string = "" self._current_search_string = ""