Introduce the SyncOrchestrator

CURA-6983
This commit is contained in:
Nino van Hooff 2020-01-07 14:34:15 +01:00
parent 6eab5e2492
commit 1a816ad010
8 changed files with 84 additions and 21 deletions

View file

@ -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)
} }

View file

@ -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()
} }
} }
} }

View file

@ -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,

View 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)

View file

@ -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:

View 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)

View 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