Пересканирование 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` надо дописать.
|
||||
@@ -181,10 +181,15 @@ Jellyfin ([jellyfin-layout.md](jellyfin-layout.md)). Правила:
|
||||
|
||||
## Пересканирование Jellyfin
|
||||
|
||||
После успешной раскладки (вход в `done`) `worker` неблокирующе просит Jellyfin
|
||||
пересканировать медиатеку, чтобы новые файлы быстрее появились в проигрывателе.
|
||||
Включается конфигом `[jellyfin]` (по умолчанию выключено); без него скан не
|
||||
дёргается.
|
||||
Когда наши библиотечные хардлинки меняются, `worker` неблокирующе просит Jellyfin
|
||||
пересканировать медиатеку, чтобы плеер не держал битые пути и быстрее подхватил
|
||||
новые файлы. Триггерят входы в `done` (файлы разложены), `reverted` (Undo снял
|
||||
ссылки) и `deleted` (Delete снял ссылки / сверка констатировала их отсутствие) —
|
||||
гейт по состоянию-цели в едином чекпоинте перехода, поэтому ловит и
|
||||
пользовательские Undo/Delete, и reconcile-производный `deleted`. Промежуточный
|
||||
рассинхрон (`target_missing`/`orphaned`) не сканируем — задача ждёт
|
||||
relink/лечения. Включается конфигом `[jellyfin]` (по умолчанию выключено); без
|
||||
него скан не дёргается.
|
||||
|
||||
- **Один вызов — `POST /Library/Refresh`** (скан всех библиотек). Скан
|
||||
инкрементальный, поэтому полный дёшев; точечный скан конкретной папки не
|
||||
@@ -260,9 +265,9 @@ Dockerfile .dockerignore config.example.toml
|
||||
задач (повторная закачка спустя время → новая задача).
|
||||
- Состояние — на persistent-томе `/srv/applications/jellybit/data`.
|
||||
- Детект завершения — поллинг; webhook — на будущее (drafts/ideas).
|
||||
- Пересканирование Jellyfin после раскладки — `POST /Library/Refresh` (скан
|
||||
всех библиотек, инкрементальный), неблокирующе на входе в `done`; опц.,
|
||||
включается `[jellyfin]`.
|
||||
- Пересканирование Jellyfin при изменении наших ссылок — `POST /Library/Refresh`
|
||||
(скан всех библиотек, инкрементальный), неблокирующе на входе в `done`/
|
||||
`reverted`/`deleted`; опц., включается `[jellyfin]`.
|
||||
- Источник (magnet/URL/.torrent) отдаём в qBittorrent — без SSRF.
|
||||
- Авто-раскладка требует подтверждённого матча в базе; иначе review.
|
||||
- Веб-UI в v1 без авторизации (доверенная LAN, опц. allowlist подсетей).
|
||||
|
||||
@@ -100,7 +100,9 @@ stateDiagram-v2
|
||||
- **done** — при входе неблокирующе дёргаем пересканирование Jellyfin
|
||||
(опц., см. [architecture.md](architecture.md) → «Пересканирование
|
||||
Jellyfin»); доступен **Undo** → `reverted` (убрать созданные ссылки) и
|
||||
**Удалить** → `deleted` (полное удаление, см. ниже).
|
||||
**Удалить** → `deleted` (полное удаление, см. ниже). Скан дёргается и при
|
||||
входе в `reverted`/`deleted` — наши ссылки там сняты, Jellyfin не должен
|
||||
держать битые пути.
|
||||
- **stuck / failed / cancelled** — не качается дольше таймаута; ошибка
|
||||
(ретраибельна); «Отклонить».
|
||||
- **reverted / cancelled → recognizing** — «Привязать заново»: после
|
||||
|
||||
Reference in New Issue
Block a user