diff options
author | Nino van Hooff <ninovanhooff@gmail.com> | 2020-05-28 18:31:24 +0300 |
---|---|---|
committer | Nino van Hooff <ninovanhooff@gmail.com> | 2020-05-28 18:31:24 +0300 |
commit | 58ffc9dcae0020d7dd4f3c32b41922dfdbef37d3 (patch) | |
tree | 54902883427fa76a9dffb5068afd99532563cb4b /cura/API | |
parent | c2c96faf5fcbad942f8cf257e75c94a623ac5eaa (diff) | |
parent | 2a70813d030c678181b5c37fc82cb513d689187b (diff) |
Merge remote-tracking branch 'origin/master' into doxygen_to_restructuredtext_comments
# Conflicts:
# cura/API/__init__.py
# cura/Settings/CuraContainerRegistry.py
# cura/Settings/ExtruderManager.py
# plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
# plugins/UM3NetworkPrinting/src/Cloud/CloudApiClient.py
# plugins/UM3NetworkPrinting/src/Cloud/ToolPathUploader.py
# plugins/UM3NetworkPrinting/src/Network/LocalClusterOutputDeviceManager.py
Diffstat (limited to 'cura/API')
-rw-r--r-- | cura/API/Account.py | 57 | ||||
-rw-r--r-- | cura/API/ConnectionStatus.py | 64 | ||||
-rw-r--r-- | cura/API/__init__.py | 10 |
3 files changed, 117 insertions, 14 deletions
diff --git a/cura/API/Account.py b/cura/API/Account.py index 27adb1a0c8..581a91da04 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -23,6 +23,7 @@ class SyncState: SYNCING = 0 SUCCESS = 1 ERROR = 2 + IDLE = 3 class Account(QObject): """The account API provides a version-proof bridge to use Ultimaker Accounts @@ -54,6 +55,7 @@ class Account(QObject): """ lastSyncDateTimeChanged = pyqtSignal() syncStateChanged = pyqtSignal(int) # because SyncState is an int Enum + manualSyncEnabledChanged = pyqtSignal(bool) def __init__(self, application: "CuraApplication", parent = None) -> None: super().__init__(parent) @@ -62,7 +64,8 @@ class Account(QObject): self._error_message = None # type: Optional[Message] self._logged_in = False - self._sync_state = SyncState.SUCCESS + self._sync_state = SyncState.IDLE + self._manual_sync_enabled = False self._last_sync_str = "-" self._callback_port = 32118 @@ -110,16 +113,21 @@ class Account(QObject): :param state: One of SyncState """ + Logger.info("Service {service} enters sync state {state}", service = service_name, state = state) + 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 + self._setManualSyncEnabled(False) elif any(val == SyncState.ERROR for val in self._sync_services.values()): self._sync_state = SyncState.ERROR + self._setManualSyncEnabled(True) else: self._sync_state = SyncState.SUCCESS + self._setManualSyncEnabled(False) if self._sync_state != prev_state: self.syncStateChanged.emit(self._sync_state) @@ -162,11 +170,31 @@ class Account(QObject): self._logged_in = logged_in self.loginStateChanged.emit(logged_in) if logged_in: - self.sync() + self._setManualSyncEnabled(False) + self._sync() else: if self._update_timer.isActive(): self._update_timer.stop() + 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() + + def _setManualSyncEnabled(self, enabled: bool) -> None: + if self._manual_sync_enabled != enabled: + self._manual_sync_enabled = enabled + self.manualSyncEnabledChanged.emit(enabled) + @pyqtSlot() @pyqtSlot(bool) def login(self, force_logout_before_login: bool = False) -> None: @@ -217,20 +245,23 @@ class Account(QObject): def lastSyncDateTime(self) -> str: return self._last_sync_str - @pyqtSlot() - def sync(self) -> None: - """Signals all sync services to start syncing + @pyqtProperty(bool, notify=manualSyncEnabledChanged) + def manualSyncEnabled(self) -> bool: + return self._manual_sync_enabled - This can be considered a forced sync: even when a - sync is currently running, a sync will be requested. - """ + @pyqtSlot() + @pyqtSlot(bool) + def sync(self, user_initiated: bool = False) -> None: + if user_initiated: + self._setManualSyncEnabled(False) - 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._sync() - self.syncRequested.emit() + @pyqtSlot() + def popupOpened(self) -> None: + self._setManualSyncEnabled(True) + self._sync_state = SyncState.IDLE + self.syncStateChanged.emit(self._sync_state) @pyqtSlot() def logout(self) -> None: diff --git a/cura/API/ConnectionStatus.py b/cura/API/ConnectionStatus.py new file mode 100644 index 0000000000..332e519ca9 --- /dev/null +++ b/cura/API/ConnectionStatus.py @@ -0,0 +1,64 @@ +from typing import Optional + +from PyQt5.QtCore import QObject, pyqtSignal, QTimer, pyqtProperty +from PyQt5.QtNetwork import QNetworkReply + +from UM.TaskManagement.HttpRequestManager import HttpRequestManager +from cura.UltimakerCloud import UltimakerCloudAuthentication + + +class ConnectionStatus(QObject): + """Status info for some web services""" + + UPDATE_INTERVAL = 10.0 # seconds + ULTIMAKER_CLOUD_STATUS_URL = UltimakerCloudAuthentication.CuraCloudAPIRoot + "/connect/v1/" + + __instance = None # type: Optional[ConnectionStatus] + + internetReachableChanged = pyqtSignal() + umCloudReachableChanged = pyqtSignal() + + @classmethod + def getInstance(cls, *args, **kwargs) -> "ConnectionStatus": + if cls.__instance is None: + cls.__instance = cls(*args, **kwargs) + return cls.__instance + + def __init__(self, parent: Optional["QObject"] = None): + super().__init__(parent) + + self._http = HttpRequestManager.getInstance() + self._statuses = { + self.ULTIMAKER_CLOUD_STATUS_URL: True, + "http://example.com": True + } + + # Create a timer for automatic updates + self._update_timer = QTimer() + self._update_timer.setInterval(int(self.UPDATE_INTERVAL * 1000)) + # The timer is restarted automatically + self._update_timer.setSingleShot(False) + self._update_timer.timeout.connect(self._update) + self._update_timer.start() + + @pyqtProperty(bool, notify=internetReachableChanged) + def isInternetReachable(self) -> bool: + # Is any of the test urls reachable? + return any(self._statuses.values()) + + def _update(self): + for url in self._statuses.keys(): + self._http.get( + url = url, + callback = self._statusCallback, + error_callback = self._statusCallback, + timeout = 5 + ) + + def _statusCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError = None): + url = reply.request().url().toString() + prev_statuses = self._statuses.copy() + self._statuses[url] = HttpRequestManager.replyIndicatesSuccess(reply, error) + + if any(self._statuses.values()) != any(prev_statuses.values()): + self.internetReachableChanged.emit() diff --git a/cura/API/__init__.py b/cura/API/__init__.py index 97d0797430..447be98e4b 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -5,6 +5,7 @@ from typing import Optional, TYPE_CHECKING from PyQt5.QtCore import QObject, pyqtProperty from cura.API.Backups import Backups +from cura.API.ConnectionStatus import ConnectionStatus from cura.API.Interface import Interface from cura.API.Account import Account @@ -14,7 +15,7 @@ if TYPE_CHECKING: class CuraAPI(QObject): """The official Cura API that plug-ins can use to interact with Cura. - + Python does not technically prevent talking to other classes as well, but this API provides a version-safe interface with proper deprecation warnings etc. Usage of any other methods than the ones provided in this API can cause plug-ins to be unstable. @@ -44,6 +45,9 @@ class CuraAPI(QObject): self._backups = Backups(self._application) + self._connectionStatus = ConnectionStatus() + + # Interface API self._interface = Interface(self._application) def initialize(self) -> None: @@ -55,6 +59,10 @@ class CuraAPI(QObject): return self._account + @pyqtProperty(QObject, constant = True) + def connectionStatus(self) -> "ConnectionStatus": + return self._connectionStatus + @property def backups(self) -> "Backups": """Backups API""" |