Пересканирование Jellyfin: расширить триггер на reverted и deleted
Скан Jellyfin (POST /Library/Refresh) слался только при входе в done.
После Undo (reverted) и Delete (deleted) наши хардлинки сняты, а Jellyfin
держал битые записи до скана по расписанию.
Гейт скана в едином чекпоинте transitionErr переведён с state == done на
предикат triggersScan(state) по множеству {done, reverted, deleted}: гейт по
состоянию-цели естественно ловит пользовательские Undo/Delete и
reconcile-производный deleted, идемпотентно. target_missing/orphaned —
промежуточный рассинхрон (ждём relink/лечения) — исключены.
OpenSpec: заведена и влита дельта file-layout (требование
«Пересканирование Jellyfin после изменения библиотечных ссылок»); change
архивирован. Синк рукописных доков architecture.md/workflow.md. Тесты:
скан стреляет на reverted и deleted, молчит на входе вне множества.
Закрыта задача беклога jellyfin-skan-posle-udaleniya.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,6 @@ Tududi (проект `jellybit`) больше **не** держит беклог
|
||||
- [[идея] Сложные сериальные раздачи: все сезоны разом, паки, спецраскладки](slozhnye-serialnye-razdachi.md) — ИДЕЯ (проработать крайние случаи)
|
||||
- [Аниме с абсолютной нумерацией](anime-absolyutnaya-numeraciya.md) — Релизы аниме часто нумеруют серии сквозным числом (#137) без сезонов, а Jellyfin ждёт…
|
||||
- [Бэкап SQLite](backup-sqlite.md) — architecture
|
||||
- [Сигнал Jellyfin после отката и удаления файлов](jellyfin-skan-posle-udaleniya.md) — Скан шлётся только на done; после reverted/deleted Jellyfin держит битые записи. Клиент и гейт готовы, но скана нет в openspec-спеках
|
||||
- [Режиссёр в блоке «Распознано как» на странице загрузки](rezhisser-v-kartochke-zagruzki.md) — На /download/{id} режиссёр всегда прочерк (поля нет в шаблоне); экран ревью его уже выводит
|
||||
- [Глубокий healthcheck и статус зависимостей](healthcheck-zavisimosti.md) — /healthz проверяет только сам сервис
|
||||
- [НФТ: масштаб до 100 одновременных загрузок (потолок — 1000)](masshtab-100-zagruzok.md) — Зафиксировать в НФТ ориентир 100/1000 загрузок + аудит узких мест (SQLite, воркер, поллинг)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# Сигнал Jellyfin после отката и удаления файлов
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Сейчас пересканирование Jellyfin шлётся **только** при входе в `done`. После
|
||||
отката (`reverted`) или удаления (`deleted`) хардлинки сняты, а Jellyfin
|
||||
продолжает показывать записи с битыми путями до следующего скана по расписанию.
|
||||
|
||||
Интеграция уже есть целиком, клиент писать не надо:
|
||||
|
||||
- `internal/jellyfin/jellyfin.go` — клиент, `RefreshLibraries()` (`:76`) →
|
||||
`POST /Library/Refresh`. Тесты — `internal/jellyfin/jellyfin_test.go`.
|
||||
- Конфиг: `internal/config/config.go:99-107` (`Jellyfin{Enabled,URL,APIKey,Proxy,Timeout}`),
|
||||
валидация `:305-308`, пример `config.example.toml:59-64`.
|
||||
- Проводка: `cmd/jellybit/serve.go:137-152` → `wrk.SetScanner(jf)`; интерфейс
|
||||
`internal/worker/worker.go:171-176`.
|
||||
|
||||
Точка правки — гейт в `transitionErr` (`internal/worker/worker.go:846-852`):
|
||||
`if w.scanner != nil && state == store.StateDone`. Оба пути удаления уже проходят
|
||||
через ту же `transition`: `Undo()` (`internal/worker/review.go:535` → `:574`) и
|
||||
`Delete()` (`:601` → `:657`). То есть база правки = расширить условие; фоновый
|
||||
ctx, `capFileLayout`-скоуп и неблокирующая горутина переиспользуются как есть.
|
||||
|
||||
## Что учесть
|
||||
|
||||
1. **`StateDeleted` приходит не только от пользователя.**
|
||||
`internal/worker/reconcile.go:30-41` `deriveState()` возвращает `StateDeleted`
|
||||
при `!sourcePresent && !targetPresent` (авто-сверка). Скан там формально уместен,
|
||||
но это уже не «после удаления файлов нами». Решить: гейтить по состоянию (просто,
|
||||
ловит и reconcile) или по факту снятия ссылок (точнее — в `Undo`/`Delete` есть
|
||||
счётчик снятого, но тогда триггер уезжает из единого чекпоинта `transitionErr`).
|
||||
2. **`StateTargetMissing`** (`reconcile.go:35`) — цель пропала мимо нас. Кандидат
|
||||
по той же логике, надо явно решить, входит или нет.
|
||||
3. **`Dismiss` идёт мимо чекпоинта:** `internal/worker/worker.go:944` пишет
|
||||
состояние напрямую через `w.store.SetDownloadState`, минуя `transitionErr`. Для
|
||||
dismiss это корректно (файлы не трогаются), но если вешать скан на `cancelled` —
|
||||
не сработает.
|
||||
4. `Delete()` при ошибке qBittorrent (`internal/worker/review.go:650-652`)
|
||||
возвращается **до** `transition` → ссылки сняты, скана не будет. Идемпотентный
|
||||
повтор дожмёт, но окно рассинхрона есть.
|
||||
5. Порядок верный: `layouter.Undo` отрабатывает до `transition`, так что скан
|
||||
увидит уже снятые ссылки.
|
||||
|
||||
## Спеки — здесь дыра
|
||||
|
||||
Про Jellyfin-скан в `openspec/specs/` **нет ни слова** (грепом
|
||||
`Library/Refresh|RefreshLibraries|пересканир` — ноль попаданий). Живёт только в
|
||||
рукописных доках: `docs/specs/architecture.md:182-196` («Пересканирование
|
||||
Jellyfin», прямо сказано «После успешной раскладки (вход в `done`)»),
|
||||
`docs/specs/workflow.md:100-102`.
|
||||
|
||||
→ Задача тянет дельту в `file-layout` (пакет `jellyfin` отнесён к этой capability —
|
||||
`architecture.md:44`, код скоупится `capFileLayout`) + правку
|
||||
`docs/specs/architecture.md:182` и `docs/specs/workflow.md:100`, где формулировка
|
||||
«при входе в done» станет неверной.
|
||||
|
||||
Тесты: `internal/worker/review_test.go:88` `TestScanner_FiresOnDone` +
|
||||
`recordingScanner` (`:81-86`). Негативных тестов «не стреляет на других состояниях»
|
||||
нет → расширение безопасно, но тесты на `reverted`/`deleted` надо дописать.
|
||||
Reference in New Issue
Block a user