Commit Graph

36 Commits

Author SHA1 Message Date
Miguel Palhas 5bc9022046 fix(arr): pass the podnapisi seam to the lamp probes
#200 branched before #205's ARR_PODNAPISI_URL seam was pushed, so its new
broken::SubtitleUpstreams call site was written against the three-argument
subtitle_providers. The merge was textually clean and did not build.
2026-08-25 06:31:27 +01:00
Miguel Palhas 8043ef614a Merge #200: lamp the subtitle upstreams
Closes #200
2026-08-25 06:30:34 +01:00
Miguel Palhas a4422e26e5 style(arr): formatting 2026-08-25 06:28:20 +01:00
Miguel Palhas 8c6d4ca577 feat(daemon): fold subtitle lamps into broken notifications 2026-08-25 06:12:22 +01:00
Miguel Palhas 5fc6854705 feat(arr): add an env-only Podnapisi URL seam
ARR_PODNAPISI_URL, same shape as the existing tmdb_url seam: a test
harness can point the provider at a wiremock fake without touching
DESIGN.md §10's config surface.
2026-08-25 05:25:25 +01:00
Miguel Palhas e0e7ddf6de docs(arr): correct the subtitle lane's comment
Said translation backends are passed empty; #216 wired them and #219 made
both callers share one set.
2026-08-25 04:55:06 +01:00
Miguel Palhas e338a76c66 fix(arr): wire the command timeout to the settings row 2026-08-25 03:15:33 +01:00
Miguel Palhas 7b4cff1874 fix(arr): give the reconcile loop its translators
#196 built SubtitleAction with an empty backend list, correctly: no
translation backend existed when it was written. #216 then built
translation_backends() and wired it into AppState, so the API can translate.
Merged, the reconcile loop still got Vec::new() and its translate step
reported "no engine" in every real deployment.

Neither branch was wrong alone; the gap only exists once both are in.
2026-08-25 02:21:21 +01:00
Miguel Palhas 4110555183 Merge #196: close the subtitle gap in the reconcile loop
Closes #196
2026-08-25 02:20:11 +01:00
Miguel Palhas c2ca25895f feat(arr): reconcile subtitle gaps in the daemon
Closes each unsatisfied wanted language per DESIGN.md §8/§15: embedded
tracks satisfy for free (recording them as #189 left to this issue),
then provider search + ranked fetch + alass sync + sidecar write, then
immediate machine translation — extracting a text-format embedded track
when that is the only source — and otherwise the reason lands on the
attempt row for the missing-subtitles queue.

Closes run as detached tasks because alass and translation outlive the
25 s reconcile action budget; every outcome is recorded in domain rows
first, so a crash converges on the next tick. Failures back off on the
same §6.2 curve as movie searches; a rate-limited provider is the
'capped' queue state; unreachable providers and translators fold into
the existing §9.5 broken notification, edge-triggered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 02:10:49 +01:00
Miguel Palhas e49bc2736d feat(arr): wire translation backends into daemon
Forward each translate-* feature from arr-daemon to arr-subs, add the
missing OpenAI model bootstrap key, and construct the compiled and
credentialed backends at startup so the translate endpoint stops
answering 503 unconditionally.
2026-08-25 02:01:55 +01:00
Miguel Palhas f1a58c0810 refactor(arr): split api_state out of run
Both #195 and #214 added a builder call to the AppState chain, which pushed
run past the too-many-lines limit. The chain grows a line per upstream the
API learns to talk to, so it gets its own function.
2026-08-25 01:50:17 +01:00
Miguel Palhas 999cb8b08c Merge #214: sync manually grabbed subtitles with alass
Closes #214

# Conflicts:
#	crates/arr-api/src/state.rs
#	crates/arr-daemon/src/main.rs
2026-08-25 01:49:20 +01:00
Miguel Palhas 910d28f639 refactor(arr): give arr-api its own jellyfin client
arr-daemon depends on arr-api, so a handler in arr-api can never
reach the daemon's private JellyfinClient. Move it into arr-api and
attach an instance to AppState, so a manual subtitle write can ask
for the same refresh import already does (#195).
2026-08-25 01:31:54 +01:00
Miguel Palhas f06e0e94bc feat(arr): sync manually grabbed subtitles with alass
The grab handler runs alass before recording the row, replacing the
sidecar with the synced text on acceptance and flagging it as
rejected otherwise (§15). Wires a Syncer into AppState, defaulting
to alass on PATH; the daemon binary points it at config.alass_path.
2026-08-25 01:24:09 +01:00
Miguel Palhas faa7a0c056 feat(arr): offer subtitle providers to the API
Provider credentials are bootstrap config and never reach the database
(DESIGN.md §10), so which providers exist is settled once at startup;
which of them a search runs is the `providers_enabled` row the API reads
per request. OpenSubtitles.com cannot be called without a registered API
key, so without one it is not offered at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 23:27:08 +01:00
Miguel Palhas d8797e1996 feat(daemon): refresh metadata on add
The metadata lane runs daily, so a series added a moment ago showed no
seasons for up to 24 hours and a movie had no digital release date —
the field §6.2 gates targeted search on.

AppState now carries a MetadataCommand channel alongside the movie,
episode and season ones. Both create handlers send on it after the row
is committed, and a new daemon lane drains it. Its own task rather than
an arm of manual::run: a refresh against TMDB can take a while and must
not sit in front of an operator's manual search.

The add never waits on TMDB and never fails because of it. A refresh
that fails leaves metadata_refreshed_at NULL, which is what the daily
sweep already treats as due, so the title is retried rather than lost.
A command naming a title deleted in between finds no row and does
nothing. METADATA_INTERVAL is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 18:12:10 +01:00
Miguel Palhas 8a77049fdb feat(daemon): drain all manual command channels 2026-08-23 18:24:50 +01:00
Miguel Palhas 0c58aae707 feat(daemon): daily series metadata refresh lane 2026-08-23 17:45:09 +01:00
Miguel Palhas 83ca921501 fix(daemon): consume the MovieCommand channel
ci / web (push) Successful in 36s
ci / rust (push) Successful in 1m23s
e2e / e2e (push) Successful in 1m17s
Nothing drained AppState's movie_commands mpsc, so manual search and
one-click grab were accepted with 202 and then did nothing until the
64-slot buffer filled and the endpoint started 503ing.

A new daemon task drains it: Search resets the movie's backoff and
runs the targeted-search + grab lane immediately; Grab sends the
already-chosen release straight to Transmission.

Closes #107
2026-08-23 09:02:42 +01:00
naps62-yolo ad0eb0d9d3 feat: ntfy notifications for imported, needs-decision, broken (#96)
ci / web (push) Successful in 1m0s
e2e / e2e (push) Successful in 3m41s
ci / rust (push) Successful in 5m42s
2026-08-23 02:04:36 +01:00
naps62-yolo 9f812fac11 feat: season-pack vs per-episode grab selection (#94)
ci / web (push) Successful in 24s
e2e / e2e (push) Successful in 54s
ci / rust (push) Successful in 2m11s
2026-08-23 01:40:53 +01:00
naps62-yolo 35391f02fc feat: Jellyfin library refresh on import (#93)
ci / web (push) Successful in 28s
e2e / e2e (push) Successful in 55s
ci / rust (push) Successful in 2m12s
2026-08-23 01:14:46 +01:00
naps62-yolo 225be33b55 feat: RSS sync worker (#91)
ci / web (push) Successful in 26s
e2e / e2e (push) Successful in 53s
ci / rust (push) Successful in 2m13s
2026-08-23 01:04:15 +01:00
naps62-yolo e01505bd53 Targeted-search backoff and release-date gating (#90)
ci / web (push) Successful in 25s
e2e / e2e (push) Successful in 46s
ci / rust (push) Successful in 2m9s
2026-08-23 00:42:13 +01:00
naps62-yolo 0585811a0c Per-tracker seeding rules and torrent reaper (#88)
ci / web (push) Successful in 43s
e2e / e2e (push) Successful in 54s
ci / rust (push) Failing after 2m0s
2026-08-23 00:03:30 +01:00
naps62-yolo ffb8485939 Import pipeline: probe, hardlink, rename, layout (#82)
ci / rust (push) Failing after 1m43s
ci / web (push) Successful in 58s
e2e / e2e (push) Successful in 1m25s
2026-08-22 23:21:30 +01:00
naps62-yolo f253e2755b feat(daemon): grab pipeline for wanted movies (#77)
ci / web (push) Successful in 53s
ci / rust (push) Successful in 1m47s
e2e / e2e (push) Successful in 2m1s
2026-08-22 22:38:31 +01:00
naps62-yolo e5f4e44925 Add reconcile loop scheduler (#69)
ci / rust (push) Successful in 1m42s
ci / web (push) Successful in 32s
e2e / e2e (push) Successful in 1m42s
2026-08-22 22:00:12 +01:00
naps62-yolo e9c055675b test(e2e): cross-process harness over the §12 seams (#66)
ci / rust (push) Failing after 1m36s
ci / web (push) Successful in 26s
e2e / e2e (push) Failing after 59s
2026-08-22 21:26:16 +01:00
naps62-yolo 514628f089 feat(web): SPA skeleton embedded in the binary (#60)
ci / web (push) Successful in 33s
ci / rust (push) Successful in 57s
e2e / e2e (push) Successful in 1m7s
2026-08-22 21:07:40 +01:00
naps62-yolo 94c334ffe9 Radarr v3 shim for Jellyseerr (#62)
ci / web (push) Successful in 12s
ci / rust (push) Successful in 1m44s
e2e / e2e (push) Successful in 1m21s
2026-08-22 21:00:35 +01:00
naps62-yolo 99ac79373d feat(api): add movie CRUD API (#56)
ci / web (push) Successful in 8s
e2e / e2e (push) Successful in 1m21s
ci / rust (push) Successful in 3m53s
2026-08-22 20:40:31 +01:00
naps62-yolo 58827da647 feat(api): axum skeleton with health and OpenAPI (#53)
ci / web (push) Successful in 6s
ci / rust (push) Successful in 45s
e2e / e2e (push) Successful in 1m31s
2026-08-22 20:09:52 +01:00
naps62-yolo 1529e9fdca Bootstrap configuration loading (#46)
ci / web (push) Successful in 14s
ci / rust (push) Successful in 39s
e2e / e2e (push) Successful in 1m15s
2026-08-22 19:44:50 +01:00
Claude f4e63f1ac5 chore: cargo workspace skeleton
Eleven empty crates per DESIGN.md §11, versions and lints pinned once in the
root manifest, Justfile carrying the §12 gate, nix dev shell.

The frontend steps in `just ci` no-op until web/ exists. arr-core carries one
placeholder test because `cargo nextest` fails a zero-test run, and relaxing
that would let a broken filter go green later.

Closes #1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:20:47 +01:00