Add live request pipeline recheck
This commit is contained in:
@@ -1599,6 +1599,69 @@ async def get_snapshot(request_id: str, user: Dict[str, str] = Depends(get_curre
|
||||
return _filter_snapshot_actions_for_user(snapshot, user)
|
||||
|
||||
|
||||
@router.post("/{request_id}/actions/recheck")
|
||||
async def action_recheck(request_id: str, user: Dict[str, str] = Depends(get_current_user)) -> dict:
|
||||
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 not seerr.configured():
|
||||
raise HTTPException(status_code=400, detail="Seerr is not configured")
|
||||
await _ensure_request_access(seerr, int(request_id), user)
|
||||
|
||||
try:
|
||||
fresh_request = await seerr.get_request(request_id)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
detail = _format_upstream_error("Seerr", exc)
|
||||
await asyncio.to_thread(
|
||||
save_action,
|
||||
request_id,
|
||||
"recheck_pipeline",
|
||||
"Recheck request status",
|
||||
"failed",
|
||||
detail,
|
||||
)
|
||||
raise HTTPException(status_code=502, detail=detail) from exc
|
||||
except Exception as exc:
|
||||
logger.warning("Request recheck failed while reading Seerr: request_id=%s error=%s", request_id, exc)
|
||||
detail = "Magent could not reach Seerr to recheck this request."
|
||||
await asyncio.to_thread(
|
||||
save_action,
|
||||
request_id,
|
||||
"recheck_pipeline",
|
||||
"Recheck request status",
|
||||
"failed",
|
||||
detail,
|
||||
)
|
||||
raise HTTPException(status_code=502, detail=detail) from exc
|
||||
|
||||
if not isinstance(fresh_request, dict):
|
||||
raise HTTPException(status_code=404, detail="Request not found in Seerr")
|
||||
|
||||
parsed = _parse_request_payload(fresh_request)
|
||||
if parsed.get("request_id") != int(request_id):
|
||||
raise HTTPException(status_code=502, detail="Seerr returned an unexpected request record")
|
||||
|
||||
cache_record = _build_request_cache_record(parsed, fresh_request)
|
||||
await asyncio.to_thread(upsert_request_cache, **cache_record)
|
||||
_cache_set(f"request:{request_id}", fresh_request)
|
||||
_refresh_recent_cache_from_db()
|
||||
|
||||
snapshot = _filter_snapshot_actions_for_user(await build_snapshot(request_id), user)
|
||||
status_label = str((snapshot.presentation.get("status") or {}).get("label") or "Status updated")
|
||||
message = f"Recheck complete. {status_label}."
|
||||
await asyncio.to_thread(
|
||||
save_action,
|
||||
request_id,
|
||||
"recheck_pipeline",
|
||||
"Recheck request status",
|
||||
"ok",
|
||||
message,
|
||||
)
|
||||
return {"status": "ok", "message": message, "snapshot": snapshot}
|
||||
|
||||
|
||||
@router.get("/{request_id}/download-progress")
|
||||
async def get_download_progress(
|
||||
request_id: str, user: Dict[str, str] = Depends(get_current_user)
|
||||
|
||||
@@ -207,7 +207,20 @@ async def _get_seerr_media_details(
|
||||
|
||||
|
||||
async def _maybe_refresh_jellyfin(snapshot: Snapshot) -> None:
|
||||
if snapshot.state not in {NormalizedState.available, NormalizedState.completed}:
|
||||
collector_item = snapshot.raw.get("arr", {}).get("item") if isinstance(snapshot.raw, dict) else None
|
||||
collector_stats = collector_item.get("statistics") if isinstance(collector_item, dict) else None
|
||||
collector_has_file = bool(
|
||||
isinstance(collector_item, dict)
|
||||
and (
|
||||
collector_item.get("hasFile")
|
||||
or snapshot.request_type == RequestType.tv
|
||||
and isinstance(collector_stats, dict)
|
||||
and collector_stats.get("episodeFileCount")
|
||||
)
|
||||
)
|
||||
if snapshot.state not in {NormalizedState.available, NormalizedState.completed} and not (
|
||||
snapshot.state == NormalizedState.importing and collector_has_file
|
||||
):
|
||||
return
|
||||
runtime = get_runtime_settings()
|
||||
client = JellyfinClient(runtime.jellyfin_base_url, runtime.jellyfin_api_key)
|
||||
@@ -223,8 +236,9 @@ async def _maybe_refresh_jellyfin(snapshot: Snapshot) -> None:
|
||||
pass
|
||||
previous = await asyncio.to_thread(get_recent_snapshots, snapshot.request_id, 1)
|
||||
if previous:
|
||||
prev_state = previous[0].get("state")
|
||||
if prev_state in {NormalizedState.available.value, NormalizedState.completed.value}:
|
||||
previous_payload = previous[0].get("payload") or {}
|
||||
previous_jellyfin = (previous_payload.get("raw") or {}).get("jellyfin") or {}
|
||||
if previous_jellyfin.get("found"):
|
||||
return
|
||||
try:
|
||||
await client.refresh_library()
|
||||
@@ -463,8 +477,15 @@ def _build_presentation(
|
||||
status_label = "Download in progress"
|
||||
meaning = "A release has been collected and is currently downloading."
|
||||
elif snapshot.state == NormalizedState.importing:
|
||||
status_label = "Downloaded — waiting for library import"
|
||||
meaning = f"The download has finished and {collector} is preparing it for the media server."
|
||||
if arr_state == "available" and not jellyfin_found:
|
||||
status_label = "Collected — waiting for the media server"
|
||||
meaning = (
|
||||
f"{collector} has collected and imported this title, but it is not visible on "
|
||||
"the media server yet."
|
||||
)
|
||||
else:
|
||||
status_label = "Downloaded — waiting for library import"
|
||||
meaning = f"The download has finished and {collector} is preparing it for the media server."
|
||||
elif arr_state == "error":
|
||||
status_label = "Unable to read the library queue"
|
||||
meaning = (
|
||||
@@ -527,6 +548,13 @@ def _build_presentation(
|
||||
next_title = "Let the current download finish"
|
||||
next_description = "Magent is tracking the active download; no action is needed right now."
|
||||
recommended = []
|
||||
elif snapshot.state == NormalizedState.importing and arr_state == "available":
|
||||
next_title = "Wait for the media server to index this title"
|
||||
next_description = (
|
||||
f"{collector} has completed its work. Use Recheck request to see whether the title "
|
||||
"has appeared on the media server."
|
||||
)
|
||||
recommended = []
|
||||
elif snapshot.state in {NormalizedState.completed, NormalizedState.available}:
|
||||
next_title = "Ready to watch"
|
||||
next_description = "Collection is complete. Open the title on the media server when you are ready."
|
||||
@@ -572,6 +600,8 @@ def _build_presentation(
|
||||
|
||||
if fully_available:
|
||||
search_state, search_summary = "complete", "No further search needed"
|
||||
elif arr_state == "available":
|
||||
search_state, search_summary = "complete", "A release was collected"
|
||||
elif download_visible:
|
||||
search_state = "complete"
|
||||
search_summary = "A release was found"
|
||||
@@ -593,6 +623,11 @@ def _build_presentation(
|
||||
download_stage_state, download_summary = "complete", completed_download_summary
|
||||
pipeline_download_visible = False
|
||||
pipeline_torrents: List[Dict[str, Any]] = []
|
||||
elif arr_state == "available":
|
||||
download_stage_state = "complete"
|
||||
download_summary = f"{collector} has imported the collected file"
|
||||
pipeline_download_visible = False
|
||||
pipeline_torrents = []
|
||||
elif download_visible:
|
||||
download_stage_state = {
|
||||
"downloading": "active",
|
||||
@@ -613,6 +648,8 @@ def _build_presentation(
|
||||
available_state, available_summary = "partial", f"{available} of {total} episodes available"
|
||||
elif jellyfin_found:
|
||||
available_state, available_summary = "complete", "Available to watch"
|
||||
elif arr_state == "available":
|
||||
available_state, available_summary = "active", "Waiting for the media server to index this title"
|
||||
else:
|
||||
available_state, available_summary = "waiting", "Not available on the media server yet"
|
||||
|
||||
@@ -1098,8 +1135,8 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
||||
snapshot.state_reason = qbit_message
|
||||
elif qbit_state == "completed":
|
||||
if arr_state == "available":
|
||||
snapshot.state = NormalizedState.completed
|
||||
snapshot.state_reason = "In your library and ready to watch."
|
||||
snapshot.state = NormalizedState.importing
|
||||
snapshot.state_reason = "The collector imported the file. Waiting for the media server to index it."
|
||||
else:
|
||||
snapshot.state = NormalizedState.importing
|
||||
snapshot.state_reason = "Download finished. Waiting for library import."
|
||||
@@ -1115,8 +1152,8 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
||||
snapshot.state = NormalizedState.searching
|
||||
snapshot.state_reason = "Searching for a matching release."
|
||||
elif arr_state == "available":
|
||||
snapshot.state = NormalizedState.completed
|
||||
snapshot.state_reason = "In your library and ready to watch."
|
||||
snapshot.state = NormalizedState.importing
|
||||
snapshot.state_reason = "Collected by Sonarr/Radarr and waiting for the media server to index it."
|
||||
elif arr_state == "added" and snapshot.state == NormalizedState.approved:
|
||||
snapshot.state = NormalizedState.added_to_arr
|
||||
snapshot.state_reason = "Item is present in Sonarr/Radarr"
|
||||
|
||||
Reference in New Issue
Block a user