from __future__ import annotations import abc import time from pathlib import Path from typing import TYPE_CHECKING from app.library.log import get_logger if TYPE_CHECKING: from app.library.config import Config LOG = get_logger() class Migration(abc.ABC): name: str = "" def __init__(self, config: Config): self._migrated_dir: Path = Path(config.config_path) / "migrated" async def run(self) -> bool: if not await self.should_run(): return False self._migrated_dir.mkdir(parents=True, exist_ok=True) try: await self.migrate() except Exception as exc: LOG.exception( "Feature migration '%s' failed.", self.name, extra={"feature": self.name, "exception_type": type(exc).__name__}, ) return False return True @abc.abstractmethod async def should_run(self) -> bool: raise NotImplementedError @abc.abstractmethod async def migrate(self) -> None: raise NotImplementedError async def _move_file(self, source: Path) -> Path: destination: Path = self._migrated_dir / source.name if destination.exists(): timestamp = int(time.time()) destination: Path = self._migrated_dir / f"{source.stem}_{timestamp}{source.suffix}" source.rename(destination) return destination def _unique_name(self, name: str, seen_names: dict[str, int]) -> str: base = name count = seen_names.get(base, 0) if count == 0: seen_names[base] = 1 return base suffix = count + 1 new_name = f"{base} ({suffix})" while new_name in seen_names: suffix += 1 new_name = f"{base} ({suffix})" seen_names[base] = suffix seen_names[new_name] = 1 return new_name