From 1f50c3c666dbab5c604a0c2fc29a48fe51cedade Mon Sep 17 00:00:00 2001 From: Dymas Date: Mon, 25 May 2026 23:13:41 +0200 Subject: [PATCH] Clarify no-download queue failures --- CHANGELOG.md | 4 ++++ README.md | 3 ++- VERSION | 2 +- app_support.py | 2 +- queue_jobs.py | 9 ++++++++- test_app.py | 21 +++++++++++++++++++++ 6 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d1bda..01b5958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.45.3 - 2026-05-25 + +- Improved queue failure handling for no-op `ani-cli` runs: when the command exits successfully but downloads no files, the job now fails with a clear message explaining that nothing was downloaded and the empty staging folder is cleaned up. + ## 0.45.2 - 2026-05-25 - Fixed watchlist completion so a successful partial dub or sub download no longer moves a TV series to `Finished`; the category now changes only when the downloaded mode has every expected episode both available and downloaded. diff --git a/README.md b/README.md index 65cb389..15ee965 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Small local web UI for a system-wide `ani-cli` install. -Current version: `0.45.2` +Current version: `0.45.3` ## Project Layout @@ -222,6 +222,7 @@ Webhook details: - Download-completed notifications include the full saved file list, split across extra Discord embeds when needed. - Jellyfin handoff notifications fire when a finished library is moved successfully and also on actionable move failures such as missing targets or move errors. - Watchlist entries only move to `Finished` automatically when the downloaded mode itself is complete, and Jellyfin handoff for TV entries follows that same mode-aware completion rule. +- Queue jobs that exit successfully but produce no downloaded files are marked as failed with a clearer explanation instead of looking like a generic rename problem. ### Watchlist diff --git a/VERSION b/VERSION index 9f3a7d7..460e2af 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.45.2 +0.45.3 diff --git a/app_support.py b/app_support.py index 67b1c1e..25e59d7 100644 --- a/app_support.py +++ b/app_support.py @@ -20,7 +20,7 @@ from uuid import uuid4 PROJECT_ROOT = Path(__file__).resolve().parent ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" APP_NAME = "ani-cli-web" -VERSION = "0.45.2" +VERSION = "0.45.3" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/queue_jobs.py b/queue_jobs.py index 5206c1b..d4c748f 100644 --- a/queue_jobs.py +++ b/queue_jobs.py @@ -1124,7 +1124,14 @@ class DownloadQueue: job.setdefault("log", []).append("Canceled.") elif finalize_error: job["status"] = "failed" - job.setdefault("log", []).append(f"Library rename failed: {finalize_error}") + message = str(finalize_error).strip() + if "No downloaded files were found in the staging folder" in message: + job.setdefault("log", []).append( + "ani-cli exited without downloading any files. The selected result or episodes may not be downloadable." + ) + else: + job.setdefault("log", []).append(f"Library rename failed: {finalize_error}") + cleanup_staging = True elif exit_code == 0: job["status"] = "done" for path in moved_files: diff --git a/test_app.py b/test_app.py index beab215..ad60629 100644 --- a/test_app.py +++ b/test_app.py @@ -460,6 +460,27 @@ class DownloadQueueWorkerFailureTests(unittest.TestCase): self.assertEqual(stored["status"], "failed") self.assertFalse(staging_dir.exists()) + def test_zero_exit_without_downloaded_files_marks_job_failed_with_clear_message(self): + queue = APP.DownloadQueue(lambda: {"mode": "sub", "quality": "best", "download_dir": "/tmp/example"}, start_worker=False) + job = queue.add({"query": "Queue Show", "title": "Queue Show", "season": "1", "episodes": "1-10"}) + staging_dir = queue_jobs.job_staging_dir(job) + + class FakeProc: + pid = 4321 + stdout = ["Checking dependencies...\n"] + + def wait(self): + return 0 + + with mock.patch.object(queue_jobs.subprocess, "Popen", return_value=FakeProc()): + queue._run_job(job) + + stored = queue._find(job["id"]) + self.assertEqual(stored["status"], "failed") + self.assertEqual(stored["exit_code"], 1) + self.assertIn("without downloading any files", "\n".join(stored["log"])) + self.assertFalse(staging_dir.exists()) + def test_run_job_marks_post_start_exception_failed_without_killing_worker_state(self): queue = APP.DownloadQueue(lambda: {"mode": "sub", "quality": "best", "download_dir": "/tmp/example"}, start_worker=False) job = queue.add({"query": "Queue Show", "title": "Queue Show", "season": "1", "episodes": "1"})