Track repair collection cycles and reconcile request availability
This commit is contained in:
@@ -51,7 +51,11 @@ from ..db import (
|
||||
record_seerr_media_failure,
|
||||
clear_seerr_media_failure,
|
||||
get_request_download_evidence,
|
||||
start_request_repair,
|
||||
get_request_repairs,
|
||||
active_repair_request_ids,
|
||||
)
|
||||
from ..services.media_repair import current_cycle_torrents
|
||||
from ..models import Snapshot, TriageResult, RequestType
|
||||
from ..services.snapshot import (
|
||||
_summarize_qbit,
|
||||
@@ -1401,6 +1405,7 @@ def _get_recent_from_cache(
|
||||
status_codes: Optional[list[int]] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
items = _recent_cache.get("items") or []
|
||||
repairing = active_repair_request_ids()
|
||||
results = []
|
||||
since_dt = _parse_iso_datetime(since_iso)
|
||||
for item in items:
|
||||
@@ -1414,6 +1419,8 @@ def _get_recent_from_cache(
|
||||
item_dt = _parse_iso_datetime(candidate)
|
||||
if not item_dt or item_dt < since_dt:
|
||||
continue
|
||||
if str(item.get("request_id")) in repairing:
|
||||
item = {**item, "status": 5, "repairing": True}
|
||||
if status_codes and item.get("status") not in status_codes:
|
||||
continue
|
||||
results.append(item)
|
||||
@@ -1862,6 +1869,9 @@ def _record_replacement_activity(
|
||||
message: str,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
tracking = (metadata or {}).get("repairTracking")
|
||||
if event_type in {"replacement_started", "missing_search_started"} and isinstance(tracking, dict):
|
||||
start_request_repair(tracking)
|
||||
if not issue:
|
||||
return
|
||||
current_status = str(issue.get("status") or "new").strip().lower()
|
||||
@@ -2183,6 +2193,27 @@ async def action_replace_media(
|
||||
collector_id: Optional[int] = None
|
||||
target_episodes: List[Dict[str, int]] = []
|
||||
jellyfin_baseline: List[Dict[str, Any]] = []
|
||||
repair_tracking: Dict[str, Any] = {}
|
||||
|
||||
def record_cycle() -> None:
|
||||
repair_tracking.update({
|
||||
"requestId": request_id,
|
||||
"actionId": "replace_media",
|
||||
"mediaType": snapshot.request_type.value,
|
||||
"collectorId": collector_id,
|
||||
"originalFileIds": file_ids,
|
||||
"previousDownloadIds": list(dict.fromkeys(
|
||||
list(snapshot.raw.get("qbittorrent", {}).get("downloadIds") or [])
|
||||
+ [str(t.get("hash")) for t in snapshot.raw.get("qbittorrent", {}).get("torrents", []) if t.get("hash")]
|
||||
)),
|
||||
"episodes": target_episodes,
|
||||
"jellyfinBaseline": jellyfin_baseline,
|
||||
"jellyfinFoundAtStart": bool(snapshot.raw.get("jellyfin", {}).get("catalogFound",
|
||||
snapshot.raw.get("jellyfin", {}).get("found"))),
|
||||
"startedAt": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
start_request_repair(repair_tracking)
|
||||
|
||||
try:
|
||||
if snapshot.request_type == RequestType.movie:
|
||||
movie_id = arr_item.get("id")
|
||||
@@ -2201,6 +2232,7 @@ async def action_replace_media(
|
||||
if not radarr.configured():
|
||||
raise HTTPException(status_code=400, detail="Radarr is not configured")
|
||||
await radarr.monitor_movie(movie_id, True)
|
||||
await asyncio.to_thread(record_cycle)
|
||||
await radarr.delete_movie_file(file_ids[0])
|
||||
await radarr.search(movie_id)
|
||||
elif snapshot.request_type == RequestType.tv:
|
||||
@@ -2263,6 +2295,7 @@ async def action_replace_media(
|
||||
logger.warning("Could not capture Jellyfin episode baseline request_id=%s", request_id)
|
||||
target_names = [_replacement_file_name(file_data) for file_data in selected_files]
|
||||
await sonarr.monitor_episodes(episode_ids, True)
|
||||
await asyncio.to_thread(record_cycle)
|
||||
for selected_file_id in file_ids:
|
||||
await sonarr.delete_episode_file(selected_file_id)
|
||||
await sonarr.search_episodes(episode_ids)
|
||||
@@ -2318,17 +2351,7 @@ async def action_replace_media(
|
||||
event_type="replacement_started",
|
||||
message=message,
|
||||
metadata={
|
||||
"repairTracking": {
|
||||
"requestId": request_id,
|
||||
"actionId": "replace_media",
|
||||
"mediaType": snapshot.request_type.value,
|
||||
"collectorId": collector_id,
|
||||
"originalFileIds": file_ids,
|
||||
"episodes": target_episodes,
|
||||
"jellyfinBaseline": jellyfin_baseline,
|
||||
"jellyfinFoundAtStart": bool(snapshot.raw.get("jellyfin", {}).get("found")),
|
||||
"startedAt": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
"repairTracking": repair_tracking
|
||||
},
|
||||
)
|
||||
return {
|
||||
@@ -2688,6 +2711,8 @@ async def get_download_progress(
|
||||
if seerr.configured():
|
||||
await _ensure_request_access(seerr, int(request_id), user)
|
||||
|
||||
repairs = await asyncio.to_thread(get_request_repairs, request_id, active_only=False)
|
||||
cycle = repairs[-1]["startedAt"] if repairs else None
|
||||
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] = []
|
||||
@@ -2717,7 +2742,7 @@ async def get_download_progress(
|
||||
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 []
|
||||
torrents = current_cycle_torrents(result, cycle)
|
||||
for torrent in torrents:
|
||||
if isinstance(torrent, dict):
|
||||
torrent["progressPercent"] = _torrent_progress(torrent)
|
||||
@@ -2739,6 +2764,8 @@ async def get_download_progress(
|
||||
"summary": message,
|
||||
"torrents": torrents,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"repairCycle": cycle,
|
||||
"visible": bool(torrents) or bool(evidence.get("observed")),
|
||||
}
|
||||
|
||||
|
||||
@@ -2879,7 +2906,10 @@ async def recent_requests(
|
||||
payload_json=json.dumps(details, ensure_ascii=True),
|
||||
)
|
||||
status_label = _status_label(status)
|
||||
if status_label in {"Working on it", "Ready to watch", "Partially ready"}:
|
||||
if row.get("repairing"):
|
||||
status = 5
|
||||
status_label = "Repair in progress"
|
||||
elif status_label in {"Working on it", "Ready to watch", "Partially ready"}:
|
||||
is_available = await _request_is_available_in_jellyfin(
|
||||
jellyfin,
|
||||
title,
|
||||
|
||||
Reference in New Issue
Block a user