From 6514fdf9f294b7822370e57ba446170710c7afc3 Mon Sep 17 00:00:00 2001 From: Jelle Spijker Date: Tue, 30 Nov 2021 15:40:18 +0100 Subject: [PATCH] Check the server if a package can be updated The server is queried using the local packages_id and version to determine if they can be updated. The Manage button state is set accordingly. This is done in an async way to keep the UI responsive A.t.m I'm not sure if I might need to move this logic out, since we also need to make this query when check periodically for updates. when the list is not shown. But that would also entail creating the installed packages list before the Manage Packages tab is ever created in the Marketplace. Contributes to: CURA-8587 --- plugins/Marketplace/LocalPackageList.py | 33 +++++++++++++++++++++++- plugins/Marketplace/Marketplace.py | 1 + plugins/Marketplace/PackageList.py | 8 ++++++ plugins/Marketplace/RemotePackageList.py | 8 +----- 4 files changed, 42 insertions(+), 8 deletions(-) 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 = ""