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
This commit is contained in:
Jelle Spijker 2021-11-30 15:40:18 +01:00
parent eb156f114c
commit 6514fdf9f2
No known key found for this signature in database
GPG key ID: 6662DC033BE6B99A
4 changed files with 42 additions and 8 deletions

View file

@ -8,11 +8,14 @@ if TYPE_CHECKING:
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
from UM.Logger import Logger
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from .PackageList import PackageList from .PackageList import PackageList
from .PackageModel import PackageModel # The contents of this list. from .PackageModel import PackageModel
from . import Marketplace
catalog = i18nCatalog("cura") 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 for the sections are sorted alphabetically on the display name. These sorted sections are then added to the items
""" """
package_info = list(self._allPackageInfo()) package_info = list(self._allPackageInfo())
self.checkForUpdates(package_info)
sorted_sections: List[Dict[str, PackageModel]] = [] sorted_sections: List[Dict[str, PackageModel]] = []
for section in self._getSections(): for section in self._getSections():
packages = filter(lambda p: p.sectionTitle == section, package_info) packages = filter(lambda p: p.sectionTitle == section, package_info)
@ -91,3 +95,30 @@ class LocalPackageList(PackageList):
package_type = package_info["package_type"] package_type = package_info["package_type"]
section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type] section_title = self.PACKAGE_SECTION_HEADER[bundled_or_installed][package_type]
return PackageModel(package_info, section_title = section_title, parent = self) 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

View file

@ -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. 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. 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): class Marketplace(Extension):

View file

@ -6,6 +6,11 @@ from typing import Optional, TYPE_CHECKING
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Qt.ListModel import ListModel 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: if TYPE_CHECKING:
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject
@ -27,6 +32,9 @@ class PackageList(ListModel):
self._has_more = False self._has_more = False
self._has_footer = True self._has_footer = True
self._ongoing_request: Optional[HttpRequestData] = None
self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance()))
@pyqtSlot() @pyqtSlot()
def updatePackages(self) -> None: def updatePackages(self) -> None:
""" A Qt slot which will update the List from a source. Actual implementation should be done in the child class""" """ A Qt slot which will update the List from a source. Actual implementation should be done in the child class"""

View file

@ -5,12 +5,9 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QNetworkReply from PyQt5.QtNetwork import QNetworkReply
from typing import Optional, TYPE_CHECKING 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.i18n import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.TaskManagement.HttpRequestManager import HttpRequestManager, HttpRequestData # To request the package list from the API. from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To request the package list from the API.
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope # To request JSON responses from the API.
from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports. from . import Marketplace # To get the list of packages. Imported this way to prevent circular imports.
from .PackageList import PackageList from .PackageList import PackageList
@ -28,9 +25,6 @@ class RemotePackageList(PackageList):
def __init__(self, parent: Optional["QObject"] = None) -> None: def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent) super().__init__(parent)
self._ongoing_request: Optional[HttpRequestData] = None
self._scope = JsonDecoratorScope(UltimakerCloudScope(CuraApplication.getInstance()))
self._package_type_filter = "" self._package_type_filter = ""
self._requested_search_string = "" self._requested_search_string = ""
self._current_search_string = "" self._current_search_string = ""