Files
ohm_streaming/app/main.py
T
Roman 4e1faf4cd9 Ohm comme client de téléchargement Sonarr (API compatible qBittorrent)
- /api/v2/* : login SID (mot de passe = clé Torznab), app/version,
  torrents/info (progression temps réel), properties (content_path),
  add (rejoue le grab encodé dans le .torrent de service, dédupliqué
  par infohash SHA-1), delete (± fichiers), pause/resume
- Les grabs Sonarr sont marqués « sonarr:<hash>| » dans source_key →
  suivis de bout en bout : Sonarr importe, renomme et range les épisodes
  dans sa bibliothèque, puis retire le torrent de la file Ohm
- L'indexeur Torznab embarque les paramètres du grab dans l'announce
- README : nouveau mode « client de téléchargement » recommandé (Remote
  Path Mapping documenté), blackhole en variante minimale
- 3 nouveaux tests (flux complet add → suivi → import → delete)
2026-09-22 16:42:12 +00:00

85 lines
2.2 KiB
Python

import asyncio
import logging
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from app.config import BASE_DIR, get_settings
from app.db import db
from app.logging_config import setup_logging
from app.routers import (
admin,
auth,
discover,
downloads,
library,
pages,
proxy,
qbit,
search,
system,
torznab,
)
from app.scrapers.http import close_client
from app.services.discover import discover as discover_service
from app.services.downloads import download_manager
from app.services.settings import apply_source_base_urls
logger = logging.getLogger(__name__)
def _log_task_error(task: asyncio.Task) -> None:
if task.cancelled():
return
exc = task.exception()
if exc is not None:
logger.warning("Réchauffe découverte échouée : %s", exc)
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
settings = get_settings()
setup_logging(settings.debug)
await db.connect()
await apply_source_base_urls()
await download_manager.start()
warmup = asyncio.create_task(discover_service.latest())
warmup.add_done_callback(_log_task_error)
logger.info("%s prêt", settings.app_name)
yield
warmup.cancel()
await download_manager.stop()
await close_client()
await db.close()
def create_app() -> FastAPI:
settings = get_settings()
app = FastAPI(title=settings.app_name, lifespan=lifespan)
app.include_router(torznab.router)
app.include_router(qbit.router)
app.include_router(system.router)
app.include_router(pages.router)
app.include_router(pages.protected)
app.include_router(auth.router)
app.include_router(search.router)
app.include_router(discover.router)
app.include_router(downloads.router)
app.include_router(library.router)
app.include_router(admin.router)
app.include_router(proxy.router)
app.mount("/static", StaticFiles(directory=BASE_DIR / "app" / "static"), name="static")
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}
return app
app = create_app()