66 lines
1.8 KiB
Python
66 lines
1.8 KiB
Python
from __future__ import annotations
|
|
|
|
import abc
|
|
import logging
|
|
import time
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from app.library.config import Config
|
|
|
|
LOG: logging.Logger = logging.getLogger(__name__)
|
|
|
|
|
|
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: %s", self.name, exc)
|
|
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
|