Принимаем .torrent как загруженные байты — через файл-пикер в веб-форме и Telegram-документ, наряду с magnet. Файл несёт полные метаданные: работает там, где magnet не резолвится (закрытые трекеры, без DHT), и даёт максимум контекста для распознавания без сети. - internal/torrent: парсер поверх anacrolix/torrent/metainfo — инфохэш(и) (v1 SHA1 исходных байтов info; v2 BEP52 при наличии) + Context() из имени, дерева файлов, размера, трекеров. Извлечение файлов панико-безопасно (недоверенный вход). - Персистентность байтов: таблица-спутник download_torrent (миграция 0009); пишется в транзакции создания загрузки, только на ветке создания (не при дедупе). Байты живут весь срок строки — нужны для повторного добавления при retry. - ingest: Request.TorrentData/TorrentName, диспетч парсера; source_ref — человекочитаемый референс (имя раздачи/файла), не адрес добавления. - worker: общий sourceAddParts ветвит по source_type в ОБОИХ add-путях — processCatched и Retry (torrent добавляется файлом, не magnet-хешем). - Транспорты: multipart-форма с файл-пикером (деградация без JS) и приём Telegram-документа (скачивание с редактированием токена из ошибок — секрет не в логи; обработка до ветки pending/текста). Разработка по OpenSpec (SDD): change torrent-file-ingest, два чекпоинта ревью (дизайн до кода, код до архива) сабагентами; дельты влиты в спеки, change архивирован. Ручная проверка на живом qBittorrent (7.3) — за деплоем. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.0 KiB
MODIFIED Requirements
Requirement: Добавление пойманной загрузки в qBittorrent
Worker SHALL периодически (в поллинг-цикле, под единой блокировкой переходов)
подхватывать загрузки в состоянии catched и для каждой: вывести отображаемое
имя из контекста (см. ingest «Отображаемое имя торрента из контекста»),
добавить источник в qBittorrent (категория qbittorrent.category, savepath,
rename) и перевести загрузку catched → downloading. Отдельного состояния
между catched и downloading быть SHALL NOT — успешный add сразу переводит
в downloading (которое и означает «в qBit, возможно metaDL»).
Добавление в qBittorrent worker SHALL выполнять по типу источника
(source_type):
- Для
magnet/url— передаватьsource_refкак ссылку (urlsAPI/torrents/add); подсказку отображаемого имени брать из полей самой ссылки. - Для
torrent— загружать сохранённые байты.torrent(привязанные к загрузке при приёме) и передавать их файлом (torrentsAPI/torrents/add), НЕ как ссылку; подсказку отображаемого имени брать из метаданных торрента (имя раздачи). Добавление байтами SHALL сохранять полные метаданные (qBittorrent стартует без докачки), поэтому воскрешать раздачу по magnet-хешу вместо файла система SHALL NOT.
Неуспешный add (qBittorrent недоступен и т.п.) SHALL оставлять загрузку в
catched для повторной попытки на следующем тике; переход в терминальное
состояние по единичному сбою происходить SHALL NOT (ретраи — естественными
тиками поллинга).
Медленные вызовы (вывод имени через LLM, qbt.Add) SHALL выполняться вне
блокировки сериализации переходов, чтобы не задерживать команды транспортов и
поллинг. Под блокировкой сериализуется только запись перехода catched → downloading (см. «Переходы состояний сериализуются воркером»), с
ре-валидацией, что загрузка всё ещё в catched (иначе переход отклоняется —
например, при параллельной отмене).
Scenario: Пойманная magnet-загрузка добавляется в qBittorrent
- GIVEN загрузка в состоянии
catchedсsource_type = magnet - WHEN worker обрабатывает тик
- THEN выводится отображаемое имя, ссылка добавляется в qBittorrent с
нашей категорией и
rename - AND загрузка переходит в
downloading
Scenario: Пойманная .torrent-загрузка добавляется файлом
- GIVEN загрузка в состоянии
catchedсsource_type = torrentи сохранёнными байтами файла - WHEN worker обрабатывает тик
- THEN сохранённые байты добавляются в qBittorrent файлом (
torrents), с нашей категорией иrename, без обращения к magnet-хешу - AND загрузка переходит в
downloading
Scenario: Временный сбой добавления — повтор
- GIVEN загрузка в
catched, qBittorrent временно недоступен - WHEN worker пытается добавить источник и
addне удался - THEN загрузка остаётся в
catched - AND на следующем тике попытка добавления повторяется
Scenario: Отмена во время добавления
- GIVEN загрузка в
catched, worker выводит имя и добавляет её вне блокировки - WHEN параллельно приходит команда отмены (
catched → cancelled), а затем worker берёт блокировку для записи перехода - THEN ре-валидация видит, что загрузка уже не в
catched, и переход вdownloadingне применяется