Expose Account.SyncState as an Enum to QML

Provides a single source of truth

CURA-7290
This commit is contained in:
Nino van Hooff 2020-05-08 11:09:48 +02:00
parent 8937c63219
commit 1ae050bbc5
5 changed files with 38 additions and 35 deletions

View file

@ -1,10 +1,9 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from datetime import datetime from datetime import datetime
from enum import Enum
from typing import Optional, Dict, TYPE_CHECKING from typing import Optional, Dict, TYPE_CHECKING
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS
from UM.Message import Message from UM.Message import Message
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -18,6 +17,13 @@ if TYPE_CHECKING:
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
class SyncState(QObject):
"""QML: Cura.AccountSyncState"""
SYNCING = 0
SUCCESS = 1
ERROR = 2
## The account API provides a version-proof bridge to use Ultimaker Accounts ## The account API provides a version-proof bridge to use Ultimaker Accounts
# #
# Usage: # Usage:
@ -30,13 +36,7 @@ i18n_catalog = i18nCatalog("cura")
class Account(QObject): class Account(QObject):
# The interval with which the remote clusters are checked # The interval with which the remote clusters are checked
SYNC_INTERVAL = 30.0 # seconds SYNC_INTERVAL = 30.0 # seconds
Q_ENUMS(SyncState)
class SyncState(Enum):
"""Caution: values used in qml (eg. SyncState.qml)"""
SYNCING = "syncing",
SUCCESS = "success",
ERROR = "error"
# Signal emitted when user logged in or out. # Signal emitted when user logged in or out.
loginStateChanged = pyqtSignal(bool) loginStateChanged = pyqtSignal(bool)
@ -44,7 +44,7 @@ class Account(QObject):
cloudPrintersDetectedChanged = pyqtSignal(bool) cloudPrintersDetectedChanged = pyqtSignal(bool)
syncRequested = pyqtSignal() syncRequested = pyqtSignal()
lastSyncDateTimeChanged = pyqtSignal() lastSyncDateTimeChanged = pyqtSignal()
syncStateChanged = pyqtSignal(str) syncStateChanged = pyqtSignal(int) # because it's an int Enum
def __init__(self, application: "CuraApplication", parent = None) -> None: def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -53,7 +53,7 @@ class Account(QObject):
self._error_message = None # type: Optional[Message] self._error_message = None # type: Optional[Message]
self._logged_in = False self._logged_in = False
self._sync_state = self.SyncState.SUCCESS self._sync_state = SyncState.SUCCESS
self._last_sync_str = "-" self._last_sync_str = "-"
self._callback_port = 32118 self._callback_port = 32118
@ -81,7 +81,7 @@ class Account(QObject):
self._update_timer.setSingleShot(True) self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self.syncRequested) self._update_timer.timeout.connect(self.syncRequested)
self._sync_services = {} # type: Dict[str, Account.SyncState] self._sync_services = {} # type: Dict[str, SyncState]
"""contains entries "service_name" : SyncState""" """contains entries "service_name" : SyncState"""
def initialize(self) -> None: def initialize(self) -> None:
@ -94,30 +94,30 @@ class Account(QObject):
def setSyncState(self, service_name: str, state: SyncState) -> None: def setSyncState(self, service_name: str, state: SyncState) -> None:
""" Can be used to register sync services and update account sync states """ Can be used to register sync services and update account sync states
Example: `setSyncState("PluginSyncService", Account.SyncState.SYNCING)` Example: `setSyncState("PluginSyncService", SyncState.SYNCING)`
:param service_name: A unique name for your service, such as `plugins` or `backups` :param service_name: A unique name for your service, such as `plugins` or `backups`
:param state: One of Account.SyncState :param state: One of SyncState
""" """
prev_state = self._sync_state prev_state = self._sync_state
self._sync_services[service_name] = state self._sync_services[service_name] = state
if any(val == self.SyncState.SYNCING for val in self._sync_services.values()): if any(val == SyncState.SYNCING for val in self._sync_services.values()):
self._sync_state = self.SyncState.SYNCING self._sync_state = SyncState.SYNCING
elif any(val == self.SyncState.ERROR for val in self._sync_services.values()): elif any(val == SyncState.ERROR for val in self._sync_services.values()):
self._sync_state = self.SyncState.ERROR self._sync_state = SyncState.ERROR
else: else:
self._sync_state = self.SyncState.SUCCESS self._sync_state = SyncState.SUCCESS
if self._sync_state != prev_state: if self._sync_state != prev_state:
self.syncStateChanged.emit(self._sync_state.value[0]) self.syncStateChanged.emit(self._sync_state)
if self._sync_state == self.SyncState.SUCCESS: if self._sync_state == SyncState.SUCCESS:
self._last_sync_str = datetime.now().strftime("%d/%m/%Y %H:%M") self._last_sync_str = datetime.now().strftime("%d/%m/%Y %H:%M")
self.lastSyncDateTimeChanged.emit() self.lastSyncDateTimeChanged.emit()
if self._sync_state != self.SyncState.SYNCING: if self._sync_state != SyncState.SYNCING:
# schedule new auto update after syncing completed (for whatever reason) # schedule new auto update after syncing completed (for whatever reason)
if not self._update_timer.isActive(): if not self._update_timer.isActive():
self._update_timer.start() self._update_timer.start()

View file

@ -48,6 +48,7 @@ from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura import ApplicationMetadata from cura import ApplicationMetadata
from cura.API import CuraAPI from cura.API import CuraAPI
from cura.API.Account import Account
from cura.Arranging.Arrange import Arrange from cura.Arranging.Arrange import Arrange
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
@ -1106,6 +1107,7 @@ class CuraApplication(QtApplication):
from cura.API import CuraAPI from cura.API import CuraAPI
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI) qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)
qmlRegisterUncreatableType(Account, "Cura", 1, 0, "AccountSyncState", "Could not create AccountSyncState")
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))

View file

@ -13,7 +13,7 @@ from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Signal import Signal from UM.Signal import Signal
from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
from cura.API import Account from cura.API.Account import SyncState
from cura.CuraApplication import CuraApplication, ApplicationMetadata from cura.CuraApplication import CuraApplication, ApplicationMetadata
from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
from .SubscribedPackagesModel import SubscribedPackagesModel from .SubscribedPackagesModel import SubscribedPackagesModel
@ -62,7 +62,7 @@ class CloudPackageChecker(QObject):
self._hideSyncMessage() self._hideSyncMessage()
def _getUserSubscribedPackages(self) -> None: def _getUserSubscribedPackages(self) -> None:
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.SYNCING) self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SYNCING)
Logger.debug("Requesting subscribed packages metadata from server.") Logger.debug("Requesting subscribed packages metadata from server.")
url = CloudApiModel.api_url_user_packages url = CloudApiModel.api_url_user_packages
self._application.getHttpRequestManager().get(url, self._application.getHttpRequestManager().get(url,
@ -75,7 +75,7 @@ class CloudPackageChecker(QObject):
Logger.log("w", Logger.log("w",
"Requesting user packages failed, response code %s while trying to connect to %s", "Requesting user packages failed, response code %s while trying to connect to %s",
reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url()) reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, "error") self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
return return
try: try:
@ -84,13 +84,13 @@ class CloudPackageChecker(QObject):
if "errors" in json_data: if "errors" in json_data:
for error in json_data["errors"]: for error in json_data["errors"]:
Logger.log("e", "%s", error["title"]) Logger.log("e", "%s", error["title"])
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, "error") self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
return return
self._handleCompatibilityData(json_data["data"]) self._handleCompatibilityData(json_data["data"])
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace") Logger.log("w", "Received invalid JSON for user subscribed packages from the Web Marketplace")
self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, "success") self._application.getCuraAPI().account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SUCCESS)
def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None: def _handleCompatibilityData(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
user_subscribed_packages = [plugin["package_id"] for plugin in subscribed_packages_payload] user_subscribed_packages = [plugin["package_id"] for plugin in subscribed_packages_payload]

View file

@ -10,6 +10,7 @@ from UM.Logger import Logger # To log errors talking to the API.
from UM.Message import Message from UM.Message import Message
from UM.Signal import Signal from UM.Signal import Signal
from cura.API import Account from cura.API import Account
from cura.API.Account import SyncState
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
@ -89,7 +90,7 @@ class CloudOutputDeviceManager:
Logger.info("Syncing cloud printer clusters") Logger.info("Syncing cloud printer clusters")
self._syncing = True self._syncing = True
self._account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.SYNCING) self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SYNCING)
self._api.getClusters(self._onGetRemoteClustersFinished, self._onGetRemoteClusterFailed) self._api.getClusters(self._onGetRemoteClustersFinished, self._onGetRemoteClusterFailed)
def _onGetRemoteClustersFinished(self, clusters: List[CloudClusterResponse]) -> None: def _onGetRemoteClustersFinished(self, clusters: List[CloudClusterResponse]) -> None:
@ -119,11 +120,11 @@ class CloudOutputDeviceManager:
self._connectToActiveMachine() self._connectToActiveMachine()
self._syncing = False self._syncing = False
self._account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.SUCCESS) self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SUCCESS)
def _onGetRemoteClusterFailed(self): def _onGetRemoteClusterFailed(self):
self._syncing = False self._syncing = False
self._account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.ERROR) self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.ERROR)
def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None: def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None:
"""**Synchronously** create machines for discovered devices """**Synchronously** create machines for discovered devices

View file

@ -82,20 +82,20 @@ Row // sync state icon + message
signal syncStateChanged(string newState) signal syncStateChanged(string newState)
onSyncStateChanged: { onSyncStateChanged: {
if(newState == "syncing"){ if(newState == Cura.AccountSyncState.SYNCING){
syncRow.iconSource = UM.Theme.getIcon("update") syncRow.iconSource = UM.Theme.getIcon("update")
syncRow.labelText = catalog.i18nc("@label", "Checking...") syncRow.labelText = catalog.i18nc("@label", "Checking...")
} else if (newState == "success") { } else if (newState == Cura.AccountSyncState.SUCCESS) {
syncRow.iconSource = UM.Theme.getIcon("checked") syncRow.iconSource = UM.Theme.getIcon("checked")
syncRow.labelText = catalog.i18nc("@label", "You are up to date") syncRow.labelText = catalog.i18nc("@label", "You are up to date")
} else if (newState == "error") { } else if (newState == Cura.AccountSyncState.ERROR) {
syncRow.iconSource = UM.Theme.getIcon("warning-light") syncRow.iconSource = UM.Theme.getIcon("warning_light")
syncRow.labelText = catalog.i18nc("@label", "Something went wrong...") syncRow.labelText = catalog.i18nc("@label", "Something went wrong...")
} else { } else {
print("Error: unexpected sync state: " + newState) print("Error: unexpected sync state: " + newState)
} }
if(newState == "syncing"){ if(newState == Cura.AccountSyncState.SYNCING){
syncRow.animateIconRotation = true syncRow.animateIconRotation = true
syncRow.syncButtonVisible = false syncRow.syncButtonVisible = false
} else { } else {