import logging from app.core.config import settings from app.services.backends.base import AbstractStorageBackend from app.services.backends.local import LocalFSBackend from app.services.backends.s3 import S3Backend from app.services.backends.webdav import WebDAVBackend logger = logging.getLogger(__name__) _active_backend: AbstractStorageBackend | None = None def build_backend(driver: str, config: dict) -> AbstractStorageBackend: """Construct a backend instance from a driver name + config dict.""" if driver == "local": return LocalFSBackend(data_dir=config.get("data_dir", settings.DATA_DIR)) if driver == "s3": return S3Backend( endpoint_url=config.get("endpoint_url", ""), access_key=config.get("access_key", ""), secret_key=config.get("secret_key", ""), region=config.get("region", "us-east-1"), ) if driver == "webdav": return WebDAVBackend( url=config.get("url", ""), username=config.get("username", ""), password=config.get("password", ""), root_path=config.get("root_path", "/"), ) raise ValueError(f"Unknown driver: {driver!r}. Valid options: local, s3, webdav") def initialize_backend() -> None: """Build the initial backend from environment variables at startup.""" global _active_backend driver = settings.STORAGE_BACKEND config: dict = {} if driver == "s3": config = { "endpoint_url": settings.S3_ENDPOINT_URL, "access_key": settings.S3_ACCESS_KEY, "secret_key": settings.S3_SECRET_KEY, "region": settings.S3_REGION, } elif driver == "webdav": config = { "url": settings.WEBDAV_URL, "username": settings.WEBDAV_USERNAME, "password": settings.WEBDAV_PASSWORD, "root_path": settings.WEBDAV_ROOT_PATH, } # local needs no extra config — DATA_DIR is read from settings inside build_backend _active_backend = build_backend(driver, config) logger.info("Storage backend initialized: %s", driver) def get_backend() -> AbstractStorageBackend: if _active_backend is None: raise RuntimeError("Backend not initialized — call initialize_backend() at startup") return _active_backend def switch_backend(new_backend: AbstractStorageBackend) -> None: """Replace the active backend. Called by the migration job after all data is verified.""" global _active_backend old_name = _active_backend.driver_name if _active_backend else "none" _active_backend = new_backend logger.info("Storage backend switched: %s → %s", old_name, new_backend.driver_name)