Coverage for opt/mealie/lib/python3.12/site-packages/mealie/services/migrations/nextcloud.py: 31%
62 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 03:04 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 03:04 +0000
1import tempfile
2import zipfile
3from dataclasses import dataclass
4from pathlib import Path
6from slugify import slugify
8from mealie.schema.reports.reports import ReportEntryCreate
10from ._migration_base import BaseMigrator
11from .utils.migration_alias import MigrationAlias
12from .utils.migration_helpers import MigrationReaders, glob_walker, parse_iso8601_duration, split_by_comma
15@dataclass
16class NextcloudDir:
17 name: str
18 recipe: dict
19 image: Path | None = None
21 @property
22 def slug(self):
23 return slugify(self.recipe.get("name"))
25 @classmethod
26 def from_dir(cls, dir: Path):
27 try:
28 json_file = next(dir.glob("*.json"))
29 except StopIteration:
30 return None
32 try: # TODO: There's got to be a better way to do this.
33 image_file = next(dir.glob("full.*"))
34 except StopIteration:
35 image_file = None
37 return cls(name=dir.name, recipe=MigrationReaders.json(json_file), image=image_file)
40class NextcloudMigrator(BaseMigrator):
41 def __init__(self, **kwargs):
42 super().__init__(**kwargs)
44 self.name = "nextcloud"
46 self.key_aliases = [
47 MigrationAlias(key="tags", alias="keywords", func=split_by_comma),
48 MigrationAlias(key="orgURL", alias="url", func=None),
49 MigrationAlias(key="totalTime", alias="totalTime", func=parse_iso8601_duration),
50 MigrationAlias(key="prepTime", alias="prepTime", func=parse_iso8601_duration),
51 MigrationAlias(key="performTime", alias="cookTime", func=parse_iso8601_duration),
52 ]
54 @classmethod
55 def get_zip_base_path(cls, path: Path) -> Path:
56 potential_path = super().get_zip_base_path(path)
57 if path == potential_path:
58 return path
60 # make sure we didn't accidentally open a recipe dir
61 if (potential_path / "recipe.json").exists():
62 return path
63 else:
64 return potential_path
66 def _migrate(self) -> None:
67 # Unzip File into temp directory
69 # get potential recipe dirs
70 with tempfile.TemporaryDirectory() as tmpdir:
71 with zipfile.ZipFile(self.archive) as zip_file:
72 zip_file.extractall(tmpdir)
74 base_dir = self.get_zip_base_path(Path(tmpdir))
75 potential_recipe_dirs = glob_walker(base_dir, glob_str="**/[!.]*.json", return_parent=True)
76 nextcloud_dirs = {y.slug: y for x in potential_recipe_dirs if (y := NextcloudDir.from_dir(x))}
78 all_recipes = []
79 for _, nc_dir in nextcloud_dirs.items():
80 try:
81 recipe = self.clean_recipe_dictionary(nc_dir.recipe)
82 all_recipes.append(recipe)
83 except Exception as e:
84 self.logger.exception(e)
85 self.report_entries.append(
86 ReportEntryCreate(
87 report_id=self.report_id,
88 success=False,
89 message=f"Failed to import {nc_dir.name}",
90 exception=f"{e.__class__.__name__}: {e}",
91 )
92 )
94 all_statuses = self.import_recipes_to_database(all_recipes)
96 for slug, recipe_id, status in all_statuses:
97 if status:
98 nc_dir = nextcloud_dirs[slug]
99 if nc_dir.image:
100 self.import_image(slug, nc_dir.image, recipe_id)