mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Use network manager in Toolbox
CURA-6387
This commit is contained in:
parent
e98cf83cb3
commit
77511c2590
1 changed files with 116 additions and 101 deletions
|
@ -7,7 +7,7 @@ import tempfile
|
|||
import platform
|
||||
from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
@ -28,6 +28,7 @@ from .SubscribedPackagesModel import SubscribedPackagesModel
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from cura.TaskManagement.HttpNetworkRequestManager import HttpNetworkRequestData
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -45,15 +46,13 @@ class Toolbox(QObject, Extension):
|
|||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
self._download_request = None # type: Optional[QNetworkRequest]
|
||||
self._download_reply = None # type: Optional[QNetworkReply]
|
||||
self._download_request_data = None # type: Optional[HttpNetworkRequestData]
|
||||
self._download_progress = 0 # type: float
|
||||
self._is_downloading = False # type: bool
|
||||
self._network_manager = None # type: Optional[QNetworkAccessManager]
|
||||
self._request_headers = [] # type: List[Tuple[bytes, bytes]]
|
||||
self._request_headers = dict() # type: Dict[str, str]
|
||||
self._updateRequestHeader()
|
||||
|
||||
self._request_urls = {} # type: Dict[str, QUrl]
|
||||
self._request_urls = {} # type: Dict[str, str]
|
||||
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
|
||||
self._old_plugin_ids = set() # type: Set[str]
|
||||
self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
@ -142,20 +141,15 @@ class Toolbox(QObject, Extension):
|
|||
self._fetchPackageData()
|
||||
|
||||
def _updateRequestHeader(self):
|
||||
self._request_headers = [
|
||||
(b"User-Agent",
|
||||
str.encode(
|
||||
"%s/%s (%s %s)" % (
|
||||
self._application.getApplicationName(),
|
||||
self._application.getVersion(),
|
||||
platform.system(),
|
||||
platform.machine(),
|
||||
)
|
||||
))
|
||||
]
|
||||
self._request_headers = {
|
||||
"User-Agent": "%s/%s (%s %s)" % (self._application.getApplicationName(),
|
||||
self._application.getVersion(),
|
||||
platform.system(),
|
||||
platform.machine())
|
||||
}
|
||||
access_token = self._application.getCuraAPI().account.accessToken
|
||||
if access_token:
|
||||
self._request_headers.append((b"Authorization", "Bearer {}".format(access_token).encode()))
|
||||
self._request_headers["Authorization"] = "Bearer {}".format(access_token)
|
||||
|
||||
def _resetUninstallVariables(self) -> None:
|
||||
self._package_id_to_uninstall = None # type: Optional[str]
|
||||
|
@ -165,13 +159,11 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
@pyqtSlot(str, int)
|
||||
def ratePackage(self, package_id: str, rating: int) -> None:
|
||||
url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url = self._api_url, package_id = package_id))
|
||||
|
||||
self._rate_request = QNetworkRequest(url)
|
||||
for header_name, header_value in self._request_headers:
|
||||
cast(QNetworkRequest, self._rate_request).setRawHeader(header_name, header_value)
|
||||
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._rate_reply = cast(QNetworkAccessManager, self._network_manager).put(self._rate_request, data.encode())
|
||||
|
||||
self._application.getHttpNetworkRequestManager().put(url, headers_dict = self._request_headers,
|
||||
data = data.encode())
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getLicenseDialogPluginName(self) -> str:
|
||||
|
@ -213,11 +205,11 @@ class Toolbox(QObject, Extension):
|
|||
installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions)
|
||||
|
||||
self._request_urls = {
|
||||
"authors": QUrl("{base_url}/authors".format(base_url = self._api_url)),
|
||||
"packages": QUrl("{base_url}/packages".format(base_url = self._api_url)),
|
||||
"updates": QUrl("{base_url}/packages/package-updates?installed_packages={query}".format(
|
||||
base_url = self._api_url, query = installed_packages_query)),
|
||||
"subscribed_packages": QUrl(self._api_url_user_packages)
|
||||
"authors": "{base_url}/authors".format(base_url = self._api_url),
|
||||
"packages": "{base_url}/packages".format(base_url = self._api_url),
|
||||
"updates": "{base_url}/packages/package-updates?installed_packages={query}".format(
|
||||
base_url = self._api_url, query = installed_packages_query),
|
||||
"subscribed_packages": self._api_url_user_packages,
|
||||
}
|
||||
|
||||
self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
|
||||
|
@ -226,26 +218,13 @@ class Toolbox(QObject, Extension):
|
|||
# On boot we check which packages have updates.
|
||||
if CuraApplication.getInstance().getPreferences().getValue("info/automatic_update_check") and len(installed_package_ids_with_versions) > 0:
|
||||
# Request the latest and greatest!
|
||||
self._fetchPackageUpdates()
|
||||
self._makeRequestByType("updates")
|
||||
self._fetchUserSubscribedPackages()
|
||||
|
||||
def _prepareNetworkManager(self):
|
||||
if self._network_manager is not None:
|
||||
self._network_manager.finished.disconnect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
|
||||
self._network_manager = QNetworkAccessManager()
|
||||
self._network_manager.finished.connect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccessibleChanged)
|
||||
|
||||
def _fetchPackageUpdates(self):
|
||||
self._prepareNetworkManager()
|
||||
self._makeRequestByType("updates")
|
||||
|
||||
def _fetchPackageData(self):
|
||||
self._prepareNetworkManager()
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
# Make remote requests:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
self._fetchPackageData()
|
||||
# Gather installed packages:
|
||||
self._updateInstalledModels()
|
||||
|
||||
|
@ -254,10 +233,13 @@ class Toolbox(QObject, Extension):
|
|||
self._prepareNetworkManager()
|
||||
self._makeRequestByType("subscribed_packages")
|
||||
|
||||
def _fetchPackageData(self) -> None:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
|
||||
# Displays the toolbox
|
||||
@pyqtSlot()
|
||||
def launch(self) -> None:
|
||||
|
||||
if not self._dialog:
|
||||
self._dialog = self._createDialog("Toolbox.qml")
|
||||
|
||||
|
@ -268,7 +250,6 @@ class Toolbox(QObject, Extension):
|
|||
self._restart()
|
||||
|
||||
self._dialog.show()
|
||||
|
||||
# Apply enabled/disabled state to installed plugins
|
||||
self.enabledChanged.emit()
|
||||
|
||||
|
@ -576,52 +557,85 @@ class Toolbox(QObject, Extension):
|
|||
# Make API Calls
|
||||
# --------------------------------------------------------------------------
|
||||
def _makeRequestByType(self, request_type: str) -> None:
|
||||
Logger.log("d", "Requesting '%s' metadata from server.", request_type)
|
||||
request = QNetworkRequest(self._request_urls[request_type])
|
||||
for header_name, header_value in self._request_headers:
|
||||
request.setRawHeader(header_name, header_value)
|
||||
Logger.log("d", "Requesting [%s] metadata from server.", request_type)
|
||||
self._updateRequestHeader()
|
||||
if self._network_manager:
|
||||
self._network_manager.get(request)
|
||||
url = self._request_urls[request_type]
|
||||
|
||||
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)
|
||||
|
||||
def _onAuthorsDataRequestFinished(self, request_type: str,
|
||||
reply: "QNetworkReply",
|
||||
error: Optional["QNetworkReply.NetworkError"] = None) -> None:
|
||||
if error is not None or reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
Logger.log("w",
|
||||
"Unable to connect with the server, we got a response code %s while trying to connect to %s",
|
||||
reply.attribute(QNetworkRequest.HttpStatusCodeAttribute), reply.url())
|
||||
self.setViewPage("errored")
|
||||
return
|
||||
|
||||
try:
|
||||
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
|
||||
# Check for errors:
|
||||
if "errors" in json_data:
|
||||
for error in json_data["errors"]:
|
||||
Logger.log("e", "%s", error["title"])
|
||||
return
|
||||
|
||||
# Create model and apply metadata:
|
||||
if not self._models[request_type]:
|
||||
Logger.log("e", "Could not find the %s model.", request_type)
|
||||
return
|
||||
|
||||
self._server_response_data[request_type] = json_data["data"]
|
||||
self._models[request_type].setMetadata(self._server_response_data[request_type])
|
||||
|
||||
if request_type == "packages":
|
||||
self._models[request_type].setFilter({"type": "plugin"})
|
||||
self.reBuildMaterialsModels()
|
||||
self.reBuildPluginsModels()
|
||||
elif request_type == "authors":
|
||||
self._models[request_type].setFilter({"package_types": "material"})
|
||||
self._models[request_type].setFilter({"tags": "generic"})
|
||||
|
||||
self.metadataChanged.emit()
|
||||
|
||||
if self.isLoadingComplete():
|
||||
self.setViewPage("overview")
|
||||
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.log("w", "Received invalid JSON for %s.", request_type)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def startDownload(self, url: str) -> None:
|
||||
Logger.log("i", "Attempting to download & install package from %s.", url)
|
||||
url = QUrl(url)
|
||||
self._download_request = QNetworkRequest(url)
|
||||
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
||||
# Patch for Qt 5.6-5.8
|
||||
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
|
||||
# Patch for Qt 5.9+
|
||||
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
||||
for header_name, header_value in self._request_headers:
|
||||
cast(QNetworkRequest, self._download_request).setRawHeader(header_name, header_value)
|
||||
self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request)
|
||||
|
||||
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)
|
||||
|
||||
self._download_request_data = request_data
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(True)
|
||||
cast(QNetworkReply, self._download_reply).downloadProgress.connect(self._onDownloadProgress)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelDownload(self) -> None:
|
||||
Logger.log("i", "User cancelled the download of a package.")
|
||||
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._download_request_data = None
|
||||
self.resetDownload()
|
||||
|
||||
def resetDownload(self) -> None:
|
||||
if self._download_reply:
|
||||
try:
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
except (TypeError, RuntimeError): # Raised when the method is not connected to the signal yet.
|
||||
pass # Don't need to disconnect.
|
||||
try:
|
||||
self._download_reply.abort()
|
||||
except RuntimeError:
|
||||
# In some cases the garbage collector is a bit to agressive, which causes the dowload_reply
|
||||
# to be deleted (especially if the machine has been put to sleep). As we don't know what exactly causes
|
||||
# this (The issue probably lives in the bowels of (py)Qt somewhere), we can only catch and ignore it.
|
||||
pass
|
||||
self._download_reply = None
|
||||
self._download_request = None
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(False)
|
||||
|
||||
|
@ -730,30 +744,31 @@ class Toolbox(QObject, Extension):
|
|||
for package in self._server_response_data["packages"]:
|
||||
self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
|
||||
|
||||
def _onDownloadFinished(self, reply: "QNetworkReply") -> None:
|
||||
self.resetDownload()
|
||||
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s",
|
||||
json.loads(reply.readAll().data().decode("utf-8")))
|
||||
return
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
# Write first and close, otherwise on Windows, it cannot read the file
|
||||
self._temp_plugin_file.write(reply.readAll())
|
||||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
|
||||
def _onDownloadFailed(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", error)
|
||||
|
||||
self.resetDownload()
|
||||
|
||||
def _onDownloadProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
if bytes_total > 0:
|
||||
new_progress = bytes_sent / bytes_total * 100
|
||||
self.setDownloadProgress(new_progress)
|
||||
if bytes_sent == bytes_total:
|
||||
self.setIsDownloading(False)
|
||||
self._download_reply = cast(QNetworkReply, self._download_reply)
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
|
||||
# Check if the download was sucessfull
|
||||
if self._download_reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
|
||||
try:
|
||||
Logger.log("w", "Failed to download package. The following error was returned: %s", json.loads(bytes(self._download_reply.readAll()).decode("utf-8")))
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.logException("w", "Failed to download package and failed to parse a response from it")
|
||||
finally:
|
||||
return
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
# Write first and close, otherwise on Windows, it cannot read the file
|
||||
self._temp_plugin_file.write(cast(QNetworkReply, self._download_reply).readAll())
|
||||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
Logger.log("d", "new download progress %s / %s : %s%%", bytes_sent, bytes_total, new_progress)
|
||||
|
||||
def _onDownloadComplete(self, file_path: str) -> None:
|
||||
Logger.log("i", "Download complete.")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue