|
@@ -15,12 +15,10 @@ from UM.Logger import Logger
|
|
|
from UM.Resources import Resources
|
|
|
from UM.Version import Version
|
|
|
|
|
|
-
|
|
|
class CuraPackageManager(QObject):
|
|
|
Version = 1
|
|
|
|
|
|
- # The prefix that's added to all files for an installed package to avoid naming conflicts with user created
|
|
|
- # files.
|
|
|
+ # The prefix that's added to all files for an installed package to avoid naming conflicts with user created files.
|
|
|
PREFIX_PLACE_HOLDER = "-CP;"
|
|
|
|
|
|
def __init__(self, parent = None):
|
|
@@ -31,13 +29,21 @@ class CuraPackageManager(QObject):
|
|
|
self._plugin_registry = self._application.getPluginRegistry()
|
|
|
|
|
|
# JSON file that keeps track of all installed packages.
|
|
|
- self._package_management_file_path = os.path.join(os.path.abspath(Resources.getDataStoragePath()),
|
|
|
- "packages.json")
|
|
|
- self._installed_package_dict = {} # a dict of all installed packages
|
|
|
- self._to_remove_package_set = set() # a set of packages that need to be removed at the next start
|
|
|
- self._to_install_package_dict = {} # a dict of packages that need to be installed at the next start
|
|
|
-
|
|
|
- installedPackagesChanged = pyqtSignal() # Emitted whenever the installed packages collection have been changed.
|
|
|
+ self._bundled_package_management_file_path = os.path.join(
|
|
|
+ os.path.abspath(Resources.getBundledResourcesPath()),
|
|
|
+ "packages.json"
|
|
|
+ )
|
|
|
+ self._user_package_management_file_path = os.path.join(
|
|
|
+ os.path.abspath(Resources.getDataStoragePath()),
|
|
|
+ "packages.json"
|
|
|
+ )
|
|
|
+
|
|
|
+ self._bundled_package_dict = {} # A dict of all bundled packages
|
|
|
+ self._installed_package_dict = {} # A dict of all installed packages
|
|
|
+ self._to_remove_package_set = set() # A set of packages that need to be removed at the next start
|
|
|
+ self._to_install_package_dict = {} # A dict of packages that need to be installed at the next start
|
|
|
+
|
|
|
+ installedPackagesChanged = pyqtSignal() # Emitted whenever the installed packages collection have been changed.
|
|
|
|
|
|
def initialize(self):
|
|
|
self._loadManagementData()
|
|
@@ -46,48 +52,62 @@ class CuraPackageManager(QObject):
|
|
|
|
|
|
# (for initialize) Loads the package management file if exists
|
|
|
def _loadManagementData(self) -> None:
|
|
|
- if not os.path.exists(self._package_management_file_path):
|
|
|
- Logger.log("i", "Package management file %s doesn't exist, do nothing", self._package_management_file_path)
|
|
|
+ # The bundles package management file should always be there
|
|
|
+ if not os.path.exists(self._bundled_package_management_file_path):
|
|
|
+ Logger.log("w", "Bundled package management file could not be found!")
|
|
|
+ return
|
|
|
+ # Load the bundled packages:
|
|
|
+ with open(self._bundled_package_management_file_path, "r", encoding = "utf-8") as f:
|
|
|
+ self._bundled_package_dict = json.load(f, encoding = "utf-8")
|
|
|
+ Logger.log("i", "Loaded bundled packages data from %s", self._bundled_package_management_file_path)
|
|
|
+
|
|
|
+ # Load the user package management file
|
|
|
+ if not os.path.exists(self._user_package_management_file_path):
|
|
|
+ Logger.log("i", "User package management file %s doesn't exist, do nothing", self._user_package_management_file_path)
|
|
|
return
|
|
|
|
|
|
# Need to use the file lock here to prevent concurrent I/O from other processes/threads
|
|
|
container_registry = self._application.getContainerRegistry()
|
|
|
with container_registry.lockFile():
|
|
|
- with open(self._package_management_file_path, "r", encoding = "utf-8") as f:
|
|
|
- management_dict = json.load(f, encoding = "utf-8")
|
|
|
|
|
|
+ # Load the user packages:
|
|
|
+ with open(self._user_package_management_file_path, "r", encoding="utf-8") as f:
|
|
|
+ management_dict = json.load(f, encoding="utf-8")
|
|
|
self._installed_package_dict = management_dict.get("installed", {})
|
|
|
self._to_remove_package_set = set(management_dict.get("to_remove", []))
|
|
|
self._to_install_package_dict = management_dict.get("to_install", {})
|
|
|
-
|
|
|
- Logger.log("i", "Package management file %s is loaded", self._package_management_file_path)
|
|
|
+ Logger.log("i", "Loaded user packages management file from %s", self._user_package_management_file_path)
|
|
|
|
|
|
def _saveManagementData(self) -> None:
|
|
|
# Need to use the file lock here to prevent concurrent I/O from other processes/threads
|
|
|
container_registry = self._application.getContainerRegistry()
|
|
|
with container_registry.lockFile():
|
|
|
- with open(self._package_management_file_path, "w", encoding = "utf-8") as f:
|
|
|
+ with open(self._user_package_management_file_path, "w", encoding = "utf-8") as f:
|
|
|
data_dict = {"version": CuraPackageManager.Version,
|
|
|
"installed": self._installed_package_dict,
|
|
|
"to_remove": list(self._to_remove_package_set),
|
|
|
"to_install": self._to_install_package_dict}
|
|
|
data_dict["to_remove"] = list(data_dict["to_remove"])
|
|
|
- json.dump(data_dict, f)
|
|
|
- Logger.log("i", "Package management file %s is saved", self._package_management_file_path)
|
|
|
+ json.dump(data_dict, f, sort_keys = True, indent = 4)
|
|
|
+ Logger.log("i", "Package management file %s was saved", self._user_package_management_file_path)
|
|
|
|
|
|
# (for initialize) Removes all packages that have been scheduled to be removed.
|
|
|
def _removeAllScheduledPackages(self) -> None:
|
|
|
for package_id in self._to_remove_package_set:
|
|
|
self._purgePackage(package_id)
|
|
|
+ del self._installed_package_dict[package_id]
|
|
|
self._to_remove_package_set.clear()
|
|
|
self._saveManagementData()
|
|
|
|
|
|
# (for initialize) Installs all packages that have been scheduled to be installed.
|
|
|
def _installAllScheduledPackages(self) -> None:
|
|
|
- for package_id, installation_package_data in self._to_install_package_dict.items():
|
|
|
- self._installPackage(installation_package_data)
|
|
|
- self._to_install_package_dict.clear()
|
|
|
- self._saveManagementData()
|
|
|
+
|
|
|
+ while self._to_install_package_dict:
|
|
|
+ package_id, package_info = list(self._to_install_package_dict.items())[0]
|
|
|
+ self._installPackage(package_info)
|
|
|
+ self._installed_package_dict[package_id] = self._to_install_package_dict[package_id]
|
|
|
+ del self._to_install_package_dict[package_id]
|
|
|
+ self._saveManagementData()
|
|
|
|
|
|
# Checks the given package is installed. If so, return a dictionary that contains the package's information.
|
|
|
def getInstalledPackageInfo(self, package_id: str) -> Optional[dict]:
|
|
@@ -99,87 +119,66 @@ class CuraPackageManager(QObject):
|
|
|
return package_info
|
|
|
|
|
|
if package_id in self._installed_package_dict:
|
|
|
- package_info = self._installed_package_dict.get(package_id)
|
|
|
+ package_info = self._installed_package_dict[package_id]["package_info"]
|
|
|
return package_info
|
|
|
|
|
|
- for section, packages in self.getAllInstalledPackagesInfo().items():
|
|
|
- for package in packages:
|
|
|
- if package["package_id"] == package_id:
|
|
|
- return package
|
|
|
+ if package_id in self._bundled_package_dict:
|
|
|
+ package_info = self._bundled_package_dict[package_id]["package_info"]
|
|
|
+ return package_info
|
|
|
|
|
|
return None
|
|
|
|
|
|
def getAllInstalledPackagesInfo(self) -> dict:
|
|
|
- installed_package_id_set = set(self._installed_package_dict.keys()) | set(self._to_install_package_dict.keys())
|
|
|
- installed_package_id_set = installed_package_id_set.difference(self._to_remove_package_set)
|
|
|
+ # Add bundled, installed, and to-install packages to the set of installed package IDs
|
|
|
+ all_installed_ids = set()
|
|
|
|
|
|
- managed_package_id_set = installed_package_id_set | self._to_remove_package_set
|
|
|
-
|
|
|
- # TODO: For absolutely no reason, this function seems to run in a loop
|
|
|
- # even though no loop is ever called with it.
|
|
|
+ if self._bundled_package_dict.keys():
|
|
|
+ all_installed_ids = all_installed_ids.union(set(self._bundled_package_dict.keys()))
|
|
|
+ if self._installed_package_dict.keys():
|
|
|
+ all_installed_ids = all_installed_ids.union(set(self._installed_package_dict.keys()))
|
|
|
+ if self._to_install_package_dict.keys():
|
|
|
+ all_installed_ids = all_installed_ids.union(set(self._to_install_package_dict.keys()))
|
|
|
+ all_installed_ids = all_installed_ids.difference(self._to_remove_package_set)
|
|
|
|
|
|
# map of <package_type> -> <package_id> -> <package_info>
|
|
|
installed_packages_dict = {}
|
|
|
- for package_id in installed_package_id_set:
|
|
|
+ for package_id in all_installed_ids:
|
|
|
+
|
|
|
+ # Skip required plugins as they should not be tampered with
|
|
|
if package_id in Application.getInstance().getRequiredPlugins():
|
|
|
continue
|
|
|
+
|
|
|
+ package_info = None
|
|
|
+ # Add bundled plugins
|
|
|
+ if package_id in self._bundled_package_dict:
|
|
|
+ package_info = self._bundled_package_dict[package_id]["package_info"]
|
|
|
+
|
|
|
+ # Add installed plugins
|
|
|
+ if package_id in self._installed_package_dict:
|
|
|
+ package_info = self._installed_package_dict[package_id]["package_info"]
|
|
|
+
|
|
|
+ # Add to install plugins
|
|
|
if package_id in self._to_install_package_dict:
|
|
|
package_info = self._to_install_package_dict[package_id]["package_info"]
|
|
|
- else:
|
|
|
- package_info = self._installed_package_dict[package_id]
|
|
|
- package_info["is_bundled"] = False
|
|
|
|
|
|
- package_type = package_info["package_type"]
|
|
|
- if package_type not in installed_packages_dict:
|
|
|
- installed_packages_dict[package_type] = []
|
|
|
- installed_packages_dict[package_type].append( package_info )
|
|
|
+ if package_info is None:
|
|
|
+ continue
|
|
|
|
|
|
# We also need to get information from the plugin registry such as if a plugin is active
|
|
|
package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id)
|
|
|
|
|
|
- # Also get all bundled plugins
|
|
|
- all_metadata = self._plugin_registry.getAllMetaData()
|
|
|
- for item in all_metadata:
|
|
|
- if item == {}:
|
|
|
- continue
|
|
|
-
|
|
|
- plugin_package_info = self.__convertPluginMetadataToPackageMetadata(item)
|
|
|
- # Only gather the bundled plugins here.
|
|
|
- package_id = plugin_package_info["package_id"]
|
|
|
- if package_id in managed_package_id_set:
|
|
|
- continue
|
|
|
- if package_id in Application.getInstance().getRequiredPlugins():
|
|
|
- continue
|
|
|
+ # If the package ID is in bundled, label it as such
|
|
|
+ package_info["is_bundled"] = package_info["package_id"] in self._bundled_package_dict.keys()
|
|
|
|
|
|
- plugin_package_info["is_bundled"] = True if plugin_package_info["author"]["display_name"] == "Ultimaker B.V." else False
|
|
|
- plugin_package_info["is_active"] = self._plugin_registry.isActivePlugin(package_id)
|
|
|
- package_type = "plugin"
|
|
|
- if package_type not in installed_packages_dict:
|
|
|
- installed_packages_dict[package_type] = []
|
|
|
- installed_packages_dict[package_type].append( plugin_package_info )
|
|
|
+ # If there is not a section in the dict for this type, add it
|
|
|
+ if package_info["package_type"] not in installed_packages_dict:
|
|
|
+ installed_packages_dict[package_info["package_type"]] = []
|
|
|
+
|
|
|
+ # Finally, add the data
|
|
|
+ installed_packages_dict[package_info["package_type"]].append(package_info)
|
|
|
|
|
|
return installed_packages_dict
|
|
|
|
|
|
- def __convertPluginMetadataToPackageMetadata(self, plugin_metadata: dict) -> dict:
|
|
|
- package_metadata = {
|
|
|
- "package_id": plugin_metadata["id"],
|
|
|
- "package_type": "plugin",
|
|
|
- "display_name": plugin_metadata["plugin"]["name"],
|
|
|
- "description": plugin_metadata["plugin"].get("description"),
|
|
|
- "package_version": plugin_metadata["plugin"]["version"],
|
|
|
- "cura_version": int(plugin_metadata["plugin"]["api"]),
|
|
|
- "website": "",
|
|
|
- "author_id": plugin_metadata["plugin"].get("author", "UnknownID"),
|
|
|
- "author": {
|
|
|
- "author_id": plugin_metadata["plugin"].get("author", "UnknownID"),
|
|
|
- "display_name": plugin_metadata["plugin"].get("author", ""),
|
|
|
- "email": "",
|
|
|
- "website": "",
|
|
|
- },
|
|
|
- "tags": ["plugin"],
|
|
|
- }
|
|
|
- return package_metadata
|
|
|
-
|
|
|
# Checks if the given package is installed.
|
|
|
def isPackageInstalled(self, package_id: str) -> bool:
|
|
|
return self.getInstalledPackageInfo(package_id) is not None
|
|
@@ -293,7 +292,7 @@ class CuraPackageManager(QObject):
|
|
|
from cura.CuraApplication import CuraApplication
|
|
|
installation_dirs_dict = {
|
|
|
"materials": Resources.getStoragePath(CuraApplication.ResourceTypes.MaterialInstanceContainer),
|
|
|
- "quality": Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer),
|
|
|
+ "qualities": Resources.getStoragePath(CuraApplication.ResourceTypes.QualityInstanceContainer),
|
|
|
"plugins": os.path.abspath(Resources.getStoragePath(Resources.Plugins)),
|
|
|
}
|
|
|
|