Fix animdl discovery in Docker runtime
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## 0.49.1 - 2026-07-19
|
||||
|
||||
- Fixed Docker fallback tool discovery by exposing `anipy-cli` and `animdl` from fixed system paths and checking common system install locations at runtime.
|
||||
- Changed queue jobs to fail immediately with a clear message when every selected download method is unavailable, instead of trying to launch an unavailable command.
|
||||
|
||||
## 0.49.0 - 2026-07-19
|
||||
|
||||
- Added `animdl` as a third queue download backend and Docker-installed fallback tool.
|
||||
|
||||
@@ -60,6 +60,8 @@ RUN python3 -m pip install \
|
||||
animdl \
|
||||
&& test -x /usr/local/bin/anipy-cli \
|
||||
&& test -x /usr/local/bin/animdl \
|
||||
&& ln -sf /usr/local/bin/anipy-cli /usr/bin/anipy-cli \
|
||||
&& ln -sf /usr/local/bin/animdl /usr/bin/animdl \
|
||||
&& /usr/local/bin/anipy-cli --version \
|
||||
&& /usr/local/bin/animdl --version
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Local web UI for a system-wide `ani-cli` install.
|
||||
|
||||
Current version: `0.49.0`
|
||||
Current version: `0.49.1`
|
||||
|
||||
## What it does
|
||||
|
||||
@@ -96,7 +96,7 @@ Docker notes:
|
||||
- `UPDATE_ON_START=true` runs `ani-cli --update` before startup
|
||||
- `ANI_CLI_WEB_BRANCH` lets the image clone a specific `ani-cli-web` branch at build time
|
||||
- `botan` is installed inside the container for providers or scripts that need Botan cryptography tooling
|
||||
- `anipy-cli` and `animdl` are installed systemwide inside the container, so fallback retries are available without extra container setup
|
||||
- `anipy-cli` and `animdl` are installed systemwide inside the container at `/usr/local/bin` and linked from `/usr/bin`, so fallback retries are available without extra container setup
|
||||
|
||||
## Key behavior
|
||||
|
||||
|
||||
+18
-3
@@ -22,9 +22,24 @@ 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"
|
||||
ANIPY_CLI = os.environ.get("ANIPY_CLI_BIN") or shutil.which("anipy-cli") or "anipy-cli"
|
||||
ANIMDL = os.environ.get("ANIMDL_BIN") or shutil.which("animdl") or "animdl"
|
||||
|
||||
|
||||
def configured_cli_command(env_name, executable_name):
|
||||
configured = str(os.environ.get(env_name) or "").strip()
|
||||
if configured:
|
||||
return configured
|
||||
resolved = shutil.which(executable_name)
|
||||
if resolved:
|
||||
return resolved
|
||||
for candidate in (Path("/usr/local/bin") / executable_name, Path("/usr/bin") / executable_name):
|
||||
if candidate.exists() and os.access(candidate, os.X_OK):
|
||||
return str(candidate)
|
||||
return executable_name
|
||||
|
||||
|
||||
ANI_CLI = configured_cli_command("ANI_CLI_BIN", "ani-cli")
|
||||
ANIPY_CLI = configured_cli_command("ANIPY_CLI_BIN", "anipy-cli")
|
||||
ANIMDL = configured_cli_command("ANIMDL_BIN", "animdl")
|
||||
APP_NAME = "ani-cli-web"
|
||||
VERSION_FILE = PROJECT_ROOT / "VERSION"
|
||||
CHANGELOG_FILE = PROJECT_ROOT / "CHANGELOG.md"
|
||||
|
||||
+29
-2
@@ -1535,8 +1535,8 @@ class DownloadQueue:
|
||||
unavailable_methods.append(method)
|
||||
continue
|
||||
attempts.append(attempt_for(method))
|
||||
if not attempts:
|
||||
attempts.append(attempt_for(selected_methods[0]))
|
||||
if not attempts and selected_methods == ["ani-cli"]:
|
||||
attempts.append(attempt_for("ani-cli"))
|
||||
return attempts, selected_methods, unavailable_methods
|
||||
|
||||
def _run(self):
|
||||
@@ -1554,6 +1554,33 @@ class DownloadQueue:
|
||||
staging_dir.mkdir(parents=True, exist_ok=True)
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
attempts, selected_methods, unavailable_methods = self._command_attempts(job, staging_dir)
|
||||
if not attempts:
|
||||
with self.lock:
|
||||
job["status"] = "failed"
|
||||
job["started_at"] = now_iso()
|
||||
job["finished_at"] = job["started_at"]
|
||||
job["updated_at"] = job["finished_at"]
|
||||
job["exit_code"] = 1
|
||||
job["pid"] = None
|
||||
job["staging_dir"] = str(staging_dir)
|
||||
job["target_dir"] = str(target_dir)
|
||||
job["download_backend"] = selected_methods[0] if selected_methods else None
|
||||
job["fallback_used"] = False
|
||||
job["log"] = [
|
||||
f"Staging in: {job['staging_dir']}",
|
||||
f"Final folder: {job['target_dir']}",
|
||||
f"Download methods: {', '.join(selected_methods)}",
|
||||
(
|
||||
"No selected download methods are installed or executable: "
|
||||
f"{', '.join(unavailable_methods or selected_methods)}."
|
||||
),
|
||||
]
|
||||
self.current_process = None
|
||||
self.current_job_id = None
|
||||
self.current_job = None
|
||||
self._save_job_locked(job)
|
||||
self._cleanup_staging_dir(job)
|
||||
return
|
||||
primary_command = attempts[0]["command"]
|
||||
|
||||
with self.lock:
|
||||
|
||||
+25
@@ -1036,6 +1036,31 @@ class DownloadQueueWorkerFailureTests(unittest.TestCase):
|
||||
self.assertEqual(commands[1][0], APP.app_support.ANIPY_CLI)
|
||||
self.assertIn("Fallback download completed with anipy-cli.", stored["log"])
|
||||
|
||||
def test_selected_unavailable_animdl_fails_without_start_attempt(self):
|
||||
queue = APP.DownloadQueue(
|
||||
lambda: {
|
||||
"mode": "sub",
|
||||
"quality": "best",
|
||||
"download_dir": "/tmp/example",
|
||||
"download_methods": ["animdl"],
|
||||
},
|
||||
start_worker=False,
|
||||
)
|
||||
job = queue.add({"query": "Queue Show", "title": "Queue Show", "season": "1", "episodes": "1"})
|
||||
staging_dir = queue_jobs.job_staging_dir(job)
|
||||
|
||||
with mock.patch.object(queue_jobs, "cli_executable_exists", return_value=False), mock.patch.object(
|
||||
queue_jobs.subprocess, "Popen"
|
||||
) as popen:
|
||||
queue._run_job(job)
|
||||
|
||||
stored = queue._find(job["id"])
|
||||
self.assertEqual(stored["status"], "failed")
|
||||
self.assertEqual(stored["download_backend"], "animdl")
|
||||
self.assertIn("No selected download methods are installed or executable: animdl.", stored["log"])
|
||||
self.assertFalse(staging_dir.exists())
|
||||
popen.assert_not_called()
|
||||
|
||||
def test_selected_download_methods_retry_through_animdl(self):
|
||||
queue = APP.DownloadQueue(
|
||||
lambda: {
|
||||
|
||||
Reference in New Issue
Block a user