Восстановление зависших загрузок и уведомления о падении (state-reconciliation)

Долгий metaDL больше не убивается агрессивным таймаутом: дефолт
magnet_timeout 30m → 24h (страховочный предохранитель), базис отсчёта —
added_on из qBittorrent, а не created_at (переживает retry/усыновление).

Авто-восстановление: фоновая сверка возвращает в поток задачи, упавшие по
нашей нетерпеливости (magnet_timeout/stalled), когда источник ожил и
продвинулся за условие падения (downloading/completed по статусу торрента);
qbit_error не воскрешается. Конфликт idempotency (infohash занят другой
активной задачей) — оставляем в failed.

Уведомления: любой переход в failed/stuck пингует автора (включая приёмный
qbit_add через ingest), с дебаунсом против спама при флаппинге stalled.
Ручной retry добавлен в веб-UI и Telegram; Retry перецепляется к живому
торренту вместо слепого Add.

Дельта state-reconciliation влита в живые спеки; обновлён workflow.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
av
2026-06-30 14:51:57 +03:00
co-authored by Claude Opus 4.8
parent dfa182a5a9
commit 70d8758646
24 changed files with 1168 additions and 40 deletions
+32 -7
View File
@@ -14,7 +14,7 @@ stateDiagram-v2
downloading --> completed: файлы на месте
downloading --> stuck: stalledDL дольше stuck_after
downloading --> failed: metaDL дольше magnet_timeout / error
downloading --> failed: metaDL дольше magnet_timeout (страховка) / error
completed --> recognizing
@@ -36,8 +36,10 @@ stateDiagram-v2
reverted --> recognizing: Привязать заново
cancelled --> recognizing: Привязать заново
stuck --> downloading: Retry
failed --> downloading: Retry
stuck --> downloading: Retry / сверка (раздача ожила)
failed --> downloading: Retry / сверка (метаданные пришли)
failed --> completed: сверка (торрент уже готов)
stuck --> completed: сверка (торрент уже готов)
done --> target_missing: сверка — цель удалена
done --> orphaned: сверка — источник пропал
@@ -147,10 +149,33 @@ SQLite; `worker` периодически сверяет qBittorrent с БД и
проверку (готовность не объявляем, даже если флаги «UP»).
- **ещё качается:** `downloading`/`stalledDL`/`metaDL`/`forcedMetaDL`/
`queuedDL`/`checkingDL`/`forcedDL`/`pausedDL`/`stoppedDL`.
- **застряло/ошибка по таймауту:** `metaDL`/`forcedMetaDL` дольше
`magnet_timeout``failed`; `stalledDL` дольше `stuck_after``stuck`
(восстановимо ретраем). Возраст считаем от создания задачи.
- **ошибка:** `error`/`missingFiles``failed`.
- **застряло по таймауту (страховка):** `metaDL`/`forcedMetaDL` дольше
`magnet_timeout``failed`; `stalledDL` дольше `stuck_after``stuck`.
`magnet_timeout`**редкий страховочный предохранитель** (дефолт `24h`), а
не рабочий механизм: долгий `metaDL` (медленные трекеры/мало пиров) — это
норма, его не убиваем агрессивно. Возраст считаем от времени добавления
торрента в qBittorrent (`added_on`), а не от создания задачи (базис
переживает retry и усыновление).
- **ошибка:** `error`/`missingFiles``failed` (`error_code` `qbit_error`) —
это настоящий провал, в отличие от таймаута.
### Уведомление и восстановление
- Любой переход в `failed`/`stuck` **уведомляет** автора загрузки
(`notifier`), чтобы падение не оставалось незамеченным — включая приёмное
падение `qbit_add` (не удалось добавить в qBittorrent), которое идёт мимо
поллинг-цикла. Повторные падения одной задачи в пределах окна дебаунса
уведомляют лишь раз — чтобы мерцающий `stalled`-торрент
(`stuck``downloading`) не спамил.
- `failed`/`stuck` из-за нашей нетерпеливости (`error_code` `magnet_timeout`/
`stalled`) **не тупик**: фоновая сверка возвращает задачу в поток, как
только источник в qBittorrent ожил и продвинулся за условие падения
(получил метаданные → `downloading`; уже готов → `completed`). Пока торрент
всё ещё в `metaDL`/`stalledDL`, задача остаётся упавшей (без зацикливания).
Настоящие провалы (`qbit_error`) сверкой не воскрешаются.
- Дополнительно доступен **ручной retry** из веб-UI и Telegram (не только
REST): возвращает в `downloading`, перецепляясь к живому торренту без
повторного `Add`.
Пути файлов берём из API (`save_path` + относительные имена из
`/torrents/files`, уже включающие корневую папку торрента), не из