Skip to content

Commit

Permalink
feat: Handle Safari-mangled backup ZIPs and improve backup UI (#3674)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-genson authored Jun 1, 2024
1 parent 94e91d3 commit 4bc88e6
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 3 deletions.
1 change: 1 addition & 0 deletions frontend/lang/messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@
"backup-created-at-response-export_path": "Backup Created at {path}",
"backup-deleted": "Backup deleted",
"restore-success": "Restore successful",
"restore-fail": "Restore failed. Check your server logs for more details",
"backup-tag": "Backup Tag",
"create-heading": "Create A Backup",
"delete-backup": "Delete Backup",
Expand Down
2 changes: 2 additions & 0 deletions frontend/pages/admin/backups.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export default defineComponent({
if (error) {
console.log(error);
state.importDialog = false;
state.runningRestore = false;
alert.error(i18n.tc("settings.backup.restore-fail"));
} else {
alert.success(i18n.tc("settings.backup.restore-success"));
$auth.logout();
Expand Down
36 changes: 33 additions & 3 deletions mealie/services/backups_v2/backup_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,39 @@ class BackupContents:
_tables: dict | None = None

def __init__(self, file: Path) -> None:
self.base = file
self.data_directory = self.base / "data"
self.tables = self.base / "database.json"
self.base = self._find_base(file)
self.data_directory = self._find_data_dir_from_base(self.base)
self.tables = self._find_database_from_base(self.base)

@classmethod
def _find_base(cls, file: Path) -> Path:
# Safari mangles our ZIP structure and adds a "__MACOSX" directory at the root along with
# an arbitrarily-named directory containing the actual contents. So, if we find a dunder directory
# at the root (i.e. __MACOSX) we traverse down the first non-dunder directory and assume this is the base.
# This works because our backups never contain a directory that starts with "__".
dirs = [d for d in file.iterdir() if d.is_dir()]
dunder_dirs = [d for d in dirs if d.name.startswith("__")]
normal_dirs = [d for d in dirs if not d.name.startswith("__")]

if not dunder_dirs:
return file

# If the backup somehow adds a __MACOSX directory alongside the data directory, rather than in the
# parent directory, we don't want to traverse down. We check for our database.json file, and if it exists,
# we're already at the correct base.
if cls._find_database_from_base(file).exists():
return file

# This ZIP file was mangled, so we return the first non-dunder directory (if it exists).
return normal_dirs[0] if normal_dirs else file

@classmethod
def _find_data_dir_from_base(cls, base: Path) -> Path:
return base / "data"

@classmethod
def _find_database_from_base(cls, base: Path) -> Path:
return base / "database.json"

def validate(self) -> bool:
if not self.base.is_dir():
Expand Down
3 changes: 3 additions & 0 deletions tests/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
backup_version_bcfdad6b7355_1 = CWD / "backups/backup_version_bcfdad6b7355_1.zip"
"""bcfdad6b7355: remove tool name and slug unique contraints"""

backup_version_09aba125b57a_1 = CWD / "backups/backup_version_09aba125b57a_1.zip"
"""09aba125b57a: add OIDC auth method (Safari-mangled ZIP structure)"""

migrations_paprika = CWD / "migrations/paprika.zip"

migrations_chowdown = CWD / "migrations/chowdown.zip"
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_database_restore():
test_data.backup_version_44e8d670719d_4,
test_data.backup_version_ba1e4a6cfe99_1,
test_data.backup_version_bcfdad6b7355_1,
test_data.backup_version_09aba125b57a_1,
],
ids=[
"44e8d670719d_1: add extras to shopping lists, list items, and ingredient foods",
Expand All @@ -89,6 +90,7 @@ def test_database_restore():
"44e8d670719d_4: add extras to shopping lists, list items, and ingredient foods",
"ba1e4a6cfe99_1: added plural names and alias tables for foods and units",
"bcfdad6b7355_1: remove tool name and slug unique contraints",
"09aba125b57a: add OIDC auth method (Safari-mangled ZIP structure)",
],
)
def test_database_restore_data(backup_path: Path):
Expand Down

0 comments on commit 4bc88e6

Please sign in to comment.