Browse Source

Restore NetworkedPrinterOutputDevice for now

ChrisTerBeke 6 years ago
parent
commit
a9fedb4f66
1 changed files with 177 additions and 34 deletions
  1. 177 34
      cura/PrinterOutput/NetworkedPrinterOutputDevice.py

+ 177 - 34
cura/PrinterOutput/NetworkedPrinterOutputDevice.py

@@ -1,19 +1,21 @@
 # Copyright (c) 2018 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
-import os
-import gzip
-from time import time
-from typing import Dict, List, Optional
-from enum import IntEnum
 
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
-from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QCoreApplication
+from UM.FileHandler.FileHandler import FileHandler #For typing.
+from UM.Logger import Logger
+from UM.Scene.SceneNode import SceneNode #For typing.
+from cura.CuraApplication import CuraApplication
 
-from UM.FileHandler.FileHandler import FileHandler
-from UM.Scene.SceneNode import SceneNode
-from cura.NetworkClient import NetworkClient
 from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
 
+from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
+from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
+from time import time
+from typing import Any, Callable, Dict, List, Optional
+from enum import IntEnum
+
+import os  # To get the username
+import gzip
 
 class AuthState(IntEnum):
     NotAuthenticated = 1
@@ -23,29 +25,35 @@ class AuthState(IntEnum):
     AuthenticationReceived = 5
 
 
-class NetworkedPrinterOutputDevice(PrinterOutputDevice, NetworkClient):
+class NetworkedPrinterOutputDevice(PrinterOutputDevice):
     authenticationStateChanged = pyqtSignal()
 
     def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None:
-        PrinterOutputDevice.__init__(self, device_id = device_id, parent = parent)
-        NetworkClient.__init__(self)
-        
+        super().__init__(device_id = device_id, parent = parent)
+        self._manager = None    # type: Optional[QNetworkAccessManager]
+        self._last_manager_create_time = None       # type: Optional[float]
+        self._recreate_network_manager_time = 30
+        self._timeout_time = 10  # After how many seconds of no response should a timeout occur?
+
+        self._last_response_time = None     # type: Optional[float]
+        self._last_request_time = None      # type: Optional[float]
+
         self._api_prefix = ""
         self._address = address
         self._properties = properties
+        self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(), CuraApplication.getInstance().getVersion())
+
+        self._onFinishedCallbacks = {}      # type: Dict[str, Callable[[QNetworkReply], None]]
         self._authentication_state = AuthState.NotAuthenticated
+
+        # QHttpMultiPart objects need to be kept alive and not garbage collected during the
+        # HTTP which uses them. We hold references to these QHttpMultiPart objects here.
+        self._kept_alive_multiparts = {}        # type: Dict[QNetworkReply, QHttpMultiPart]
+
         self._sending_gcode = False
         self._compressing_gcode = False
-        self._gcode = []  # type: List[str]
-        
+        self._gcode = []                    # type: List[str]
         self._connection_state_before_timeout = None    # type: Optional[ConnectionState]
-        self._timeout_time = 10  # After how many seconds of no response should a timeout occur?
-        self._recreate_network_manager_time = 30
-        
-    ##  Override creating empty request to compile the full URL.
-    #   Needed to keep NetworkedPrinterOutputDevice backwards compatible after refactoring NetworkClient out of it.
-    def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
-        return super()._createEmptyRequest("http://" + self._address + self._api_prefix + target, content_type)
 
     def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
         raise NotImplementedError("requestWrite needs to be implemented")
@@ -131,6 +139,27 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice, NetworkClient):
                 self.setConnectionState(self._connection_state_before_timeout)
                 self._connection_state_before_timeout = None
 
+    def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
+        url = QUrl("http://" + self._address + self._api_prefix + target)
+        request = QNetworkRequest(url)
+        if content_type is not None:
+            request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
+        request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
+        return request
+
+    def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
+        part = QHttpPart()
+
+        if not content_header.startswith("form-data;"):
+            content_header = "form_data; " + content_header
+        part.setHeader(QNetworkRequest.ContentDispositionHeader, content_header)
+
+        if content_type is not None:
+            part.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
+
+        part.setBody(data)
+        return part
+
     ##  Convenience function to get the username from the OS.
     #   The code was copied from the getpass module, as we try to use as little dependencies as possible.
     def _getUserName(self) -> str:
@@ -140,7 +169,130 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice, NetworkClient):
                 return user
         return "Unknown User"  # Couldn't find out username.
 
-    @pyqtSlot(str, result = str)
+    def _clearCachedMultiPart(self, reply: QNetworkReply) -> None:
+        if reply in self._kept_alive_multiparts:
+            del self._kept_alive_multiparts[reply]
+
+    def _validateManager(self) -> None:
+        if self._manager is None:
+            self._createNetworkManager()
+        assert (self._manager is not None)
+
+    def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+        self._validateManager()
+        request = self._createEmptyRequest(target)
+        self._last_request_time = time()
+        if self._manager is not None:
+            reply = self._manager.put(request, data.encode())
+            self._registerOnFinishedCallback(reply, on_finished)
+        else:
+            Logger.log("e", "Could not find manager.")
+
+    def delete(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+        self._validateManager()
+        request = self._createEmptyRequest(target)
+        self._last_request_time = time()
+        if self._manager is not None:
+            reply = self._manager.deleteResource(request)
+            self._registerOnFinishedCallback(reply, on_finished)
+        else:
+            Logger.log("e", "Could not find manager.")
+
+    def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+        self._validateManager()
+        request = self._createEmptyRequest(target)
+        self._last_request_time = time()
+        if self._manager is not None:
+            reply = self._manager.get(request)
+            self._registerOnFinishedCallback(reply, on_finished)
+        else:
+            Logger.log("e", "Could not find manager.")
+
+    def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
+        self._validateManager()
+        request = self._createEmptyRequest(target)
+        self._last_request_time = time()
+        if self._manager is not None:
+            reply = self._manager.post(request, data.encode())
+            if on_progress is not None:
+                reply.uploadProgress.connect(on_progress)
+            self._registerOnFinishedCallback(reply, on_finished)
+        else:
+            Logger.log("e", "Could not find manager.")
+
+    def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
+        self._validateManager()
+        request = self._createEmptyRequest(target, content_type=None)
+        multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
+        for part in parts:
+            multi_post_part.append(part)
+
+        self._last_request_time = time()
+
+        if self._manager is not None:
+            reply = self._manager.post(request, multi_post_part)
+
+            self._kept_alive_multiparts[reply] = multi_post_part
+
+            if on_progress is not None:
+                reply.uploadProgress.connect(on_progress)
+            self._registerOnFinishedCallback(reply, on_finished)
+
+            return reply
+        else:
+            Logger.log("e", "Could not find manager.")
+
+    def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
+        post_part = QHttpPart()
+        post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
+        post_part.setBody(body_data)
+
+        self.postFormWithParts(target, [post_part], on_finished, on_progress)
+
+    def _onAuthenticationRequired(self, reply: QNetworkReply, authenticator: QAuthenticator) -> None:
+        Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString()))
+
+    def _createNetworkManager(self) -> None:
+        Logger.log("d", "Creating network manager")
+        if self._manager:
+            self._manager.finished.disconnect(self.__handleOnFinished)
+            self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
+
+        self._manager = QNetworkAccessManager()
+        self._manager.finished.connect(self.__handleOnFinished)
+        self._last_manager_create_time = time()
+        self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
+
+        if self._properties.get(b"temporary", b"false") != b"true":
+            CuraApplication.getInstance().getMachineManager().checkCorrectGroupName(self.getId(), self.name)
+
+    def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
+        if on_finished is not None:
+            self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished
+
+    def __handleOnFinished(self, reply: QNetworkReply) -> None:
+        # Due to garbage collection, we need to cache certain bits of post operations.
+        # As we don't want to keep them around forever, delete them if we get a reply.
+        if reply.operation() == QNetworkAccessManager.PostOperation:
+            self._clearCachedMultiPart(reply)
+
+        if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None:
+            # No status code means it never even reached remote.
+            return
+
+        self._last_response_time = time()
+
+        if self._connection_state == ConnectionState.connecting:
+            self.setConnectionState(ConnectionState.connected)
+
+        callback_key = reply.url().toString() + str(reply.operation())
+        try:
+            if callback_key in self._onFinishedCallbacks:
+                self._onFinishedCallbacks[callback_key](reply)
+        except Exception:
+            Logger.logException("w", "something went wrong with callback")
+
+    @pyqtSlot(str, result=str)
     def getProperty(self, key: str) -> str:
         bytes_key = key.encode("utf-8")
         if bytes_key in self._properties:
@@ -176,16 +328,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice, NetworkClient):
     def printerType(self) -> str:
         return self._properties.get(b"printer_type", b"Unknown").decode("utf-8")
 
-    ## IP address of this printer
+    ## IP adress of this printer
     @pyqtProperty(str, constant = True)
     def ipAddress(self) -> str:
         return self._address
-
-    def __handleOnFinished(self, reply: QNetworkReply) -> None:
-        
-        # Since we got a 200 reply from the network manager we can now be sure we are actually connected.
-        if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200 and \
-                self._connection_state == ConnectionState.connecting:
-            self.setConnectionState(ConnectionState.connected)
-
-        super().__handleOnFinished(reply)