Files
jellybit/openspec/specs/download-tracking/spec.md
T
avandClaude Opus 4.8 2ed9c9020f Беклог: 3 пункта из аудита спек↔код + правка формулировки per-download (docs)
По итогам аудита соответствия спек и кода (11 сабагентов, по одному на capability):

Беклог:
- новый пункт: гейт авто-раскладки по confidence (спека говорит «вспомогательный
  сигнал», код делает жёсткий AutoThreshold=0.85) — определиться, что правда
- новый пункт: привязка внешних субтитров к серии (спека требует, для сериала
  связь субтитр→эпизод и пары .idx/.sub в коде не выражены)
- новый пункт: раздачи-копии диска DVD/BluRay (VIDEO_TS/BDMV — каталог целиком,
  не пофайловый разбор)
- дополнен существующий баг TVDB /series/: спека metadata-match теперь тоже
  кодифицирует баг — фикс должен править и требование

Спеки (правка на точность, поведение не меняется):
- download-tracking, review: «per-download блокировка» → «единая блокировка
  воркера» (по факту глобальный w.mu, а не per-download)

openspec validate --strict — проходит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:32:02 +03:00

6.3 KiB
Raw Blame History

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 переходы применяются последовательно под блокировкой, без гонки