121 lines
4.5 KiB
Python
121 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any, cast
|
|
|
|
from app.features.core.migration import Migration as FeatureMigration
|
|
from app.features.dl_fields.schemas import DLField
|
|
from app.library.config import Config
|
|
from app.library.log import get_logger
|
|
|
|
if TYPE_CHECKING:
|
|
from app.features.dl_fields.repository import DLFieldsRepository
|
|
|
|
LOG = get_logger()
|
|
|
|
|
|
class Migration(FeatureMigration):
|
|
name: str = "dl_fields"
|
|
|
|
def __init__(self, repo: DLFieldsRepository, config: Config | None = None):
|
|
self._config: Config = config or Config.get_instance()
|
|
super().__init__(config=self._config)
|
|
self._repo: DLFieldsRepository = repo
|
|
self._source_file: Path = Path(self._config.config_path) / "dl_fields.json"
|
|
|
|
async def should_run(self) -> bool:
|
|
return self._source_file.exists()
|
|
|
|
async def migrate(self) -> None:
|
|
if await self._repo.count() > 0:
|
|
LOG.warning("DL fields already exist in the database; skipping migration.")
|
|
await self._move_file(self._source_file)
|
|
return
|
|
|
|
try:
|
|
items: list[dict[str, Any]] | None = json.loads(self._source_file.read_text())
|
|
except Exception as exc:
|
|
LOG.exception(
|
|
"Failed to read download fields migration file '%s'. Ignoring.",
|
|
self._source_file,
|
|
extra={"file_path": str(self._source_file), "exception_type": type(exc).__name__},
|
|
)
|
|
await self._move_file(self._source_file)
|
|
return
|
|
|
|
if items is None:
|
|
LOG.warning("No dl fields found in %s; skipping migration.", self._source_file)
|
|
await self._move_file(self._source_file)
|
|
return
|
|
|
|
inserted = 0
|
|
seen_names: dict[str, int] = {}
|
|
for index, item in enumerate(items):
|
|
if not (normalized := self._normalize(item, index, seen_names)):
|
|
continue
|
|
try:
|
|
await self._repo.create(normalized)
|
|
inserted += 1
|
|
except Exception as exc:
|
|
LOG.exception(
|
|
"Failed to insert download field '%s'.",
|
|
normalized["name"],
|
|
extra={"field_name": normalized["name"], "exception_type": type(exc).__name__},
|
|
)
|
|
|
|
LOG.info("Migrated %s dl field(s) from %s.", inserted, self._source_file)
|
|
await self._move_file(self._source_file)
|
|
|
|
def _normalize(self, item: Any, index: int, seen_names: dict[str, int]) -> dict[str, Any] | None:
|
|
if not isinstance(item, dict):
|
|
LOG.warning("Skipping dl field at index %s due to invalid type.", index)
|
|
return None
|
|
|
|
assert isinstance(item, dict)
|
|
|
|
name: str | None = item.get("name")
|
|
if not name or not isinstance(name, str):
|
|
LOG.warning("Skipping dl field at index %s due to missing name.", index)
|
|
return None
|
|
|
|
normalized_name = name.strip()
|
|
if not normalized_name:
|
|
LOG.warning("Skipping dl field at index %s due to empty name.", index)
|
|
return None
|
|
|
|
name = self._unique_name(normalized_name, seen_names)
|
|
description: str = item.get("description") if isinstance(item.get("description"), str) else ""
|
|
field_value: str | None = item.get("field")
|
|
if not field_value or not isinstance(field_value, str):
|
|
LOG.warning("Skipping dl field '%s' due to missing field value.", name)
|
|
return None
|
|
|
|
kind: str = item.get("kind") if isinstance(item.get("kind"), str) else "text"
|
|
icon: str = item.get("icon") if isinstance(item.get("icon"), str) else ""
|
|
value: str = item.get("value") if isinstance(item.get("value"), str) else ""
|
|
extras = item.get("extras") if isinstance(item.get("extras"), dict) else {}
|
|
extras = cast("dict[str, Any]", extras)
|
|
|
|
order_value: int = 0
|
|
if isinstance(item.get("order"), int) and not isinstance(item.get("order"), bool):
|
|
order_value = item.get("order", 0)
|
|
|
|
payload = {
|
|
"name": name,
|
|
"description": description,
|
|
"field": field_value,
|
|
"kind": kind,
|
|
"icon": icon,
|
|
"order": order_value,
|
|
"value": value,
|
|
"extras": extras,
|
|
}
|
|
|
|
try:
|
|
DLField.model_validate(payload)
|
|
except Exception as exc:
|
|
LOG.warning("Skipping dl field '%s' due to validation error: %s", name, exc)
|
|
return None
|
|
|
|
return payload
|