mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-08 07:27:29 -06:00
Download packages to be installed and show a message with progress.
CURA-6983
This commit is contained in:
parent
883243b533
commit
84e676c1f8
4 changed files with 133 additions and 7 deletions
|
@ -1,5 +1,5 @@
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
from typing import Optional, Dict
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject
|
from PyQt5.QtCore import QObject
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class DiscrepanciesPresenter(QObject):
|
||||||
def __init__(self, app: QtApplication):
|
def __init__(self, app: QtApplication):
|
||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
|
|
||||||
self.packageMutations = Signal() # {"SettingsGuide" : "install", "PrinterSettings" : "uninstall"}
|
self.packageMutations = Signal() # Emits SubscribedPackagesModel
|
||||||
|
|
||||||
self._app = app
|
self._app = app
|
||||||
self._dialog = None # type: Optional[QObject]
|
self._dialog = None # type: Optional[QObject]
|
||||||
|
@ -28,6 +28,5 @@ class DiscrepanciesPresenter(QObject):
|
||||||
|
|
||||||
def _onConfirmClicked(self, model: SubscribedPackagesModel):
|
def _onConfirmClicked(self, model: SubscribedPackagesModel):
|
||||||
# For now, all packages presented to the user should be installed.
|
# For now, all packages presented to the user should be installed.
|
||||||
# Later, we will support uninstall ?or ignoring? of a certain package
|
# Later, we might remove items for which the user unselected the package
|
||||||
choices = {item["package_id"]: "install" for item in model.items}
|
self.packageMutations.emit(model)
|
||||||
self.packageMutations.emit(choices)
|
|
||||||
|
|
114
plugins/Toolbox/src/CloudSync/DownloadPresenter.py
Normal file
114
plugins/Toolbox/src/CloudSync/DownloadPresenter.py
Normal 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)
|
||||||
|
|
|
@ -33,7 +33,13 @@ class SubscribedPackagesModel(ListModel):
|
||||||
for item in self._metadata:
|
for item in self._metadata:
|
||||||
if item["package_id"] not in self._discrepancies:
|
if item["package_id"] not in self._discrepancies:
|
||||||
continue
|
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"]:
|
if self._sdk_version not in item["sdk_versions"]:
|
||||||
package.update({"is_compatible": False})
|
package.update({"is_compatible": False})
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -3,6 +3,7 @@ from UM.PluginRegistry import PluginRegistry
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from plugins.Toolbox import CloudPackageChecker
|
from plugins.Toolbox import CloudPackageChecker
|
||||||
from plugins.Toolbox.src.CloudSync.DiscrepanciesPresenter import DiscrepanciesPresenter
|
from plugins.Toolbox.src.CloudSync.DiscrepanciesPresenter import DiscrepanciesPresenter
|
||||||
|
from plugins.Toolbox.src.CloudSync.DownloadPresenter import DownloadPresenter
|
||||||
from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
|
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`
|
# - 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 user selected to be performed
|
||||||
# - The SyncOrchestrator uses PackageManager to remove local packages the users wants to see removed
|
# - 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
|
# - 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
|
# - be installed. It emits the `licenceAnswers` {'packageId' : bool} for accept or declines
|
||||||
# - The CloudPackageManager removes the declined packages from the account
|
# - The CloudPackageManager removes the declined packages from the account
|
||||||
|
@ -28,7 +29,13 @@ class SyncOrchestrator(Extension):
|
||||||
self._checker.discrepancies.connect(self._onDiscrepancies)
|
self._checker.discrepancies.connect(self._onDiscrepancies)
|
||||||
|
|
||||||
self._discrepanciesPresenter = DiscrepanciesPresenter(app)
|
self._discrepanciesPresenter = DiscrepanciesPresenter(app)
|
||||||
|
self._discrepanciesPresenter.packageMutations.connect(self._onPackageMutations)
|
||||||
|
|
||||||
|
self._downloadPresenter = DownloadPresenter(app)
|
||||||
|
|
||||||
def _onDiscrepancies(self, model: SubscribedPackagesModel):
|
def _onDiscrepancies(self, model: SubscribedPackagesModel):
|
||||||
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||||
self._discrepanciesPresenter.present(plugin_path, model)
|
self._discrepanciesPresenter.present(plugin_path, model)
|
||||||
|
|
||||||
|
def _onPackageMutations(self, mutations: SubscribedPackagesModel):
|
||||||
|
self._downloadPresenter.download(mutations)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue