Cura/cura/PrinterOutput/NetworkedPrinterOutputDevice.py
Jaime van Kessel c1c59925de Removed duplicated code
CL-541
2017-11-27 17:14:30 +01:00

256 lines
11 KiB
Python

# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Logger import Logger
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, pyqtSignal, QUrl
from time import time
from typing import Callable, Any, Optional
from enum import IntEnum
class AuthState(IntEnum):
NotAuthenticated = 1
AuthenticationRequested = 2
Authenticated = 3
AuthenticationDenied = 4
AuthenticationReceived = 5
class NetworkedPrinterOutputDevice(PrinterOutputDevice):
authenticationStateChanged = pyqtSignal()
def __init__(self, device_id, address: str, properties, parent = None):
super().__init__(device_id = device_id, parent = parent)
self._manager = None
self._last_manager_create_time = None
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
self._last_request_time = None
self._api_prefix = ""
self._address = address
self._properties = properties
self._user_agent = "%s/%s " % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion())
self._onFinishedCallbacks = {}
self._authentication_state = AuthState.NotAuthenticated
self._cached_multiparts = {}
self._sending_gcode = False
self._compressing_gcode = False
self._gcode = []
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
raise NotImplementedError("requestWrite needs to be implemented")
def setAuthenticationState(self, authentication_state):
if self._authentication_state != authentication_state:
self._authentication_state = authentication_state
self.authenticationStateChanged.emit()
@pyqtProperty(int, notify=authenticationStateChanged)
def authenticationState(self):
return self._authentication_state
def _compressGCode(self):
self._compressing_gcode = True
## Mash the data into single string
max_chars_per_line = int(1024 * 1024 / 4) # 1/4 MB per line.
byte_array_file_data = b""
batched_line = ""
for line in self._gcode:
if not self._compressing_gcode:
self._progress_message.hide()
# Stop trying to zip / send as abort was called.
return
batched_line += line
# if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
# Compressing line by line in this case is extremely slow, so we need to batch them.
if len(batched_line) < max_chars_per_line:
continue
byte_array_file_data += self.__compressDataAndNotifyQt(batched_line)
batched_line = ""
# Don't miss the last batch (If any)
if batched_line:
byte_array_file_data += self.__compressDataAndNotifyQt(batched_line)
self._compressing_gcode = False
return byte_array_file_data
def _update(self):
if self._last_response_time:
time_since_last_response = time() - self._last_response_time
else:
time_since_last_response = 0
if self._last_request_time:
time_since_last_request = time() - self._last_request_time
else:
time_since_last_request = float("inf") # An irrelevantly large number of seconds
if time_since_last_response > self._timeout_time >= time_since_last_request:
# Go (or stay) into timeout.
self.setConnectionState(ConnectionState.closed)
# We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
# sleep.
if time_since_last_response > self._recreate_network_manager_time:
if self._last_manager_create_time is None:
self._createNetworkManager()
if time() - self._last_manager_create_time > self._recreate_network_manager_time:
self._createNetworkManager()
return True
def _createEmptyRequest(self, target, content_type: Optional[str] = "application/json"):
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 _clearCachedMultiPart(self, reply):
if id(reply) in self._cached_multiparts:
del self._cached_multiparts[id(reply)]
def put(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]):
if self._manager is None:
self._createNetworkManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
reply = self._manager.put(request, data.encode())
if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
def get(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]):
if self._manager is None:
self._createNetworkManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
reply = self._manager.get(request)
if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
def delete(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]):
if self._manager is None:
self._createNetworkManager()
self._last_request_time = time()
pass
def post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None):
if self._manager is None:
self._createNetworkManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
reply = self._manager.post(request, data)
if onProgress is not None:
reply.uploadProgress.connect(onProgress)
if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None):
if self._manager is None:
self._createNetworkManager()
request = self._createEmptyRequest(target, content_type=None)
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
post_part = QHttpPart()
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
post_part.setBody(body_data)
multi_post_part.append(post_part)
self._last_request_time = time()
reply = self._manager.post(request, multi_post_part)
# Due to garbage collection on python doing some weird stuff, we need to keep hold of a reference
self._cached_multiparts[id(reply)] = (post_part, multi_post_part, reply)
if onProgress is not None:
reply.uploadProgress.connect(onProgress)
if onFinished is not None:
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
def _onAuthenticationRequired(self, reply, authenticator):
Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString()))
def _createNetworkManager(self):
Logger.log("d", "Creating network manager")
if self._manager:
self._manager.finished.disconnect(self.__handleOnFinished)
#self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
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)
#self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged) # for debug purposes
def __handleOnFinished(self, reply: QNetworkReply):
# 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):
key = key.encode("utf-8")
if key in self._properties:
return self._properties.get(key, b"").decode("utf-8")
else:
return ""
## Get the unique key of this machine
# \return key String containing the key of the machine.
@pyqtProperty(str, constant=True)
def key(self):
return self._id
## The IP address of the printer.
@pyqtProperty(str, constant=True)
def address(self):
return self._properties.get(b"address", b"").decode("utf-8")
## Name of the printer (as returned from the ZeroConf properties)
@pyqtProperty(str, constant=True)
def name(self):
return self._properties.get(b"name", b"").decode("utf-8")
## Firmware version (as returned from the ZeroConf properties)
@pyqtProperty(str, constant=True)
def firmwareVersion(self):
return self._properties.get(b"firmware_version", b"").decode("utf-8")
## IPadress of this printer
@pyqtProperty(str, constant=True)
def ipAddress(self):
return self._address