Download packages to be installed and show a message with progress.

CURA-6983
This commit is contained in:
Nino van Hooff 2020-01-08 11:27:56 +01:00
parent 883243b533
commit 84e676c1f8
4 changed files with 133 additions and 7 deletions

View file

@ -1,5 +1,5 @@
import os
from typing import Optional
from typing import Optional, Dict
from PyQt5.QtCore import QObject
@ -15,7 +15,7 @@ class DiscrepanciesPresenter(QObject):
def __init__(self, app: QtApplication):
super().__init__(app)
self.packageMutations = Signal() # {"SettingsGuide" : "install", "PrinterSettings" : "uninstall"}
self.packageMutations = Signal() # Emits SubscribedPackagesModel
self._app = app
self._dialog = None # type: Optional[QObject]
@ -28,6 +28,5 @@ class DiscrepanciesPresenter(QObject):
def _onConfirmClicked(self, model: SubscribedPackagesModel):
# For now, all packages presented to the user should be installed.
# Later, we will support uninstall ?or ignoring? of a certain package
choices = {item["package_id"]: "install" for item in model.items}
self.packageMutations.emit(choices)
# Later, we might remove items for which the user unselected the package
self.packageMutations.emit(model)

View file

@ -0,0 +1,114 @@
import os
import tempfile
from functools import reduce
from typing import Dict, List, Optional
from PyQt5.QtNetwork import QNetworkReply
from UM import i18n_catalog
from UM.Logger import Logger
from UM.Message import Message
from UM.Signal import Signal
from UM.TaskManagement.HttpRequestManager import HttpRequestManager
from cura.CuraApplication import CuraApplication
from plugins.Toolbox.src.UltimakerCloudScope import UltimakerCloudScope
from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
## Downloads a set of packages from the Ultimaker Cloud Marketplace
# use download() exactly once: should not be used for multiple sets of downloads since this class contains state
class DownloadPresenter:
def __init__(self, app: CuraApplication):
# Emits (Dict[str, str], List[str]) # (success_items, error_items)
# Dict{success_package_id, temp_file_path}
# List[errored_package_id]
self.done = Signal()
self._app = app
self._scope = UltimakerCloudScope(app)
self._started = False
self._progress_message = None # type: Optional[Message]
self._progress = {} # type: Dict[str, Dict[str, int]] # package_id, Dict
self._error = [] # type: List[str] # package_id
def download(self, model: SubscribedPackagesModel):
if self._started:
Logger.error("Download already started. Create a new %s instead", self.__class__.__name__)
return
manager = HttpRequestManager.getInstance()
for item in model.items:
package_id = item["package_id"]
self._progress[package_id] = {
"received": 0,
"total": 1 # make sure this is not considered done yet. Also divByZero-safe
}
manager.get(
item["download_url"],
callback = lambda reply: self._onFinished(package_id, reply),
download_progress_callback = lambda rx, rt: self._onProgress(package_id, rx, rt),
error_callback = lambda rx, rt: self._onProgress(package_id, rx, rt),
scope = self._scope)
self._started = True
self._showProgressMessage()
def _showProgressMessage(self):
self._progress_message = Message(i18n_catalog.i18nc(
"@info:generic",
"\nSyncing..."),
lifetime = 0,
progress = 0.0,
title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", ))
self._progress_message.show()
def _onFinished(self, package_id: str, reply: QNetworkReply):
self._progress[package_id]["received"] = self._progress[package_id]["total"]
file_path = self._getTempFile(package_id)
try:
with open(file_path) as temp_file:
# todo buffer this
temp_file.write(reply.readAll())
except IOError:
self._onError(package_id)
self._checkDone()
def _onProgress(self, package_id: str, rx: int, rt: int):
self._progress[package_id]["received"] = rx
self._progress[package_id]["total"] = rt
received = 0
total = 0
for item in self._progress.values():
received += item["received"]
total += item["total"]
self._progress_message.setProgress(100.0 * (received / total)) # [0 .. 100] %
self._checkDone()
def _onError(self, package_id: str):
self._progress.pop(package_id)
self._error.append(package_id)
self._checkDone()
def _checkDone(self) -> bool:
for item in self._progress.values():
if item["received"] != item["total"] or item["total"] == -1:
return False
success_items = {package_id : self._getTempFile(package_id) for package_id in self._progress.keys()}
error_items = [package_id for package_id in self._error]
self._progress_message.hide()
self.done.emit(success_items, error_items)
def _getTempFile(self, package_id: str) -> str:
temp_dir = tempfile.gettempdir()
return os.path.join(temp_dir, package_id)

View file

@ -33,7 +33,13 @@ class SubscribedPackagesModel(ListModel):
for item in self._metadata:
if item["package_id"] not in self._discrepancies:
continue
package = {"package_id": item["package_id"], "name": item["display_name"], "sdk_versions": item["sdk_versions"]}
package = {
"package_id": item["package_id"],
"name": item["display_name"],
"sdk_versions": item["sdk_versions"],
"download_url": item["download_url"],
"md5_hash": item["md5_hash"],
}
if self._sdk_version not in item["sdk_versions"]:
package.update({"is_compatible": False})
else:

View file

@ -3,6 +3,7 @@ from UM.PluginRegistry import PluginRegistry
from cura.CuraApplication import CuraApplication
from plugins.Toolbox import CloudPackageChecker
from plugins.Toolbox.src.CloudSync.DiscrepanciesPresenter import DiscrepanciesPresenter
from plugins.Toolbox.src.CloudSync.DownloadPresenter import DownloadPresenter
from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
@ -13,7 +14,7 @@ from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPack
# - DiscrepanciesPresenter shows a list of packages to be added or removed to the user. It emits the `packageMutations`
# the user selected to be performed
# - The SyncOrchestrator uses PackageManager to remove local packages the users wants to see removed
# - The DownloadPresenter shows a download progress dialog
# - The DownloadPresenter shows a download progress dialog. It emits A tuple of succeeded and failed downloads
# - The LicencePresenter extracts licences from the downloaded packages and presents a licence for each package to
# - be installed. It emits the `licenceAnswers` {'packageId' : bool} for accept or declines
# - The CloudPackageManager removes the declined packages from the account
@ -28,7 +29,13 @@ class SyncOrchestrator(Extension):
self._checker.discrepancies.connect(self._onDiscrepancies)
self._discrepanciesPresenter = DiscrepanciesPresenter(app)
self._discrepanciesPresenter.packageMutations.connect(self._onPackageMutations)
self._downloadPresenter = DownloadPresenter(app)
def _onDiscrepancies(self, model: SubscribedPackagesModel):
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
self._discrepanciesPresenter.present(plugin_path, model)
def _onPackageMutations(self, mutations: SubscribedPackagesModel):
self._downloadPresenter.download(mutations)