diff --git a/cura/API/Account.py b/cura/API/Account.py index e9d4a52c08..7ad2a1802c 100644 --- a/cura/API/Account.py +++ b/cura/API/Account.py @@ -4,7 +4,7 @@ from datetime import datetime from enum import Enum from typing import Optional, Dict, TYPE_CHECKING -from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty +from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QTimer from UM.Message import Message from UM.i18n import i18nCatalog @@ -28,6 +28,8 @@ i18n_catalog = i18nCatalog("cura") # api.account.userProfile # Who is logged in`` # class Account(QObject): + # The interval with which the remote clusters are checked + SYNC_INTERVAL = 30.0 # seconds class SyncState(Enum): """Caution: values used in qml (eg. SyncState.qml)""" @@ -40,7 +42,7 @@ class Account(QObject): loginStateChanged = pyqtSignal(bool) accessTokenChanged = pyqtSignal() cloudPrintersDetectedChanged = pyqtSignal(bool) - manualSyncRequested = pyqtSignal() + syncRequested = pyqtSignal() lastSyncDateTimeChanged = pyqtSignal() syncStateChanged = pyqtSignal(str) @@ -72,6 +74,13 @@ 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, Account.SyncState] """contains entries "service_name" : SyncState""" @@ -83,9 +92,9 @@ class Account(QObject): self._authorization_service.loadAuthDataFromPreferences() def setSyncState(self, service_name: str, state: SyncState) -> None: - """ Can be used to register and update account sync states + """ Can be used to register sync services and update account sync states - Example: `setSyncState("packages", Account.SyncState.SYNCING)` + Example: `setSyncState("PluginSyncService", Account.SyncState.SYNCING)` :param service_name: A unique name for your service, such as `plugins` or `backups` :param state: One of Account.SYNC_STATES """ @@ -108,6 +117,11 @@ class Account(QObject): self._last_sync_str = datetime.now().strftime("%d/%m/%Y %H:%M") self.lastSyncDateTimeChanged.emit() + if self._sync_state != "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() @@ -180,7 +194,7 @@ class Account(QObject): def sync(self) -> None: """Checks for new cloud printers""" - self.manualSyncRequested.emit() + self.syncRequested.emit() @pyqtSlot() def logout(self) -> None: diff --git a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py index 049d9409fd..0c51b7ff8b 100644 --- a/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py +++ b/plugins/Toolbox/src/CloudSync/CloudPackageChecker.py @@ -36,6 +36,9 @@ class CloudPackageChecker(QObject): self._application.initializationFinished.connect(self._onAppInitialized) self._i18n_catalog = i18nCatalog("cura") self._sdk_version = ApplicationMetadata.CuraSDKVersion + self._last_check_packages = [] # type: List[str] + """Result from a previous check within the same user session. + Used to prevent duplicate notifications""" # 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. @@ -44,8 +47,13 @@ class CloudPackageChecker(QObject): # initial check self._getPackagesIfLoggedIn() - self._application.getCuraAPI().account.loginStateChanged.connect(self._getPackagesIfLoggedIn) - self._application.getCuraAPI().account.manualSyncRequested.connect(self._getPackagesIfLoggedIn) + self._application.getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged) + self._application.getCuraAPI().account.syncRequested.connect(self._getPackagesIfLoggedIn) + + def _onLoginStateChanged(self) -> None: + # reset session + self._last_check_packages = [] + self._getPackagesIfLoggedIn() def _getPackagesIfLoggedIn(self) -> None: if self._application.getCuraAPI().account.isLoggedIn: @@ -88,6 +96,10 @@ class CloudPackageChecker(QObject): user_subscribed_packages = [plugin["package_id"] for plugin in subscribed_packages_payload] user_installed_packages = self._package_manager.getUserInstalledPackages() + if user_subscribed_packages == self._last_check_packages: + # nothing new here + return + # 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) @@ -103,6 +115,7 @@ class CloudPackageChecker(QObject): self._model.addDiscrepancies(package_discrepancy) self._model.initialize(self._package_manager, subscribed_packages_payload) self._showSyncMessage() + self._last_check_packages = user_subscribed_packages def _showSyncMessage(self) -> None: """Show the message if it is not already shown""" diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index efb0b30e4b..10ef460044 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -29,9 +29,6 @@ class CloudOutputDeviceManager: META_NETWORK_KEY = "um_network_key" SYNC_SERVICE_NAME = "CloudOutputDeviceManager" - # The interval with which the remote clusters are checked - CHECK_CLUSTER_INTERVAL = 30.0 # seconds - # The translation catalog for this device. I18N_CATALOG = i18nCatalog("cura") @@ -45,13 +42,6 @@ class CloudOutputDeviceManager: self._api = CloudApiClient(self._account, on_error = lambda error: Logger.log("e", str(error))) self._account.loginStateChanged.connect(self._onLoginStateChanged) - # Create a timer to update the remote cluster list - self._update_timer = QTimer() - self._update_timer.setInterval(int(self.CHECK_CLUSTER_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._getRemoteClusters) - # Ensure we don't start twice. self._running = False @@ -65,11 +55,9 @@ class CloudOutputDeviceManager: if not self._account.isLoggedIn: return self._running = True - if not self._update_timer.isActive(): - self._update_timer.start() self._getRemoteClusters() - self._account.manualSyncRequested.connect(self._getRemoteClusters) + self._account.syncRequested.connect(self._getRemoteClusters) def stop(self): """Stops running the cloud output device manager.""" @@ -77,8 +65,6 @@ class CloudOutputDeviceManager: if not self._running: return self._running = False - if self._update_timer.isActive(): - self._update_timer.stop() self._onGetRemoteClustersFinished([]) # Make sure we remove all cloud output devices. def refreshConnections(self) -> None: @@ -100,8 +86,7 @@ class CloudOutputDeviceManager: if self._syncing: return - if self._update_timer.isActive(): - self._update_timer.stop() + Logger.info("Syncing cloud printer clusters") self._syncing = True self._account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.SYNCING) @@ -135,14 +120,10 @@ class CloudOutputDeviceManager: self._syncing = False self._account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.SUCCESS) - # Schedule a new update - self._update_timer.start() def _onGetRemoteClusterFailed(self): self._syncing = False self._account.setSyncState(self.SYNC_SERVICE_NAME, Account.SyncState.ERROR) - # Schedule a new update - self._update_timer.start() def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None: """**Synchronously** create machines for discovered devices