|
@@ -6,11 +6,15 @@ from time import time
|
|
|
from typing import Callable, List, Type, TypeVar, Union, Optional, Tuple, Dict, Any, cast
|
|
|
|
|
|
from PyQt5.QtCore import QUrl
|
|
|
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
|
|
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
|
|
|
|
|
from UM.Logger import Logger
|
|
|
+from UM.TaskManagement.HttpRequestManager import HttpRequestManager
|
|
|
+from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
|
|
|
from cura.API import Account
|
|
|
+from cura.CuraApplication import CuraApplication
|
|
|
from cura.UltimakerCloud import UltimakerCloudAuthentication
|
|
|
+from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
|
|
|
from .ToolPathUploader import ToolPathUploader
|
|
|
from ..Models.BaseModel import BaseModel
|
|
|
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
|
|
@@ -33,16 +37,20 @@ class CloudApiClient:
|
|
|
CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
|
|
|
CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)
|
|
|
|
|
|
+ DEFAULT_REQUEST_TIMEOUT = 10 # seconds
|
|
|
+
|
|
|
# In order to avoid garbage collection we keep the callbacks in this list.
|
|
|
- _anti_gc_callbacks = [] # type: List[Callable[[], None]]
|
|
|
+ _anti_gc_callbacks = [] # type: List[Callable[[Any], None]]
|
|
|
|
|
|
## Initializes a new cloud API client.
|
|
|
# \param account: The user's account object
|
|
|
# \param on_error: The callback to be called whenever we receive errors from the server.
|
|
|
- def __init__(self, account: Account, on_error: Callable[[List[CloudError]], None]) -> None:
|
|
|
+ def __init__(self, app: CuraApplication, on_error: Callable[[List[CloudError]], None]) -> None:
|
|
|
super().__init__()
|
|
|
- self._manager = QNetworkAccessManager()
|
|
|
- self._account = account
|
|
|
+ self._app = app
|
|
|
+ self._account = app.getCuraAPI().account
|
|
|
+ self._scope = JsonDecoratorScope(UltimakerCloudScope(app))
|
|
|
+ self._http = HttpRequestManager.getInstance()
|
|
|
self._on_error = on_error
|
|
|
self._upload = None # type: Optional[ToolPathUploader]
|
|
|
|
|
@@ -55,16 +63,21 @@ class CloudApiClient:
|
|
|
# \param on_finished: The function to be called after the result is parsed.
|
|
|
def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None:
|
|
|
url = "{}/clusters?status=active".format(self.CLUSTER_API_ROOT)
|
|
|
- reply = self._manager.get(self._createEmptyRequest(url))
|
|
|
- self._addCallback(reply, on_finished, CloudClusterResponse, failed)
|
|
|
+ self._http.get(url,
|
|
|
+ scope = self._scope,
|
|
|
+ callback = self._parseCallback(on_finished, CloudClusterResponse, failed),
|
|
|
+ error_callback = failed,
|
|
|
+ timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
|
|
|
|
|
## Retrieves the status of the given cluster.
|
|
|
# \param cluster_id: The ID of the cluster.
|
|
|
# \param on_finished: The function to be called after the result is parsed.
|
|
|
def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None:
|
|
|
url = "{}/clusters/{}/status".format(self.CLUSTER_API_ROOT, cluster_id)
|
|
|
- reply = self._manager.get(self._createEmptyRequest(url))
|
|
|
- self._addCallback(reply, on_finished, CloudClusterStatus)
|
|
|
+ self._http.get(url,
|
|
|
+ scope = self._scope,
|
|
|
+ callback = self._parseCallback(on_finished, CloudClusterStatus),
|
|
|
+ timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
|
|
|
|
|
## Requests the cloud to register the upload of a print job mesh.
|
|
|
# \param request: The request object.
|
|
@@ -72,9 +85,13 @@ class CloudApiClient:
|
|
|
def requestUpload(self, request: CloudPrintJobUploadRequest,
|
|
|
on_finished: Callable[[CloudPrintJobResponse], Any]) -> None:
|
|
|
url = "{}/jobs/upload".format(self.CURA_API_ROOT)
|
|
|
- body = json.dumps({"data": request.toDict()})
|
|
|
- reply = self._manager.put(self._createEmptyRequest(url), body.encode())
|
|
|
- self._addCallback(reply, on_finished, CloudPrintJobResponse)
|
|
|
+ data = json.dumps({"data": request.toDict()}).encode()
|
|
|
+
|
|
|
+ self._http.put(url,
|
|
|
+ scope = self._scope,
|
|
|
+ data = data,
|
|
|
+ callback = self._parseCallback(on_finished, CloudPrintJobResponse),
|
|
|
+ timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
|
|
|
|
|
## Uploads a print job tool path to the cloud.
|
|
|
# \param print_job: The object received after requesting an upload with `self.requestUpload`.
|
|
@@ -84,7 +101,7 @@ class CloudApiClient:
|
|
|
# \param on_error: A function to be called if the upload fails.
|
|
|
def uploadToolPath(self, print_job: CloudPrintJobResponse, mesh: bytes, on_finished: Callable[[], Any],
|
|
|
on_progress: Callable[[int], Any], on_error: Callable[[], Any]):
|
|
|
- self._upload = ToolPathUploader(self._manager, print_job, mesh, on_finished, on_progress, on_error)
|
|
|
+ self._upload = ToolPathUploader(self._http, print_job, mesh, on_finished, on_progress, on_error)
|
|
|
self._upload.start()
|
|
|
|
|
|
# Requests a cluster to print the given print job.
|
|
@@ -93,8 +110,11 @@ class CloudApiClient:
|
|
|
# \param on_finished: The function to be called after the result is parsed.
|
|
|
def requestPrint(self, cluster_id: str, job_id: str, on_finished: Callable[[CloudPrintResponse], Any]) -> None:
|
|
|
url = "{}/clusters/{}/print/{}".format(self.CLUSTER_API_ROOT, cluster_id, job_id)
|
|
|
- reply = self._manager.post(self._createEmptyRequest(url), b"")
|
|
|
- self._addCallback(reply, on_finished, CloudPrintResponse)
|
|
|
+ self._http.post(url,
|
|
|
+ scope = self._scope,
|
|
|
+ data = b"",
|
|
|
+ callback = self._parseCallback(on_finished, CloudPrintResponse),
|
|
|
+ timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
|
|
|
|
|
## Send a print job action to the cluster for the given print job.
|
|
|
# \param cluster_id: The ID of the cluster.
|
|
@@ -104,7 +124,10 @@ class CloudApiClient:
|
|
|
data: Optional[Dict[str, Any]] = None) -> None:
|
|
|
body = json.dumps({"data": data}).encode() if data else b""
|
|
|
url = "{}/clusters/{}/print_jobs/{}/action/{}".format(self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action)
|
|
|
- self._manager.post(self._createEmptyRequest(url), body)
|
|
|
+ self._http.post(url,
|
|
|
+ scope = self._scope,
|
|
|
+ data = body,
|
|
|
+ timeout = self.DEFAULT_REQUEST_TIMEOUT)
|
|
|
|
|
|
## We override _createEmptyRequest in order to add the user credentials.
|
|
|
# \param url: The URL to request
|
|
@@ -162,13 +185,12 @@ class CloudApiClient:
|
|
|
# \param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either
|
|
|
# a list or a single item.
|
|
|
# \param model: The type of the model to convert the response to.
|
|
|
- def _addCallback(self,
|
|
|
- reply: QNetworkReply,
|
|
|
- on_finished: Union[Callable[[CloudApiClientModel], Any],
|
|
|
- Callable[[List[CloudApiClientModel]], Any]],
|
|
|
- model: Type[CloudApiClientModel],
|
|
|
- on_error: Optional[Callable] = None) -> None:
|
|
|
- def parse() -> None:
|
|
|
+ def _parseCallback(self,
|
|
|
+ on_finished: Union[Callable[[CloudApiClientModel], Any],
|
|
|
+ Callable[[List[CloudApiClientModel]], Any]],
|
|
|
+ model: Type[CloudApiClientModel],
|
|
|
+ on_error: Optional[Callable] = None) -> Callable[[QNetworkReply], None]:
|
|
|
+ def parse(reply: QNetworkReply) -> None:
|
|
|
self._anti_gc_callbacks.remove(parse)
|
|
|
|
|
|
# Don't try to parse the reply if we didn't get one
|
|
@@ -184,6 +206,4 @@ class CloudApiClient:
|
|
|
self._parseModels(response, on_finished, model)
|
|
|
|
|
|
self._anti_gc_callbacks.append(parse)
|
|
|
- reply.finished.connect(parse)
|
|
|
- if on_error is not None:
|
|
|
- reply.error.connect(on_error)
|
|
|
+ return parse
|