# download-tracking Specification ## Purpose Отслеживание скачивания и прямой путь машины состояний загрузки: поллинг qBittorrent и сопоставление его состояний (downloading → completed; готовность только когда файлы на месте), таймауты-предохранители (`magnet_timeout`/ `stuck_after`), ошибка qBit → failed, усыновление раздач по категории/тегу и переходы под per-download блокировкой. Сверка уже разложенного с реальностью — в `state-reconciliation`. ## Requirements ### Requirement: Поллинг qBittorrent и сопоставление состояний Worker SHALL периодически (`worker.poll_interval`, дефолт 5 с) опрашивать qBittorrent и сопоставлять его состояния раздачи с состоянием загрузки в БД. Готовые к раскладке состояния (`uploading`/`stalledUP`/`pausedUP`/`stoppedUP`/ `queuedUP`/`forcedUP`, с учётом различий имён между qBit v4 и v5) SHALL переводить загрузку в `completed`. Ещё качающиеся состояния (`downloading`/`stalledDL`/ `metaDL`/…) SHALL оставлять её в `downloading`. #### Scenario: Раздача завершилась - **GIVEN** загрузка в `downloading` - **WHEN** qBittorrent сообщает состояние `stalledUP` и файлы на месте - **THEN** загрузка переходит в `completed` ### Requirement: Готовность только когда файлы на месте Переходные состояния qBittorrent система SHALL трактовать как «ждём» (`moving`/`checkingUP`/`checkingResumeData`/`allocating`): оставаться в `downloading` и НЕ объявлять готовность, даже если выставлены флаги `UP`, пока qBit не завершит перенос/проверку. Финальные пути файлов система SHALL брать из API после завершения переноса. #### Scenario: Ждём завершения переноса - **GIVEN** загрузка, у которой qBittorrent в состоянии `moving` - **WHEN** идёт тик поллинга - **THEN** загрузка остаётся в `downloading`, готовность не объявляется ### Requirement: Таймауты-предохранители downloading Система SHALL переводить `metaDL`/`forcedMetaDL` дольше `magnet_timeout` (дефолт `24h`, редкий предохранитель) в `failed` (`error_code` `magnet_timeout`), а `stalledDL` дольше `stuck_after` — в `stuck` (`error_code` `stalled`). Возраст система SHALL считать от времени добавления в qBittorrent (`added_on`), а не от создания задачи, чтобы базис переживал retry и усыновление. Долгий `metaDL` система НЕ SHALL убивать агрессивно (медленные трекеры — норма). #### Scenario: Завис на метаданных дольше таймаута - **GIVEN** раздача в `metaDL` дольше `magnet_timeout` от `added_on` - **WHEN** идёт тик поллинга - **THEN** загрузка переходит в `failed` с `error_code` `magnet_timeout` ### Requirement: Ошибка qBittorrent переводит в failed Состояния `error`/`missingFiles` система SHALL трактовать как настоящий провал и переводить загрузку в `failed` (`error_code` `qbit_error`) — в отличие от таймаутов-предохранителей, такой провал сверкой не воскрешается. #### Scenario: qBit сообщает об ошибке - **GIVEN** раздача в состоянии `missingFiles` - **WHEN** идёт тик поллинга - **THEN** загрузка переходит в `failed` с `error_code` `qbit_error` ### Requirement: Усыновление раздач по категории или тегу Worker SHALL периодически сверять раздачи qBittorrent с БД и **усыновлять** те, у которых наша категория (`qbittorrent.category`) ИЛИ тег (`qbittorrent.tag`), а записи в БД ещё нет, заводя для них загрузку в состоянии `downloading`. Категория ставится на добавляемые нами раздачи (push); тег позволяет подхватить уже существующую раздачу (pull), не трогая её категорию и файлы. #### Scenario: Подхват существующей раздачи по тегу - **GIVEN** в qBittorrent есть раздача с тегом `qbittorrent.tag`, которой нет в БД - **WHEN** worker сверяет qBittorrent с БД - **THEN** для раздачи заводится загрузка в состоянии `downloading` ### Requirement: Переходы состояний сериализуются воркером Все переходы состояний загрузки SHALL сериализоваться worker'ом под единой блокировкой (поллинг-цикл и команды всех транспортов проходят через неё), чтобы два источника перехода не гонялись за одно состояние. Состояние SHALL быть персистентным в SQLite; активность загрузки SHALL выводиться только из `state`, без отдельного флага. #### Scenario: Команды сериализуются - **GIVEN** две одновременные команды к одной загрузке из разных транспортов - **WHEN** они обрабатываются - **THEN** переходы применяются последовательно под блокировкой, без гонки