Files
ani-cli-web/README.md
T
2026-05-15 23:56:11 +02:00

283 lines
14 KiB
Markdown

# ani-cli web
A local web UI for a system-wide `ani-cli` install.
Current version: `0.26.0`
## Project Layout
```text
ani-cli-web/
app.py Public entrypoint, runtime wiring, watchlist domain logic, and CLI startup.
app_support.py Shared runtime/state/config helpers plus queue and library file utilities.
queue_jobs.py Download queue and background watchlist-refresh workers.
http_handler.py HTTP route handling, request parsing, and remote-access guard.
web_templates.py Shared Search, Config, and Watchlist page markup aggregation.
search_page.py Search page template.
config_page.py Config page template.
watchlist_page.py Watchlist page template.
template_helpers.py Shared page-shell navigation and sidebar brand helpers.
ani-cli-web Launcher script.
test_app.py Regression tests with isolated temp-state setup.
VERSION Project version.
CHANGELOG.md Change history.
README.md Project documentation.
```
## Run
Start the web app with:
```sh
./ani-cli-web
```
For thumbnail debugging, run:
```sh
./ani-cli-web --debug
```
The launcher also accepts `---debug`. You can also enable debug logging with `ANI_CLI_WEB_DEBUG=1`.
Then open:
```text
http://127.0.0.1:8421/
```
By default the server binds to `127.0.0.1:8421`.
## Overview
`ani-cli-web` keeps search and download management on `/`, saved defaults on `/config`, and the watchlist on `/watchlist`, all with the same sidebar navigation layout and a Tsuki-inspired visual style.
The app currently includes:
- Search against AllAnime with `sub` or `dub` mode selection.
- Episode loading for the selected result.
- Queueing downloads with editable library name, season, episode range, quality, and target folder.
- A dedicated Config page for saved default folder, mode, and quality settings.
- A paged SQLite-backed download queue with retry, cancel, remove, retry-failed, and clear-finished actions.
- A paged SQLite-backed watchlist with summary cards, per-show refresh, bulk refresh, source links, and thumbnail tools.
- Watchlist category tabs for `Watching`, `Planned`, `Finished`, and `Dropped`, with per-anime category editing.
- AnimeSchedule-backed watchlist completion tracking that compares live sub/dub availability with the expected total episode count when known.
- Automatic watchlist sync after successful downloads, including downloaded-vs-manual finished markers.
- Per-show background watchlist refresh queueing so adding a show or syncing a finished download does not block the request on upstream episode, schedule, and thumbnail lookups.
- Queued per-show watchlist refresh work now survives app restarts and resumes from rows still marked `queued`.
- Per-show watchlist refresh queueing now also deduplicates in-flight refreshes, so the same show is not fetched twice back-to-back while one refresh is already running.
- The watchlist page now silently refreshes itself while queued per-show refresh work is still being processed, so queued entries settle into their updated state without a manual reload.
- The watchlist page now also avoids re-triggering the same unresolved thumbnail warm-up request on every silent poll cycle while a show's thumbnail state is unchanged.
- Background `Refresh All` execution with status polling, parallel per-show refresh work, and no inline thumbnail downloads during the bulk job.
- Search and Watchlist polling now use a shared serial browser polling helper so slow responses do not cause overlapping poll requests to pile up in the background.
- Controlled shutdown and runtime reset now cancel active download workers when waiting for teardown, reducing the chance of orphaned background downloads outliving the web process.
- Persistent watchlist refresh history that survives restarts and marks interrupted bulk refresh jobs clearly.
- Local thumbnail caching plus manual thumbnail upload when automatic cover lookup fails.
- A refreshed glassy dark UI inspired by the Tsuki frontend design language.
- Shared page templates and backend services split into dedicated modules while keeping `app.py` and `web_templates.py` as stable public aggregation points.
- Broader regression coverage for higher-risk queue, watchlist, remote-access, and handler flows.
- Search and watchlist requests guard against stale out-of-order async responses during rapid repeated actions.
- Search-page queue polling also guards against stale out-of-order refresh responses now.
- Queue log writes are batched during active downloads to reduce SQLite churn on noisy jobs.
- Queue persistence now stores core job metadata and logs in structured SQLite columns instead of treating the whole job as one large JSON blob.
- Project-local runtime state under `.ani-cli-web/`.
## Search Page
The main page at `/` is where you search and queue downloads.
Workflow:
1. Search by anime title or keyword.
2. Pick a result to load available episodes.
3. Adjust library name, season, episode range, quality, and download folder.
4. Add the selected show either to the watchlist or directly to the queue.
The page also includes:
- A live queue view that refreshes automatically.
## Config Page
The Config page at `/config` is where saved defaults now live.
Use it to:
1. Set the default download folder.
2. Choose the default `sub` or `dub` search mode.
3. Choose the default search quality used to prefill the Search page.
Those saved defaults are written into the project-local `config.json` and loaded automatically when the Search page opens.
The Config page also shows the current app version plus dependency status in a compact centered layout.
## Access Guard
By default, `ani-cli-web` only serves loopback clients such as `127.0.0.1` and `::1`.
If you intentionally want LAN or remote access, set:
```sh
ANI_CLI_WEB_ALLOW_REMOTE=1
ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token
```
Non-local clients must then send either:
- `Authorization: Bearer <token>`
- `X-AniCli-Web-Token: <token>`
When the app is placed behind a loopback reverse proxy, forwarded external client IP headers such as `X-Forwarded-For` are also treated as non-local, so proxied internet or LAN traffic still needs the token.
This keeps the queue, config, and watchlist APIs safer when the app is intentionally exposed beyond localhost.
## Download Queue
The queue is stored in SQLite and shown 10 jobs at a time.
Core queue fields and logs are now stored in structured SQLite columns, while JSON payload storage is kept only for extra fields that do not have dedicated columns yet.
Queue actions currently available:
- `Retry` for finished or failed jobs.
- `Cancel` for pending or running jobs.
- `Remove` for completed or failed jobs.
- `Retry failed` for all failed jobs.
- `Remove finished` for completed jobs.
Downloads are staged inside the project-local app state first. After `ani-cli` finishes, files are moved into a Plex/Jellyfin-friendly layout:
```text
ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4
```
## Watchlist
The watchlist stores one row per show in SQLite, keeps separate `sub` and `dub` episode counts, and assigns each show to one of four categories:
- `Watching`
- `Planned`
- `Finished`
- `Dropped`
You can add entries in two ways:
1. From the Search page with `Add to watchlist`, choosing the category first.
2. Manually on `/watchlist` with a title, AllAnime `show_id`, and category.
Watchlist features:
1. `Refresh` updates one tracked show.
2. `Refresh All` runs in the background, rejects duplicate queueing while a refresh is already pending or running, preserves interrupted job status across restarts, and refreshes shows in parallel without doing inline thumbnail downloads for every entry.
3. `Open` jumps back to the Search page with the title pre-filled.
4. Clicking the anime title opens the AllManga source page at `bangumi/<show_id>` in a new tab.
5. `Remove` deletes the watchlist entry and clears its cached poster file.
6. Poster cards use a compact multi-column grid with pagination at 30 cards per page.
7. Summary cards show tracked shows plus total `sub` and `dub` episode counts.
8. Episode pills show `current / total` progress when AnimeSchedule exposes the planned episode count.
9. `Sub` and `Dub` pills turn green and gain `Sub ready` or `Dub ready` tags when that release track appears fully finished and ready for download.
10. Each category has its own tab on the watchlist page.
11. Every card includes an inline category selector so you can move tracked anime between tabs at any time.
12. Finished shows display a small check badge: green means the anime was downloaded through the app, gray means it was marked finished manually.
### Download Sync
Successful downloads now feed back into the watchlist automatically.
Current behavior:
- If the anime is already on the watchlist, a successful download marks it as downloaded and keeps its current category.
- If the anime is not yet on the watchlist, `ani-cli-web` creates a new watchlist entry in `Finished` after the download succeeds.
- Those add and download-sync paths now queue a background refresh for episode counts, AnimeSchedule metadata, and thumbnails instead of waiting for all upstream fetches before returning.
- Existing watchlist entries from older versions are migrated into the `Watching` category automatically.
### Thumbnail Behavior
Watchlist thumbnails use the local `/api/watchlist/thumb/...` route, not direct remote image URLs.
Current thumbnail behavior:
- Cached thumbnails render from the local project cache when available.
- The page adds a fresh nonce to thumbnail URLs to avoid stale browser-cached misses.
- Missing visible covers are warmed up in small batches instead of one request per card all at once.
- The page only retries an unresolved cover warm-up once per thumbnail state change during the current browser session, so silent watchlist polling does not keep hammering the same missing poster.
- If a show disappears while a thumbnail warm-up batch is running, that entry is skipped cleanly instead of failing the whole batch request.
- Thumbnail image tags only hit the local route for already-cached files, so opening the watchlist no longer triggers a per-card remote lookup burst.
- AnimeSchedule is tried first for cover lookup.
- If a season-labeled title fails as-is, lookup retries with simplified base-title queries.
- AniDB is used as a fallback when AnimeSchedule does not resolve a cover.
- AniDB fallback reuses the cached parsed title list directly to keep repeated lookups lighter.
- Failed fetch attempts no longer refresh the retry window, so a bad lookup does not get stuck in an immediate repeat-404 state.
Per-card thumbnail tools:
1. `Reload` forces a fresh redownload attempt for that show's poster.
2. `Upload` appears when no thumbnail is currently available and lets you assign a local image manually.
3. Hovering the poster tile or card shows the current thumbnail source hint.
Manual uploads are saved into the same local thumbnail cache used by automatic cover downloads.
### Completion Detection
Watchlist refresh still uses AllAnime, the same upstream source that `ani-cli` uses, to count which `sub` and `dub` episodes are actually available right now.
To decide whether a release track looks complete, `ani-cli-web` also checks AnimeSchedule for:
- the anime's expected total episode count
- the upstream airing status such as `Finished` or `Ongoing`
When AnimeSchedule reports a known total and `Finished`, the watchlist compares that total with the live AllAnime `sub` and `dub` counts:
- `Sub` becomes ready when available sub episodes reach the expected total.
- `Dub` becomes ready when available dub episodes reach the expected total.
If AnimeSchedule does not know the total yet, the watchlist keeps showing the plain available-count value without a completion highlight.
## Runtime State
All runtime state is stored under:
```text
ani-cli-web/.ani-cli-web/
```
That folder currently holds:
- `config.json`
- `state.sqlite3`
- legacy `queue.json` migration input when present
- legacy `watchlist.json` migration input when present
- `thumbnails/`
- AniDB title cache data
- staging files for active downloads
Legacy files from `~/.config/ani-cli-web/` and `~/.local/state/ani-cli-web/` are migrated into the project-local folder automatically on startup.
The earlier project-local `queue.sqlite3` filename is also migrated automatically into `state.sqlite3`.
## Configuration
Environment variables:
- `ANI_CLI_BIN`: path or command name for `ani-cli`; defaults to `ani-cli` from `PATH`.
- `ANI_CLI_DOWNLOAD_DIR`: initial default download folder.
- `ANI_CLI_MODE`: initial mode, `sub` or `dub`.
- `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720`.
- `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1`.
- `ANI_CLI_WEB_PORT`: server port, default `8421`.
- `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients when set to `1`, `true`, `yes`, or `on`.
- `ANI_CLI_WEB_REMOTE_TOKEN`: required shared secret for non-loopback clients when remote access is enabled.
- `ANI_CLI_WEB_DEBUG`: enable debug logging when set to `1`, `true`, `yes`, or `on`.
- `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory path; useful for isolated test runs or custom storage locations.
Saved defaults from the UI are written into the project-local `config.json`.
## Notes
- Importing `app.py` no longer boots the runtime worker threads automatically; runtime setup is deferred until startup or first use.
- Importing `app_support.py` no longer migrates or creates runtime state on its own; migrations now happen during runtime bootstrap.
- Cheap routes such as the page shells, `/api/version`, `/api/dependencies`, and `/api/config` do not need to boot the full runtime before responding.
- The CLI startup path itself also stays lazy now; runtime bootstrap happens when the first runtime-backed request arrives.
- Watchlist and queue data are stored in the same project-local SQLite database file, `state.sqlite3`.
- Runtime folders such as `.ani-cli-web/`, `downloads/`, and Python bytecode cache directories at any depth are ignored by git.
- Focused regression tests live in `test_app.py`, run against an isolated temporary state root, and can be started with `python3 -m unittest test_app.py`.