mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
fix: Recipe Keeper Errors and Other Safari Issues (#3712)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
parent
eab7c0d9e5
commit
4634ad5666
@ -132,14 +132,14 @@ export default defineComponent({
|
|||||||
text: i18n.tc("migration.plantoeat.title"),
|
text: i18n.tc("migration.plantoeat.title"),
|
||||||
value: MIGRATIONS.plantoeat,
|
value: MIGRATIONS.plantoeat,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: i18n.tc("migration.tandoor.title"),
|
|
||||||
value: MIGRATIONS.tandoor,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: i18n.tc("migration.recipekeeper.title"),
|
text: i18n.tc("migration.recipekeeper.title"),
|
||||||
value: MIGRATIONS.recipekeeper,
|
value: MIGRATIONS.recipekeeper,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: i18n.tc("migration.tandoor.title"),
|
||||||
|
value: MIGRATIONS.tandoor,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const _content = {
|
const _content = {
|
||||||
[MIGRATIONS.mealie]: {
|
[MIGRATIONS.mealie]: {
|
||||||
@ -312,6 +312,26 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
[MIGRATIONS.recipekeeper]: {
|
||||||
|
text: i18n.tc("migration.recipekeeper.description-long"),
|
||||||
|
acceptedFileType: ".zip",
|
||||||
|
tree: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
icon: $globals.icons.zip,
|
||||||
|
name: "recipekeeperhtml.zip",
|
||||||
|
children: [
|
||||||
|
{ id: 2, name: "recipes.html", icon: $globals.icons.codeJson },
|
||||||
|
{ id: 3, name: "images", icon: $globals.icons.folderOutline,
|
||||||
|
children: [
|
||||||
|
{ id: 4, name: "image1.jpg", icon: $globals.icons.fileImage },
|
||||||
|
{ id: 5, name: "image2.jpg", icon: $globals.icons.fileImage },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
[MIGRATIONS.tandoor]: {
|
[MIGRATIONS.tandoor]: {
|
||||||
text: i18n.tc("migration.tandoor.description-long"),
|
text: i18n.tc("migration.tandoor.description-long"),
|
||||||
acceptedFileType: ".zip",
|
acceptedFileType: ".zip",
|
||||||
@ -352,26 +372,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
[MIGRATIONS.recipekeeper]: {
|
|
||||||
text: i18n.tc("migration.recipekeeper.description-long"),
|
|
||||||
acceptedFileType: ".zip",
|
|
||||||
tree: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
icon: $globals.icons.zip,
|
|
||||||
name: "recipekeeperhtml.zip",
|
|
||||||
children: [
|
|
||||||
{ id: 2, name: "recipes.html", icon: $globals.icons.codeJson },
|
|
||||||
{ id: 3, name: "images", icon: $globals.icons.folderOutline,
|
|
||||||
children: [
|
|
||||||
{ id: 4, name: "image1.jpeg", icon: $globals.icons.fileImage },
|
|
||||||
{ id: 5, name: "image2.jpeg", icon: $globals.icons.fileImage },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function setFileObject(fileObject: File) {
|
function setFileObject(fileObject: File) {
|
||||||
|
@ -74,6 +74,28 @@ class BaseMigrator(BaseService):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_zip_base_path(cls, path: 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.
|
||||||
|
# We assume migration exports never contain a directory that starts with "__".
|
||||||
|
normal_dirs: list[Path] = []
|
||||||
|
dunder_dirs: list[Path] = []
|
||||||
|
for dir in path.iterdir():
|
||||||
|
if not dir.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if dir.name.startswith("__"):
|
||||||
|
dunder_dirs.append(dir)
|
||||||
|
else:
|
||||||
|
normal_dirs.append(dir)
|
||||||
|
|
||||||
|
if len(normal_dirs) == 1 and len(dunder_dirs) == 1:
|
||||||
|
return normal_dirs[0]
|
||||||
|
else:
|
||||||
|
return path
|
||||||
|
|
||||||
def _migrate(self) -> None:
|
def _migrate(self) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -20,12 +20,24 @@ class ChowdownMigrator(BaseMigrator):
|
|||||||
MigrationAlias(key="tags", alias="tags", func=split_by_comma),
|
MigrationAlias(key="tags", alias="tags", func=split_by_comma),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_zip_base_path(cls, path: Path) -> Path:
|
||||||
|
potential_path = super().get_zip_base_path(path)
|
||||||
|
if path == potential_path:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# make sure we didn't accidentally open a recipe dir
|
||||||
|
if (potential_path / "recipe.json").exists():
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
return potential_path
|
||||||
|
|
||||||
def _migrate(self) -> None:
|
def _migrate(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
with zipfile.ZipFile(self.archive) as zip_file:
|
with zipfile.ZipFile(self.archive) as zip_file:
|
||||||
zip_file.extractall(tmpdir)
|
zip_file.extractall(tmpdir)
|
||||||
|
|
||||||
temp_path = Path(tmpdir)
|
temp_path = self.get_zip_base_path(Path(tmpdir))
|
||||||
|
|
||||||
chow_dir = next(temp_path.iterdir())
|
chow_dir = next(temp_path.iterdir())
|
||||||
image_dir = temp_path.joinpath(chow_dir, "images")
|
image_dir = temp_path.joinpath(chow_dir, "images")
|
||||||
|
@ -86,7 +86,7 @@ class CopyMeThatMigrator(BaseMigrator):
|
|||||||
with zipfile.ZipFile(self.archive) as zip_file:
|
with zipfile.ZipFile(self.archive) as zip_file:
|
||||||
zip_file.extractall(tmpdir)
|
zip_file.extractall(tmpdir)
|
||||||
|
|
||||||
source_dir = Path(tmpdir)
|
source_dir = self.get_zip_base_path(Path(tmpdir))
|
||||||
|
|
||||||
recipes_as_dicts: list[dict] = []
|
recipes_as_dicts: list[dict] = []
|
||||||
for recipes_data_file in source_dir.glob("*.html"):
|
for recipes_data_file in source_dir.glob("*.html"):
|
||||||
|
@ -25,6 +25,18 @@ class MealieAlphaMigrator(BaseMigrator):
|
|||||||
MigrationAlias(key="tags", alias="tags", func=split_by_comma),
|
MigrationAlias(key="tags", alias="tags", func=split_by_comma),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_zip_base_path(cls, path: Path) -> Path:
|
||||||
|
potential_path = super().get_zip_base_path(path)
|
||||||
|
if path == potential_path:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# make sure we didn't accidentally open the "recipes" dir
|
||||||
|
if potential_path.name == "recipes":
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
return potential_path
|
||||||
|
|
||||||
def _convert_to_new_schema(self, recipe: dict) -> Recipe:
|
def _convert_to_new_schema(self, recipe: dict) -> Recipe:
|
||||||
if recipe.get("categories", False):
|
if recipe.get("categories", False):
|
||||||
recipe["recipeCategory"] = recipe.get("categories")
|
recipe["recipeCategory"] = recipe.get("categories")
|
||||||
@ -55,7 +67,7 @@ class MealieAlphaMigrator(BaseMigrator):
|
|||||||
with zipfile.ZipFile(self.archive) as zip_file:
|
with zipfile.ZipFile(self.archive) as zip_file:
|
||||||
zip_file.extractall(tmpdir)
|
zip_file.extractall(tmpdir)
|
||||||
|
|
||||||
temp_path = Path(tmpdir)
|
temp_path = self.get_zip_base_path(Path(tmpdir))
|
||||||
recipe_lookup: dict[str, Path] = {}
|
recipe_lookup: dict[str, Path] = {}
|
||||||
|
|
||||||
recipes: list[Recipe] = []
|
recipes: list[Recipe] = []
|
||||||
|
@ -57,6 +57,18 @@ class NextcloudMigrator(BaseMigrator):
|
|||||||
MigrationAlias(key="performTime", alias="cookTime", func=parse_iso8601_duration),
|
MigrationAlias(key="performTime", alias="cookTime", func=parse_iso8601_duration),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_zip_base_path(cls, path: Path) -> Path:
|
||||||
|
potential_path = super().get_zip_base_path(path)
|
||||||
|
if path == potential_path:
|
||||||
|
return path
|
||||||
|
|
||||||
|
# make sure we didn't accidentally open a recipe dir
|
||||||
|
if (potential_path / "recipe.json").exists():
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
return potential_path
|
||||||
|
|
||||||
def _migrate(self) -> None:
|
def _migrate(self) -> None:
|
||||||
# Unzip File into temp directory
|
# Unzip File into temp directory
|
||||||
|
|
||||||
@ -65,7 +77,8 @@ class NextcloudMigrator(BaseMigrator):
|
|||||||
with zipfile.ZipFile(self.archive) as zip_file:
|
with zipfile.ZipFile(self.archive) as zip_file:
|
||||||
zip_file.extractall(tmpdir)
|
zip_file.extractall(tmpdir)
|
||||||
|
|
||||||
potential_recipe_dirs = glob_walker(Path(tmpdir), glob_str="**/[!.]*.json", return_parent=True)
|
base_dir = self.get_zip_base_path(Path(tmpdir))
|
||||||
|
potential_recipe_dirs = glob_walker(base_dir, glob_str="**/[!.]*.json", return_parent=True)
|
||||||
nextcloud_dirs = {y.slug: y for x in potential_recipe_dirs if (y := NextcloudDir.from_dir(x))}
|
nextcloud_dirs = {y.slug: y for x in potential_recipe_dirs if (y := NextcloudDir.from_dir(x))}
|
||||||
|
|
||||||
all_recipes = []
|
all_recipes = []
|
||||||
|
@ -11,6 +11,17 @@ from .utils.migration_alias import MigrationAlias
|
|||||||
from .utils.migration_helpers import import_image, parse_iso8601_duration
|
from .utils.migration_helpers import import_image, parse_iso8601_duration
|
||||||
|
|
||||||
|
|
||||||
|
def clean_instructions(instructions: list[str]) -> list[str]:
|
||||||
|
try:
|
||||||
|
for i, instruction in enumerate(instructions):
|
||||||
|
if instruction.startswith(f"{i + 1}. "):
|
||||||
|
instructions[i] = instruction.removeprefix(f"{i + 1}. ")
|
||||||
|
|
||||||
|
return instructions
|
||||||
|
except Exception:
|
||||||
|
return instructions
|
||||||
|
|
||||||
|
|
||||||
def parse_recipe_div(recipe, image_path):
|
def parse_recipe_div(recipe, image_path):
|
||||||
meta = {}
|
meta = {}
|
||||||
for item in recipe.find_all(lambda x: x.has_attr("itemprop")):
|
for item in recipe.find_all(lambda x: x.has_attr("itemprop")):
|
||||||
@ -59,7 +70,7 @@ class RecipeKeeperMigrator(BaseMigrator):
|
|||||||
key="recipeIngredient",
|
key="recipeIngredient",
|
||||||
alias="recipeIngredients",
|
alias="recipeIngredients",
|
||||||
),
|
),
|
||||||
MigrationAlias(key="recipeInstructions", alias="recipeDirections"),
|
MigrationAlias(key="recipeInstructions", alias="recipeDirections", func=clean_instructions),
|
||||||
MigrationAlias(key="performTime", alias="cookTime", func=parse_iso8601_duration),
|
MigrationAlias(key="performTime", alias="cookTime", func=parse_iso8601_duration),
|
||||||
MigrationAlias(key="prepTime", alias="prepTime", func=parse_iso8601_duration),
|
MigrationAlias(key="prepTime", alias="prepTime", func=parse_iso8601_duration),
|
||||||
MigrationAlias(key="image", alias="photo0"),
|
MigrationAlias(key="image", alias="photo0"),
|
||||||
@ -77,7 +88,7 @@ class RecipeKeeperMigrator(BaseMigrator):
|
|||||||
with zipfile.ZipFile(self.archive) as zip_file:
|
with zipfile.ZipFile(self.archive) as zip_file:
|
||||||
zip_file.extractall(tmpdir)
|
zip_file.extractall(tmpdir)
|
||||||
|
|
||||||
source_dir = Path(tmpdir) / "recipekeeperhtml"
|
source_dir = self.get_zip_base_path(Path(tmpdir))
|
||||||
|
|
||||||
recipes_as_dicts: list[dict] = []
|
recipes_as_dicts: list[dict] = []
|
||||||
with open(source_dir / "recipes.html") as fp:
|
with open(source_dir / "recipes.html") as fp:
|
||||||
|
@ -109,7 +109,7 @@ class TandoorMigrator(BaseMigrator):
|
|||||||
with zipfile.ZipFile(self.archive) as zip_file:
|
with zipfile.ZipFile(self.archive) as zip_file:
|
||||||
zip_file.extractall(tmpdir)
|
zip_file.extractall(tmpdir)
|
||||||
|
|
||||||
source_dir = Path(tmpdir)
|
source_dir = self.get_zip_base_path(Path(tmpdir))
|
||||||
|
|
||||||
recipes_as_dicts: list[dict] = []
|
recipes_as_dicts: list[dict] = []
|
||||||
for i, recipe_zip_file in enumerate(source_dir.glob("*.zip")):
|
for i, recipe_zip_file in enumerate(source_dir.glob("*.zip")):
|
||||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user