Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/Toolbox/src/Toolbox.py')
-rw-r--r--plugins/Toolbox/src/Toolbox.py878
1 files changed, 0 insertions, 878 deletions
diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py
deleted file mode 100644
index 44b62b64af..0000000000
--- a/plugins/Toolbox/src/Toolbox.py
+++ /dev/null
@@ -1,878 +0,0 @@
-# Copyright (c) 2021 Ultimaker B.V.
-# Toolbox is released under the terms of the LGPLv3 or higher.
-
-import json
-import os
-import tempfile
-from typing import cast, Any, Dict, List, Set, TYPE_CHECKING, Tuple, Optional, Union
-
-from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
-from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
-
-from UM.Extension import Extension
-from UM.Logger import Logger
-from UM.PluginRegistry import PluginRegistry
-from UM.TaskManagement.HttpRequestScope import JsonDecoratorScope
-from UM.Version import Version
-from UM.i18n import i18nCatalog
-from cura import ApplicationMetadata
-from cura.CuraApplication import CuraApplication
-from cura.Machines.ContainerTree import ContainerTree
-from cura.UltimakerCloud.UltimakerCloudScope import UltimakerCloudScope
-from .AuthorsModel import AuthorsModel
-from .CloudApiModel import CloudApiModel
-from .CloudSync.LicenseModel import LicenseModel
-from .PackagesModel import PackagesModel
-
-if TYPE_CHECKING:
- from UM.TaskManagement.HttpRequestData import HttpRequestData
- from cura.Settings.GlobalStack import GlobalStack
-
-i18n_catalog = i18nCatalog("cura")
-
-DEFAULT_MARKETPLACE_ROOT = "https://marketplace.ultimaker.com" # type: str
-
-try:
- from cura.CuraVersion import CuraMarketplaceRoot
-except ImportError:
- CuraMarketplaceRoot = DEFAULT_MARKETPLACE_ROOT
-
-
-class Toolbox(QObject, Extension):
- """Provides a marketplace for users to download plugins an materials"""
-
- def __init__(self, application: CuraApplication) -> None:
- super().__init__()
-
- self._application = application # type: CuraApplication
-
- # Network:
- self._download_request_data = None # type: Optional[HttpRequestData]
- self._download_progress = 0 # type: float
- self._is_downloading = False # type: bool
- self._cloud_scope = UltimakerCloudScope(application) # type: UltimakerCloudScope
- self._json_scope = JsonDecoratorScope(self._cloud_scope) # type: JsonDecoratorScope
-
- self._request_urls = {} # type: Dict[str, str]
- self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
- self._old_plugin_ids = set() # type: Set[str]
- self._old_plugin_metadata = dict() # type: Dict[str, Dict[str, Any]]
-
- # The responses as given by the server parsed to a list.
- self._server_response_data = {
- "authors": [],
- "packages": [],
- "updates": []
- } # type: Dict[str, List[Any]]
-
- # Models:
- self._models = {
- "authors": AuthorsModel(self),
- "packages": PackagesModel(self),
- "updates": PackagesModel(self)
- } # type: Dict[str, Union[AuthorsModel, PackagesModel]]
-
- self._plugins_showcase_model = PackagesModel(self)
- self._plugins_available_model = PackagesModel(self)
- self._plugins_installed_model = PackagesModel(self)
- self._plugins_installed_model.setFilter({"is_bundled": "False"})
- self._plugins_bundled_model = PackagesModel(self)
- self._plugins_bundled_model.setFilter({"is_bundled": "True"})
- self._materials_showcase_model = AuthorsModel(self)
- self._materials_available_model = AuthorsModel(self)
- self._materials_installed_model = PackagesModel(self)
- self._materials_installed_model.setFilter({"is_bundled": "False"})
- self._materials_bundled_model = PackagesModel(self)
- self._materials_bundled_model.setFilter({"is_bundled": "True"})
- self._materials_generic_model = PackagesModel(self)
-
- self._license_model = LicenseModel()
-
- # These properties are for keeping track of the UI state:
- # ----------------------------------------------------------------------
- # View category defines which filter to use, and therefore effectively
- # which category is currently being displayed. For example, possible
- # values include "plugin" or "material", but also "installed".
- self._view_category = "plugin" # type: str
-
- # View page defines which type of page layout to use. For example,
- # possible values include "overview", "detail" or "author".
- self._view_page = "welcome" # type: str
-
- # Active package refers to which package is currently being downloaded,
- # installed, or otherwise modified.
- self._active_package = None # type: Optional[Dict[str, Any]]
-
- self._dialog = None # type: Optional[QObject]
- self._confirm_reset_dialog = None # type: Optional[QObject]
- self._resetUninstallVariables()
-
- self._restart_required = False # type: bool
-
- # variables for the license agreement dialog
- self._license_dialog_plugin_file_location = "" # type: str
-
- self._application.initializationFinished.connect(self._onAppInitialized)
-
- # Signals:
- # --------------------------------------------------------------------------
- # Downloading changes
- activePackageChanged = pyqtSignal()
- onDownloadProgressChanged = pyqtSignal()
- onIsDownloadingChanged = pyqtSignal()
- restartRequiredChanged = pyqtSignal()
- installChanged = pyqtSignal()
- toolboxEnabledChanged = pyqtSignal()
-
- # UI changes
- viewChanged = pyqtSignal()
- detailViewChanged = pyqtSignal()
- filterChanged = pyqtSignal()
- metadataChanged = pyqtSignal()
- showLicenseDialog = pyqtSignal()
- closeLicenseDialog = pyqtSignal()
- uninstallVariablesChanged = pyqtSignal()
-
- def _restart(self):
- """Go back to the start state (welcome screen or loading if no login required)"""
-
- # For an Essentials build, login is mandatory
- if not self._application.getCuraAPI().account.isLoggedIn and ApplicationMetadata.IsEnterpriseVersion:
- self.setViewPage("welcome")
- else:
- self.setViewPage("loading")
- self._fetchPackageData()
-
- def _resetUninstallVariables(self) -> None:
- self._package_id_to_uninstall = None # type: Optional[str]
- self._package_name_to_uninstall = ""
- self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
- self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
-
- def getLicenseDialogPluginFileLocation(self) -> str:
- return self._license_dialog_plugin_file_location
-
- def openLicenseDialog(self, plugin_name: str, license_content: str, plugin_file_location: str, icon_url: str) -> None:
- # Set page 1/1 when opening the dialog for a single package
- self._license_model.setCurrentPageIdx(0)
- self._license_model.setPageCount(1)
- self._license_model.setIconUrl(icon_url)
-
- self._license_model.setPackageName(plugin_name)
- self._license_model.setLicenseText(license_content)
- self._license_dialog_plugin_file_location = plugin_file_location
- self.showLicenseDialog.emit()
-
- # This is a plugin, so most of the components required are not ready when
- # this is initialized. Therefore, we wait until the application is ready.
- def _onAppInitialized(self) -> None:
- self._plugin_registry = self._application.getPluginRegistry()
- self._package_manager = self._application.getPackageManager()
-
- # 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()]
- installed_packages_query = "&installed_packages=".join(installed_package_ids_with_versions)
-
- self._request_urls = {
- "authors": "{base_url}/authors".format(base_url = CloudApiModel.api_url),
- "packages": "{base_url}/packages".format(base_url = CloudApiModel.api_url),
- "updates": "{base_url}/packages/package-updates?installed_packages={query}".format(
- base_url = CloudApiModel.api_url, query = installed_packages_query)
- }
-
- self._application.getCuraAPI().account.loginStateChanged.connect(self._restart)
-
- preferences = CuraApplication.getInstance().getPreferences()
-
- preferences.addPreference("info/automatic_plugin_update_check", True)
-
- # On boot we check which packages have updates.
- if preferences.getValue("info/automatic_plugin_update_check") and len(installed_package_ids_with_versions) > 0:
- # Request the latest and greatest!
- self._makeRequestByType("updates")
-
- def _fetchPackageData(self) -> None:
- self._makeRequestByType("packages")
- self._makeRequestByType("authors")
- self._updateInstalledModels()
-
- # Displays the toolbox
- @pyqtSlot()
- def launch(self) -> None:
- if not self._dialog:
- self._dialog = self._createDialog("Toolbox.qml")
-
- if not self._dialog:
- Logger.log("e", "Unexpected error trying to create the 'Marketplace' dialog.")
- return
-
- self._restart()
-
- self._dialog.show()
- # Apply enabled/disabled state to installed plugins
- self.toolboxEnabledChanged.emit()
-
- def _createDialog(self, qml_name: str) -> Optional[QObject]:
- Logger.log("d", "Marketplace: Creating dialog [%s].", qml_name)
- plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
- if not plugin_path:
- return None
- path = os.path.join(plugin_path, "resources", "qml", qml_name)
-
- dialog = self._application.createQmlComponent(path, {
- "toolbox": self,
- "handler": self,
- "licenseModel": self._license_model
- })
- if not dialog:
- return None
- return dialog
-
- def _convertPluginMetadata(self, plugin_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
- try:
- highest_sdk_version_supported = Version(0)
- for supported_version in plugin_data["plugin"]["supported_sdk_versions"]:
- if supported_version > highest_sdk_version_supported:
- highest_sdk_version_supported = supported_version
-
- formatted = {
- "package_id": plugin_data["id"],
- "package_type": "plugin",
- "display_name": plugin_data["plugin"]["name"],
- "package_version": plugin_data["plugin"]["version"],
- "sdk_version": highest_sdk_version_supported,
- "author": {
- "author_id": plugin_data["plugin"]["author"],
- "display_name": plugin_data["plugin"]["author"]
- },
- "is_installed": True,
- "description": plugin_data["plugin"]["description"]
- }
- return formatted
- except KeyError:
- Logger.log("w", "Unable to convert plugin meta data %s", str(plugin_data))
- return None
-
- @pyqtSlot()
- def _updateInstalledModels(self) -> None:
- # This is moved here to avoid code duplication and so that after installing plugins they get removed from the
- # list of old plugins
- old_plugin_ids = self._plugin_registry.getInstalledPlugins()
- installed_package_ids = self._package_manager.getAllInstalledPackageIDs()
- scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
-
- self._old_plugin_ids = set()
- self._old_plugin_metadata = dict()
-
- for plugin_id in old_plugin_ids:
- # Neither the installed packages nor the packages that are scheduled to remove are old plugins
- if plugin_id not in installed_package_ids and plugin_id not in scheduled_to_remove_package_ids:
- Logger.log("d", "Found a plugin that was installed with the old plugin browser: %s", plugin_id)
-
- old_metadata = self._plugin_registry.getMetaData(plugin_id)
- new_metadata = self._convertPluginMetadata(old_metadata)
- if new_metadata is None:
- # Something went wrong converting it.
- continue
- self._old_plugin_ids.add(plugin_id)
- self._old_plugin_metadata[new_metadata["package_id"]] = new_metadata
-
- all_packages = self._package_manager.getAllInstalledPackagesInfo()
- if "plugin" in all_packages:
- # For old plugins, we only want to include the old custom plugin that were installed via the old toolbox.
- # The bundled plugins will be included in JSON files in the "bundled_packages" folder, so the bundled
- # plugins should be excluded from the old plugins list/dict.
- all_plugin_package_ids = set(package["package_id"] for package in all_packages["plugin"])
- self._old_plugin_ids = set(plugin_id for plugin_id in self._old_plugin_ids
- if plugin_id not in all_plugin_package_ids)
- self._old_plugin_metadata = {k: v for k, v in self._old_plugin_metadata.items() if k in self._old_plugin_ids}
-
- self._plugins_installed_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
- self._plugins_bundled_model.setMetadata(all_packages["plugin"] + list(self._old_plugin_metadata.values()))
- self.metadataChanged.emit()
- if "material" in all_packages:
- self._materials_installed_model.setMetadata(all_packages["material"])
- self._materials_bundled_model.setMetadata(all_packages["material"])
- self.metadataChanged.emit()
-
- @pyqtSlot(str)
- def install(self, file_path: str) -> Optional[str]:
- package_id = self._package_manager.installPackage(file_path)
- self.installChanged.emit()
- self._updateInstalledModels()
- self.metadataChanged.emit()
- self._restart_required = True
- self.restartRequiredChanged.emit()
- return package_id
-
- @pyqtSlot(str)
- def checkPackageUsageAndUninstall(self, package_id: str) -> None:
- """Check package usage and uninstall
-
- If the package is in use, you'll get a confirmation dialog to set everything to default
- """
-
- package_used_materials, package_used_qualities = self._package_manager.getMachinesUsingPackage(package_id)
- if package_used_materials or package_used_qualities:
- # Set up "uninstall variables" for resetMaterialsQualitiesAndUninstall
- self._package_id_to_uninstall = package_id
- package_info = self._package_manager.getInstalledPackageInfo(package_id)
- self._package_name_to_uninstall = package_info.get("display_name", package_info.get("package_id"))
- self._package_used_materials = package_used_materials
- self._package_used_qualities = package_used_qualities
- # Ask change to default material / profile
- if self._confirm_reset_dialog is None:
- self._confirm_reset_dialog = self._createDialog("dialogs/ToolboxConfirmUninstallResetDialog.qml")
- self.uninstallVariablesChanged.emit()
- if self._confirm_reset_dialog is None:
- Logger.log("e", "ToolboxConfirmUninstallResetDialog should have been initialized, but it is not. Not showing dialog and not uninstalling package.")
- else:
- self._confirm_reset_dialog.show()
- else:
- # Plain uninstall
- self.uninstall(package_id)
-
- @pyqtProperty(str, notify = uninstallVariablesChanged)
- def pluginToUninstall(self) -> str:
- return self._package_name_to_uninstall
-
- @pyqtProperty(str, notify = uninstallVariablesChanged)
- def uninstallUsedMaterials(self) -> str:
- return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
-
- @pyqtProperty(str, notify = uninstallVariablesChanged)
- def uninstallUsedQualities(self) -> str:
- return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
-
- @pyqtSlot()
- def closeConfirmResetDialog(self) -> None:
- if self._confirm_reset_dialog is not None:
- self._confirm_reset_dialog.close()
-
- @pyqtSlot()
- def resetMaterialsQualitiesAndUninstall(self) -> None:
- """Uses "uninstall variables" to reset qualities and materials, then uninstall
-
- It's used as an action on Confirm reset on Uninstall
- """
-
- application = CuraApplication.getInstance()
- machine_manager = application.getMachineManager()
- container_tree = ContainerTree.getInstance()
-
- for global_stack, extruder_nr, container_id in self._package_used_materials:
- extruder = global_stack.extruderList[int(extruder_nr)]
- approximate_diameter = extruder.getApproximateMaterialDiameter()
- variant_node = container_tree.machines[global_stack.definition.getId()].variants[extruder.variant.getName()]
- default_material_node = variant_node.preferredMaterial(approximate_diameter)
- machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack)
- for global_stack, extruder_nr, container_id in self._package_used_qualities:
- variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
- material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
- extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
- definition_id = global_stack.definition.getId()
- machine_node = container_tree.machines[definition_id]
- default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type]
- machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
-
- if self._package_id_to_uninstall is not None:
- self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
- self.uninstall(self._package_id_to_uninstall)
- self._resetUninstallVariables()
- self.closeConfirmResetDialog()
-
- @pyqtSlot()
- def onLicenseAccepted(self):
- self.closeLicenseDialog.emit()
- package_id = self.install(self.getLicenseDialogPluginFileLocation())
-
-
- @pyqtSlot()
- def onLicenseDeclined(self):
- self.closeLicenseDialog.emit()
-
- def _markPackageMaterialsAsToBeUninstalled(self, package_id: str) -> None:
- container_registry = self._application.getContainerRegistry()
-
- all_containers = self._package_manager.getPackageContainerIds(package_id)
- for container_id in all_containers:
- containers = container_registry.findInstanceContainers(id = container_id)
- if not containers:
- continue
- container = containers[0]
- if container.getMetaDataEntry("type") != "material":
- continue
- root_material_id = container.getMetaDataEntry("base_file")
- root_material_containers = container_registry.findInstanceContainers(id = root_material_id)
- if not root_material_containers:
- continue
- root_material_container = root_material_containers[0]
- root_material_container.setMetaDataEntry("removed", True)
-
- @pyqtSlot(str)
- def uninstall(self, package_id: str) -> None:
- self._package_manager.removePackage(package_id, force_add = True)
- self.installChanged.emit()
- self._updateInstalledModels()
- self.metadataChanged.emit()
- self._restart_required = True
- self.restartRequiredChanged.emit()
-
- def _update(self) -> None:
- """Actual update packages that are in self._to_update"""
-
- if self._to_update:
- plugin_id = self._to_update.pop(0)
- remote_package = self.getRemotePackage(plugin_id)
- if remote_package:
- download_url = remote_package["download_url"]
- Logger.log("d", "Updating package [%s]..." % plugin_id)
- self.startDownload(download_url)
- else:
- Logger.log("e", "Could not update package [%s] because there is no remote package info available.", plugin_id)
-
- if self._to_update:
- self._application.callLater(self._update)
-
- @pyqtSlot(str)
- def update(self, plugin_id: str) -> None:
- """Update a plugin by plugin_id"""
-
- self._to_update.append(plugin_id)
- self._application.callLater(self._update)
-
- @pyqtSlot(str)
- def enable(self, plugin_id: str) -> None:
- self._plugin_registry.enablePlugin(plugin_id)
- self.toolboxEnabledChanged.emit()
- Logger.log("i", "%s was set as 'active'.", plugin_id)
- self._restart_required = True
- self.restartRequiredChanged.emit()
-
- @pyqtSlot(str)
- def disable(self, plugin_id: str) -> None:
- self._plugin_registry.disablePlugin(plugin_id)
- self.toolboxEnabledChanged.emit()
- Logger.log("i", "%s was set as 'deactive'.", plugin_id)
- self._restart_required = True
- self.restartRequiredChanged.emit()
-
- @pyqtProperty(bool, notify = metadataChanged)
- def dataReady(self) -> bool:
- return self._packages_model is not None
-
- @pyqtProperty(bool, notify = restartRequiredChanged)
- def restartRequired(self) -> bool:
- return self._restart_required
-
- @pyqtSlot()
- def restart(self) -> None:
- self._application.windowClosed()
-
- def getRemotePackage(self, package_id: str) -> Optional[Dict]:
- # TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
- remote_package = None
- for package in self._server_response_data["packages"]:
- if package["package_id"] == package_id:
- remote_package = package
- break
- return remote_package
-
- @pyqtSlot(str, result = bool)
- def canDowngrade(self, package_id: str) -> bool:
- # If the currently installed version is higher than the bundled version (if present), the we can downgrade
- # this package.
- local_package = self._package_manager.getInstalledPackageInfo(package_id)
- if local_package is None:
- return False
-
- bundled_package = self._package_manager.getBundledPackageInfo(package_id)
- if bundled_package is None:
- return False
-
- local_version = Version(local_package["package_version"])
- bundled_version = Version(bundled_package["package_version"])
- return bundled_version < local_version
-
- @pyqtSlot(str, result = bool)
- def isInstalled(self, package_id: str) -> bool:
- result = self._package_manager.isPackageInstalled(package_id)
- # Also check the old plugins list if it's not found in the package manager.
- if not result:
- result = self.isOldPlugin(package_id)
- return result
-
- @pyqtSlot(str, result = int)
- def getNumberOfInstalledPackagesByAuthor(self, author_id: str) -> int:
- count = 0
- for package in self._materials_installed_model.items:
- if package["author_id"] == author_id:
- count += 1
- return count
-
- # This slot is only used to get the number of material packages by author, not any other type of packages.
- @pyqtSlot(str, result = int)
- def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
- count = 0
- for package in self._server_response_data["packages"]:
- if package["package_type"] == "material":
- if package["author"]["author_id"] == author_id:
- count += 1
- return count
-
- @pyqtSlot(str, result = bool)
- def isEnabled(self, package_id: str) -> bool:
- 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:
- return plugin_id in self._old_plugin_ids
-
- def getOldPluginPackageMetadata(self, plugin_id: str) -> Optional[Dict[str, Any]]:
- return self._old_plugin_metadata.get(plugin_id)
-
- def isLoadingComplete(self) -> bool:
- populated = 0
- for metadata_list in self._server_response_data.items():
- if metadata_list:
- populated += 1
- return populated == len(self._server_response_data.items())
-
- # Make API Calls
- # --------------------------------------------------------------------------
- def _makeRequestByType(self, request_type: str) -> None:
- Logger.debug(f"Requesting {request_type} metadata from server.")
- url = self._request_urls[request_type]
-
- callback = lambda r, rt = request_type: self._onDataRequestFinished(rt, r)
- error_callback = lambda r, e, rt = request_type: self._onDataRequestError(rt, r, e)
- self._application.getHttpRequestManager().get(url,
- callback = callback,
- error_callback = error_callback,
- scope=self._json_scope)
-
- @pyqtSlot(str)
- def startDownload(self, url: str) -> None:
- Logger.info(f"Attempting to download & install package from {url}.")
-
- callback = lambda r: self._onDownloadFinished(r)
- error_callback = lambda r, e: self._onDownloadFailed(r, e)
- download_progress_callback = self._onDownloadProgress
- request_data = self._application.getHttpRequestManager().get(url,
- callback = callback,
- error_callback = error_callback,
- download_progress_callback = download_progress_callback,
- scope=self._cloud_scope
- )
-
- self._download_request_data = request_data
- self.setDownloadProgress(0)
- self.setIsDownloading(True)
-
- @pyqtSlot()
- def cancelDownload(self) -> None:
- Logger.info(f"User cancelled the download of a package. request {self._download_request_data}")
- if self._download_request_data is not None:
- self._application.getHttpRequestManager().abortRequest(self._download_request_data)
- self._download_request_data = None
- self.resetDownload()
-
- def resetDownload(self) -> None:
- self.setDownloadProgress(0)
- self.setIsDownloading(False)
-
- # Handlers for Network Events
- # --------------------------------------------------------------------------
- def _onDataRequestError(self, request_type: str, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
- Logger.error(f"Request {request_type} failed due to error {error}: {reply.errorString()}")
- self.setViewPage("errored")
-
- def _onDataRequestFinished(self, request_type: str, reply: "QNetworkReply") -> None:
- if reply.operation() != QNetworkAccessManager.Operation.GetOperation:
- Logger.log("e", "_onDataRequestFinished() only handles GET requests but got [%s] instead", reply.operation())
- return
-
- http_status_code = reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute)
- if http_status_code != 200:
- Logger.log("e", "Request type [%s] got non-200 HTTP response: [%s]", http_status_code)
- self.setViewPage("errored")
- return
-
- data = bytes(reply.readAll())
- try:
- json_data = json.loads(data.decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("e", "Failed to decode response data as JSON for request type [%s], response data [%s]",
- request_type, data)
- self.setViewPage("errored")
- return
-
- # Check for errors:
- if "errors" in json_data:
- for error in json_data["errors"]:
- Logger.log("e", "Request type [%s] got response showing error: %s", error.get("title", "No error title found"))
- self.setViewPage("errored")
- return
-
- # Create model and apply metadata:
- if not self._models[request_type]:
- Logger.log("e", "Could not find the model for request type [%s].", request_type)
- self.setViewPage("errored")
- return
-
- self._server_response_data[request_type] = json_data["data"]
- self._models[request_type].setMetadata(self._server_response_data[request_type])
-
- if request_type == "packages":
- self._models[request_type].setFilter({"type": "plugin"})
- self.reBuildMaterialsModels()
- self.reBuildPluginsModels()
- self._notifyPackageManager()
- elif request_type == "authors":
- self._models[request_type].setFilter({"package_types": "material"})
- self._models[request_type].setFilter({"tags": "generic"})
- elif request_type == "updates":
- # 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[request_type]])
- self._package_manager.setPackagesWithUpdate(packages)
-
- self.metadataChanged.emit()
-
- if self.isLoadingComplete():
- self.setViewPage("overview")
-
- # 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"]:
- self._package_manager.addAvailablePackageVersion(package["package_id"], Version(package["package_version"]))
-
- def _onDownloadFinished(self, reply: "QNetworkReply") -> None:
- self.resetDownload()
-
- if reply.attribute(QNetworkRequest.Attribute.HttpStatusCodeAttribute) != 200:
- try:
- reply_error = json.loads(reply.readAll().data().decode("utf-8"))
- except Exception as e:
- reply_error = str(e)
- Logger.log("w", "Failed to download package. The following error was returned: %s", reply_error)
- return
- # Must not delete the temporary file on Windows
- self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
- file_path = self._temp_plugin_file.name
- # Write first and close, otherwise on Windows, it cannot read the file
- self._temp_plugin_file.write(reply.readAll())
- self._temp_plugin_file.close()
- self._onDownloadComplete(file_path)
-
- def _onDownloadFailed(self, reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None:
- Logger.log("w", "Failed to download package. The following error was returned: %s", error)
-
- self.resetDownload()
-
- def _onDownloadProgress(self, bytes_sent: int, bytes_total: int) -> None:
- if bytes_total > 0:
- new_progress = bytes_sent / bytes_total * 100
- self.setDownloadProgress(new_progress)
- Logger.log("d", "new download progress %s / %s : %s%%", bytes_sent, bytes_total, new_progress)
-
- def _onDownloadComplete(self, file_path: str) -> None:
- Logger.log("i", "Download complete.")
- package_info = self._package_manager.getPackageInfo(file_path)
- if not package_info:
- Logger.log("w", "Package file [%s] was not a valid CuraPackage.", file_path)
- return
- package_id = package_info["package_id"]
-
- try:
- license_content = self._package_manager.getPackageLicense(file_path)
- except EnvironmentError as e:
- Logger.error(f"Could not open downloaded package {package_id} to read license file! {type(e)} - {e}")
- return
- if license_content is not None:
- # get the icon url for package_id, make sure the result is a string, never None
- icon_url = next((x["icon_url"] for x in self.packagesModel.items if x["id"] == package_id), None) or ""
- self.openLicenseDialog(package_info["display_name"], license_content, file_path, icon_url)
- return
-
- installed_id = self.install(file_path)
- if installed_id != package_id:
- Logger.error("Installed package {} does not match {}".format(installed_id, package_id))
-
- # Getter & Setters for Properties:
- # --------------------------------------------------------------------------
- def setDownloadProgress(self, progress: float) -> None:
- if progress != self._download_progress:
- self._download_progress = progress
- self.onDownloadProgressChanged.emit()
-
- @pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged)
- def downloadProgress(self) -> float:
- return self._download_progress
-
- def setIsDownloading(self, is_downloading: bool) -> None:
- if self._is_downloading != is_downloading:
- self._is_downloading = is_downloading
- self.onIsDownloadingChanged.emit()
-
- @pyqtProperty(bool, fset = setIsDownloading, notify = onIsDownloadingChanged)
- def isDownloading(self) -> bool:
- return self._is_downloading
-
- def setActivePackage(self, package: QObject) -> None:
- if self._active_package != package:
- self._active_package = package
- self.activePackageChanged.emit()
-
- @pyqtProperty(QObject, fset = setActivePackage, notify = activePackageChanged)
- def activePackage(self) -> Optional[QObject]:
- """The active package is the package that is currently being downloaded"""
-
- return self._active_package
-
- def setViewCategory(self, category: str = "plugin") -> None:
- if self._view_category != category:
- self._view_category = category
- self.viewChanged.emit()
-
- # Function explicitly defined so that it can be called through the callExtensionsMethod
- # which cannot receive arguments.
- def setViewCategoryToMaterials(self) -> None:
- self.setViewCategory("material")
-
- @pyqtProperty(str, fset = setViewCategory, notify = viewChanged)
- def viewCategory(self) -> str:
- return self._view_category
-
- def setViewPage(self, page: str = "overview") -> None:
- if self._view_page != page:
- self._view_page = page
- self.viewChanged.emit()
-
- @pyqtProperty(str, fset = setViewPage, notify = viewChanged)
- def viewPage(self) -> str:
- return self._view_page
-
- # Exposed Models:
- # --------------------------------------------------------------------------
- @pyqtProperty(QObject, constant = True)
- def authorsModel(self) -> AuthorsModel:
- return cast(AuthorsModel, self._models["authors"])
-
- @pyqtProperty(QObject, constant = True)
- def packagesModel(self) -> PackagesModel:
- return cast(PackagesModel, self._models["packages"])
-
- @pyqtProperty(QObject, constant = True)
- def pluginsShowcaseModel(self) -> PackagesModel:
- return self._plugins_showcase_model
-
- @pyqtProperty(QObject, constant = True)
- def pluginsAvailableModel(self) -> PackagesModel:
- return self._plugins_available_model
-
- @pyqtProperty(QObject, constant = True)
- def pluginsInstalledModel(self) -> PackagesModel:
- return self._plugins_installed_model
-
- @pyqtProperty(QObject, constant = True)
- def pluginsBundledModel(self) -> PackagesModel:
- return self._plugins_bundled_model
-
- @pyqtProperty(QObject, constant = True)
- def materialsShowcaseModel(self) -> AuthorsModel:
- return self._materials_showcase_model
-
- @pyqtProperty(QObject, constant = True)
- def materialsAvailableModel(self) -> AuthorsModel:
- return self._materials_available_model
-
- @pyqtProperty(QObject, constant = True)
- def materialsInstalledModel(self) -> PackagesModel:
- return self._materials_installed_model
-
- @pyqtProperty(QObject, constant = True)
- def materialsBundledModel(self) -> PackagesModel:
- return self._materials_bundled_model
-
- @pyqtProperty(QObject, constant = True)
- def materialsGenericModel(self) -> PackagesModel:
- return self._materials_generic_model
-
- @pyqtSlot(str, result = str)
- def getWebMarketplaceUrl(self, page: str) -> str:
- root = CuraMarketplaceRoot
- if root == "":
- root = DEFAULT_MARKETPLACE_ROOT
- return root + "/app/cura/" + page
-
- # Filter Models:
- # --------------------------------------------------------------------------
- @pyqtSlot(str, str, str)
- def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
- if not self._models[model_type]:
- Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
- return
- self._models[model_type].setFilter({filter_type: parameter})
- self.filterChanged.emit()
-
- @pyqtSlot(str, "QVariantMap")
- def setFilters(self, model_type: str, filter_dict: dict) -> None:
- if not self._models[model_type]:
- Logger.log("w", "Couldn't filter %s model because it doesn't exist.", model_type)
- return
- self._models[model_type].setFilter(filter_dict)
- self.filterChanged.emit()
-
- @pyqtSlot(str)
- def removeFilters(self, model_type: str) -> None:
- if not self._models[model_type]:
- Logger.log("w", "Couldn't remove filters on %s model because it doesn't exist.", model_type)
- return
- self._models[model_type].setFilter({})
- self.filterChanged.emit()
-
- # HACK(S):
- # --------------------------------------------------------------------------
- def reBuildMaterialsModels(self) -> None:
- materials_showcase_metadata = []
- materials_available_metadata = []
- materials_generic_metadata = []
-
- processed_authors = [] # type: List[str]
-
- for item in self._server_response_data["packages"]:
- if item["package_type"] == "material":
-
- author = item["author"]
- if author["author_id"] in processed_authors:
- continue
-
- # Generic materials to be in the same section
- if "generic" in item["tags"]:
- materials_generic_metadata.append(item)
- else:
- if "showcase" in item["tags"]:
- materials_showcase_metadata.append(author)
- else:
- materials_available_metadata.append(author)
-
- processed_authors.append(author["author_id"])
-
- self._materials_showcase_model.setMetadata(materials_showcase_metadata)
- self._materials_available_model.setMetadata(materials_available_metadata)
- self._materials_generic_model.setMetadata(materials_generic_metadata)
-
- def reBuildPluginsModels(self) -> None:
- plugins_showcase_metadata = []
- plugins_available_metadata = []
-
- for item in self._server_response_data["packages"]:
- if item["package_type"] == "plugin":
- if "showcase" in item["tags"]:
- plugins_showcase_metadata.append(item)
- else:
- plugins_available_metadata.append(item)
-
- self._plugins_showcase_model.setMetadata(plugins_showcase_metadata)
- self._plugins_available_model.setMetadata(plugins_available_metadata)