In CloudOutputDeviceManager start a thread to periodically check for changes in the connected clusters

This commit is contained in:
Marijn Deé 2018-11-27 14:48:47 +01:00
parent 2d7588903c
commit fb019ba987
2 changed files with 40 additions and 12 deletions

View file

@ -30,6 +30,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
I18N_CATALOG = i18nCatalog("cura") I18N_CATALOG = i18nCatalog("cura")
# The cloud URL to use for this remote cluster. # The cloud URL to use for this remote cluster.
# TODO: Make sure that this url goes to the live api before release
API_ROOT_PATH_FORMAT = "https://api-staging.ultimaker.com/connect/v1/clusters/{cluster_id}" API_ROOT_PATH_FORMAT = "https://api-staging.ultimaker.com/connect/v1/clusters/{cluster_id}"
# Signal triggered when the printers in the remote cluster were changed. # Signal triggered when the printers in the remote cluster were changed.

View file

@ -1,6 +1,8 @@
# 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.
import json import json
from time import sleep
from threading import Thread
from typing import Dict, Optional, List from typing import Dict, Optional, List
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
@ -24,6 +26,9 @@ class CloudOutputDeviceManager(NetworkClient):
# The cloud URL to use for remote clusters. # The cloud URL to use for remote clusters.
API_ROOT_PATH = "https://api.ultimaker.com/connect/v1" API_ROOT_PATH = "https://api.ultimaker.com/connect/v1"
# The interval with wich the remote clusters are checked
CHECK_CLUSTER_INTERVAL = 5 # seconds
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -42,6 +47,11 @@ class CloudOutputDeviceManager(NetworkClient):
# TODO: update remote clusters periodically # TODO: update remote clusters periodically
self._account.loginStateChanged.connect(self._getRemoteClusters) self._account.loginStateChanged.connect(self._getRemoteClusters)
# Periodically check the cloud for an update on the clusters connected to the user's account
self._update_clusters_thread = Thread(target=self._updateClusters, daemon=True)
self._update_clusters_thread.start()
## Override _createEmptyRequest to add the needed authentication header for talking to the Ultimaker Cloud API. ## Override _createEmptyRequest to add the needed authentication header for talking to the Ultimaker Cloud API.
def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest: def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
request = super()._createEmptyRequest(self.API_ROOT_PATH + path, content_type = content_type) request = super()._createEmptyRequest(self.API_ROOT_PATH + path, content_type = content_type)
@ -50,6 +60,12 @@ class CloudOutputDeviceManager(NetworkClient):
# TODO: don't create the client when not signed in? # TODO: don't create the client when not signed in?
request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode()) request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode())
return request return request
## Update the clusters
def _updateClusters(self) -> None:
while True:
self._getRemoteClusters()
sleep(self.CHECK_CLUSTER_INTERVAL)
## Gets all remote clusters from the API. ## Gets all remote clusters from the API.
def _getRemoteClusters(self) -> None: def _getRemoteClusters(self) -> None:
@ -64,36 +80,47 @@ class CloudOutputDeviceManager(NetworkClient):
return return
# Parse the response (returns the "data" field from the body). # Parse the response (returns the "data" field from the body).
clusters = self._parseStatusResponse(reply) found_clusters = self._parseStatusResponse(reply)
if not clusters: if not found_clusters:
return return
# Add an output device for each remote cluster. known_cluster_ids = set(self._remote_clusters.keys())
# The clusters are an array of objects in a field called "data". found_clusters_ids = set(found_clusters.keys())
for cluster in clusters:
self._addCloudOutputDevice(cluster) # Add an output device for each new remote cluster.
for cluster_id in found_clusters_ids.difference(known_cluster_ids):
self._addCloudOutputDevice(found_clusters[cluster_id])
# Remove output devices that are gone
for cluster_id in known_cluster_ids.difference(found_clusters_ids):
self._removeCloudOutputDevice(found_clusters[cluster_id])
# For testing we add a dummy device: # For testing we add a dummy device:
# self._addCloudOutputDevice(CloudCluster(cluster_id = "LJ0tciiuZZjarrXAvFLEZ6ox4Cvx8FvtXUlQv4vIhV6w")) # self._addCloudOutputDevice(CloudCluster(cluster_id = "LJ0tciiuZZjarrXAvFLEZ6ox4Cvx8FvtXUlQv4vIhV6w"))
@staticmethod @staticmethod
def _parseStatusResponse(reply: QNetworkReply) -> List[CloudCluster]: def _parseStatusResponse(reply: QNetworkReply) -> Dict[str, CloudCluster]:
try: try:
return [CloudCluster(**c) for c in json.loads(reply.readAll().data().decode("utf-8"))] return {c["guid"]: CloudCluster(**c) for c in json.loads(reply.readAll().data().decode("utf-8"))}
except UnicodeDecodeError: except UnicodeDecodeError:
Logger.log("w", "Unable to read server response") Logger.log("w", "Unable to read server response")
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
Logger.logException("w", "Unable to decode JSON from reply.") Logger.logException("w", "Unable to decode JSON from reply.")
except ValueError: except ValueError:
Logger.logException("w", "Response was missing values.") Logger.logException("w", "Response was missing values.")
return [] return {}
## Adds a CloudOutputDevice for each entry in the remote cluster list from the API. ## Adds a CloudOutputDevice for each entry in the remote cluster list from the API.
def _addCloudOutputDevice(self, cluster: CloudCluster): def _addCloudOutputDevice(self, cluster: CloudCluster):
device = CloudOutputDevice(cluster.cluster_id) device = CloudOutputDevice(cluster.cluster_id)
self._output_device_manager.addOutputDevice(device) self._output_device_manager.addOutputDevice(device)
self._remote_clusters[cluster.cluster_id] = device self._remote_clusters[cluster.cluster_id] = device
## Remove a CloudOutputDevice
def _removeCloudOutputDevice(self, cluster: CloudCluster):
self._output_device_manager.removeOutputDevice(cluster.cluster_id)
del self._remote_clusters[cluster.cluster_id]
## Callback for when the active machine was changed by the user. ## Callback for when the active machine was changed by the user.
def _activeMachineChanged(self): def _activeMachineChanged(self):
active_machine = CuraApplication.getInstance().getGlobalContainerStack() active_machine = CuraApplication.getInstance().getGlobalContainerStack()