Add live download progress updates
This commit is contained in:
@@ -47,9 +47,15 @@ from ..db import (
|
||||
is_seerr_media_failure_suppressed,
|
||||
record_seerr_media_failure,
|
||||
clear_seerr_media_failure,
|
||||
get_request_download_evidence,
|
||||
)
|
||||
from ..models import Snapshot, TriageResult, RequestType
|
||||
from ..services.snapshot import build_snapshot, jellyfin_item_matches_request
|
||||
from ..services.snapshot import (
|
||||
_summarize_qbit,
|
||||
_torrent_progress,
|
||||
build_snapshot,
|
||||
jellyfin_item_matches_request,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/requests", tags=["requests"], dependencies=[Depends(get_current_user)])
|
||||
|
||||
@@ -1644,6 +1650,73 @@ async def get_snapshot(request_id: str, user: Dict[str, str] = Depends(get_curre
|
||||
return _filter_snapshot_actions_for_user(snapshot, user)
|
||||
|
||||
|
||||
@router.get("/{request_id}/download-progress")
|
||||
async def get_download_progress(
|
||||
request_id: str, user: Dict[str, str] = Depends(get_current_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Return a lightweight qBittorrent update for an open request page."""
|
||||
if not request_id.isdigit():
|
||||
raise HTTPException(status_code=400, detail="Invalid request id")
|
||||
|
||||
runtime = get_runtime_settings()
|
||||
seerr = JellyseerrClient(runtime.jellyseerr_base_url, runtime.jellyseerr_api_key)
|
||||
if seerr.configured():
|
||||
await _ensure_request_access(seerr, int(request_id), user)
|
||||
|
||||
evidence = await asyncio.to_thread(get_request_download_evidence, request_id, 20)
|
||||
historical_torrents = evidence.get("torrents") if isinstance(evidence, dict) else []
|
||||
hashes: List[str] = []
|
||||
if isinstance(historical_torrents, list):
|
||||
hashes = list(
|
||||
dict.fromkeys(
|
||||
str(torrent.get("hash") or "").strip()
|
||||
for torrent in historical_torrents
|
||||
if isinstance(torrent, dict) and torrent.get("hash")
|
||||
)
|
||||
)
|
||||
|
||||
qbittorrent = QBittorrentClient(
|
||||
runtime.qbittorrent_base_url,
|
||||
runtime.qbittorrent_username,
|
||||
runtime.qbittorrent_password,
|
||||
)
|
||||
if not qbittorrent.configured():
|
||||
raise HTTPException(status_code=503, detail="qBittorrent is not configured")
|
||||
|
||||
try:
|
||||
if hashes:
|
||||
result = await qbittorrent.get_torrents_by_hashes("|".join(hashes))
|
||||
else:
|
||||
result = await qbittorrent.get_torrents_by_tag(f"magent-{request_id}")
|
||||
except Exception as exc:
|
||||
logger.warning("Live qBittorrent progress failed request_id=%s: %s", request_id, exc)
|
||||
raise HTTPException(status_code=502, detail="Live download progress is temporarily unavailable") from exc
|
||||
|
||||
torrents = result if isinstance(result, list) else []
|
||||
for torrent in torrents:
|
||||
if isinstance(torrent, dict):
|
||||
torrent["progressPercent"] = _torrent_progress(torrent)
|
||||
|
||||
if torrents:
|
||||
summary = _summarize_qbit(torrents)
|
||||
state = str(summary.get("state") or "idle")
|
||||
message = str(summary.get("message") or "Download found in qBittorrent.")
|
||||
elif evidence.get("observed"):
|
||||
state = "missing"
|
||||
message = "The previous download is no longer visible in qBittorrent."
|
||||
else:
|
||||
state = "not_started"
|
||||
message = "No download attempt has been observed."
|
||||
|
||||
return {
|
||||
"request_id": request_id,
|
||||
"state": state,
|
||||
"summary": message,
|
||||
"torrents": torrents,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/recent")
|
||||
async def recent_requests(
|
||||
take: int = 6,
|
||||
|
||||
@@ -382,14 +382,14 @@ def _artwork_url(path: Optional[str], size: str, cache_mode: str) -> Optional[st
|
||||
return f"https://image.tmdb.org/t/p/{size}{path}"
|
||||
|
||||
|
||||
def _torrent_progress(torrent: Dict[str, Any]) -> Optional[int]:
|
||||
def _torrent_progress(torrent: Dict[str, Any]) -> Optional[float]:
|
||||
progress = torrent.get("progress")
|
||||
try:
|
||||
numeric = float(progress)
|
||||
except (TypeError, ValueError):
|
||||
numeric = -1
|
||||
if 0 <= numeric <= 1:
|
||||
return round(numeric * 100)
|
||||
return round(numeric * 100, 1)
|
||||
try:
|
||||
size = float(torrent.get("size"))
|
||||
amount_left = float(torrent.get("amount_left"))
|
||||
@@ -397,7 +397,7 @@ def _torrent_progress(torrent: Dict[str, Any]) -> Optional[int]:
|
||||
return None
|
||||
if size <= 0:
|
||||
return None
|
||||
return max(0, min(100, round(((size - amount_left) / size) * 100)))
|
||||
return max(0.0, min(100.0, round(((size - amount_left) / size) * 100, 1)))
|
||||
|
||||
|
||||
def _build_presentation(
|
||||
|
||||
Reference in New Issue
Block a user