mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-14 03:55:56 +08:00
Start implementing backups functionality
This commit is contained in:
parent
32e2723c26
commit
64819d517e
@ -10,8 +10,10 @@ class Backups:
|
||||
The backups API provides a version-proof bridge between Cura's BackupManager and plugins that hook into it.
|
||||
|
||||
Usage:
|
||||
cura.Api.backups.createBackup()
|
||||
cura.Api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})
|
||||
from cura.Api import CuraApi
|
||||
api = CuraApi()
|
||||
api.backups.createBackup()
|
||||
api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})
|
||||
"""
|
||||
|
||||
manager = BackupsManager() # Re-used instance of the backups manager.
|
||||
@ -19,9 +21,14 @@ class Backups:
|
||||
def createBackup(self) -> ("ZipFile", dict):
|
||||
"""
|
||||
Create a new backup using the BackupsManager.
|
||||
:return:
|
||||
:return: Tuple containing a ZIP file with the backup data and a dict with meta data about the backup.
|
||||
"""
|
||||
return self.manager.createBackup()
|
||||
|
||||
def restoreBackup(self, zip_file: "ZipFile", meta_data: dict) -> None:
|
||||
"""
|
||||
Restore a backup using the BackupManager.
|
||||
:param zip_file: A ZIP file containing the actual backup data.
|
||||
:param meta_data: Some meta data needed for restoring a backup, like the Cura version number.
|
||||
"""
|
||||
return self.manager.restoreBackup(zip_file, meta_data)
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from cura.Api.Backups import Backups
|
||||
|
||||
|
||||
@ -11,5 +12,8 @@ class CuraApi:
|
||||
Usage of any other methods than the ones provided in this API can cause plugins to be unstable.
|
||||
"""
|
||||
|
||||
# For now we use the same API version to be consistent.
|
||||
VERSION = PluginRegistry.APIVersion
|
||||
|
||||
# Backups API.
|
||||
backups = Backups()
|
||||
|
@ -1,5 +1,13 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Resources import Resources
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
|
||||
class Backup:
|
||||
@ -8,22 +16,67 @@ class Backup:
|
||||
It is also responsible for reading and writing the zip file to the user data folder.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.generated = False # type: bool
|
||||
self.backup_id = None # type: str
|
||||
self.target_cura_version = None # type: str
|
||||
self.zip_file = None
|
||||
self.meta_data = None # type: dict
|
||||
def __init__(self, zip_file: "ZipFile" = None, meta_data: dict = None):
|
||||
self.zip_file = zip_file # type: Optional[ZipFile]
|
||||
self.meta_data = meta_data # type: Optional[dict
|
||||
|
||||
def getZipFile(self):
|
||||
pass
|
||||
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()
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
def getMetaData(self):
|
||||
pass
|
||||
Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir)
|
||||
|
||||
def create(self):
|
||||
self.generated = True
|
||||
pass
|
||||
# We're using an easy to parse filename for when we're restoring edge cases:
|
||||
# TIMESTAMP.backup.VERSION.cura.zip
|
||||
archive = self._makeArchive("{}.backup.{}.cura.zip".format(timestamp, cura_release), version_data_dir)
|
||||
|
||||
def restore(self):
|
||||
pass
|
||||
self.zip_file = archive
|
||||
self.meta_data = {
|
||||
"cura_release": cura_release
|
||||
}
|
||||
# TODO: fill meta data with machine/material/etc counts.
|
||||
|
||||
@staticmethod
|
||||
def _makeArchive(root_path: str, archive_name: 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.
|
||||
:param archive_name: The name of the archive to create.
|
||||
:return: The archive as ZipFile.
|
||||
"""
|
||||
parent_folder = os.path.dirname(root_path)
|
||||
contents = os.walk(root_path)
|
||||
try:
|
||||
archive = ZipFile(archive_name, "w", ZIP_DEFLATED)
|
||||
for root, folders, files in contents:
|
||||
for folder_name in folders:
|
||||
# Add all folders, even empty ones.
|
||||
absolute_path = os.path.join(root, folder_name)
|
||||
relative_path = absolute_path.replace(parent_folder + '\\', '')
|
||||
archive.write(absolute_path, relative_path)
|
||||
for file_name in files:
|
||||
# Add all files.
|
||||
absolute_path = os.path.join(root, file_name)
|
||||
relative_path = absolute_path.replace(parent_folder + '\\', '')
|
||||
archive.write(absolute_path, relative_path)
|
||||
archive.close()
|
||||
return archive
|
||||
except (IOError, OSError, BadZipfile) as error:
|
||||
Logger.log("e", "Could not create archive from user data directory: %s", error)
|
||||
return None
|
||||
|
||||
def restore(self) -> None:
|
||||
"""
|
||||
Restore this backup.
|
||||
"""
|
||||
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.
|
||||
Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
|
||||
return
|
||||
|
||||
# global_data_dir = os.path.dirname(version_data_dir)
|
||||
# TODO: restore logic.
|
||||
|
@ -2,6 +2,9 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from zipfile import ZipFile
|
||||
|
||||
from UM.Logger import Logger
|
||||
from cura.Backups.Backup import Backup
|
||||
|
||||
|
||||
class BackupsManager:
|
||||
"""
|
||||
@ -9,15 +12,17 @@ class BackupsManager:
|
||||
Backups themselves are represented in a different class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def createBackup(self) -> ("ZipFile", dict):
|
||||
"""
|
||||
Get a backup of the current configuration.
|
||||
:return: A Tuple containing a ZipFile (the actual backup) and a dict containing some meta data (like version).
|
||||
"""
|
||||
pass
|
||||
self._disableAutoSave()
|
||||
backup = Backup()
|
||||
backup.makeFromCurrent()
|
||||
self._enableAutoSave()
|
||||
# We don't return a Backup here because we want plugins only to interact with our API and not full objects.
|
||||
return backup.zip_file, backup.meta_data
|
||||
|
||||
def restoreBackup(self, zip_file: "ZipFile", meta_data: dict) -> None:
|
||||
"""
|
||||
@ -25,4 +30,22 @@ class BackupsManager:
|
||||
:param zip_file: A ZipFile containing the actual backup.
|
||||
:param meta_data: A dict containing some meta data that is needed to restore the backup correctly.
|
||||
"""
|
||||
pass
|
||||
if not meta_data.get("cura_release", None):
|
||||
# If there is no "cura_release" specified in the meta data, we don't execute a backup restore.
|
||||
Logger.log("w", "Tried to restore a backup without specifying a Cura version number.")
|
||||
return
|
||||
|
||||
# TODO: first make a new backup to prevent data loss when restoring fails.
|
||||
|
||||
self._disableAutoSave()
|
||||
|
||||
backup = Backup(zip_file = zip_file, meta_data = meta_data)
|
||||
backup.restore() # At this point, Cura will need to restart for the changes to take effect
|
||||
|
||||
def _disableAutoSave(self):
|
||||
"""Here we try to disable the auto-save plugin as it might interfere with restoring a backup."""
|
||||
# TODO: Disable auto-save if possible.
|
||||
|
||||
def _enableAutoSave(self):
|
||||
"""Re-enable auto-save after we're done."""
|
||||
# TODO: Enable auto-save if possible.
|
||||
|
Loading…
x
Reference in New Issue
Block a user