mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-19 04:37:51 -06:00
Introduce the SyncOrchestrator
CURA-6983
This commit is contained in:
parent
6eab5e2492
commit
1a816ad010
8 changed files with 84 additions and 21 deletions
|
@ -2,7 +2,8 @@
|
||||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from .src import Toolbox
|
from .src import Toolbox
|
||||||
from .src.SubscriptionChecker import SubscriptionChecker
|
from plugins.Toolbox.src.CloudSync.CloudPackageChecker import CloudPackageChecker
|
||||||
|
from .src.CloudSync.SyncOrchestrator import SyncOrchestrator
|
||||||
|
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
|
@ -11,6 +12,5 @@ def getMetaData():
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return {
|
return {
|
||||||
"extension": Toolbox.Toolbox(app),
|
"extension": [Toolbox.Toolbox(app), SyncOrchestrator(app)]
|
||||||
"subscription_checker": SubscriptionChecker(app)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,7 @@ UM.Dialog{
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||||
text: catalog.i18nc("@button", "Next")
|
text: catalog.i18nc("@button", "Next")
|
||||||
|
onClicked: accept()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
import json
|
import json
|
||||||
import os
|
from typing import Optional
|
||||||
from typing import Dict, Optional
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject
|
from PyQt5.QtCore import QObject
|
||||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
|
|
||||||
from UM.Extension import Extension
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.Signal import Signal
|
||||||
from UM.TaskManagement.HttpRequestScope import UltimakerCloudScope
|
from UM.TaskManagement.HttpRequestScope import UltimakerCloudScope
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
from plugins.Toolbox.src.CloudApiModel import CloudApiModel
|
from plugins.Toolbox.src.CloudApiModel import CloudApiModel
|
||||||
from plugins.Toolbox.src.SubscribedPackagesModel import SubscribedPackagesModel
|
from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
|
||||||
from plugins.Toolbox.src.Toolbox import i18n_catalog
|
from plugins.Toolbox.src.Toolbox import i18n_catalog
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionChecker(QObject, Extension):
|
class CloudPackageChecker(QObject):
|
||||||
|
|
||||||
def __init__(self, application: CuraApplication) -> None:
|
def __init__(self, application: CuraApplication) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
self.discrepancies = Signal() # Emits SubscribedPackagesModel
|
||||||
self._application = application # type: CuraApplication
|
self._application = application # type: CuraApplication
|
||||||
self._scope = UltimakerCloudScope(application)
|
self._scope = UltimakerCloudScope(application)
|
||||||
self._model = SubscribedPackagesModel()
|
self._model = SubscribedPackagesModel()
|
||||||
|
@ -39,7 +38,7 @@ class SubscriptionChecker(QObject, Extension):
|
||||||
|
|
||||||
def _fetchUserSubscribedPackages(self):
|
def _fetchUserSubscribedPackages(self):
|
||||||
if self._application.getCuraAPI().account.isLoggedIn:
|
if self._application.getCuraAPI().account.isLoggedIn:
|
||||||
self._getUserPackages("subscribed_packages")
|
self._getUserPackages()
|
||||||
|
|
||||||
def _handleCompatibilityData(self, json_data) -> None:
|
def _handleCompatibilityData(self, json_data) -> None:
|
||||||
user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
|
user_subscribed_packages = [plugin["package_id"] for plugin in json_data]
|
||||||
|
@ -53,9 +52,9 @@ class SubscriptionChecker(QObject, Extension):
|
||||||
self._model.update()
|
self._model.update()
|
||||||
|
|
||||||
if package_discrepancy:
|
if package_discrepancy:
|
||||||
self._handlePackageDiscrepancies(package_discrepancy)
|
self._handlePackageDiscrepancies()
|
||||||
|
|
||||||
def _handlePackageDiscrepancies(self, package_discrepancy):
|
def _handlePackageDiscrepancies(self):
|
||||||
Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
|
Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages")
|
||||||
sync_message = Message(i18n_catalog.i18nc(
|
sync_message = Message(i18n_catalog.i18nc(
|
||||||
"@info:generic",
|
"@info:generic",
|
||||||
|
@ -72,14 +71,10 @@ class SubscriptionChecker(QObject, Extension):
|
||||||
|
|
||||||
def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
|
def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None:
|
||||||
sync_message.hide()
|
sync_message.hide()
|
||||||
compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml"
|
self.discrepancies.emit(self._model)
|
||||||
plugin_path_prefix = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
|
||||||
if plugin_path_prefix:
|
|
||||||
path = os.path.join(plugin_path_prefix, compatibility_dialog_path)
|
|
||||||
self.compatibility_dialog_view = self._application.createQmlComponent(path, {"subscribedPackagesModel": self._model})
|
|
||||||
|
|
||||||
def _getUserPackages(self, request_type: str) -> None:
|
def _getUserPackages(self) -> None:
|
||||||
Logger.log("d", "Requesting [%s] metadata from server.", request_type)
|
Logger.log("d", "Requesting subscribed packages metadata from server.")
|
||||||
url = CloudApiModel.api_url_user_packages
|
url = CloudApiModel.api_url_user_packages
|
||||||
|
|
||||||
self._application.getHttpRequestManager().get(url,
|
self._application.getHttpRequestManager().get(url,
|
33
plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py
Normal file
33
plugins/Toolbox/src/CloudSync/DiscrepanciesPresenter.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
|
||||||
|
from UM.Qt.QtApplication import QtApplication
|
||||||
|
from UM.Signal import Signal
|
||||||
|
from plugins.Toolbox.src.CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
|
||||||
|
|
||||||
|
|
||||||
|
## Shows a list of packages to be added or removed. The user can select which packages to (un)install. The user's
|
||||||
|
# choices are emitted on the `packageMutations` Signal.
|
||||||
|
class DiscrepanciesPresenter(QObject):
|
||||||
|
|
||||||
|
def __init__(self, app: QtApplication):
|
||||||
|
super().__init__(app)
|
||||||
|
|
||||||
|
self.packageMutations = Signal() # {"SettingsGuide" : "install", "PrinterSettings" : "uninstall"}
|
||||||
|
|
||||||
|
self._app = app
|
||||||
|
self._dialog = None # type: Optional[QObject]
|
||||||
|
self._compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml"
|
||||||
|
|
||||||
|
def present(self, plugin_path: str, model: SubscribedPackagesModel):
|
||||||
|
path = os.path.join(plugin_path, self._compatibility_dialog_path)
|
||||||
|
self._dialog = self._app.createQmlComponent(path, {"subscribedPackagesModel": model})
|
||||||
|
self._dialog.accepted.connect(lambda: self._onConfirmClicked(model))
|
||||||
|
|
||||||
|
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)
|
|
@ -33,7 +33,7 @@ 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 = {"name": item["display_name"], "sdk_versions": item["sdk_versions"]}
|
package = {"package_id": item["package_id"], "name": item["display_name"], "sdk_versions": item["sdk_versions"]}
|
||||||
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:
|
34
plugins/Toolbox/src/CloudSync/SyncOrchestrator.py
Normal file
34
plugins/Toolbox/src/CloudSync/SyncOrchestrator.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
from UM.Extension import Extension
|
||||||
|
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.SubscribedPackagesModel import SubscribedPackagesModel
|
||||||
|
|
||||||
|
|
||||||
|
## Orchestrates the synchronizing of packages from the user account to the installed packages
|
||||||
|
# Example flow:
|
||||||
|
# - CloudPackageChecker compares a list of packages the user `subscribed` to in their account
|
||||||
|
# If there are `discrepancies` between the account and locally installed packages, they are emitted
|
||||||
|
# - 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 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
|
||||||
|
# - The SyncOrchestrator uses PackageManager to install the downloaded packages.
|
||||||
|
# - Bliss / profit / done
|
||||||
|
class SyncOrchestrator(Extension):
|
||||||
|
|
||||||
|
def __init__(self, app: CuraApplication):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._checker = CloudPackageChecker(app)
|
||||||
|
self._checker.discrepancies.connect(self._onDiscrepancies)
|
||||||
|
|
||||||
|
self._discrepanciesPresenter = DiscrepanciesPresenter(app)
|
||||||
|
|
||||||
|
def _onDiscrepancies(self, model: SubscribedPackagesModel):
|
||||||
|
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
|
||||||
|
self._discrepanciesPresenter.present(plugin_path, model)
|
0
plugins/Toolbox/src/CloudSync/__init__.py
Normal file
0
plugins/Toolbox/src/CloudSync/__init__.py
Normal file
|
@ -23,7 +23,7 @@ from plugins.Toolbox.src.CloudApiModel import CloudApiModel
|
||||||
|
|
||||||
from .AuthorsModel import AuthorsModel
|
from .AuthorsModel import AuthorsModel
|
||||||
from .PackagesModel import PackagesModel
|
from .PackagesModel import PackagesModel
|
||||||
from .SubscribedPackagesModel import SubscribedPackagesModel
|
from .CloudSync.SubscribedPackagesModel import SubscribedPackagesModel
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.TaskManagement.HttpRequestData import HttpRequestData
|
from UM.TaskManagement.HttpRequestData import HttpRequestData
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue