Merge more stuff, re-use models for local networking as well

This commit is contained in:
ChrisTerBeke 2019-07-29 14:53:50 +02:00
parent 8f37c83b9c
commit 4b212d6c05
31 changed files with 688 additions and 975 deletions

View file

@ -1,10 +1,21 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Callable, List, Optional
import json
from json import JSONDecodeError
from typing import Callable, List, Optional, Dict, Union, Any, Type, cast, TypeVar, Tuple
from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from UM.Logger import Logger
from plugins.UM3NetworkPrinting.src.Models.BaseModel import BaseModel
## The generic type variable used to document the methods below.
from plugins.UM3NetworkPrinting.src.Models.Http.ClusterPrinterStatus import ClusterPrinterStatus
ClusterApiClientModel = TypeVar("ClusterApiClientModel", bound=BaseModel)
## The ClusterApiClient is responsible for all network calls to local network clusters.
class ClusterApiClient:
@ -30,32 +41,95 @@ class ClusterApiClient:
## Get printer system information.
# \param on_finished: The callback in case the response is successful.
def getSystem(self, on_finished: Callable) -> None:
url = f"{self.PRINTER_API_PREFIX}/system"
url = f"{self.PRINTER_API_PREFIX}/system/"
self._manager.get(self._createEmptyRequest(url))
## Get the printers in the cluster.
# \param on_finished: The callback in case the response is successful.
def getPrinters(self, on_finished: Callable[[List[ClusterPrinterStatus]], Any]) -> None:
url = f"{self.CLUSTER_API_PREFIX}/printers/"
reply = self._manager.get(self._createEmptyRequest(url))
self._addCallback(reply, on_finished)
self._addCallback(reply, on_finished, ClusterPrinterStatus)
## Get the print jobs in the cluster.
# \param on_finished: The callback in case the response is successful.
def getPrintJobs(self, on_finished: Callable) -> None:
url = f"{self.CLUSTER_API_PREFIX}/print_jobs/"
# reply = self._manager.get(self._createEmptyRequest(url))
# self._addCallback(reply, on_finished)
def requestPrint(self) -> None:
pass
## Send a print job action to the cluster.
# \param print_job_uuid: The UUID of the print job to perform the action on.
# \param action: The action to perform.
# \param data: The optional data to send along, used for 'move' and 'duplicate'.
def doPrintJobAction(self, print_job_uuid: str, action: str, data: Optional[Dict[str, Union[str, int]]] = None
) -> None:
url = f"{self.CLUSTER_API_PREFIX}/print_jobs/{print_job_uuid}/action/{action}/"
body = json.loads(data).encode() if data else b""
self._manager.put(self._createEmptyRequest(url), body)
## We override _createEmptyRequest in order to add the user credentials.
# \param url: The URL to request
# \param content_type: The type of the body contents.
def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
request = QNetworkRequest(QUrl(self._address + path))
url = QUrl("http://" + self._address + path)
request = QNetworkRequest(url)
if content_type:
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
return request
## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
# \param reply: The reply from the server.
# \return A tuple with a status code and a dictionary.
@staticmethod
def _parseReply(reply: QNetworkReply) -> Tuple[int, Dict[str, Any]]:
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
try:
response = bytes(reply.readAll()).decode()
return status_code, json.loads(response)
except (UnicodeDecodeError, JSONDecodeError, ValueError) as err:
Logger.logException("e", "Could not parse the cluster response: %s", err)
return status_code, {"errors": [err]}
## Parses the given models and calls the correct callback depending on the result.
# \param response: The response from the server, after being converted to a dict.
# \param on_finished: The callback in case the response is successful.
# \param model_class: The type of the model to convert the response to. It may either be a single record or a list.
def _parseModels(self, response: Dict[str, Any],
on_finished: Union[Callable[[ClusterApiClientModel], Any],
Callable[[List[ClusterApiClientModel]], Any]],
model_class: Type[ClusterApiClientModel]) -> None:
if isinstance(response, list):
results = [model_class(**c) for c in response] # type: List[ClusterApiClientModel]
on_finished_list = cast(Callable[[List[ClusterApiClientModel]], Any], on_finished)
on_finished_list(results)
else:
result = model_class(**response) # type: ClusterApiClientModel
on_finished_item = cast(Callable[[ClusterApiClientModel], Any], on_finished)
on_finished_item(result)
## Creates a callback function so that it includes the parsing of the response into the correct model.
# The callback is added to the 'finished' signal of the reply.
# \param reply: The reply that should be listened to.
# \param on_finished: The callback in case the response is successful.
def _addCallback(self, reply: QNetworkReply, on_finished: Callable) -> None:
def _addCallback(self,
reply: QNetworkReply,
on_finished: Union[Callable[[ClusterApiClientModel], Any],
Callable[[List[ClusterApiClientModel]], Any]],
model: Type[ClusterApiClientModel],
) -> None:
def parse() -> None:
# Don't try to parse the reply if we didn't get one
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) is None:
return
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
response = bytes(reply.readAll()).decode()
status_code, response = self._parseReply(reply)
self._anti_gc_callbacks.remove(parse)
on_finished(int(status_code), response)
if on_finished:
self._parseModels(response, on_finished, model)
return
self._anti_gc_callbacks.append(parse)
reply.finished.connect(parse)
if on_finished:
reply.finished.connect(parse)