Merge branch 'feature-backup-manager' of https://github.com/Ultimaker/Cura into feature-backup-manager

This commit is contained in:
ChrisTerBeke 2018-05-14 09:00:05 +02:00
commit 0723a6a63f
3 changed files with 39 additions and 24 deletions

1
.gitignore vendored
View File

@ -40,6 +40,7 @@ plugins/cura-siemensnx-plugin
plugins/CuraBlenderPlugin plugins/CuraBlenderPlugin
plugins/CuraCloudPlugin plugins/CuraCloudPlugin
plugins/CuraDrivePlugin plugins/CuraDrivePlugin
plugins/CuraDrive
plugins/CuraLiveScriptingPlugin plugins/CuraLiveScriptingPlugin
plugins/CuraOpenSCADPlugin plugins/CuraOpenSCADPlugin
plugins/CuraPrintProfileCreator plugins/CuraPrintProfileCreator

View File

@ -19,7 +19,7 @@ class Backup:
""" """
# These files should be ignored when making a backup. # These files should be ignored when making a backup.
IGNORED_FILES = {"cura.log"} IGNORED_FILES = {"cura.log", "cache"}
def __init__(self, zip_file: bytes = None, meta_data: dict = None): def __init__(self, zip_file: bytes = None, meta_data: dict = None):
self.zip_file = zip_file # type: Optional[bytes] self.zip_file = zip_file # type: Optional[bytes]
@ -34,24 +34,33 @@ class Backup:
Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir) Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir)
# TODO: support preferences file in backup under Linux (is in different directory).
# Ensure all current settings are saved. # Ensure all current settings are saved.
CuraApplication.getInstance().saveSettings() CuraApplication.getInstance().saveSettings()
# We're using an easy to parse filename for when we're restoring edge cases: # Create an empty buffer and write the archive to it.
# TIMESTAMP.backup.VERSION.cura.zip buffer = io.BytesIO()
archive = self._makeArchive(version_data_dir) archive = self._makeArchive(buffer, version_data_dir)
files = archive.namelist()
self.zip_file = archive # Count the metadata items. We do this in a rather naive way at the moment.
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])
# Store the archive and metadata so the BackupManager can fetch them when needed.
self.zip_file = buffer.getvalue()
self.meta_data = { self.meta_data = {
"cura_release": cura_release, "cura_release": cura_release,
"machine_count": 0, "machine_count": str(machine_count),
"material_count": 0, "material_count": str(material_count),
"profile_count": 0, "profile_count": str(profile_count),
"plugin_count": 0 "plugin_count": str(plugin_count)
} }
# TODO: fill meta data with real machine/material/etc counts.
def _makeArchive(self, root_path: str) -> Optional[bytes]: def _makeArchive(self, buffer: "io.BytesIO", root_path: str) -> Optional[ZipFile]:
""" """
Make a full archive from the given root path with the given name. Make a full archive from the given root path with the given name.
:param root_path: The root directory to archive recursively. :param root_path: The root directory to archive recursively.
@ -59,52 +68,57 @@ class Backup:
""" """
contents = os.walk(root_path) contents = os.walk(root_path)
try: try:
buffer = io.BytesIO()
archive = ZipFile(buffer, "w", ZIP_DEFLATED) archive = ZipFile(buffer, "w", ZIP_DEFLATED)
for root, folders, files in contents: for root, folders, files in contents:
for folder_name in folders: for folder_name in folders:
# Add all folders, even empty ones. if folder_name in self.IGNORED_FILES:
continue
absolute_path = os.path.join(root, folder_name) absolute_path = os.path.join(root, folder_name)
relative_path = absolute_path[len(root_path) + len(os.sep):] relative_path = absolute_path[len(root_path) + len(os.sep):]
archive.write(absolute_path, relative_path) archive.write(absolute_path, relative_path)
for file_name in files: for file_name in files:
# Add all files except the ignored ones.
if file_name in self.IGNORED_FILES: if file_name in self.IGNORED_FILES:
continue continue
absolute_path = os.path.join(root, file_name) absolute_path = os.path.join(root, file_name)
relative_path = absolute_path[len(root_path) + len(os.sep):] relative_path = absolute_path[len(root_path) + len(os.sep):]
archive.write(absolute_path, relative_path) archive.write(absolute_path, relative_path)
archive.close() archive.close()
return buffer.getvalue() return archive
except (IOError, OSError, BadZipfile) as error: except (IOError, OSError, BadZipfile) as error:
Logger.log("e", "Could not create archive from user data directory: %s", error) Logger.log("e", "Could not create archive from user data directory: %s", error)
# TODO: show message.
return None return None
def restore(self) -> bool: def restore(self) -> bool:
""" """
Restore this backup. 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): if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
# We can restore without the minimum required information. # We can restore without the minimum required information.
Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.") Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
# TODO: show message.
return False return False
# global_data_dir = os.path.dirname(version_data_dir)
# TODO: handle restoring older data version. # TODO: handle restoring older data version.
# TODO: support preferences file in backup under Linux (is in different directory).
# global_data_dir = os.path.dirname(version_data_dir)
version_data_dir = Resources.getDataStoragePath() version_data_dir = Resources.getDataStoragePath()
archive = ZipFile(io.BytesIO(self.zip_file), "r") archive = ZipFile(io.BytesIO(self.zip_file), "r")
extracted = self._extractArchive(archive, version_data_dir) extracted = self._extractArchive(archive, version_data_dir)
if not extracted: return extracted
return False
return True
@staticmethod @staticmethod
def _extractArchive(archive: "ZipFile", target_path: str) -> bool: 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) Logger.log("d", "Removing current data in location: %s", target_path)
shutil.rmtree(target_path) shutil.rmtree(target_path)
Logger.log("d", "Extracting backup to location: %s", target_path) Logger.log("d", "Extracting backup to location: %s", target_path)
archive.extractall(target_path) archive.extractall(target_path)
return True return True

View File

@ -54,7 +54,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._check_updates = True self._check_updates = True
self._update_thread.start() self._update_thread.start()
def stop(self): def stop(self, store_data: bool = True):
self._check_updates = False self._check_updates = False
def _onConnectionStateChanged(self, serial_port): def _onConnectionStateChanged(self, serial_port):