Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/cura/API
diff options
context:
space:
mode:
authorNino van Hooff <ninovanhooff@gmail.com>2020-05-15 14:38:37 +0300
committerNino van Hooff <ninovanhooff@gmail.com>2020-05-15 14:38:37 +0300
commitc6e6c769626143e7b1171328ac1bf6b324c9a6d3 (patch)
treee9019d1a79383ac9751770f523ad51c4d7dd982e /cura/API
parent58e43c0a07cbd349e3df433352f5f56aa5e0c26d (diff)
parent580bd13a76afec3155e86f0a4d8537595dcf1a30 (diff)
Merge remote-tracking branch 'origin/master' into doxygen_to_restructuredtext_comments
# Conflicts: # cura/API/Account.py # plugins/SimulationView/SimulationView.py
Diffstat (limited to 'cura/API')
-rw-r--r--cura/API/Account.py117
1 files changed, 110 insertions, 7 deletions
diff --git a/cura/API/Account.py b/cura/API/Account.py
index 185dcf23ce..27adb1a0c8 100644
--- a/cura/API/Account.py
+++ b/cura/API/Account.py
@@ -1,9 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
-from typing import Optional, Dict, TYPE_CHECKING
+from datetime import datetime
+from typing import Optional, Dict, TYPE_CHECKING, Union
-from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
+from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer, Q_ENUMS
+from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
from cura.OAuth2.AuthorizationService import AuthorizationService
@@ -16,9 +18,15 @@ if TYPE_CHECKING:
i18n_catalog = i18nCatalog("cura")
+class SyncState:
+ """QML: Cura.AccountSyncState"""
+ SYNCING = 0
+ SUCCESS = 1
+ ERROR = 2
+
class Account(QObject):
"""The account API provides a version-proof bridge to use Ultimaker Accounts
-
+
Usage:
.. code-block:: python
@@ -30,10 +38,22 @@ class Account(QObject):
api.account.userProfile # Who is logged in
"""
+ # The interval in which sync services are automatically triggered
+ SYNC_INTERVAL = 30.0 # seconds
+ Q_ENUMS(SyncState)
+
loginStateChanged = pyqtSignal(bool)
"""Signal emitted when user logged in or out"""
accessTokenChanged = pyqtSignal()
+ syncRequested = pyqtSignal()
+ """Sync services may connect to this signal to receive sync triggers.
+ Services should be resilient to receiving a signal while they are still syncing,
+ either by ignoring subsequent signals or restarting a sync.
+ See setSyncState() for providing user feedback on the state of your service.
+ """
+ lastSyncDateTimeChanged = pyqtSignal()
+ syncStateChanged = pyqtSignal(int) # because SyncState is an int Enum
def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
@@ -42,6 +62,8 @@ class Account(QObject):
self._error_message = None # type: Optional[Message]
self._logged_in = False
+ self._sync_state = SyncState.SUCCESS
+ self._last_sync_str = "-"
self._callback_port = 32118
self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
@@ -61,6 +83,16 @@ class Account(QObject):
self._authorization_service = AuthorizationService(self._oauth_settings)
+ # Create a timer for automatic account sync
+ self._update_timer = QTimer()
+ self._update_timer.setInterval(int(self.SYNC_INTERVAL * 1000))
+ # The timer is restarted explicitly after an update was processed. This prevents 2 concurrent updates
+ self._update_timer.setSingleShot(True)
+ self._update_timer.timeout.connect(self.syncRequested)
+
+ self._sync_services = {} # type: Dict[str, int]
+ """contains entries "service_name" : SyncState"""
+
def initialize(self) -> None:
self._authorization_service.initialize(self._application.getPreferences())
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
@@ -68,6 +100,39 @@ class Account(QObject):
self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged)
self._authorization_service.loadAuthDataFromPreferences()
+ def setSyncState(self, service_name: str, state: int) -> None:
+ """ Can be used to register sync services and update account sync states
+
+ Contract: A sync service is expected exit syncing state in all cases, within reasonable time
+
+ Example: `setSyncState("PluginSyncService", SyncState.SYNCING)`
+ :param service_name: A unique name for your service, such as `plugins` or `backups`
+ :param state: One of SyncState
+ """
+
+ prev_state = self._sync_state
+
+ self._sync_services[service_name] = state
+
+ if any(val == SyncState.SYNCING for val in self._sync_services.values()):
+ self._sync_state = SyncState.SYNCING
+ elif any(val == SyncState.ERROR for val in self._sync_services.values()):
+ self._sync_state = SyncState.ERROR
+ else:
+ self._sync_state = SyncState.SUCCESS
+
+ if self._sync_state != prev_state:
+ self.syncStateChanged.emit(self._sync_state)
+
+ if self._sync_state == SyncState.SUCCESS:
+ self._last_sync_str = datetime.now().strftime("%d/%m/%Y %H:%M")
+ self.lastSyncDateTimeChanged.emit()
+
+ if self._sync_state != SyncState.SYNCING:
+ # schedule new auto update after syncing completed (for whatever reason)
+ if not self._update_timer.isActive():
+ self._update_timer.start()
+
def _onAccessTokenChanged(self):
self.accessTokenChanged.emit()
@@ -89,18 +154,37 @@ class Account(QObject):
self._error_message.show()
self._logged_in = False
self.loginStateChanged.emit(False)
+ if self._update_timer.isActive():
+ self._update_timer.stop()
return
if self._logged_in != logged_in:
self._logged_in = logged_in
self.loginStateChanged.emit(logged_in)
+ if logged_in:
+ self.sync()
+ else:
+ if self._update_timer.isActive():
+ self._update_timer.stop()
@pyqtSlot()
- def login(self) -> None:
+ @pyqtSlot(bool)
+ def login(self, force_logout_before_login: bool = False) -> None:
+ """
+ Initializes the login process. If the user is logged in already and force_logout_before_login is true, Cura will
+ logout from the account before initiating the authorization flow. If the user is logged in and
+ force_logout_before_login is false, the function will return, as there is nothing to do.
+
+ :param force_logout_before_login: Optional boolean parameter
+ :return: None
+ """
if self._logged_in:
- # Nothing to do, user already logged in.
- return
- self._authorization_service.startAuthorizationFlow()
+ if force_logout_before_login:
+ self.logout()
+ else:
+ # Nothing to do, user already logged in.
+ return
+ self._authorization_service.startAuthorizationFlow(force_logout_before_login)
@pyqtProperty(str, notify=loginStateChanged)
def userName(self):
@@ -129,6 +213,25 @@ class Account(QObject):
return None
return user_profile.__dict__
+ @pyqtProperty(str, notify=lastSyncDateTimeChanged)
+ def lastSyncDateTime(self) -> str:
+ return self._last_sync_str
+
+ @pyqtSlot()
+ def sync(self) -> None:
+ """Signals all sync services to start syncing
+
+ This can be considered a forced sync: even when a
+ sync is currently running, a sync will be requested.
+ """
+
+ if self._update_timer.isActive():
+ self._update_timer.stop()
+ elif self._sync_state == SyncState.SYNCING:
+ Logger.warning("Starting a new sync while previous sync was not completed\n{}", str(self._sync_services))
+
+ self.syncRequested.emit()
+
@pyqtSlot()
def logout(self) -> None:
if not self._logged_in: