diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index 32b4da4823..f0df441f1d 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -20,6 +20,8 @@ UM.Dialog{ maximumHeight: minimumHeight margin: 0 + property string actionButtonText: subscribedPackagesModel.hasIncompatiblePackages && !subscribedPackagesModel.hasCompatiblePackages ? catalog.i18nc("@button", "Dismiss") : catalog.i18nc("@button", "Next") + Rectangle { id: root @@ -125,26 +127,6 @@ UM.Dialog{ color: UM.Theme.getColor("text") elide: Text.ElideRight } - UM.TooltipArea - { - width: childrenRect.width; - height: childrenRect.height; - text: catalog.i18nc("@info:tooltip", "Dismisses the package and won't be shown in this dialog anymore") - anchors.right: parent.right - anchors.verticalCenter: packageIcon.verticalCenter - Label - { - text: "(Dismiss)" - font: UM.Theme.getFont("small") - color: UM.Theme.getColor("text") - MouseArea - { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: handler.dismissIncompatiblePackage(subscribedPackagesModel, model.package_id) - } - } - } } } } @@ -158,7 +140,7 @@ UM.Dialog{ anchors.bottom: parent.bottom anchors.right: parent.right anchors.margins: UM.Theme.getSize("default_margin").height - text: catalog.i18nc("@button", "Next") + text: actionButtonText onClicked: accept() leftPadding: UM.Theme.getSize("dialog_primary_button_padding").width rightPadding: UM.Theme.getSize("dialog_primary_button_padding").width diff --git a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py index 78d13f34fe..a14341a4b7 100644 --- a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py +++ b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py @@ -8,11 +8,12 @@ from UM import i18nCatalog from UM.Logger import Logger from UM.Message import Message from UM.Signal import Signal -from cura.CuraApplication import CuraApplication +from cura.CuraApplication import CuraApplication, ApplicationMetadata from ..CloudApiModel import CloudApiModel from .SubscribedPackagesModel import SubscribedPackagesModel from ..UltimakerCloudScope import UltimakerCloudScope +from typing import List, Dict, Any class CloudPackageChecker(QObject): def __init__(self, application: CuraApplication) -> None: @@ -25,12 +26,12 @@ class CloudPackageChecker(QObject): self._application.initializationFinished.connect(self._onAppInitialized) self._i18n_catalog = i18nCatalog("cura") + self._sdk_version = ApplicationMetadata.CuraSDKVersion # This is a plugin, so most of the components required are not ready when # this is initialized. Therefore, we wait until the application is ready. def _onAppInitialized(self) -> None: self._package_manager = self._application.getPackageManager() - # initial check self._fetchUserSubscribedPackages() # check again whenever the login state changes @@ -38,25 +39,51 @@ class CloudPackageChecker(QObject): def _fetchUserSubscribedPackages(self) -> None: if self._application.getCuraAPI().account.isLoggedIn: - self._getUserPackages() + self._getUserSubscribedPackages() - def _handleCompatibilityData(self, json_data) -> None: - user_subscribed_packages = [plugin["package_id"] for plugin in json_data] + def _getUserSubscribedPackages(self) -> None: + Logger.debug("Requesting subscribed packages metadata from server.") + url = CloudApiModel.api_url_user_packages + self._application.getHttpRequestManager().get(url, + callback = self._onUserPackagesRequestFinished, + error_callback = self._onUserPackagesRequestFinished, + scope = self._scope) + + def _onUserPackagesRequestFinished(self, reply: "QNetworkReply", error: Optional["QNetworkReply.NetworkError"] = None) -> None: + if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: + Logger.log("w", + "Requesting user packages failed, response code %s while trying to connect to %s", + reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url()) + return + + try: + json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) + # Check for errors: + if "errors" in json_data: + for error in json_data["errors"]: + Logger.log("e", "%s", error["title"]) + return + self._handleCompatibilityData(json_data["data"]) + except json.decoder.JSONDecodeError: + Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace") + + def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None: + user_subscribed_packages = [plugin["package_id"] for plugin in subscribed_packages_payload] user_installed_packages = self._package_manager.getUserInstalledPackages() + + # We need to re-evaluate the dismissed packages + # (i.e. some package might got updated to the correct SDK version in the meantime, + # hence remove them from the Dismissed Incompatible list) + self._package_manager.reEvaluateDismissedPackages(subscribed_packages_payload, self._sdk_version) user_dismissed_packages = self._package_manager.getDismissedPackages() if user_dismissed_packages: user_installed_packages += user_dismissed_packages - # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace + + # We check if there are packages installed in Web Marketplace but not in Cura marketplace package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages)) - - self._model.setMetadata(json_data) - self._model.addDiscrepancies(package_discrepancy) - self._model.initialize() - - if not self._model.hasCompatiblePackages: - return None - if package_discrepancy: + self._model.addDiscrepancies(package_discrepancy) + self._model.initialize(subscribed_packages_payload) self._handlePackageDiscrepancies() def _handlePackageDiscrepancies(self) -> None: @@ -76,35 +103,4 @@ class CloudPackageChecker(QObject): def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None: sync_message.hide() - self.discrepancies.emit(self._model) - - def _getUserPackages(self) -> None: - Logger.log("d", "Requesting subscribed packages metadata from server.") - url = CloudApiModel.api_url_user_packages - - self._application.getHttpRequestManager().get(url, - callback = self._onUserPackagesRequestFinished, - error_callback = self._onUserPackagesRequestFinished, - scope = self._scope) - - def _onUserPackagesRequestFinished(self, - reply: "QNetworkReply", - error: Optional["QNetworkReply.NetworkError"] = None) -> None: - if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: - Logger.log("w", - "Requesting user packages failed, response code %s while trying to connect to %s", - reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url()) - return - - try: - json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) - - # Check for errors: - if "errors" in json_data: - for error in json_data["errors"]: - Logger.log("e", "%s", error["title"]) - return - - self._handleCompatibilityData(json_data["data"]) - except json.decoder.JSONDecodeError: - Logger.log("w", "Received invalid JSON for user packages") + self.discrepancies.emit(self._model) \ No newline at end of file diff --git a/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py b/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py index f6b5622aad..ddf1a39e78 100644 --- a/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py +++ b/plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py @@ -28,13 +28,12 @@ class DiscrepanciesPresenter(QObject): assert self._dialog self._dialog.accepted.connect(lambda: self._onConfirmClicked(model)) - @pyqtSlot("QVariant", str) - def dismissIncompatiblePackage(self, model: SubscribedPackagesModel, package_id: str) -> None: - model.dismissPackage(package_id) # update the model to update the view - self._package_manager.dismissPackage(package_id) # adds this package_id as dismissed in the user config file - def _onConfirmClicked(self, model: SubscribedPackagesModel) -> None: + # If there are incompatible packages - automatically dismiss them + if model.getIncompatiblePackages(): + self._package_manager.dismissAllIncompatiblePackages(model.getIncompatiblePackages()) # For now, all compatible packages presented to the user should be installed. # Later, we might remove items for which the user unselected the package - model.setItems(model.getCompatiblePackages()) - self.packageMutations.emit(model) + if model.getCompatiblePackages(): + model.setItems(model.getCompatiblePackages()) + self.packageMutations.emit(model) diff --git a/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py b/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py index 4a0f559748..614d397d91 100644 --- a/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py +++ b/plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py @@ -37,27 +37,18 @@ class SubscribedPackagesModel(ListModel): return True return False - # Sets the "is_compatible" to True for the given package, in memory - - @pyqtSlot() - def dismissPackage(self, package_id: str) -> None: - package = self.find(key="package_id", value=package_id) - if package != -1: - self.setProperty(package, property="is_dismissed", value=True) - Logger.debug("Package {} has been dismissed".format(package_id)) - - def setMetadata(self, data: List[Dict[str, List[Any]]]) -> None: - self._metadata = data - def addDiscrepancies(self, discrepancy: List[str]) -> None: self._discrepancies = discrepancy - def getCompatiblePackages(self): - return [x for x in self._items if x["is_compatible"]] + def getCompatiblePackages(self) -> List[Dict[str, Any]]: + return [package for package in self._items if package["is_compatible"]] - def initialize(self) -> None: + def getIncompatiblePackages(self) -> List[str]: + return [package["package_id"] for package in self._items if not package["is_compatible"]] + + def initialize(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None: self._items.clear() - for item in self._metadata: + for item in subscribed_packages_payload: if item["package_id"] not in self._discrepancies: continue package = {