|
@@ -0,0 +1,155 @@
|
|
|
+
|
|
|
+
|
|
|
+import io
|
|
|
+import os
|
|
|
+import re
|
|
|
+
|
|
|
+import shutil
|
|
|
+
|
|
|
+from typing import Optional
|
|
|
+from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
|
|
|
+
|
|
|
+from UM import i18nCatalog
|
|
|
+from UM.Logger import Logger
|
|
|
+from UM.Message import Message
|
|
|
+from UM.Platform import Platform
|
|
|
+from UM.Resources import Resources
|
|
|
+from cura.CuraApplication import CuraApplication
|
|
|
+
|
|
|
+
|
|
|
+class Backup:
|
|
|
+ """
|
|
|
+ The backup class holds all data about a backup.
|
|
|
+ It is also responsible for reading and writing the zip file to the user data folder.
|
|
|
+ """
|
|
|
+
|
|
|
+
|
|
|
+ IGNORED_FILES = [r"cura\.log", r"plugins\.json", r"cache", r"__pycache__", r"\.qmlc", r"\.pyc"]
|
|
|
+
|
|
|
+
|
|
|
+ catalog = i18nCatalog("cura")
|
|
|
+
|
|
|
+ def __init__(self, zip_file: bytes = None, meta_data: dict = None):
|
|
|
+ self.zip_file = zip_file
|
|
|
+ self.meta_data = meta_data
|
|
|
+
|
|
|
+ def makeFromCurrent(self) -> (bool, Optional[str]):
|
|
|
+ """
|
|
|
+ Create a backup from the current user config folder.
|
|
|
+ """
|
|
|
+ cura_release = CuraApplication.getInstance().getVersion()
|
|
|
+ version_data_dir = Resources.getDataStoragePath()
|
|
|
+
|
|
|
+ Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir)
|
|
|
+
|
|
|
+
|
|
|
+ CuraApplication.getInstance().saveSettings()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if Platform.isLinux():
|
|
|
+ preferences_file_name = CuraApplication.getInstance().getApplicationName()
|
|
|
+ preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
|
|
|
+ backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
|
|
|
+ Logger.log("d", "Copying preferences file from %s to %s", preferences_file, backup_preferences_file)
|
|
|
+ shutil.copyfile(preferences_file, backup_preferences_file)
|
|
|
+
|
|
|
+
|
|
|
+ buffer = io.BytesIO()
|
|
|
+ archive = self._makeArchive(buffer, version_data_dir)
|
|
|
+ files = archive.namelist()
|
|
|
+
|
|
|
+
|
|
|
+ machine_count = len([s for s in files if "machine_instances/" in s]) - 1
|
|
|
+ material_count = len([s for s in files if "materials/" in s]) - 1
|
|
|
+ profile_count = len([s for s in files if "quality_changes/" in s]) - 1
|
|
|
+ plugin_count = len([s for s in files if "plugin.json" in s])
|
|
|
+
|
|
|
+
|
|
|
+ self.zip_file = buffer.getvalue()
|
|
|
+ self.meta_data = {
|
|
|
+ "cura_release": cura_release,
|
|
|
+ "machine_count": str(machine_count),
|
|
|
+ "material_count": str(material_count),
|
|
|
+ "profile_count": str(profile_count),
|
|
|
+ "plugin_count": str(plugin_count)
|
|
|
+ }
|
|
|
+
|
|
|
+ def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]:
|
|
|
+ """
|
|
|
+ Make a full archive from the given root path with the given name.
|
|
|
+ :param root_path: The root directory to archive recursively.
|
|
|
+ :return: The archive as bytes.
|
|
|
+ """
|
|
|
+ ignore_string = re.compile("|".join(self.IGNORED_FILES))
|
|
|
+ try:
|
|
|
+ archive = ZipFile(buffer, "w", ZIP_DEFLATED)
|
|
|
+ for root, folders, files in os.walk(root_path):
|
|
|
+ for item_name in folders + files:
|
|
|
+ absolute_path = os.path.join(root, item_name)
|
|
|
+ if ignore_string.search(absolute_path):
|
|
|
+ continue
|
|
|
+ archive.write(absolute_path, absolute_path[len(root_path) + len(os.sep):])
|
|
|
+ archive.close()
|
|
|
+ return archive
|
|
|
+ except (IOError, OSError, BadZipfile) as error:
|
|
|
+ Logger.log("e", "Could not create archive from user data directory: %s", error)
|
|
|
+ self._showMessage(
|
|
|
+ self.catalog.i18nc("@info:backup_failed",
|
|
|
+ "Could not create archive from user data directory: {}".format(error)))
|
|
|
+ return None
|
|
|
+
|
|
|
+ def _showMessage(self, message: str) -> None:
|
|
|
+ """Show a UI message"""
|
|
|
+ Message(message, title=self.catalog.i18nc("@info:title", "Backup"), lifetime=30).show()
|
|
|
+
|
|
|
+ def restore(self) -> bool:
|
|
|
+ """
|
|
|
+ Restore this backups
|
|
|
+ :return: A boolean whether we had success or not.
|
|
|
+ """
|
|
|
+ if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
|
|
|
+
|
|
|
+ Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
|
|
|
+ self._showMessage(
|
|
|
+ self.catalog.i18nc("@info:backup_failed",
|
|
|
+ "Tried to restore a Cura backup without having proper data or meta data."))
|
|
|
+ return False
|
|
|
+
|
|
|
+ current_version = CuraApplication.getInstance().getVersion()
|
|
|
+ version_to_restore = self.meta_data.get("cura_release", "master")
|
|
|
+ if current_version != version_to_restore:
|
|
|
+
|
|
|
+
|
|
|
+ self._showMessage(
|
|
|
+ self.catalog.i18nc("@info:backup_failed",
|
|
|
+ "Tried to restore a Cura backup that does not match your current version."))
|
|
|
+ return False
|
|
|
+
|
|
|
+ version_data_dir = Resources.getDataStoragePath()
|
|
|
+ archive = ZipFile(io.BytesIO(self.zip_file), "r")
|
|
|
+ extracted = self._extractArchive(archive, version_data_dir)
|
|
|
+
|
|
|
+
|
|
|
+ if Platform.isLinux():
|
|
|
+ preferences_file_name = CuraApplication.getInstance().getApplicationName()
|
|
|
+ preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
|
|
|
+ backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
|
|
|
+ Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file)
|
|
|
+ shutil.move(backup_preferences_file, preferences_file)
|
|
|
+
|
|
|
+ return extracted
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
|
|
|
+ """
|
|
|
+ Extract the whole archive to the given target path.
|
|
|
+ :param archive: The archive as ZipFile.
|
|
|
+ :param target_path: The target path.
|
|
|
+ :return: A boolean whether we had success or not.
|
|
|
+ """
|
|
|
+ Logger.log("d", "Removing current data in location: %s", target_path)
|
|
|
+ Resources.factoryReset()
|
|
|
+ Logger.log("d", "Extracting backup to location: %s", target_path)
|
|
|
+ archive.extractall(target_path)
|
|
|
+ return True
|