mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Move HttpRequestManager to Uranium
CURA-6387
This commit is contained in:
parent
6fbce74523
commit
55d31b9846
4 changed files with 14 additions and 353 deletions
|
@ -130,7 +130,6 @@ from . import CameraAnimation
|
|||
from . import CuraActions
|
||||
from . import PrintJobPreviewImageProvider
|
||||
|
||||
from cura.TaskManagement.HttpNetworkRequestManager import HttpNetworkRequestManager
|
||||
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
||||
|
||||
from cura import ApplicationMetadata, UltimakerCloudAuthentication
|
||||
|
@ -204,8 +203,6 @@ class CuraApplication(QtApplication):
|
|||
self.empty_quality_container = None # type: EmptyInstanceContainer
|
||||
self.empty_quality_changes_container = None # type: EmptyInstanceContainer
|
||||
|
||||
self._http_network_request_manager = HttpNetworkRequestManager(parent = self)
|
||||
|
||||
self._material_manager = None
|
||||
self._machine_manager = None
|
||||
self._extruder_manager = None
|
||||
|
@ -892,9 +889,6 @@ class CuraApplication(QtApplication):
|
|||
# Hide the splash screen
|
||||
self.closeSplash()
|
||||
|
||||
def getHttpNetworkRequestManager(self) -> "HttpNetworkRequestManager":
|
||||
return self._http_network_request_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getDiscoveredPrintersModel(self, *args) -> "DiscoveredPrintersModel":
|
||||
return self._discovered_printer_model
|
||||
|
|
|
@ -1,333 +0,0 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from collections import deque
|
||||
from threading import RLock
|
||||
import uuid
|
||||
from typing import Callable, Deque, Dict, Set, Union, Optional
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl, Qt
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
#
|
||||
# This is an internal data class which holds all data regarding a network request.
|
||||
# - request_id: A unique ID that's generated for each request.
|
||||
# - http_method: The HTTP method to use for this request, e.g. GET, PUT, POST, etc.
|
||||
# - request: The QNetworkRequest object that's created for this request
|
||||
# - data (optional): The data in binary form that needs to be sent.
|
||||
# - callback (optional): The callback function that will be triggered when the request is finished.
|
||||
# - error_callback (optional): The callback function for handling errors.
|
||||
# - download_progress_callback (optional): The callback function for handling download progress.
|
||||
# - upload_progress_callback (optional): The callback function for handling upload progress.
|
||||
# - reply: The QNetworkReply for this request. It will only present after this request gets processed.
|
||||
#
|
||||
class HttpNetworkRequestData:
|
||||
def __init__(self, request_id: str,
|
||||
http_method: str, request: "QNetworkRequest",
|
||||
data: Optional[Union[bytes, bytearray]] = None,
|
||||
callback: Optional[Callable[["QNetworkReply"], None]] = None,
|
||||
error_callback: Optional[Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
|
||||
download_progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
upload_progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
reply: Optional["QNetworkReply"] = None) -> None:
|
||||
self._request_id = request_id
|
||||
self.http_method = http_method.lower()
|
||||
self.request = request
|
||||
self.data = data
|
||||
self.callback = callback
|
||||
self.error_callback = error_callback
|
||||
self.download_progress_callback = download_progress_callback
|
||||
self.upload_progress_callback = upload_progress_callback
|
||||
self.reply = reply
|
||||
|
||||
@property
|
||||
def request_id(self) -> str:
|
||||
return self._request_id
|
||||
|
||||
# Since Qt 5.12, pyqtSignal().connect() will return a Connection instance that represents a connection. This
|
||||
# Connection instance can later be used to disconnect for cleanup purpose. We are using Qt 5.10 and this feature
|
||||
# is not available yet, and I'm not sure if disconnecting a lambda can potentially cause issues. For this reason,
|
||||
# I'm using the following facade callback functions to handle the lambda function cases.
|
||||
def onCallback(self, reply: "QNetworkReply") -> None:
|
||||
self.callback(reply)
|
||||
|
||||
def onErrorCallback(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
self.error_callback(reply, error)
|
||||
|
||||
def onDownloadProgressCallback(self, bytes_received: int, bytes_total: int) -> None:
|
||||
self.download_progress_callback(bytes_received, bytes_total)
|
||||
|
||||
def onUploadProgressCallback(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
self.upload_progress_callback(bytes_sent, bytes_total)
|
||||
|
||||
def __str__(self) -> str:
|
||||
data = "no-data"
|
||||
if self.data:
|
||||
data = str(self.data[:10])
|
||||
if len(self.data) > 10:
|
||||
data += "..."
|
||||
|
||||
return "request[{id}][{method}][{url}][{data}]".format(id = self._request_id[:8],
|
||||
method = self.http_method,
|
||||
url = self.request.url(),
|
||||
data = data)
|
||||
|
||||
|
||||
#
|
||||
# A dedicated manager that processes and schedules HTTP requests. It provides public APIs for issuing HTTP requests
|
||||
# and the results, successful or not, will be communicated back via callback functions. For each request, 2 callback
|
||||
# functions can be optionally specified:
|
||||
#
|
||||
# - callback: This function will be invoked when a request finishes. (bound to QNetworkReply.finished signal)
|
||||
# Its signature should be "def callback(QNetworkReply) -> None" or other compatible form.
|
||||
#
|
||||
# - error_callback: This function will be invoked when a request fails. (bound to QNetworkReply.error signal)
|
||||
# Its signature should be "def callback(QNetworkReply, QNetworkReply.NetworkError) -> None" or other compatible
|
||||
# form.
|
||||
#
|
||||
# - download_progress_callback: This function will be invoked whenever the download progress changed. (bound to
|
||||
# QNetworkReply.downloadProgress signal)
|
||||
# Its signature should be "def callback(bytesReceived: int, bytesTotal: int) -> None" or other compatible form.
|
||||
#
|
||||
# - upload_progress_callback: This function will be invoked whenever the upload progress changed. (bound to
|
||||
# QNetworkReply.downloadProgress signal)
|
||||
# Its signature should be "def callback(bytesSent: int, bytesTotal: int) -> None" or other compatible form.
|
||||
#
|
||||
class HttpNetworkRequestManager(QObject):
|
||||
|
||||
def __init__(self, max_concurrent_requests: int = 10, parent: Optional["QObject"] = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
self._application = CuraApplication.getInstance()
|
||||
|
||||
self._network_manager = QNetworkAccessManager(self)
|
||||
|
||||
# Max number of concurrent requests that can be issued
|
||||
self._max_concurrent_requests = max_concurrent_requests
|
||||
|
||||
# A FIFO queue for the pending requests.
|
||||
self._request_queue = deque() # type: Deque[HttpNetworkRequestData]
|
||||
|
||||
# A set of all currently in progress requests
|
||||
self._current_requests = set() # type: Set[HttpNetworkRequestData]
|
||||
self._request_lock = RLock()
|
||||
self._process_requests_scheduled = False
|
||||
|
||||
# Public API for creating an HTTP GET request.
|
||||
# Returns an HttpNetworkRequestData instance that represents this request.
|
||||
def get(self, url: str,
|
||||
headers_dict: Optional[Dict[str, str]] = None,
|
||||
callback: Optional[Callable[["QNetworkReply"], None]] = None,
|
||||
error_callback: Optional[Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
|
||||
download_progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
upload_progress_callback: Optional[Callable[[int, int], None]] = None) -> "HttpNetworkRequestData":
|
||||
return self._createRequest("get", url, headers_dict = headers_dict,
|
||||
callback = callback, error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback,
|
||||
upload_progress_callback = upload_progress_callback)
|
||||
|
||||
# Public API for creating an HTTP PUT request.
|
||||
# Returns an HttpNetworkRequestData instance that represents this request.
|
||||
def put(self, url: str,
|
||||
headers_dict: Optional[Dict[str, str]] = None,
|
||||
data: Optional[Union[bytes, bytearray]] = None,
|
||||
callback: Optional[Callable[["QNetworkReply"], None]] = None,
|
||||
error_callback: Optional[Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
|
||||
download_progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
upload_progress_callback: Optional[Callable[[int, int], None]] = None) -> "HttpNetworkRequestData":
|
||||
return self._createRequest("put", url, headers_dict = headers_dict, data = data,
|
||||
callback = callback, error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback,
|
||||
upload_progress_callback = upload_progress_callback)
|
||||
|
||||
# Public API for creating an HTTP POST request. Returns a unique request ID for this request.
|
||||
# Returns an HttpNetworkRequestData instance that represents this request.
|
||||
def post(self, url: str,
|
||||
headers_dict: Optional[Dict[str, str]] = None,
|
||||
data: Optional[Union[bytes, bytearray]] = None,
|
||||
callback: Optional[Callable[["QNetworkReply"], None]] = None,
|
||||
error_callback: Optional[Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
|
||||
download_progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
upload_progress_callback: Optional[Callable[[int, int], None]] = None) -> "HttpNetworkRequestData":
|
||||
return self._createRequest("post", url, headers_dict = headers_dict, data = data,
|
||||
callback = callback, error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback,
|
||||
upload_progress_callback = upload_progress_callback)
|
||||
|
||||
# Public API for aborting a given HttpNetworkRequestData. If the request is not pending or in progress, nothing
|
||||
# will be done.
|
||||
def abortRequest(self, request: "HttpNetworkRequestData") -> None:
|
||||
with self._request_lock:
|
||||
# If the request is currently pending, just remove it from the pending queue.
|
||||
if request in self._request_queue:
|
||||
self._request_queue.remove(request)
|
||||
|
||||
# If the request is currently in progress, abort it.
|
||||
if request in self._current_requests:
|
||||
request.reply.abort()
|
||||
Logger.log("d", "%s aborted", request)
|
||||
|
||||
# This function creates a HttpNetworkRequestData with the given data and puts it into the pending request queue.
|
||||
# If no request processing call has been scheduled, it will schedule it too.
|
||||
# Returns an HttpNetworkRequestData instance that represents this request.
|
||||
def _createRequest(self, http_method: str, url: str,
|
||||
headers_dict: Optional[Dict[str, str]] = None,
|
||||
data: Optional[Union[bytes, bytearray]] = None,
|
||||
callback: Optional[Callable[["QNetworkReply"], None]] = None,
|
||||
error_callback: Optional[Callable[["QNetworkReply", "QNetworkReply.NetworkError"], None]] = None,
|
||||
download_progress_callback: Optional[Callable[[int, int], None]] = None,
|
||||
upload_progress_callback: Optional[Callable[[int, int], None]] = None) -> "HttpNetworkRequestData":
|
||||
request = QNetworkRequest(QUrl(url))
|
||||
|
||||
# Make sure that Qt handles redirects
|
||||
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
||||
# Patch for Qt 5.6-5.8
|
||||
request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
|
||||
# Patch for Qt 5.9+
|
||||
request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
||||
|
||||
# Set headers
|
||||
if headers_dict is not None:
|
||||
for key, value in headers_dict.items():
|
||||
if isinstance(key, str):
|
||||
key = key.encode("utf-8")
|
||||
if isinstance(value, str):
|
||||
value = value.encode("utf-8")
|
||||
request.setRawHeader(key, value)
|
||||
|
||||
# Generate a unique request ID
|
||||
request_id = uuid.uuid4().hex
|
||||
|
||||
# Create the request data
|
||||
request_data = HttpNetworkRequestData(request_id,
|
||||
http_method = http_method,
|
||||
request = request,
|
||||
data = data,
|
||||
callback = callback,
|
||||
error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback,
|
||||
upload_progress_callback = upload_progress_callback)
|
||||
|
||||
with self._request_lock:
|
||||
Logger.log("d", "%s has been queued", request_data)
|
||||
self._request_queue.append(request_data)
|
||||
|
||||
# Schedule a call to process pending requests in the queue
|
||||
if not self._process_requests_scheduled:
|
||||
self._application.callLater(self._processRequestsInQueue)
|
||||
self._process_requests_scheduled = True
|
||||
Logger.log("d", "process requests call has been scheduled")
|
||||
|
||||
return request_data
|
||||
|
||||
# Processes the next request in the pending queue. Stops if there is no more pending requests. It also stops if
|
||||
# the maximum number of concurrent requests has been reached.
|
||||
def _processRequestsInQueue(self) -> None:
|
||||
with self._request_lock:
|
||||
# do nothing if there's no more requests to process
|
||||
if not self._request_queue:
|
||||
self._process_requests_scheduled = False
|
||||
Logger.log("d", "No more requests to process, stop")
|
||||
return
|
||||
|
||||
# do not exceed the max request limit
|
||||
if len(self._current_requests) >= self._max_concurrent_requests:
|
||||
self._process_requests_scheduled = False
|
||||
Logger.log("d", "The in-progress requests has reached the limit %s, stop",
|
||||
self._max_concurrent_requests)
|
||||
return
|
||||
|
||||
# fetch the next request and process
|
||||
next_request_data = self._request_queue.popleft()
|
||||
self._processRequest(next_request_data)
|
||||
|
||||
# Processes the given HttpNetworkRequestData by issuing the request using QNetworkAccessManager and moves the
|
||||
# request into the currently in-progress list.
|
||||
def _processRequest(self, request_data: "HttpNetworkRequestData") -> None:
|
||||
Logger.log("d", "Start processing %s", request_data)
|
||||
|
||||
# get the right http_method function and prepare arguments.
|
||||
method = getattr(self._network_manager, request_data.http_method)
|
||||
args = [request_data.request]
|
||||
if request_data.data is not None:
|
||||
args.append(request_data.data)
|
||||
|
||||
# issue the request and add the reply into the currently in-progress requests set
|
||||
reply = method(*args)
|
||||
request_data.reply = reply
|
||||
|
||||
# connect callback signals
|
||||
reply.error.connect(lambda err, rd = request_data: self._onRequestError(rd, err), type = Qt.QueuedConnection)
|
||||
reply.finished.connect(lambda rd = request_data: self._onRequestFinished(rd), type = Qt.QueuedConnection)
|
||||
if request_data.download_progress_callback is not None:
|
||||
reply.downloadProgress.connect(request_data.onDownloadProgressCallback, type = Qt.QueuedConnection)
|
||||
if request_data.upload_progress_callback is not None:
|
||||
reply.uploadProgress.connect(request_data.onUploadProgressCallback, type = Qt.QueuedConnection)
|
||||
|
||||
with self._request_lock:
|
||||
self._current_requests.add(request_data)
|
||||
|
||||
def _onRequestError(self, request_data: "HttpNetworkRequestData", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.log("d", "%s got an error %s, %s", request_data, error, request_data.reply.errorString())
|
||||
with self._request_lock:
|
||||
# safeguard: make sure that we have the reply in the currently in-progress requests set
|
||||
if request_data not in self._current_requests:
|
||||
# TODO: ERROR, should not happen
|
||||
Logger.log("e", "%s not found in the in-progress set", request_data)
|
||||
pass
|
||||
|
||||
# disconnect callback signals
|
||||
if request_data.reply is not None:
|
||||
if request_data.download_progress_callback is not None:
|
||||
request_data.reply.downloadProgress.disconnect(request_data.onDownloadProgressCallback)
|
||||
if request_data.upload_progress_callback is not None:
|
||||
request_data.reply.uploadProgress.disconnect(request_data.onUploadProgressCallback)
|
||||
|
||||
self._current_requests.remove(request_data)
|
||||
|
||||
# schedule the error callback if there is one
|
||||
if request_data.error_callback is not None:
|
||||
Logger.log("d", "%s error callback scheduled", request_data)
|
||||
self._application.callLater(request_data.error_callback, request_data.reply, error)
|
||||
|
||||
# continue to process the next request
|
||||
self._processRequestsInQueue()
|
||||
|
||||
def _onRequestFinished(self, request_data: "HttpNetworkRequestData") -> None:
|
||||
# Do nothing if a request was aborted.
|
||||
if request_data.reply.error() == QNetworkReply.OperationCanceledError:
|
||||
Logger.log("d", "%s was aborted, do nothing", request_data)
|
||||
return
|
||||
|
||||
Logger.log("d", "%s finished", request_data)
|
||||
with self._request_lock:
|
||||
# safeguard: ake sure that we have the reply in the currently in-progress requests set.
|
||||
if request_data not in self._current_requests:
|
||||
# This can happen if a request has been aborted. The finished() signal will still be triggered at the
|
||||
# end. In this case, do nothing with this request.
|
||||
Logger.log("e", "%s not found in the in-progress set", request_data)
|
||||
else:
|
||||
# disconnect callback signals
|
||||
if request_data.reply is not None:
|
||||
if request_data.download_progress_callback is not None:
|
||||
request_data.reply.downloadProgress.disconnect(request_data.onDownloadProgressCallback)
|
||||
if request_data.upload_progress_callback is not None:
|
||||
request_data.reply.uploadProgress.disconnect(request_data.onUploadProgressCallback)
|
||||
|
||||
self._current_requests.remove(request_data)
|
||||
|
||||
# schedule the callback if there is one
|
||||
if request_data.callback is not None:
|
||||
Logger.log("d", "%s callback scheduled", request_data)
|
||||
self._application.callLater(request_data.callback, request_data.reply)
|
||||
|
||||
# continue to process the next request
|
||||
self._processRequestsInQueue()
|
||||
|
||||
|
||||
__all__ = ["HttpNetworkRequestData", "HttpNetworkRequestManager"]
|
|
@ -261,7 +261,7 @@ class SliceInfo(QObject, Extension):
|
|||
binary_data = json.dumps(data).encode("utf-8")
|
||||
|
||||
# Send slice info non-blocking
|
||||
network_manager = self._application.getHttpNetworkRequestManager()
|
||||
network_manager = self._application.getHttpRequestManager()
|
||||
network_manager.post(self.info_url, data = binary_data,
|
||||
callback = self._onRequestFinished, error_callback = self._onRequestError)
|
||||
except Exception:
|
||||
|
|
|
@ -27,8 +27,8 @@ from .PackagesModel import PackagesModel
|
|||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.TaskManagement.HttpRequestManager import HttpRequestData
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.TaskManagement.HttpNetworkRequestManager import HttpNetworkRequestData
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -46,7 +46,7 @@ class Toolbox(QObject, Extension):
|
|||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
self._download_request_data = None # type: Optional[HttpNetworkRequestData]
|
||||
self._download_request_data = None # type: Optional[HttpRequestData]
|
||||
self._download_progress = 0 # type: float
|
||||
self._is_downloading = False # type: bool
|
||||
self._request_headers = dict() # type: Dict[str, str]
|
||||
|
@ -162,8 +162,8 @@ class Toolbox(QObject, Extension):
|
|||
url = "{base_url}/packages/{package_id}/ratings".format(base_url = self._api_url, package_id = package_id)
|
||||
data = "{\"data\": {\"cura_version\": \"%s\", \"rating\": %i}}" % (Version(self._application.getVersion()), rating)
|
||||
|
||||
self._application.getHttpNetworkRequestManager().put(url, headers_dict = self._request_headers,
|
||||
data = data.encode())
|
||||
self._application.getHttpRequestManager().put(url, headers_dict = self._request_headers,
|
||||
data = data.encode())
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getLicenseDialogPluginName(self) -> str:
|
||||
|
@ -563,10 +563,10 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
callback = lambda r, rt = request_type: self._onAuthorsDataRequestFinished(rt, r)
|
||||
error_callback = lambda r, e, rt = request_type: self._onAuthorsDataRequestFinished(rt, r, e)
|
||||
self._application.getHttpNetworkRequestManager().get(url,
|
||||
headers_dict = self._request_headers,
|
||||
callback = callback,
|
||||
error_callback = error_callback)
|
||||
self._application.getHttpRequestManager().get(url,
|
||||
headers_dict = self._request_headers,
|
||||
callback = callback,
|
||||
error_callback = error_callback)
|
||||
|
||||
def _onAuthorsDataRequestFinished(self, request_type: str,
|
||||
reply: "QNetworkReply",
|
||||
|
@ -618,10 +618,10 @@ class Toolbox(QObject, Extension):
|
|||
callback = lambda r: self._onDownloadFinished(r)
|
||||
error_callback = lambda r, e: self._onDownloadFailed(r, e)
|
||||
download_progress_callback = self._onDownloadProgress
|
||||
request_data = self._application.getHttpNetworkRequestManager().get(url, headers_dict = self._request_headers,
|
||||
callback = callback,
|
||||
error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback)
|
||||
request_data = self._application.getHttpRequestManager().get(url, headers_dict = self._request_headers,
|
||||
callback = callback,
|
||||
error_callback = error_callback,
|
||||
download_progress_callback = download_progress_callback)
|
||||
|
||||
self._download_request_data = request_data
|
||||
self.setDownloadProgress(0)
|
||||
|
@ -631,7 +631,7 @@ class Toolbox(QObject, Extension):
|
|||
def cancelDownload(self) -> None:
|
||||
Logger.log("i", "User cancelled the download of a package. request %s", self._download_request_data)
|
||||
if self._download_request_data is not None:
|
||||
self._application.getHttpNetworkRequestManager().abortRequest(self._download_request_data)
|
||||
self._application.getHttpRequestManager().abortRequest(self._download_request_data)
|
||||
self._download_request_data = None
|
||||
self.resetDownload()
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue