Browse Source

Add CloudOutputDeviceManager, test implementation

ChrisTerBeke 6 years ago
parent
commit
228325eb89

+ 1 - 0
cura/PrinterOutput/NetworkedPrinterOutputDevice.py

@@ -17,6 +17,7 @@ from enum import IntEnum
 import os  # To get the username
 import gzip
 
+
 class AuthState(IntEnum):
     NotAuthenticated = 1
     AuthenticationRequested = 2

+ 6 - 2
plugins/UM3NetworkPrinting/__init__.py

@@ -1,11 +1,15 @@
 # Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
-
 from .src import DiscoverUM3Action
 from .src import UM3OutputDevicePlugin
 
+
 def getMetaData():
     return {}
 
+
 def register(app):
-    return { "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
+    return {
+        "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(app),
+        "machine_action": DiscoverUM3Action.DiscoverUM3Action()
+    }

+ 23 - 7
plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDevice.py

@@ -3,13 +3,14 @@
 import json
 from typing import List, Optional, Dict
 
-from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
+from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QUrl
 from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
 
 from UM import i18nCatalog
 from UM.FileHandler.FileHandler import FileHandler
 from UM.Logger import Logger
 from UM.Scene.SceneNode import SceneNode
+from cura.CuraApplication import CuraApplication
 from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
 from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
 from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController
@@ -37,19 +38,34 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
     # Signal triggered when the print jobs in the queue were changed.
     printJobsChanged = pyqtSignal()
     
-    def __init__(self, device_id: str, address: str, properties: Dict[bytes, bytes], parent: QObject = None):
-        super().__init__(device_id = device_id, address = address, properties = properties, parent = parent)
+    def __init__(self, device_id: str, parent: QObject = None):
+        super().__init__(device_id = device_id, address = "", properties = {}, parent = parent)
         self._setInterfaceElements()
         
-        # The API prefix is automatically added when doing any HTTP call on the device.
-        self._api_prefix = self.API_ROOT_PATH_FORMAT.format(device_id)  # TODO: verify we can use device_id here
-        self._authentication_state = AuthState.Authenticated  # TODO: use cura.API.Account to set this?
+        self._device_id = device_id
+        self._account = CuraApplication.getInstance().getCuraAPI().account
         
         # Properties to populate later on with received cloud data.
         self._printers = []
         self._print_jobs = []
         self._number_of_extruders = 2  # All networked printers are dual-extrusion Ultimaker machines.
     
+    ##  We need to override _createEmptyRequest to work for the cloud.
+    def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
+        url = QUrl(self.API_ROOT_PATH_FORMAT.format(cluster_id = self._device_id) + path)
+        request = QNetworkRequest(url)
+        request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
+        request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
+        
+        if not self._account.isLoggedIn:
+            # TODO: show message to user to sign in
+            self.setAuthenticationState(AuthState.NotAuthenticated)
+        else:
+            self.setAuthenticationState(AuthState.Authenticated)
+            request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode())
+
+        return request
+    
     ##  Set all the interface elements and texts for this output device.
     def _setInterfaceElements(self):
         self.setPriority(3)
@@ -90,7 +106,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
         status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
         if status_code != 200:
             Logger.log("w", "Got unexpected response while trying to get cloud cluster data: {}, {}"
-                       .format(status_code, reply.getErrorString()))
+                       .format(status_code, reply.readAll()))
             return
         
         data = self._parseStatusResponse(reply)

+ 31 - 0
plugins/UM3NetworkPrinting/src/Cloud/CloudOutputDeviceManager.py

@@ -0,0 +1,31 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+from typing import TYPE_CHECKING
+
+from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputDevice import CloudOutputDevice
+
+
+if TYPE_CHECKING:
+    from cura.CuraApplication import CuraApplication
+    
+
+##  The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
+#   Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
+class CloudOutputDeviceManager:
+    
+    def __init__(self, application: "CuraApplication"):
+        self._output_device_manager = application.getOutputDeviceManager()
+        self._account = application.getCuraAPI().account
+        self._getRemoteClusters()
+        
+        # For testing:
+        application.globalContainerStackChanged.connect(self._addCloudOutputDevice)
+        
+    def _getRemoteClusters(self):
+        # TODO: get list of remote clusters and create an output device for each.
+        pass
+    
+    def _addCloudOutputDevice(self):
+        device = CloudOutputDevice("xxxx-xxxx-xxxx-xxxx")
+        self._output_device_manager.addOutputDevice(device)
+        device.connect()

+ 17 - 8
plugins/UM3NetworkPrinting/src/UM3OutputDevicePlugin.py

@@ -1,11 +1,12 @@
 # Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
+from typing import TYPE_CHECKING
 
 from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
 from UM.Logger import Logger
-from UM.Application import Application
 from UM.Signal import Signal, signalemitter
 from UM.Version import Version
+from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
 
 from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
 
@@ -19,6 +20,9 @@ from time import time
 
 import json
 
+if TYPE_CHECKING:
+    from cura.CuraApplication import CuraApplication
+
 
 ##      This plugin handles the connection detection & creation of output device objects for the UM3 printer.
 #       Zero-Conf is used to detect printers, which are saved in a dict.
@@ -29,8 +33,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
     removeDeviceSignal = Signal()
     discoveredDevicesChanged = Signal()
 
-    def __init__(self):
+    def __init__(self, application: "CuraApplication"):
         super().__init__()
+        self._application = application
+        
         self._zero_conf = None
         self._zero_conf_browser = None
 
@@ -38,7 +44,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
         self.addDeviceSignal.connect(self._onAddDevice)
         self.removeDeviceSignal.connect(self._onRemoveDevice)
 
-        Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
+        application.globalContainerStackChanged.connect(self.reCheckConnections)
 
         self._discovered_devices = {}
         
@@ -53,7 +59,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
         self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
 
         # Get list of manual instances from preferences
-        self._preferences = Application.getInstance().getPreferences()
+        self._preferences = self._application.getPreferences()
         self._preferences.addPreference("um3networkprinting/manual_instances",
                                         "")  # A comma-separated list of ip adresses or hostnames
 
@@ -70,6 +76,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
         self._service_changed_request_event = Event()
         self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True)
         self._service_changed_request_thread.start()
+        
+        # Create a cloud output device manager that abstract all cloud connection logic away.
+        self._cloud_output_device_manager = CloudOutputDeviceManager(self._application)
 
     def getDiscoveredDevices(self):
         return self._discovered_devices
@@ -104,7 +113,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
         self.resetLastManualDevice()
 
     def reCheckConnections(self):
-        active_machine = Application.getInstance().getGlobalContainerStack()
+        active_machine = self._application.getGlobalContainerStack()
         if not active_machine:
             return
 
@@ -129,7 +138,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
             return
         if self._discovered_devices[key].isConnected():
             # Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine
-            um_network_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key")
+            um_network_key = self._application.getGlobalContainerStack().getMetaDataEntry("um_network_key")
             if key == um_network_key:
                 self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
         else:
@@ -281,7 +290,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
         self._discovered_devices[device.getId()] = device
         self.discoveredDevicesChanged.emit()
 
-        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        global_container_stack = self._application.getGlobalContainerStack()
         if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"):
             device.connect()
             device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
@@ -299,7 +308,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
             self._service_changed_request_event.wait(timeout = 5.0)
 
             # Stop if the application is shutting down
-            if Application.getInstance().isShuttingDown():
+            if self._application.isShuttingDown():
                 return
 
             self._service_changed_request_event.clear()