Fix animdl discovery in Docker runtime
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# 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
|
## 0.49.0 - 2026-07-19
|
||||||
|
|
||||||
- Added `animdl` as a third queue download backend and Docker-installed fallback tool.
|
- Added `animdl` as a third queue download backend and Docker-installed fallback tool.
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ RUN python3 -m pip install \
|
|||||||
animdl \
|
animdl \
|
||||||
&& test -x /usr/local/bin/anipy-cli \
|
&& test -x /usr/local/bin/anipy-cli \
|
||||||
&& test -x /usr/local/bin/animdl \
|
&& 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/anipy-cli --version \
|
||||||
&& /usr/local/bin/animdl --version
|
&& /usr/local/bin/animdl --version
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Local web UI for a system-wide `ani-cli` install.
|
Local web UI for a system-wide `ani-cli` install.
|
||||||
|
|
||||||
Current version: `0.49.0`
|
Current version: `0.49.1`
|
||||||
|
|
||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ Docker notes:
|
|||||||
- `UPDATE_ON_START=true` runs `ani-cli --update` before startup
|
- `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
|
- `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
|
- `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
|
## Key behavior
|
||||||
|
|
||||||
|
|||||||
+18
-3
@@ -22,9 +22,24 @@ from uuid import uuid4
|
|||||||
|
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
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"
|
APP_NAME = "ani-cli-web"
|
||||||
VERSION_FILE = PROJECT_ROOT / "VERSION"
|
VERSION_FILE = PROJECT_ROOT / "VERSION"
|
||||||
CHANGELOG_FILE = PROJECT_ROOT / "CHANGELOG.md"
|
CHANGELOG_FILE = PROJECT_ROOT / "CHANGELOG.md"
|
||||||
|
|||||||
+29
-2
@@ -1535,8 +1535,8 @@ class DownloadQueue:
|
|||||||
unavailable_methods.append(method)
|
unavailable_methods.append(method)
|
||||||
continue
|
continue
|
||||||
attempts.append(attempt_for(method))
|
attempts.append(attempt_for(method))
|
||||||
if not attempts:
|
if not attempts and selected_methods == ["ani-cli"]:
|
||||||
attempts.append(attempt_for(selected_methods[0]))
|
attempts.append(attempt_for("ani-cli"))
|
||||||
return attempts, selected_methods, unavailable_methods
|
return attempts, selected_methods, unavailable_methods
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
@@ -1554,6 +1554,33 @@ class DownloadQueue:
|
|||||||
staging_dir.mkdir(parents=True, exist_ok=True)
|
staging_dir.mkdir(parents=True, exist_ok=True)
|
||||||
target_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)
|
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"]
|
primary_command = attempts[0]["command"]
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
|||||||
+25
@@ -1036,6 +1036,31 @@ class DownloadQueueWorkerFailureTests(unittest.TestCase):
|
|||||||
self.assertEqual(commands[1][0], APP.app_support.ANIPY_CLI)
|
self.assertEqual(commands[1][0], APP.app_support.ANIPY_CLI)
|
||||||
self.assertIn("Fallback download completed with anipy-cli.", stored["log"])
|
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):
|
def test_selected_download_methods_retry_through_animdl(self):
|
||||||
queue = APP.DownloadQueue(
|
queue = APP.DownloadQueue(
|
||||||
lambda: {
|
lambda: {
|
||||||
|
|||||||
Reference in New Issue
Block a user