Приём: добавление загрузки по .torrent-файлу
Принимаем .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>
This commit is contained in:
+68
@@ -0,0 +1,68 @@
|
||||
## 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` как ссылку (`urls` API
|
||||
`/torrents/add`); подсказку отображаемого имени брать из полей самой ссылки.
|
||||
- Для `torrent` — загружать сохранённые байты `.torrent` (привязанные к
|
||||
загрузке при приёме) и передавать их **файлом** (`torrents` API
|
||||
`/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` не применяется
|
||||
Reference in New Issue
Block a user