Use network manager in Toolbox

CURA-6387
This commit is contained in:
Lipu Fei 2019-03-15 09:56:02 +01:00
parent e98cf83cb3
commit 77511c2590

View file

@ -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._request_headers = {
"User-Agent": "%s/%s (%s %s)" % (self._application.getApplicationName(),
self._application.getVersion(),
platform.system(),
platform.machine(),
)
))
]
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,31 +744,32 @@ class Toolbox(QObject, Extension):
for package in self._server_response_data["packages"]:
self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
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)
def _onDownloadFinished(self, reply: "QNetworkReply") -> None:
self.resetDownload()
# 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:
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(cast(QNetworkReply, self._download_reply).readAll())
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)
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.")
package_info = self._package_manager.getPackageInfo(file_path)