From b048c27238ec267e32137ebddbc6c9916d2c762b Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 16 Dec 2019 09:54:49 +0100 Subject: [PATCH 01/22] Added check for discrepancies between Cloud subscribed packages and Cura installed packages CURA-69679 --- plugins/Toolbox/src/Toolbox.py | 54 ++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 27197275b8..4b44ba7268 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -15,6 +15,7 @@ from UM.PluginRegistry import PluginRegistry from UM.Extension import Extension from UM.i18n import i18nCatalog from UM.Version import Version +from UM.Message import Message from cura import ApplicationMetadata from cura import UltimakerCloudAuthentication @@ -61,6 +62,7 @@ class Toolbox(QObject, Extension): "authors": [], "packages": [], "updates": [], + "installed_plugins": [], } # type: Dict[str, List[Any]] # Models: @@ -68,6 +70,7 @@ class Toolbox(QObject, Extension): "authors": AuthorsModel(self), "packages": PackagesModel(self), "updates": PackagesModel(self), + "installed_plugins": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]] self._plugins_showcase_model = PackagesModel(self) @@ -198,6 +201,12 @@ class Toolbox(QObject, Extension): sdk_version = self._sdk_version ) + # https://api.ultimaker.com/cura-packages/v1/ + self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}".format( + cloud_api_root=self._cloud_api_root, + cloud_api_version=self._cloud_api_version, + ) + # We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc. installed_package_ids_with_versions = [":".join(items) for items in self._package_manager.getAllInstalledPackageIdsAndVersions()] @@ -207,15 +216,18 @@ class Toolbox(QObject, Extension): "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)) + base_url = self._api_url, query = installed_packages_query)), + "installed_plugins": QUrl("{base_url}/user/packages".format(base_url=self._api_url_user_packages)) } self._application.getCuraAPI().account.loginStateChanged.connect(self._restart) + self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserInstalledPlugins) # 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._fetchUserInstalledPlugins() def _prepareNetworkManager(self): if self._network_manager is not None: @@ -237,6 +249,11 @@ class Toolbox(QObject, Extension): # Gather installed packages: self._updateInstalledModels() + def _fetchUserInstalledPlugins(self): + self._prepareNetworkManager() + if self._application.getCuraAPI().account.isLoggedIn: + self._makeRequestByType("installed_plugins") + # Displays the toolbox @pyqtSlot() def launch(self) -> None: @@ -540,9 +557,7 @@ class Toolbox(QObject, Extension): @pyqtSlot(str, result = bool) def isEnabled(self, package_id: str) -> bool: - if package_id in self._plugin_registry.getActivePlugins(): - return True - return False + return package_id in self._plugin_registry.getActivePlugins() # Check for plugins that were installed with the old plugin browser def isOldPlugin(self, plugin_id: str) -> bool: @@ -561,10 +576,11 @@ class Toolbox(QObject, Extension): # Make API Calls # -------------------------------------------------------------------------- def _makeRequestByType(self, request_type: str) -> None: - Logger.log("d", "Requesting %s metadata from server.", request_type) + 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) + self._updateRequestHeader() if self._network_manager: self._network_manager.get(request) @@ -646,8 +662,10 @@ class Toolbox(QObject, Extension): Logger.log("e", "Could not find the %s model.", response_type) break - self._server_response_data[response_type] = json_data["data"] - self._models[response_type].setMetadata(self._server_response_data[response_type]) + # Workaround: Do not add Metadata for "installed_plugins" check JUST YET + if response_type != "installed_plugins": + self._server_response_data[response_type] = json_data["data"] + self._models[response_type].setMetadata(self._server_response_data[response_type]) if response_type == "packages": self._models[response_type].setFilter({"type": "plugin"}) @@ -661,9 +679,27 @@ class Toolbox(QObject, Extension): # Tell the package manager that there's a new set of updates available. packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) + elif response_type == "installed_plugins": + user_subscribed = [(plugin['package_id'], plugin['package_version']) for plugin in json_data['data']] + Logger.log("i", "- User is subscribed to {} package(s).".format(len(user_subscribed))) + user_installed = self._package_manager.getUserInstalledPackagesOnMarketplace() + Logger.log("i", "- User has installed locally {} package(s).".format(len(user_installed))) - self.metadataChanged.emit() - + # Check for discrepancies between Cura installed and Cloud subscribed packages + # convert them to set() to check if they are equal + if set(user_installed) != set(user_subscribed): + Logger.log('d', "Mismatch found between Cloud subscribed packages and Cura installed packages") + sync_message = Message(i18n_catalog.i18nc( + "@info:generic", + "\nDo you want to sync material and software packages with your account?"), + lifetime=0, + title=i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", )) + sync_message.addAction("sync", + name=i18n_catalog.i18nc("@action:button", "Sync"), + icon="", + description="Sync your Cloud subscribed packages to your local environment.", + button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) + sync_message.show() if self.isLoadingComplete(): self.setViewPage("overview") From 1f5df8fe790c01766a9fdb21b78bc0a16d5fb341 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 16 Dec 2019 10:43:55 +0100 Subject: [PATCH 02/22] Restore wrongly deleted code CURA-6979 --- plugins/Toolbox/src/Toolbox.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 4b44ba7268..d60b40f91b 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -700,6 +700,9 @@ class Toolbox(QObject, Extension): description="Sync your Cloud subscribed packages to your local environment.", button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) sync_message.show() + + self.metadataChanged.emit() + if self.isLoadingComplete(): self.setViewPage("overview") From ebc0813603004c62addf4a4ebe81ca49d2bab4ba Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 16 Dec 2019 10:53:56 +0100 Subject: [PATCH 03/22] Refactored 'installed_plugins' to 'installed_packages' for brevity CURA-6979 --- plugins/Toolbox/src/Toolbox.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index d60b40f91b..2b244e6432 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -62,7 +62,7 @@ class Toolbox(QObject, Extension): "authors": [], "packages": [], "updates": [], - "installed_plugins": [], + "installed_packages": [], } # type: Dict[str, List[Any]] # Models: @@ -70,7 +70,7 @@ class Toolbox(QObject, Extension): "authors": AuthorsModel(self), "packages": PackagesModel(self), "updates": PackagesModel(self), - "installed_plugins": PackagesModel(self), + "installed_packages": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]] self._plugins_showcase_model = PackagesModel(self) @@ -217,7 +217,7 @@ class Toolbox(QObject, Extension): "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)), - "installed_plugins": QUrl("{base_url}/user/packages".format(base_url=self._api_url_user_packages)) + "installed_packages": QUrl("{base_url}/user/packages".format(base_url=self._api_url_user_packages)) } self._application.getCuraAPI().account.loginStateChanged.connect(self._restart) @@ -252,7 +252,7 @@ class Toolbox(QObject, Extension): def _fetchUserInstalledPlugins(self): self._prepareNetworkManager() if self._application.getCuraAPI().account.isLoggedIn: - self._makeRequestByType("installed_plugins") + self._makeRequestByType("installed_packages") # Displays the toolbox @pyqtSlot() @@ -662,8 +662,8 @@ class Toolbox(QObject, Extension): Logger.log("e", "Could not find the %s model.", response_type) break - # Workaround: Do not add Metadata for "installed_plugins" check JUST YET - if response_type != "installed_plugins": + # Workaround: Do not add Metadata for "installed_packages" check JUST YET + if response_type != "installed_packages": self._server_response_data[response_type] = json_data["data"] self._models[response_type].setMetadata(self._server_response_data[response_type]) @@ -679,7 +679,7 @@ class Toolbox(QObject, Extension): # Tell the package manager that there's a new set of updates available. packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) - elif response_type == "installed_plugins": + elif response_type == "installed_packages": user_subscribed = [(plugin['package_id'], plugin['package_version']) for plugin in json_data['data']] Logger.log("i", "- User is subscribed to {} package(s).".format(len(user_subscribed))) user_installed = self._package_manager.getUserInstalledPackagesOnMarketplace() From 3c2da64c9479c5ca429bd23baef60efa15fe7192 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 16 Dec 2019 15:39:21 +0100 Subject: [PATCH 04/22] Some refactoring done after code review CURA-6979 --- plugins/Toolbox/src/Toolbox.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 2b244e6432..3f6e3472dc 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -62,7 +62,7 @@ class Toolbox(QObject, Extension): "authors": [], "packages": [], "updates": [], - "installed_packages": [], + "subscribed_packages": [], } # type: Dict[str, List[Any]] # Models: @@ -70,7 +70,7 @@ class Toolbox(QObject, Extension): "authors": AuthorsModel(self), "packages": PackagesModel(self), "updates": PackagesModel(self), - "installed_packages": PackagesModel(self), + "subscribed_packages": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]] self._plugins_showcase_model = PackagesModel(self) @@ -217,7 +217,7 @@ class Toolbox(QObject, Extension): "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)), - "installed_packages": QUrl("{base_url}/user/packages".format(base_url=self._api_url_user_packages)) + "subscribed_packages": QUrl("{base_url}/user/packages".format(base_url=self._api_url_user_packages)) } self._application.getCuraAPI().account.loginStateChanged.connect(self._restart) @@ -250,9 +250,9 @@ class Toolbox(QObject, Extension): self._updateInstalledModels() def _fetchUserInstalledPlugins(self): - self._prepareNetworkManager() if self._application.getCuraAPI().account.isLoggedIn: - self._makeRequestByType("installed_packages") + self._prepareNetworkManager() + self._makeRequestByType("subscribed_packages") # Displays the toolbox @pyqtSlot() @@ -662,8 +662,8 @@ class Toolbox(QObject, Extension): Logger.log("e", "Could not find the %s model.", response_type) break - # Workaround: Do not add Metadata for "installed_packages" check JUST YET - if response_type != "installed_packages": + # Workaround: Do not add Metadata for "subscribed_packages" check JUST YET + if response_type != "subscribed_packages": self._server_response_data[response_type] = json_data["data"] self._models[response_type].setMetadata(self._server_response_data[response_type]) @@ -679,11 +679,11 @@ class Toolbox(QObject, Extension): # Tell the package manager that there's a new set of updates available. packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) - elif response_type == "installed_packages": + elif response_type == "subscribed_packages": user_subscribed = [(plugin['package_id'], plugin['package_version']) for plugin in json_data['data']] - Logger.log("i", "- User is subscribed to {} package(s).".format(len(user_subscribed))) + Logger.log("d", "User is subscribed to {} package(s).".format(len(user_subscribed))) user_installed = self._package_manager.getUserInstalledPackagesOnMarketplace() - Logger.log("i", "- User has installed locally {} package(s).".format(len(user_installed))) + Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) # Check for discrepancies between Cura installed and Cloud subscribed packages # convert them to set() to check if they are equal From 46d3a73e7ecf337f7a8d659f0198573d2b100e0a Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 16 Dec 2019 15:52:00 +0100 Subject: [PATCH 05/22] Some more refactoring CURA-6979 --- plugins/Toolbox/src/Toolbox.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 3f6e3472dc..5cca504fd2 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -59,17 +59,17 @@ class Toolbox(QObject, Extension): # The responses as given by the server parsed to a list. self._server_response_data = { - "authors": [], - "packages": [], - "updates": [], - "subscribed_packages": [], + "authors": [], + "packages": [], + "updates": [], + "subscribed_packages": [], } # type: Dict[str, List[Any]] # Models: self._models = { - "authors": AuthorsModel(self), - "packages": PackagesModel(self), - "updates": PackagesModel(self), + "authors": AuthorsModel(self), + "packages": PackagesModel(self), + "updates": PackagesModel(self), "subscribed_packages": PackagesModel(self), } # type: Dict[str, Union[AuthorsModel, PackagesModel]] @@ -200,9 +200,8 @@ class Toolbox(QObject, Extension): cloud_api_version = self._cloud_api_version, sdk_version = self._sdk_version ) - - # https://api.ultimaker.com/cura-packages/v1/ - self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}".format( + # https://api.ultimaker.com/cura-packages/v1/user/packages + self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format( cloud_api_root=self._cloud_api_root, cloud_api_version=self._cloud_api_version, ) @@ -217,17 +216,17 @@ class Toolbox(QObject, Extension): "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("{base_url}/user/packages".format(base_url=self._api_url_user_packages)) + "subscribed_packages": QUrl(self._api_url_user_packages) } self._application.getCuraAPI().account.loginStateChanged.connect(self._restart) - self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserInstalledPlugins) + self._application.getCuraAPI().account.loginStateChanged.connect(self._fetchUserSubscribedPackages) # 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._fetchUserInstalledPlugins() + self._fetchUserSubscribedPackages() def _prepareNetworkManager(self): if self._network_manager is not None: @@ -249,7 +248,7 @@ class Toolbox(QObject, Extension): # Gather installed packages: self._updateInstalledModels() - def _fetchUserInstalledPlugins(self): + def _fetchUserSubscribedPackages(self): if self._application.getCuraAPI().account.isLoggedIn: self._prepareNetworkManager() self._makeRequestByType("subscribed_packages") From d958e74467349f98eaf6595e3ef552cc4c625be3 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 16 Dec 2019 15:57:49 +0100 Subject: [PATCH 06/22] Use the new name of the function from Uranium's PackageManager CURA-6979 --- plugins/Toolbox/src/Toolbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 5cca504fd2..5837de9061 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -681,7 +681,7 @@ class Toolbox(QObject, Extension): elif response_type == "subscribed_packages": user_subscribed = [(plugin['package_id'], plugin['package_version']) for plugin in json_data['data']] Logger.log("d", "User is subscribed to {} package(s).".format(len(user_subscribed))) - user_installed = self._package_manager.getUserInstalledPackagesOnMarketplace() + user_installed = self._package_manager.getUserSubscribedPackagesAndVersions() Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) # Check for discrepancies between Cura installed and Cloud subscribed packages From 1155b4bab24b1c838b4d41e1db90f97a542506d5 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 17 Dec 2019 16:10:09 +0100 Subject: [PATCH 07/22] Code style: Double quotes around strings Contributes to issue CURA-6979. --- plugins/Toolbox/src/Toolbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 5837de9061..61e220fbba 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -679,7 +679,7 @@ class Toolbox(QObject, Extension): packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) elif response_type == "subscribed_packages": - user_subscribed = [(plugin['package_id'], plugin['package_version']) for plugin in json_data['data']] + user_subscribed = [(plugin["package_id"], plugin["package_version"]) for plugin in json_data["data"]] Logger.log("d", "User is subscribed to {} package(s).".format(len(user_subscribed))) user_installed = self._package_manager.getUserSubscribedPackagesAndVersions() Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) @@ -687,7 +687,7 @@ class Toolbox(QObject, Extension): # Check for discrepancies between Cura installed and Cloud subscribed packages # convert them to set() to check if they are equal if set(user_installed) != set(user_subscribed): - Logger.log('d', "Mismatch found between Cloud subscribed packages and Cura installed packages") + Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") sync_message = Message(i18n_catalog.i18nc( "@info:generic", "\nDo you want to sync material and software packages with your account?"), From 9870097ab5325401316d7116b24c654611b0a269 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 17 Dec 2019 16:11:47 +0100 Subject: [PATCH 08/22] Code style: Spaces around binary operators Contributes to issue CURA-6979. --- plugins/Toolbox/src/Toolbox.py | 36 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 61e220fbba..52b2456fae 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -164,7 +164,7 @@ 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)) + 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: @@ -202,8 +202,8 @@ class Toolbox(QObject, Extension): ) # https://api.ultimaker.com/cura-packages/v1/user/packages self._api_url_user_packages = "{cloud_api_root}/cura-packages/v{cloud_api_version}/user/packages".format( - cloud_api_root=self._cloud_api_root, - cloud_api_version=self._cloud_api_version, + cloud_api_root = self._cloud_api_root, + cloud_api_version = self._cloud_api_version, ) # We need to construct a query like installed_packages=ID:VERSION&installed_packages=ID:VERSION, etc. @@ -691,13 +691,13 @@ class Toolbox(QObject, Extension): sync_message = Message(i18n_catalog.i18nc( "@info:generic", "\nDo you want to sync material and software packages with your account?"), - lifetime=0, - title=i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", )) + lifetime = 0, + title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", )) sync_message.addAction("sync", - name=i18n_catalog.i18nc("@action:button", "Sync"), - icon="", - description="Sync your Cloud subscribed packages to your local environment.", - button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) + name = i18n_catalog.i18nc("@action:button", "Sync"), + icon = "", + description = "Sync your Cloud subscribed packages to your local environment.", + button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) sync_message.show() self.metadataChanged.emit() @@ -810,39 +810,39 @@ class Toolbox(QObject, Extension): # Exposed Models: # -------------------------------------------------------------------------- - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def authorsModel(self) -> AuthorsModel: return cast(AuthorsModel, self._models["authors"]) - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def packagesModel(self) -> PackagesModel: return cast(PackagesModel, self._models["packages"]) - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def pluginsShowcaseModel(self) -> PackagesModel: return self._plugins_showcase_model - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def pluginsAvailableModel(self) -> PackagesModel: return self._plugins_available_model - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def pluginsInstalledModel(self) -> PackagesModel: return self._plugins_installed_model - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def materialsShowcaseModel(self) -> AuthorsModel: return self._materials_showcase_model - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def materialsAvailableModel(self) -> AuthorsModel: return self._materials_available_model - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def materialsInstalledModel(self) -> PackagesModel: return self._materials_installed_model - @pyqtProperty(QObject, constant=True) + @pyqtProperty(QObject, constant = True) def materialsGenericModel(self) -> PackagesModel: return self._materials_generic_model From 799e7fc48c0d4205c05c446730fff8bfdd4b12de Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Tue, 17 Dec 2019 16:26:59 +0100 Subject: [PATCH 09/22] changed to use the new function name from UM CURA-6979 --- plugins/Toolbox/src/Toolbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 52b2456fae..e3b41f9bbc 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -681,7 +681,7 @@ class Toolbox(QObject, Extension): elif response_type == "subscribed_packages": user_subscribed = [(plugin["package_id"], plugin["package_version"]) for plugin in json_data["data"]] Logger.log("d", "User is subscribed to {} package(s).".format(len(user_subscribed))) - user_installed = self._package_manager.getUserSubscribedPackagesAndVersions() + user_installed = self._package_manager.getUserInstalledPackagesAndVersions() Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) # Check for discrepancies between Cura installed and Cloud subscribed packages From dc20db393e012ba4ca77a183fd00ef557580185c Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Fri, 20 Dec 2019 13:10:22 +0100 Subject: [PATCH 10/22] Initial work for Compatiility Dialog done CURA-7038 --- cura/CuraApplication.py | 3 + .../qml/dialogs/CompatibilityDialog.qml | 39 +++++ .../Toolbox/src/SubscribedPackagesModel.py | 160 ++++++++++++++++++ plugins/Toolbox/src/Toolbox.py | 56 +++++- 4 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml create mode 100644 plugins/Toolbox/src/SubscribedPackagesModel.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 93f7fa97ff..d0f81920c7 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1062,6 +1062,9 @@ class CuraApplication(QtApplication): qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel") + # from plugins.Toolbox.src.SubscribedPackagesModel import SubscribedPackagesModel + # qmlRegisterType(SubscribedPackagesModel, "Cura", 1, 6, "SubscribedPackagesModel") ### This might not be needed + qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml new file mode 100644 index 0000000000..ea39cb5e99 --- /dev/null +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -0,0 +1,39 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Toolbox is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.10 +import QtQuick.Dialogs 1.1 +import QtQuick.Window 2.2 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles + +import UM 1.1 as UM +import Cura 1.6 as Cura + + +UM.Dialog +{ + visible: true + + title: "Some title" + minimumWidth: UM.Theme.getSize("license_window_minimum").width + minimumHeight: UM.Theme.getSize("license_window_minimum").height + width: minimumWidth + height: minimumHeight + + ListView + { + id: listView + anchors.fill: parent + + + model: toolbox.subscribedPackagesModel + + delegate: Label + { + text: "A :)" + } + } +} diff --git a/plugins/Toolbox/src/SubscribedPackagesModel.py b/plugins/Toolbox/src/SubscribedPackagesModel.py new file mode 100644 index 0000000000..1d2e98913b --- /dev/null +++ b/plugins/Toolbox/src/SubscribedPackagesModel.py @@ -0,0 +1,160 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import re +from typing import Dict + +from PyQt5.QtCore import Qt, pyqtProperty + +from UM.Logger import Logger +from UM.Qt.ListModel import ListModel + +from .ConfigsModel import ConfigsModel + + +from UM.PluginRegistry import PluginRegistry + +## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed. +class SubscribedPackagesModel(ListModel): + def __init__(self, parent = None): + super().__init__(parent) + + # self._metadata = None + + self.addRoleName(Qt.UserRole + 1, "name") + self.addRoleName(Qt.UserRole + 2, "icon_url") + self.addRoleName(Qt.UserRole + 3, "is_compatible") + + + # List of filters for queries. The result is the union of the each list of results. + self._filter = {} # type: Dict[str, str] + # + # def setMetadata(self, data): + # if self._metadata != data: + # self._metadata = data + # self._update() + + def update(self): + print("---- in update function") + items1 = [] + items2 = [] + + # if self._metadata is None: + # Logger.logException("w", "Failed to load packages for Marketplace") + # self.setItems(items) + # return + + toolbox = PluginRegistry.getInstance().getPluginObject("Toolbox") + print(toolbox.subscribed_compatible_packages) + print(toolbox.subscribed_incompatible_packages) + + for incompatible in toolbox.subscribed_incompatible_packages: + items1.append({ + "name": incompatible.package_id + }) + + for compatible in toolbox.subscribed_compatible_packages: + items2.append({ + "name": compatible.package_id + }) + print("======================0----------------------") + print(items1) + print(items2) + + # for package in self._metadata: + # has_configs = False + # configs_model = None + # + # links_dict = {} + # if "data" in package: + # if "supported_configs" in package["data"]: + # if len(package["data"]["supported_configs"]) > 0: + # has_configs = True + # configs_model = ConfigsModel() + # configs_model.setConfigs(package["data"]["supported_configs"]) + # + # # Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier + # # to process. + # link_list = package["data"]["links"] if "links" in package["data"] else [] + # links_dict = {d["title"]: d["url"] for d in link_list} + # + # if "author_id" not in package["author"] or "display_name" not in package["author"]: + # package["author"]["author_id"] = "" + # package["author"]["display_name"] = "" + # + # items.append({ + # "id": package["package_id"], + # "type": package["package_type"], + # "name": package["display_name"], + # "version": package["package_version"], + # "author_id": package["author"]["author_id"], + # "author_name": package["author"]["display_name"], + # "author_email": package["author"]["email"] if "email" in package["author"] else None, + # "description": package["description"] if "description" in package else None, + # "icon_url": package["icon_url"] if "icon_url" in package else None, + # "image_urls": package["image_urls"] if "image_urls" in package else None, + # "download_url": package["download_url"] if "download_url" in package else None, + # "last_updated": package["last_updated"] if "last_updated" in package else None, + # "is_bundled": package["is_bundled"] if "is_bundled" in package else False, + # "is_active": package["is_active"] if "is_active" in package else False, + # "is_installed": package["is_installed"] if "is_installed" in package else False, + # "has_configs": has_configs, + # "supported_configs": configs_model, + # "download_count": package["download_count"] if "download_count" in package else 0, + # "tags": package["tags"] if "tags" in package else [], + # "links": links_dict, + # "website": package["website"] if "website" in package else None, + # "login_required": "login-required" in package.get("tags", []), + # "average_rating": float(package.get("rating", {}).get("average", 0)), + # "num_ratings": package.get("rating", {}).get("count", 0), + # "user_rating": package.get("rating", {}).get("user_rating", 0) + # }) + # + # # Filter on all the key-word arguments. + # for key, value in self._filter.items(): + # if key == "tags": + # key_filter = lambda item, v = value: v in item["tags"] + # elif "*" in value: + # key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v) + # else: + # key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v) + # items = filter(key_filter, items) + # + # # Execute all filters. + # filtered_items = list(items) + # + # filtered_items.sort(key = lambda k: k["name"]) + # self.setItems(filtered_items) + final_list = items1 + items2 + print(final_list) + self.setItems(final_list) + + ## Set the filter of this model based on a string. + # \param filter_dict \type{Dict} Dictionary to do the filtering by. + def setFilter(self, filter_dict: Dict[str, str]) -> None: + if filter_dict != self._filter: + self._filter = filter_dict + self._update() + + @pyqtProperty("QVariantMap", fset = setFilter, constant = True) + def filter(self) -> Dict[str, str]: + return self._filter + + # Check to see if a container matches with a regular expression + def _matchRegExp(self, metadata, property_name, value): + if property_name not in metadata: + return False + value = re.escape(value) #Escape for regex patterns. + value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match. + if self._ignore_case: + value_pattern = re.compile(value, re.IGNORECASE) + else: + value_pattern = re.compile(value) + + return value_pattern.match(str(metadata[property_name])) + + # Check to see if a container matches with a string + def _matchString(self, metadata, property_name, value): + if property_name not in metadata: + return False + return value.lower() == str(metadata[property_name]).lower() diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index e3b41f9bbc..f5c15007e7 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -24,6 +24,9 @@ from cura.Machines.ContainerTree import ContainerTree from .AuthorsModel import AuthorsModel from .PackagesModel import PackagesModel +from .SubscribedPackagesModel import SubscribedPackagesModel + +from PyQt5.QtQml import qmlRegisterType if TYPE_CHECKING: from cura.Settings.GlobalStack import GlobalStack @@ -38,6 +41,9 @@ class Toolbox(QObject, Extension): self._application = application # type: CuraApplication + # self._application.qm + # qmlRegisterType(Toolbox, "Cura", 1, 6, "Toolbox") + self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int] self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: str self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str @@ -57,6 +63,9 @@ class Toolbox(QObject, Extension): self._old_plugin_ids = set() # type: Set[str] self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] + self.subscribed_compatible_packages = [] # type: List[str] + self.subscribed_incompatible_packages = [] # type: List[str] + # The responses as given by the server parsed to a list. self._server_response_data = { "authors": [], @@ -70,8 +79,8 @@ class Toolbox(QObject, Extension): "authors": AuthorsModel(self), "packages": PackagesModel(self), "updates": PackagesModel(self), - "subscribed_packages": PackagesModel(self), - } # type: Dict[str, Union[AuthorsModel, PackagesModel]] + "subscribed_packages": SubscribedPackagesModel(self), + } # type: Dict[str, Union[AuthorsModel, PackagesModel, SubscribedPackagesModel]] self._plugins_showcase_model = PackagesModel(self) self._plugins_available_model = PackagesModel(self) @@ -679,14 +688,32 @@ class Toolbox(QObject, Extension): packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) elif response_type == "subscribed_packages": - user_subscribed = [(plugin["package_id"], plugin["package_version"]) for plugin in json_data["data"]] - Logger.log("d", "User is subscribed to {} package(s).".format(len(user_subscribed))) - user_installed = self._package_manager.getUserInstalledPackagesAndVersions() + + import collections + Package = collections.namedtuple("Package", ["package_id", "sdk_versions"]) + + user_subscribed = [Package(plugin['package_id'], plugin['sdk_versions']) for plugin in json_data["data"]] + user_subscribed_list = [plugin["package_id"] for plugin in json_data["data"]] + + self.subscribed_compatible_packages.clear() + self.subscribed_incompatible_packages.clear() + + for subscribed in user_subscribed: + if self._sdk_version not in subscribed.sdk_versions: + self.subscribed_incompatible_packages.append(subscribed) + else: + self.subscribed_compatible_packages.append(subscribed) + + + print("compatible packages: \n {}".format(self.subscribed_compatible_packages)) + print("incompatible packages: \n {}".format(self.subscribed_incompatible_packages)) + + self._models["subscribed_packages"].update() + + user_installed = self._package_manager.getUserInstalledPackages() Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) - # Check for discrepancies between Cura installed and Cloud subscribed packages - # convert them to set() to check if they are equal - if set(user_installed) != set(user_subscribed): + if set(user_installed) != set(user_subscribed_list): Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") sync_message = Message(i18n_catalog.i18nc( "@info:generic", @@ -699,6 +726,7 @@ class Toolbox(QObject, Extension): description = "Sync your Cloud subscribed packages to your local environment.", button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) sync_message.show() + sync_message.actionTriggered.connect(self.some_function) self.metadataChanged.emit() @@ -716,6 +744,14 @@ class Toolbox(QObject, Extension): # Ignore any operation that is not a get operation pass + def some_function(self, messageId: str, actionId: str) -> None: + print("Clicked the BUTTON") + + compatibilityDialog = "resources/qml/dialogs/CompatibilityDialog.qml" + path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), compatibilityDialog) + self._view = self._application.getInstance().createQmlComponent(path, {"toolbox": self}) # what is toolbox: self + + # This function goes through all known remote versions of a package and notifies the package manager of this change def _notifyPackageManager(self): for package in self._server_response_data["packages"]: @@ -814,6 +850,10 @@ class Toolbox(QObject, Extension): def authorsModel(self) -> AuthorsModel: return cast(AuthorsModel, self._models["authors"]) + @pyqtProperty(QObject, constant = True) + def subscribedPackagesModel(self) -> SubscribedPackagesModel: + return cast(SubscribedPackagesModel, self._models["subscribed_packages"]) + @pyqtProperty(QObject, constant = True) def packagesModel(self) -> PackagesModel: return cast(PackagesModel, self._models["packages"]) From 9cf5a388f3482b6b945cd144f3ad7fb75b85e86f Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Fri, 20 Dec 2019 13:59:45 +0100 Subject: [PATCH 11/22] Change for CURA-6979. Check for packages installed in Cloud MP but not in Cura MP. CURA-7038 --- plugins/Toolbox/src/Toolbox.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index f5c15007e7..dfe6de9726 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -705,15 +705,13 @@ class Toolbox(QObject, Extension): self.subscribed_compatible_packages.append(subscribed) - print("compatible packages: \n {}".format(self.subscribed_compatible_packages)) - print("incompatible packages: \n {}".format(self.subscribed_incompatible_packages)) - self._models["subscribed_packages"].update() user_installed = self._package_manager.getUserInstalledPackages() Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) - if set(user_installed) != set(user_subscribed_list): + # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace + if list(set(user_subscribed_list).difference(user_installed)): Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") sync_message = Message(i18n_catalog.i18nc( "@info:generic", From bd8c1e4c96c05ba4acbf070c33035e1cba379951 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 23 Dec 2019 15:18:09 +0100 Subject: [PATCH 12/22] Initial implementation for the Compatibility Dialog window CURA-7038 --- .../qml/dialogs/CompatibilityDialog.qml | 70 ++++++--- .../Toolbox/src/SubscribedPackagesModel.py | 144 +++--------------- plugins/Toolbox/src/Toolbox.py | 56 +++++-- 3 files changed, 115 insertions(+), 155 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index ea39cb5e99..894cab532c 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -13,27 +13,63 @@ import UM 1.1 as UM import Cura 1.6 as Cura -UM.Dialog -{ +UM.Dialog{ visible: true + title: "Changes from your account" + Label{ + text: "Some text here" + height: 50 + } +Rectangle +{ + id: compatibleRectangle + width: parent.width + height: 300 + Label{ + text: "Some text here" + height: 50 + } - title: "Some title" - minimumWidth: UM.Theme.getSize("license_window_minimum").width - minimumHeight: UM.Theme.getSize("license_window_minimum").height - width: minimumWidth - height: minimumHeight - ListView - { - id: listView + + // Compatible packages + Column{ + id: compatibleColumn + anchors.fill: parent + spacing: 2 + + Repeater{ + model: toolbox.subscribedPackagesModel + delegate: Rectangle{ + id: someRect + width: parent.width + height: 50 + border.color: "black" + Image{ + source: model.icon_url || "../../images/logobot.svg" + width: 50 + height: parent.height + //anchors.left: parent.left + //anchors.right: packageName.left + anchors.rightMargin: 20 + } + Text{ + id: packageName + text: model.name + anchors.centerIn: parent + } + MouseArea{ anchors.fill: parent - - - model: toolbox.subscribedPackagesModel - - delegate: Label - { - text: "A :)" + onClicked: { + console.log("Clicked!") } } + + } + } + } +} + + + } diff --git a/plugins/Toolbox/src/SubscribedPackagesModel.py b/plugins/Toolbox/src/SubscribedPackagesModel.py index 1d2e98913b..79df620ca8 100644 --- a/plugins/Toolbox/src/SubscribedPackagesModel.py +++ b/plugins/Toolbox/src/SubscribedPackagesModel.py @@ -19,142 +19,32 @@ class SubscribedPackagesModel(ListModel): def __init__(self, parent = None): super().__init__(parent) - # self._metadata = None self.addRoleName(Qt.UserRole + 1, "name") self.addRoleName(Qt.UserRole + 2, "icon_url") self.addRoleName(Qt.UserRole + 3, "is_compatible") - - # List of filters for queries. The result is the union of the each list of results. - self._filter = {} # type: Dict[str, str] - # - # def setMetadata(self, data): - # if self._metadata != data: - # self._metadata = data - # self._update() - def update(self): - print("---- in update function") - items1 = [] - items2 = [] - - # if self._metadata is None: - # Logger.logException("w", "Failed to load packages for Marketplace") - # self.setItems(items) - # return - + # items1 = [] + # items2 = [] toolbox = PluginRegistry.getInstance().getPluginObject("Toolbox") - print(toolbox.subscribed_compatible_packages) - print(toolbox.subscribed_incompatible_packages) + # print("Compatible: {}".format(toolbox.subscribed_compatible_packages)) + # print("Incompatible: {}".format(toolbox.subscribed_incompatible_packages)) - for incompatible in toolbox.subscribed_incompatible_packages: - items1.append({ - "name": incompatible.package_id - }) - - for compatible in toolbox.subscribed_compatible_packages: - items2.append({ - "name": compatible.package_id - }) - print("======================0----------------------") - print(items1) - print(items2) - - # for package in self._metadata: - # has_configs = False - # configs_model = None - # - # links_dict = {} - # if "data" in package: - # if "supported_configs" in package["data"]: - # if len(package["data"]["supported_configs"]) > 0: - # has_configs = True - # configs_model = ConfigsModel() - # configs_model.setConfigs(package["data"]["supported_configs"]) - # - # # Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier - # # to process. - # link_list = package["data"]["links"] if "links" in package["data"] else [] - # links_dict = {d["title"]: d["url"] for d in link_list} - # - # if "author_id" not in package["author"] or "display_name" not in package["author"]: - # package["author"]["author_id"] = "" - # package["author"]["display_name"] = "" - # - # items.append({ - # "id": package["package_id"], - # "type": package["package_type"], - # "name": package["display_name"], - # "version": package["package_version"], - # "author_id": package["author"]["author_id"], - # "author_name": package["author"]["display_name"], - # "author_email": package["author"]["email"] if "email" in package["author"] else None, - # "description": package["description"] if "description" in package else None, - # "icon_url": package["icon_url"] if "icon_url" in package else None, - # "image_urls": package["image_urls"] if "image_urls" in package else None, - # "download_url": package["download_url"] if "download_url" in package else None, - # "last_updated": package["last_updated"] if "last_updated" in package else None, - # "is_bundled": package["is_bundled"] if "is_bundled" in package else False, - # "is_active": package["is_active"] if "is_active" in package else False, - # "is_installed": package["is_installed"] if "is_installed" in package else False, - # "has_configs": has_configs, - # "supported_configs": configs_model, - # "download_count": package["download_count"] if "download_count" in package else 0, - # "tags": package["tags"] if "tags" in package else [], - # "links": links_dict, - # "website": package["website"] if "website" in package else None, - # "login_required": "login-required" in package.get("tags", []), - # "average_rating": float(package.get("rating", {}).get("average", 0)), - # "num_ratings": package.get("rating", {}).get("count", 0), - # "user_rating": package.get("rating", {}).get("user_rating", 0) + # for incompatible in toolbox.subscribed_incompatible_packages: + # items1.append({ + # "name": incompatible.package_id, + # "icon_url": incompatible.icon_url # }) # - # # Filter on all the key-word arguments. - # for key, value in self._filter.items(): - # if key == "tags": - # key_filter = lambda item, v = value: v in item["tags"] - # elif "*" in value: - # key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v) - # else: - # key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v) - # items = filter(key_filter, items) - # - # # Execute all filters. - # filtered_items = list(items) - # - # filtered_items.sort(key = lambda k: k["name"]) - # self.setItems(filtered_items) - final_list = items1 + items2 - print(final_list) - self.setItems(final_list) + # for compatible in toolbox.subscribed_compatible_packages: + # items2.append({ + # "name": compatible.package_id, + # "icon_url": compatible.icon_url + # }) - ## Set the filter of this model based on a string. - # \param filter_dict \type{Dict} Dictionary to do the filtering by. - def setFilter(self, filter_dict: Dict[str, str]) -> None: - if filter_dict != self._filter: - self._filter = filter_dict - self._update() + print("self.subscribed_packages: {}".format(toolbox.subscribed_packages)) - @pyqtProperty("QVariantMap", fset = setFilter, constant = True) - def filter(self) -> Dict[str, str]: - return self._filter - - # Check to see if a container matches with a regular expression - def _matchRegExp(self, metadata, property_name, value): - if property_name not in metadata: - return False - value = re.escape(value) #Escape for regex patterns. - value = "^" + value.replace("\\*", ".*") + "$" #Instead of (now escaped) asterisks, match on any string. Also add anchors for a complete match. - if self._ignore_case: - value_pattern = re.compile(value, re.IGNORECASE) - else: - value_pattern = re.compile(value) - - return value_pattern.match(str(metadata[property_name])) - - # Check to see if a container matches with a string - def _matchString(self, metadata, property_name, value): - if property_name not in metadata: - return False - return value.lower() == str(metadata[property_name]).lower() + # final_list = items1 + items2 + self.setItems(toolbox.subscribed_packages) + # self.setItems(final_list) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index dfe6de9726..a79c52dbe8 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -63,8 +63,9 @@ class Toolbox(QObject, Extension): self._old_plugin_ids = set() # type: Set[str] self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] - self.subscribed_compatible_packages = [] # type: List[str] - self.subscribed_incompatible_packages = [] # type: List[str] + # self.subscribed_compatible_packages = [] # type: List[str] + # self.subscribed_incompatible_packages = [] # type: List[str] + self.subscribed_packages = [] # type: List[Dict[str, str]] # The responses as given by the server parsed to a list. self._server_response_data = { @@ -689,20 +690,53 @@ class Toolbox(QObject, Extension): self._package_manager.setPackagesWithUpdate(packages) elif response_type == "subscribed_packages": - import collections - Package = collections.namedtuple("Package", ["package_id", "sdk_versions"]) + # import collections + # Package = collections.namedtuple("Package", ["package_id", "icon_url", "sdk_versions", "is_compatible"]) + # Package.__new__.__defaults__ = (None, ) * len(Package._fields) - user_subscribed = [Package(plugin['package_id'], plugin['sdk_versions']) for plugin in json_data["data"]] + # There is not always an ICON_URL in the response payload ! + # user_subscribed = [Package(plugin['package_id'], plugin.get("icon_url", ""), plugin['sdk_versions']) for plugin in json_data["data"]] user_subscribed_list = [plugin["package_id"] for plugin in json_data["data"]] - self.subscribed_compatible_packages.clear() - self.subscribed_incompatible_packages.clear() + all_subscribed_packages = [] - for subscribed in user_subscribed: - if self._sdk_version not in subscribed.sdk_versions: - self.subscribed_incompatible_packages.append(subscribed) + self.subscribed_packages.clear() + + for package in json_data["data"]: + packagex = { + "name": package["package_id"], + "sdk_versions": package["sdk_versions"] + } + + # packagex = Package(package["package_id"], package["sdk_versions"], ) + if self._sdk_version not in package["sdk_versions"]: + packagex.update({"is_compatible": False}) + # packagex._replace(is_compatible=0) + # packagex.is_compatible = "1" else: - self.subscribed_compatible_packages.append(subscribed) + # packagex._replace(is_compatible="1") + # packagex.is_compatible = "0" + packagex.update({"is_compatible": True}) + + try: + packagex.update({"icon_url": package["icon_url"]}) + except KeyError: # There is no 'icon_url" in the response payload for this package + packagex.update({"icon_url": ""}) + + self.subscribed_packages.append(packagex) + # all_subscribed_packages.append(packagex) + # print("ALL PACKAGES: {}".format(all_subscribed_packages)) + + # self.subscribed_compatible_packages.clear() + # self.subscribed_incompatible_packages.clear() + + + + # for subscribed in user_subscribed: + # if self._sdk_version not in subscribed.sdk_versions: + # self.subscribed_incompatible_packages.append(subscribed) + # else: + # self.subscribed_compatible_packages.append(subscribed) self._models["subscribed_packages"].update() From 4375118a9f31ce9f670b39cc480a8a120a7ba0b3 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 23 Dec 2019 16:58:44 +0100 Subject: [PATCH 13/22] Refactored most of the code into separate functions CURA-7038 --- .../Toolbox/src/SubscribedPackagesModel.py | 32 +---- plugins/Toolbox/src/Toolbox.py | 124 ++++++------------ 2 files changed, 44 insertions(+), 112 deletions(-) diff --git a/plugins/Toolbox/src/SubscribedPackagesModel.py b/plugins/Toolbox/src/SubscribedPackagesModel.py index 79df620ca8..28fadd3765 100644 --- a/plugins/Toolbox/src/SubscribedPackagesModel.py +++ b/plugins/Toolbox/src/SubscribedPackagesModel.py @@ -1,17 +1,9 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import re -from typing import Dict +from PyQt5.QtCore import Qt -from PyQt5.QtCore import Qt, pyqtProperty - -from UM.Logger import Logger from UM.Qt.ListModel import ListModel - -from .ConfigsModel import ConfigsModel - - from UM.PluginRegistry import PluginRegistry ## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed. @@ -19,32 +11,10 @@ class SubscribedPackagesModel(ListModel): def __init__(self, parent = None): super().__init__(parent) - self.addRoleName(Qt.UserRole + 1, "name") self.addRoleName(Qt.UserRole + 2, "icon_url") self.addRoleName(Qt.UserRole + 3, "is_compatible") def update(self): - # items1 = [] - # items2 = [] toolbox = PluginRegistry.getInstance().getPluginObject("Toolbox") - # print("Compatible: {}".format(toolbox.subscribed_compatible_packages)) - # print("Incompatible: {}".format(toolbox.subscribed_incompatible_packages)) - - # for incompatible in toolbox.subscribed_incompatible_packages: - # items1.append({ - # "name": incompatible.package_id, - # "icon_url": incompatible.icon_url - # }) - # - # for compatible in toolbox.subscribed_compatible_packages: - # items2.append({ - # "name": compatible.package_id, - # "icon_url": compatible.icon_url - # }) - - print("self.subscribed_packages: {}".format(toolbox.subscribed_packages)) - - # final_list = items1 + items2 self.setItems(toolbox.subscribed_packages) - # self.setItems(final_list) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index a79c52dbe8..ae8396ad67 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -5,6 +5,7 @@ import json import os import tempfile import platform +import functools from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot @@ -26,8 +27,6 @@ from .AuthorsModel import AuthorsModel from .PackagesModel import PackagesModel from .SubscribedPackagesModel import SubscribedPackagesModel -from PyQt5.QtQml import qmlRegisterType - if TYPE_CHECKING: from cura.Settings.GlobalStack import GlobalStack @@ -41,9 +40,6 @@ class Toolbox(QObject, Extension): self._application = application # type: CuraApplication - # self._application.qm - # qmlRegisterType(Toolbox, "Cura", 1, 6, "Toolbox") - self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int] self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: str self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str @@ -63,8 +59,6 @@ class Toolbox(QObject, Extension): self._old_plugin_ids = set() # type: Set[str] self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] - # self.subscribed_compatible_packages = [] # type: List[str] - # self.subscribed_incompatible_packages = [] # type: List[str] self.subscribed_packages = [] # type: List[Dict[str, str]] # The responses as given by the server parsed to a list. @@ -689,76 +683,7 @@ class Toolbox(QObject, Extension): packages = set([pkg["package_id"] for pkg in self._server_response_data[response_type]]) self._package_manager.setPackagesWithUpdate(packages) elif response_type == "subscribed_packages": - - # import collections - # Package = collections.namedtuple("Package", ["package_id", "icon_url", "sdk_versions", "is_compatible"]) - # Package.__new__.__defaults__ = (None, ) * len(Package._fields) - - # There is not always an ICON_URL in the response payload ! - # user_subscribed = [Package(plugin['package_id'], plugin.get("icon_url", ""), plugin['sdk_versions']) for plugin in json_data["data"]] - user_subscribed_list = [plugin["package_id"] for plugin in json_data["data"]] - - all_subscribed_packages = [] - - self.subscribed_packages.clear() - - for package in json_data["data"]: - packagex = { - "name": package["package_id"], - "sdk_versions": package["sdk_versions"] - } - - # packagex = Package(package["package_id"], package["sdk_versions"], ) - if self._sdk_version not in package["sdk_versions"]: - packagex.update({"is_compatible": False}) - # packagex._replace(is_compatible=0) - # packagex.is_compatible = "1" - else: - # packagex._replace(is_compatible="1") - # packagex.is_compatible = "0" - packagex.update({"is_compatible": True}) - - try: - packagex.update({"icon_url": package["icon_url"]}) - except KeyError: # There is no 'icon_url" in the response payload for this package - packagex.update({"icon_url": ""}) - - self.subscribed_packages.append(packagex) - # all_subscribed_packages.append(packagex) - # print("ALL PACKAGES: {}".format(all_subscribed_packages)) - - # self.subscribed_compatible_packages.clear() - # self.subscribed_incompatible_packages.clear() - - - - # for subscribed in user_subscribed: - # if self._sdk_version not in subscribed.sdk_versions: - # self.subscribed_incompatible_packages.append(subscribed) - # else: - # self.subscribed_compatible_packages.append(subscribed) - - - self._models["subscribed_packages"].update() - - user_installed = self._package_manager.getUserInstalledPackages() - Logger.log("d", "User has installed locally {} package(s).".format(len(user_installed))) - - # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace - if list(set(user_subscribed_list).difference(user_installed)): - Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") - sync_message = Message(i18n_catalog.i18nc( - "@info:generic", - "\nDo you want to sync material and software packages with your account?"), - lifetime = 0, - title = i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", )) - sync_message.addAction("sync", - name = i18n_catalog.i18nc("@action:button", "Sync"), - icon = "", - description = "Sync your Cloud subscribed packages to your local environment.", - button_align = Message.ActionButtonAlignment.ALIGN_RIGHT) - sync_message.show() - sync_message.actionTriggered.connect(self.some_function) + self._checkCompatibilities(json_data["data"]) self.metadataChanged.emit() @@ -776,13 +701,50 @@ class Toolbox(QObject, Extension): # Ignore any operation that is not a get operation pass - def some_function(self, messageId: str, actionId: str) -> None: - print("Clicked the BUTTON") + def _checkCompatibilities(self, json_data): + user_subscribed_list = [plugin["package_id"] for plugin in json_data] + user_installed_packages = self._package_manager.getUserInstalledPackages() + + # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace + if list(set(user_subscribed_list).difference(user_installed_packages)): + Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") + sync_message = Message(i18n_catalog.i18nc( + "@info:generic", + "\nDo you want to sync material and software packages with your account?"), + lifetime=0, + title=i18n_catalog.i18nc("@info:title", "Changes detected from your Ultimaker account", )) + sync_message.addAction("sync", + name=i18n_catalog.i18nc("@action:button", "Sync"), + icon="", + description="Sync your Cloud subscribed packages to your local environment.", + button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) + + self._onSyncButtonClickedHelper = functools.partial(self._onSyncButtonClicked, json_data) + sync_message.actionTriggered.connect(self._onSyncButtonClickedHelper) + sync_message.show() + + def _onSyncButtonClicked(self, json_data, messageId: str, actionId: str) -> None: + self.subscribed_packages.clear() + # We create the packages from the HTTP payload + for item in json_data: + package = {"name": item["package_id"], "sdk_versions": item["sdk_versions"]} + + if self._sdk_version not in item["sdk_versions"]: + package.update({"is_compatible": False}) + else: + package.update({"is_compatible": True}) + + try: + package.update({"icon_url": item["icon_url"]}) + except KeyError: # There is no 'icon_url" in the response payload for this package + package.update({"icon_url": ""}) + + self.subscribed_packages.append(package) + self._models["subscribed_packages"].update() compatibilityDialog = "resources/qml/dialogs/CompatibilityDialog.qml" path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), compatibilityDialog) - self._view = self._application.getInstance().createQmlComponent(path, {"toolbox": self}) # what is toolbox: self - + self.compatibility_dialog_view = self._application.getInstance().createQmlComponent(path, {"toolbox": self}) # This function goes through all known remote versions of a package and notifies the package manager of this change def _notifyPackageManager(self): From 12be21e5948ab84258d16612398a753e7c5a6beb Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 23 Dec 2019 17:23:26 +0100 Subject: [PATCH 14/22] Reworked the flow to show only not installed packages for installing in the Compatibility Dialog CURA-7038 --- .../qml/dialogs/CompatibilityDialog.qml | 2 +- plugins/Toolbox/src/Toolbox.py | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index 894cab532c..dd178cc0a4 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -55,7 +55,7 @@ Rectangle } Text{ id: packageName - text: model.name + text: model.name + " (Compatible: " + model.is_compatible + ")" anchors.centerIn: parent } MouseArea{ diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index ae8396ad67..91eb235328 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -702,12 +702,13 @@ class Toolbox(QObject, Extension): pass def _checkCompatibilities(self, json_data): - user_subscribed_list = [plugin["package_id"] for plugin in json_data] + user_subscribed_packages = [plugin["package_id"] for plugin in json_data] user_installed_packages = self._package_manager.getUserInstalledPackages() - # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace - if list(set(user_subscribed_list).difference(user_installed_packages)): - Logger.log("d", "Mismatch found between Cloud subscribed packages and Cura installed packages") + # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace (discrepancy) + package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages)) + if package_discrepancy: + Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages") sync_message = Message(i18n_catalog.i18nc( "@info:generic", "\nDo you want to sync material and software packages with your account?"), @@ -719,27 +720,28 @@ class Toolbox(QObject, Extension): description="Sync your Cloud subscribed packages to your local environment.", button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) - self._onSyncButtonClickedHelper = functools.partial(self._onSyncButtonClicked, json_data) + self._onSyncButtonClickedHelper = functools.partial(self._onSyncButtonClicked, json_data, package_discrepancy) sync_message.actionTriggered.connect(self._onSyncButtonClickedHelper) sync_message.show() - def _onSyncButtonClicked(self, json_data, messageId: str, actionId: str) -> None: - self.subscribed_packages.clear() - # We create the packages from the HTTP payload + def _onSyncButtonClicked(self, json_data, package_discrepancy, messageId: str, actionId: str) -> None: + # self.subscribed_packages.clear() + # We 'create' the packages from the HTTP payload for item in json_data: + if item["package_id"] not in package_discrepancy: # But we skip packages that the user has locally installed + continue package = {"name": item["package_id"], "sdk_versions": item["sdk_versions"]} - if self._sdk_version not in item["sdk_versions"]: package.update({"is_compatible": False}) else: package.update({"is_compatible": True}) - try: package.update({"icon_url": item["icon_url"]}) except KeyError: # There is no 'icon_url" in the response payload for this package package.update({"icon_url": ""}) self.subscribed_packages.append(package) + Logger.log("d", "Package '{}' scheduled for installing.".format(package['name'])) self._models["subscribed_packages"].update() compatibilityDialog = "resources/qml/dialogs/CompatibilityDialog.qml" From 8c0f64633b7025a01be9bdb4ac088d48a137f4ab Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 23 Dec 2019 18:08:22 +0100 Subject: [PATCH 15/22] Removed comments CURA-7038 --- cura/CuraApplication.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index d0f81920c7..93f7fa97ff 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1062,9 +1062,6 @@ class CuraApplication(QtApplication): qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel") - # from plugins.Toolbox.src.SubscribedPackagesModel import SubscribedPackagesModel - # qmlRegisterType(SubscribedPackagesModel, "Cura", 1, 6, "SubscribedPackagesModel") ### This might not be needed - qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel") qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel") qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel") From ada2a776d5faec75fa338c59d93f5609a499a196 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 30 Dec 2019 17:36:46 +0100 Subject: [PATCH 16/22] Reworked the Compatibility Dialog and how it shows the subscribed packages CURA-7038 --- .../qml/dialogs/CompatibilityDialog.qml | 177 ++++++++++++------ plugins/Toolbox/src/Toolbox.py | 8 +- 2 files changed, 128 insertions(+), 57 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index dd178cc0a4..249111b9ad 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -1,13 +1,9 @@ -// Copyright (c) 2018 Ultimaker B.V. +// Copyright (c) 2020 Ultimaker B.V. // Toolbox is released under the terms of the LGPLv3 or higher. import QtQuick 2.10 -import QtQuick.Dialogs 1.1 import QtQuick.Window 2.2 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 - -// TODO: Switch to QtQuick.Controls 2.x and remove QtQuick.Controls.Styles +import QtQuick.Controls 2.3 import UM 1.1 as UM import Cura 1.6 as Cura @@ -15,61 +11,136 @@ import Cura 1.6 as Cura UM.Dialog{ visible: true - title: "Changes from your account" - Label{ - text: "Some text here" - height: 50 - } -Rectangle -{ - id: compatibleRectangle - width: parent.width - height: 300 - Label{ - text: "Some text here" - height: 50 - } + title: catalog.i18nc("@title", "Changes from your account") + width: UM.Theme.getSize("popup_dialog").width + height: UM.Theme.getSize("popup_dialog").height + minimumWidth: width + maximumWidth: minimumWidth + minimumHeight: height + maximumHeight: minimumHeight + margin: 0 - - - // Compatible packages - Column{ - id: compatibleColumn + Rectangle + { + id: root anchors.fill: parent - spacing: 2 + color: UM.Theme.getColor("main_background") - Repeater{ - model: toolbox.subscribedPackagesModel - delegate: Rectangle{ - id: someRect - width: parent.width - height: 50 - border.color: "black" - Image{ - source: model.icon_url || "../../images/logobot.svg" - width: 50 - height: parent.height - //anchors.left: parent.left - //anchors.right: packageName.left - anchors.rightMargin: 20 + UM.I18nCatalog + { + id: catalog + name: "cura" + } + + ScrollView + { + width: parent.width + height: parent.height - nextButton.height - nextButton.anchors.margins * 2 // We want some leftover space for the button at the bottom + clip: true + + Column + { + anchors.fill: parent + anchors.margins: UM.Theme.getSize("default_margin").width + + // Compatible packages + Label + { + font: UM.Theme.getFont("default") + text: catalog.i18nc("@label", "The following packages will be added:") + color: UM.Theme.getColor("text") + height: contentHeight + UM.Theme.getSize("default_margin").height } - Text{ - id: packageName - text: model.name + " (Compatible: " + model.is_compatible + ")" - anchors.centerIn: parent - } - MouseArea{ - anchors.fill: parent - onClicked: { - console.log("Clicked!") + Repeater + { + model: toolbox.subscribedPackagesModel + Component + { + id: compatibleDelegate + Item + { + width: parent.width + property var lineHeight: 60 + visible: model.is_compatible == "True" ? true : false + height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the compatible packages here + Image + { + id: packageIcon + source: model.icon_url || "../../images/logobot.svg" + height: lineHeight + width: height + mipmap: true + fillMode: Image.PreserveAspectFit + } + Label + { + id: compatibleLabel + text: model.name + font: UM.Theme.getFont("medium_bold") + anchors.left: packageIcon.right + anchors.leftMargin: 20 + anchors.verticalCenter: packageIcon.verticalCenter + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } } } + // Incompatible packages + Label + { + font: UM.Theme.getFont("default") + text: catalog.i18nc("@label", "The following packages can not be installed because of incompatible Cura version:") + color: UM.Theme.getColor("text") + height: contentHeight + UM.Theme.getSize("default_margin").height + } + Repeater + { + model: toolbox.subscribedPackagesModel + Component + { + id: incompatibleDelegate + Item + { + width: parent.width + property var lineHeight: 60 + visible: model.is_compatible == "True" ? false : true + height: visible ? (lineHeight + UM.Theme.getSize("default_margin").height) : 0 // We only show the incompatible packages here + Image + { + id: packageIcon + source: model.icon_url || "../../images/logobot.svg" + height: lineHeight + width: height + mipmap: true + fillMode: Image.PreserveAspectFit + } + Label + { + id: incompatibleLabel + text: model.name + font: UM.Theme.getFont("medium_bold") + anchors.left: packageIcon.right + anchors.leftMargin: 20 + anchors.verticalCenter: packageIcon.verticalCenter + color: UM.Theme.getColor("text") + elide: Text.ElideRight + } + } + } + } } + + } // End of ScrollView + + Cura.ActionButton + { + id: nextButton + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: UM.Theme.getSize("default_margin").height + text: catalog.i18nc("@button", "Next") } } } - - - -} diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 91eb235328..2c890c8eec 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -701,7 +701,7 @@ class Toolbox(QObject, Extension): # Ignore any operation that is not a get operation pass - def _checkCompatibilities(self, json_data): + def _checkCompatibilities(self, json_data) -> None: user_subscribed_packages = [plugin["package_id"] for plugin in json_data] user_installed_packages = self._package_manager.getUserInstalledPackages() @@ -725,16 +725,16 @@ class Toolbox(QObject, Extension): sync_message.show() def _onSyncButtonClicked(self, json_data, package_discrepancy, messageId: str, actionId: str) -> None: - # self.subscribed_packages.clear() + self.subscribed_packages.clear() # We 'create' the packages from the HTTP payload for item in json_data: if item["package_id"] not in package_discrepancy: # But we skip packages that the user has locally installed continue package = {"name": item["package_id"], "sdk_versions": item["sdk_versions"]} if self._sdk_version not in item["sdk_versions"]: - package.update({"is_compatible": False}) + package.update({"is_compatible": "False"}) else: - package.update({"is_compatible": True}) + package.update({"is_compatible": "True"}) try: package.update({"icon_url": item["icon_url"]}) except KeyError: # There is no 'icon_url" in the response payload for this package From 93c1c1a86eaba24ce13136736703f3a9a0515dc9 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Mon, 30 Dec 2019 17:38:21 +0100 Subject: [PATCH 17/22] deleted unused lines CURA-7038 --- plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index 249111b9ad..5685ddd2b5 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -56,7 +56,6 @@ UM.Dialog{ model: toolbox.subscribedPackagesModel Component { - id: compatibleDelegate Item { width: parent.width @@ -74,7 +73,6 @@ UM.Dialog{ } Label { - id: compatibleLabel text: model.name font: UM.Theme.getFont("medium_bold") anchors.left: packageIcon.right @@ -100,7 +98,6 @@ UM.Dialog{ model: toolbox.subscribedPackagesModel Component { - id: incompatibleDelegate Item { width: parent.width @@ -118,7 +115,6 @@ UM.Dialog{ } Label { - id: incompatibleLabel text: model.name font: UM.Theme.getFont("medium_bold") anchors.left: packageIcon.right From 96311c974b1d961e099e35813ecc770de2c830a9 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Tue, 31 Dec 2019 12:07:42 +0100 Subject: [PATCH 18/22] Fixed failing tests. Hid the sync Message when it's clicked. CURA-7038 --- plugins/Toolbox/src/Toolbox.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 2c890c8eec..b03b11317a 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -724,7 +724,8 @@ class Toolbox(QObject, Extension): sync_message.actionTriggered.connect(self._onSyncButtonClickedHelper) sync_message.show() - def _onSyncButtonClicked(self, json_data, package_discrepancy, messageId: str, actionId: str) -> None: + def _onSyncButtonClicked(self, json_data, package_discrepancy, sync_message: Message, actionId: str) -> None: + sync_message.hide() self.subscribed_packages.clear() # We 'create' the packages from the HTTP payload for item in json_data: @@ -744,9 +745,11 @@ class Toolbox(QObject, Extension): Logger.log("d", "Package '{}' scheduled for installing.".format(package['name'])) self._models["subscribed_packages"].update() - compatibilityDialog = "resources/qml/dialogs/CompatibilityDialog.qml" - path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), compatibilityDialog) - self.compatibility_dialog_view = self._application.getInstance().createQmlComponent(path, {"toolbox": self}) + compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml" + 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.getInstance().createQmlComponent(path, {"toolbox": self}) # This function goes through all known remote versions of a package and notifies the package manager of this change def _notifyPackageManager(self): From dde0dd8ce324059269338085ab809660804e8b4a Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Tue, 31 Dec 2019 14:02:41 +0100 Subject: [PATCH 19/22] Removed the 'workaround' that I've added earlier. Simplified the flow. The dialog is now showing display_name instead of package_id CURA-7038 --- .../Toolbox/src/SubscribedPackagesModel.py | 38 ++++++++++++++++--- plugins/Toolbox/src/Toolbox.py | 33 +++------------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/plugins/Toolbox/src/SubscribedPackagesModel.py b/plugins/Toolbox/src/SubscribedPackagesModel.py index 28fadd3765..f8340ab7a0 100644 --- a/plugins/Toolbox/src/SubscribedPackagesModel.py +++ b/plugins/Toolbox/src/SubscribedPackagesModel.py @@ -1,20 +1,46 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2020 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import Qt - from UM.Qt.ListModel import ListModel -from UM.PluginRegistry import PluginRegistry +from cura import ApplicationMetadata + -## Model that holds Cura packages. By setting the filter property the instances held by this model can be changed. class SubscribedPackagesModel(ListModel): def __init__(self, parent = None): super().__init__(parent) + self._metadata = None + self._discrepancies = None + self._sdk_version = ApplicationMetadata.CuraSDKVersion + self.addRoleName(Qt.UserRole + 1, "name") self.addRoleName(Qt.UserRole + 2, "icon_url") self.addRoleName(Qt.UserRole + 3, "is_compatible") + def setMetadata(self, data): + if self._metadata != data: + self._metadata = data + + def addValue(self, discrepancy): + if self._discrepancies != discrepancy: + self._discrepancies = discrepancy + def update(self): - toolbox = PluginRegistry.getInstance().getPluginObject("Toolbox") - self.setItems(toolbox.subscribed_packages) + items = [] + + for item in self._metadata: + if item["package_id"] not in self._discrepancies: + continue + package = {"name": item["display_name"], "sdk_versions": item["sdk_versions"]} + if self._sdk_version not in item["sdk_versions"]: + package.update({"is_compatible": "False"}) + else: + package.update({"is_compatible": "True"}) + try: + package.update({"icon_url": item["icon_url"]}) + except KeyError: # There is no 'icon_url" in the response payload for this package + package.update({"icon_url": ""}) + + items.append(package) + self.setItems(items) \ No newline at end of file diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index b03b11317a..9e785e6225 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -5,7 +5,6 @@ import json import os import tempfile import platform -import functools from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot @@ -665,10 +664,8 @@ class Toolbox(QObject, Extension): Logger.log("e", "Could not find the %s model.", response_type) break - # Workaround: Do not add Metadata for "subscribed_packages" check JUST YET - if response_type != "subscribed_packages": - self._server_response_data[response_type] = json_data["data"] - self._models[response_type].setMetadata(self._server_response_data[response_type]) + self._server_response_data[response_type] = json_data["data"] + self._models[response_type].setMetadata(self._server_response_data[response_type]) if response_type == "packages": self._models[response_type].setFilter({"type": "plugin"}) @@ -708,6 +705,8 @@ class Toolbox(QObject, Extension): # We check if there are packages installed in Cloud Marketplace but not in Cura marketplace (discrepancy) package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages)) if package_discrepancy: + self._models["subscribed_packages"].addValue(package_discrepancy) + self._models["subscribed_packages"].update() Logger.log("d", "Discrepancy found between Cloud subscribed packages and Cura installed packages") sync_message = Message(i18n_catalog.i18nc( "@info:generic", @@ -720,31 +719,11 @@ class Toolbox(QObject, Extension): description="Sync your Cloud subscribed packages to your local environment.", button_align=Message.ActionButtonAlignment.ALIGN_RIGHT) - self._onSyncButtonClickedHelper = functools.partial(self._onSyncButtonClicked, json_data, package_discrepancy) - sync_message.actionTriggered.connect(self._onSyncButtonClickedHelper) + sync_message.actionTriggered.connect(self._onSyncButtonClicked) sync_message.show() - def _onSyncButtonClicked(self, json_data, package_discrepancy, sync_message: Message, actionId: str) -> None: + def _onSyncButtonClicked(self, sync_message: Message, sync_message_action: str) -> None: sync_message.hide() - self.subscribed_packages.clear() - # We 'create' the packages from the HTTP payload - for item in json_data: - if item["package_id"] not in package_discrepancy: # But we skip packages that the user has locally installed - continue - package = {"name": item["package_id"], "sdk_versions": item["sdk_versions"]} - if self._sdk_version not in item["sdk_versions"]: - package.update({"is_compatible": "False"}) - else: - package.update({"is_compatible": "True"}) - try: - package.update({"icon_url": item["icon_url"]}) - except KeyError: # There is no 'icon_url" in the response payload for this package - package.update({"icon_url": ""}) - - self.subscribed_packages.append(package) - Logger.log("d", "Package '{}' scheduled for installing.".format(package['name'])) - self._models["subscribed_packages"].update() - compatibility_dialog_path = "resources/qml/dialogs/CompatibilityDialog.qml" plugin_path_prefix = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) if plugin_path_prefix: From 42851b0851019998a3831ec89a4b52371dcd1a02 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Tue, 31 Dec 2019 14:03:56 +0100 Subject: [PATCH 20/22] Removed unnecessary pass statement from reply.operation CURA-7038 --- plugins/Toolbox/src/Toolbox.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 9e785e6225..50a8d39a90 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -694,9 +694,6 @@ class Toolbox(QObject, Extension): 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") self.resetDownload() - elif reply.operation() == QNetworkAccessManager.PutOperation: - # Ignore any operation that is not a get operation - pass def _checkCompatibilities(self, json_data) -> None: user_subscribed_packages = [plugin["package_id"] for plugin in json_data] From 802c095c9f0fd585d0e46070dc8aa181604df660 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Tue, 31 Dec 2019 14:07:40 +0100 Subject: [PATCH 21/22] Removed unused list CURA-7038 --- plugins/Toolbox/src/Toolbox.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index 50a8d39a90..af0a0748e7 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -58,8 +58,6 @@ class Toolbox(QObject, Extension): self._old_plugin_ids = set() # type: Set[str] self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]] - self.subscribed_packages = [] # type: List[Dict[str, str]] - # The responses as given by the server parsed to a list. self._server_response_data = { "authors": [], From 1ea8145f3957c00d76e57d2e2c6280bf921c95a4 Mon Sep 17 00:00:00 2001 From: Dimitriovski Date: Tue, 31 Dec 2019 14:35:47 +0100 Subject: [PATCH 22/22] Changed the hardcoded value for leftMargin CURA-7038 --- plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml index 5685ddd2b5..f5a20986d1 100644 --- a/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml +++ b/plugins/Toolbox/resources/qml/dialogs/CompatibilityDialog.qml @@ -76,7 +76,7 @@ UM.Dialog{ text: model.name font: UM.Theme.getFont("medium_bold") anchors.left: packageIcon.right - anchors.leftMargin: 20 + anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.verticalCenter: packageIcon.verticalCenter color: UM.Theme.getColor("text") elide: Text.ElideRight @@ -118,7 +118,7 @@ UM.Dialog{ text: model.name font: UM.Theme.getFont("medium_bold") anchors.left: packageIcon.right - anchors.leftMargin: 20 + anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.verticalCenter: packageIcon.verticalCenter color: UM.Theme.getColor("text") elide: Text.ElideRight