From 04cc6193d6a6f7098732a808cd87841235a8e29b Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 19 Nov 2018 23:25:54 +0100 Subject: [PATCH] More implementation for getting remote clusters, add some TODOs --- .../src/Cloud/CloudOutputDevice.py | 1 + .../src/Cloud/CloudOutputDeviceManager.py | 67 +++++++++++++++++-- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py index ff83c3fd5e..8f0bd62035 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py @@ -60,6 +60,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice): # TODO: show message to user to sign in self.setAuthenticationState(AuthState.NotAuthenticated) else: + # TODO: not execute call at all when not signed in? self.setAuthenticationState(AuthState.Authenticated) request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode()) diff --git a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py index d5c4abae09..e88ee4dced 100644 --- a/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py +++ b/plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py @@ -1,7 +1,12 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import TYPE_CHECKING, Dict +import json +from typing import TYPE_CHECKING, Dict, Optional +from PyQt5.QtCore import QUrl +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply + +from UM.Logger import Logger from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputDevice import CloudOutputDevice @@ -19,31 +24,79 @@ if TYPE_CHECKING: class CloudOutputDeviceManager: # The cloud URL to use for remote clusters. - API_ROOT_PATH = "https://api-staging.ultimaker.com/connect/v1" + API_ROOT_PATH = "https://api.ultimaker.com/connect/v1" def __init__(self, application: "CuraApplication"): self._application = application self._output_device_manager = application.getOutputDeviceManager() self._account = application.getCuraAPI().account + # Network manager for getting the cluster list. + self._network_manager = QNetworkAccessManager() + self._network_manager.finished.connect(self._onNetworkRequestFinished) + # Persistent dict containing the remote clusters for the authenticated user. self._remote_clusters = {} # type: Dict[str, CloudOutputDevice] # When switching machines we check if we have to activate a remote cluster. self._application.globalContainerStackChanged.connect(self._activeMachineChanged) - + # Fetch all remote clusters for the authenticated user. - self._getRemoteClusters() + # TODO: update remote clusters periodically + self._account.loginStateChanged.connect(self._getRemoteClusters) ## Gets all remote clusters from the API. def _getRemoteClusters(self): - # TODO: get list of remote clusters and create an output device for each. - # For testing we add a dummy device: - self._addCloudOutputDevice({"cluster_id": "LJ0tciiuZZjarrXAvFLEZ6ox4Cvx8FvtXUlQv4vIhV6w"}) + url = QUrl("{}/clusters".format(self.API_ROOT_PATH)) + request = QNetworkRequest(url) + request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") + + if not self._account.isLoggedIn: + # TODO: show message to user to sign in + Logger.log("w", "User is not signed in, cannot get remote print clusters") + return + + request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode()) + self._network_manager.get(request) + + ## Callback for network requests. + def _onNetworkRequestFinished(self, reply: QNetworkReply): + # TODO: right now we assume that each reply is from /clusters, we should fix this + status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) + if status_code != 200: + # TODO: add correct scopes to OAuth2 client to use remote connect API. + Logger.log("w", "Got unexpected response while trying to get cloud cluster data: {}, {}" + .format(status_code, reply.readAll())) + return + + # Parse the response (returns the "data" field from the body). + clusters_data = self._parseStatusResponse(reply) + if not clusters_data: + return + + # Add an output device for each remote cluster. + # The clusters are an array of objects in a field called "data". + for cluster in clusters_data: + self._addCloudOutputDevice(cluster) + + # # For testing we add a dummy device: + # self._addCloudOutputDevice({ "cluster_id": "LJ0tciiuZZjarrXAvFLEZ6ox4Cvx8FvtXUlQv4vIhV6w" }) + + @staticmethod + def _parseStatusResponse(reply: QNetworkReply) -> Optional[dict]: + try: + result = json.loads(bytes(reply.readAll()).decode("utf-8")) + print("result=====", result) + # TODO: use model or named tuple here. + return result.data + except json.decoder.JSONDecodeError: + Logger.logException("w", "Unable to decode JSON from reply.") + return None ## Adds a CloudOutputDevice for each entry in the remote cluster list from the API. def _addCloudOutputDevice(self, cluster_data: Dict[str, any]): # TODO: use model or named tuple for cluster_data + print("cluster_data====", cluster_data) device = CloudOutputDevice(cluster_data["cluster_id"]) self._output_device_manager.addOutputDevice(device) self._remote_clusters[cluster_data["cluster_id"]] = device