Mark available downloads complete
This commit is contained in:
@@ -420,6 +420,10 @@ def _build_presentation(
|
|||||||
missing = int(availability.get("missing") or 0)
|
missing = int(availability.get("missing") or 0)
|
||||||
total = int(availability.get("total") or 0)
|
total = int(availability.get("total") or 0)
|
||||||
partial = available > 0 and missing > 0
|
partial = available > 0 and missing > 0
|
||||||
|
jellyfin_partial = bool(
|
||||||
|
jellyfin_found and snapshot.request_type == RequestType.tv and missing > 0
|
||||||
|
)
|
||||||
|
fully_available = bool(jellyfin_found and not jellyfin_partial)
|
||||||
download_visible = bool(download.get("visible"))
|
download_visible = bool(download.get("visible"))
|
||||||
download_state = str(download.get("state") or "not_started")
|
download_state = str(download.get("state") or "not_started")
|
||||||
|
|
||||||
@@ -432,7 +436,7 @@ def _build_presentation(
|
|||||||
f"The request was approved, but it has not reached the {collector} collector yet. "
|
f"The request was approved, but it has not reached the {collector} collector yet. "
|
||||||
"Adding it to the library queue is the next step."
|
"Adding it to the library queue is the next step."
|
||||||
)
|
)
|
||||||
elif jellyfin_found and partial:
|
elif jellyfin_partial:
|
||||||
status_label = f"Partially available — {available} of {total} episodes collected"
|
status_label = f"Partially available — {available} of {total} episodes collected"
|
||||||
meaning = (
|
meaning = (
|
||||||
f"Some of this request is ready to watch. {collector} is still looking for "
|
f"Some of this request is ready to watch. {collector} is still looking for "
|
||||||
@@ -497,7 +501,11 @@ def _build_presentation(
|
|||||||
meaning = snapshot.state_reason or "Magent is checking where this request is in the collection process."
|
meaning = snapshot.state_reason or "Magent is checking where this request is in the collection process."
|
||||||
|
|
||||||
action_ids = [action.id for action in snapshot.actions]
|
action_ids = [action.id for action in snapshot.actions]
|
||||||
if "resume_torrent" in action_ids:
|
if fully_available:
|
||||||
|
next_title = "Ready to watch"
|
||||||
|
next_description = "Collection is complete. Open the title on the media server when you are ready."
|
||||||
|
recommended = []
|
||||||
|
elif "resume_torrent" in action_ids:
|
||||||
next_title = "Resume the interrupted download"
|
next_title = "Resume the interrupted download"
|
||||||
next_description = "The download exists but is not currently progressing. Resume it to continue collection."
|
next_description = "The download exists but is not currently progressing. Resume it to continue collection."
|
||||||
recommended = ["resume_torrent"]
|
recommended = ["resume_torrent"]
|
||||||
@@ -562,7 +570,9 @@ def _build_presentation(
|
|||||||
else:
|
else:
|
||||||
library_state, library_summary = "waiting", "Waiting for collector information"
|
library_state, library_summary = "waiting", "Waiting for collector information"
|
||||||
|
|
||||||
if download_visible:
|
if fully_available:
|
||||||
|
search_state, search_summary = "complete", "No further search needed"
|
||||||
|
elif download_visible:
|
||||||
search_state = "complete"
|
search_state = "complete"
|
||||||
search_summary = "A release was found"
|
search_summary = "A release was found"
|
||||||
elif arr_state in {"added", "searching"} and (missing or snapshot.request_type == RequestType.movie):
|
elif arr_state in {"added", "searching"} and (missing or snapshot.request_type == RequestType.movie):
|
||||||
@@ -575,7 +585,15 @@ def _build_presentation(
|
|||||||
else:
|
else:
|
||||||
search_state, search_summary = "waiting", "Search has not started"
|
search_state, search_summary = "waiting", "Search has not started"
|
||||||
|
|
||||||
if download_visible:
|
completed_download_summary = (
|
||||||
|
"The requested content has been collected and is available to watch. "
|
||||||
|
"No further action is needed."
|
||||||
|
)
|
||||||
|
if fully_available:
|
||||||
|
download_stage_state, download_summary = "complete", completed_download_summary
|
||||||
|
pipeline_download_visible = False
|
||||||
|
pipeline_torrents: List[Dict[str, Any]] = []
|
||||||
|
elif download_visible:
|
||||||
download_stage_state = {
|
download_stage_state = {
|
||||||
"downloading": "active",
|
"downloading": "active",
|
||||||
"paused": "attention",
|
"paused": "attention",
|
||||||
@@ -584,19 +602,34 @@ def _build_presentation(
|
|||||||
"error": "attention",
|
"error": "attention",
|
||||||
}.get(download_state, "waiting")
|
}.get(download_state, "waiting")
|
||||||
download_summary = str(download.get("summary") or "A prior download attempt was found")
|
download_summary = str(download.get("summary") or "A prior download attempt was found")
|
||||||
|
pipeline_download_visible = True
|
||||||
|
pipeline_torrents = download.get("torrents") or []
|
||||||
else:
|
else:
|
||||||
download_stage_state, download_summary = "waiting", "No download attempt yet"
|
download_stage_state, download_summary = "waiting", "No download attempt yet"
|
||||||
|
pipeline_download_visible = False
|
||||||
|
pipeline_torrents = []
|
||||||
|
|
||||||
if jellyfin_found and partial:
|
if jellyfin_partial:
|
||||||
available_state, available_summary = "partial", f"{available} of {total} episodes available"
|
available_state, available_summary = "partial", f"{available} of {total} episodes available"
|
||||||
elif jellyfin_found:
|
elif jellyfin_found:
|
||||||
available_state, available_summary = "complete", "Available to watch"
|
available_state, available_summary = "complete", "Available to watch"
|
||||||
else:
|
else:
|
||||||
available_state, available_summary = "waiting", "Not available on the media server yet"
|
available_state, available_summary = "waiting", "Not available on the media server yet"
|
||||||
|
|
||||||
|
display_download = dict(download)
|
||||||
|
if fully_available:
|
||||||
|
display_download.update(
|
||||||
|
{
|
||||||
|
"visible": False,
|
||||||
|
"state": "completed",
|
||||||
|
"summary": completed_download_summary,
|
||||||
|
"torrents": [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": {"label": status_label, "meaning": meaning},
|
"status": {"label": status_label, "meaning": meaning},
|
||||||
"download": download,
|
"download": display_download,
|
||||||
"nextStep": {
|
"nextStep": {
|
||||||
"title": next_title,
|
"title": next_title,
|
||||||
"description": next_description,
|
"description": next_description,
|
||||||
@@ -621,15 +654,19 @@ def _build_presentation(
|
|||||||
"label": "Release search",
|
"label": "Release search",
|
||||||
"state": search_state,
|
"state": search_state,
|
||||||
"summary": search_summary,
|
"summary": search_summary,
|
||||||
"actionIds": [action_id for action_id in ("search_auto", "search_releases") if action_id in action_ids],
|
"actionIds": [] if fully_available else [
|
||||||
|
action_id
|
||||||
|
for action_id in ("search_auto", "search_releases")
|
||||||
|
if action_id in action_ids
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "download",
|
"id": "download",
|
||||||
"label": "Download",
|
"label": "Download complete" if fully_available else "Download",
|
||||||
"state": download_stage_state,
|
"state": download_stage_state,
|
||||||
"summary": download_summary,
|
"summary": download_summary,
|
||||||
"visible": download_visible,
|
"visible": pipeline_download_visible,
|
||||||
"torrents": download.get("torrents") or [],
|
"torrents": pipeline_torrents,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "available",
|
"id": "available",
|
||||||
@@ -1158,7 +1195,13 @@ async def build_snapshot(request_id: str) -> Snapshot:
|
|||||||
query = quote(snapshot.title or "")
|
query = quote(snapshot.title or "")
|
||||||
jellyfin_link = f"{base_url}/web/index.html#!/search?query={query}"
|
jellyfin_link = f"{base_url}/web/index.html#!/search?query={query}"
|
||||||
availability = arr_details.get("availability") or {}
|
availability = arr_details.get("availability") or {}
|
||||||
is_partial = bool(jellyfin_available and int(availability.get("missing") or 0) > 0)
|
is_partial = bool(
|
||||||
|
jellyfin_available
|
||||||
|
and snapshot.request_type == RequestType.tv
|
||||||
|
and int(availability.get("missing") or 0) > 0
|
||||||
|
)
|
||||||
|
if jellyfin_available and not is_partial:
|
||||||
|
snapshot.actions = []
|
||||||
snapshot.raw = {
|
snapshot.raw = {
|
||||||
"jellyseerr": jelly_request,
|
"jellyseerr": jelly_request,
|
||||||
"arr": {
|
"arr": {
|
||||||
|
|||||||
@@ -228,6 +228,82 @@ class RequestPresentationTests(unittest.TestCase):
|
|||||||
download_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "download")
|
download_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "download")
|
||||||
self.assertEqual(download_stage["summary"], "No download attempt yet")
|
self.assertEqual(download_stage["summary"], "No download attempt yet")
|
||||||
|
|
||||||
|
def test_available_content_replaces_stale_download_warning_with_completion(self) -> None:
|
||||||
|
snapshot = Snapshot(
|
||||||
|
request_id="3909",
|
||||||
|
title="Example",
|
||||||
|
request_type=RequestType.tv,
|
||||||
|
state=NormalizedState.completed,
|
||||||
|
actions=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
presentation = _build_presentation(
|
||||||
|
snapshot,
|
||||||
|
approved=True,
|
||||||
|
arr_state="available",
|
||||||
|
arr_details={
|
||||||
|
"availability": {"available": 6, "missing": 0, "total": 6, "seasons": []}
|
||||||
|
},
|
||||||
|
prowlarr_state="ok",
|
||||||
|
download={
|
||||||
|
"visible": True,
|
||||||
|
"state": "missing",
|
||||||
|
"summary": "A previous download was observed, but it is not currently visible in qBittorrent.",
|
||||||
|
"torrents": [],
|
||||||
|
},
|
||||||
|
jellyfin_found=True,
|
||||||
|
jellyfin_link="https://media.test/title/3909",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(presentation["download"]["visible"])
|
||||||
|
self.assertEqual(presentation["download"]["state"], "completed")
|
||||||
|
self.assertEqual(presentation["nextStep"]["title"], "Ready to watch")
|
||||||
|
self.assertEqual(presentation["nextStep"]["actionIds"], [])
|
||||||
|
download_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "download")
|
||||||
|
self.assertEqual(download_stage["label"], "Download complete")
|
||||||
|
self.assertEqual(download_stage["state"], "complete")
|
||||||
|
self.assertFalse(download_stage["visible"])
|
||||||
|
self.assertEqual(
|
||||||
|
download_stage["summary"],
|
||||||
|
"The requested content has been collected and is available to watch. No further action is needed.",
|
||||||
|
)
|
||||||
|
search_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "search")
|
||||||
|
self.assertEqual(search_stage["state"], "complete")
|
||||||
|
self.assertEqual(search_stage["actionIds"], [])
|
||||||
|
|
||||||
|
def test_partially_available_content_keeps_missing_download_attention(self) -> None:
|
||||||
|
snapshot = Snapshot(
|
||||||
|
request_id="3909",
|
||||||
|
title="Example",
|
||||||
|
request_type=RequestType.tv,
|
||||||
|
state=NormalizedState.importing,
|
||||||
|
actions=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
presentation = _build_presentation(
|
||||||
|
snapshot,
|
||||||
|
approved=True,
|
||||||
|
arr_state="added",
|
||||||
|
arr_details={
|
||||||
|
"availability": {"available": 3, "missing": 3, "total": 6, "seasons": []}
|
||||||
|
},
|
||||||
|
prowlarr_state="ok",
|
||||||
|
download={
|
||||||
|
"visible": True,
|
||||||
|
"state": "missing",
|
||||||
|
"summary": "A previous download is no longer visible.",
|
||||||
|
"torrents": [],
|
||||||
|
},
|
||||||
|
jellyfin_found=True,
|
||||||
|
jellyfin_link="https://media.test/title/3909",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(presentation["download"]["visible"])
|
||||||
|
download_stage = next(stage for stage in presentation["pipeline"] if stage["id"] == "download")
|
||||||
|
self.assertEqual(download_stage["label"], "Download")
|
||||||
|
self.assertEqual(download_stage["state"], "attention")
|
||||||
|
self.assertTrue(download_stage["visible"])
|
||||||
|
|
||||||
|
|
||||||
class LiveDownloadProgressTests(unittest.IsolatedAsyncioTestCase):
|
class LiveDownloadProgressTests(unittest.IsolatedAsyncioTestCase):
|
||||||
async def test_live_download_progress_uses_saved_hash_and_current_qbittorrent_value(self) -> None:
|
async def test_live_download_progress_uses_saved_hash_and_current_qbittorrent_value(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user