Приём: добавление загрузки по .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:
@@ -314,3 +314,87 @@ recognition (LLM-промпт) и веб-UI (страница загрузки).
|
||||
- **THEN** `download.Context` остаётся пустым
|
||||
- **AND** приём проходит штатно (пустой контекст допустим)
|
||||
|
||||
|
||||
### Requirement: Приём источника из .torrent-файла
|
||||
|
||||
Приём SHALL принимать источник в виде **байтов `.torrent`-файла** (наряду с
|
||||
magnet-ссылкой) — тем же быстрым use-case, общим для транспортов. Получив
|
||||
непустые байты торрента, система SHALL разобрать их локально (без сети),
|
||||
извлечь инфохэш(и) и завести загрузку с `source_type = torrent`, после чего
|
||||
сразу вернуть ответ транспорту (синхронный путь к qBittorrent не обращается —
|
||||
добавление делает воркер, см. `download-tracking`).
|
||||
|
||||
Инфохэши система SHALL извлекать такими, какими их сообщает qBittorrent, чтобы
|
||||
сопоставление раздач и дедупликация работали: v1-хеш (для v1/гибридного файла)
|
||||
SHALL вычисляться как SHA1 **исходных** байтов info-словаря (без переэнкода);
|
||||
v2-хеш (для v2/гибридного файла, BEP52) SHALL извлекаться как 64-hex `infohash_v2`.
|
||||
Для чистого v2-only файла система SHALL записывать v2-хеш (v1 у него нет).
|
||||
Извлечение всех известных хешей и дозапись недостающих подчиняются требованию
|
||||
«Множество инфохэшей загрузки».
|
||||
|
||||
Дедупликацию по активной задаче, атомарное заведение (`download` в состоянии
|
||||
`catched` + записи `download_infohash`) и инвариант «не более одной активной
|
||||
загрузки на infohash» torrent-приём SHALL проходить тем же атомарным путём, что
|
||||
и magnet (см. «Приём источника и заведение загрузки», «Дедупликация приёма по
|
||||
любому из хешей», «Атомарность возврата загрузки в активное состояние»).
|
||||
|
||||
Байты `.torrent` система SHALL сохранять персистентно, привязанными к загрузке,
|
||||
чтобы воркер мог добавить источник в qBittorrent именно файлом (не по magnet):
|
||||
раздачи закрытых трекеров и торренты без DHT по magnet-хешу метаданные не
|
||||
получат. Сохранение байтов SHALL выполняться в той же write-транзакции, что и
|
||||
заведение загрузки; при дедупликации (новая загрузка не создана) байты
|
||||
сохраняться SHALL NOT. Размер принимаемого `.torrent` система SHALL ограничивать
|
||||
на границе транспорта (защита от разбухания хранилища).
|
||||
|
||||
Из полей `.torrent` система SHALL синтезировать контекст распознавания (имя
|
||||
раздачи, суммарный размер, сигнал по дереву файлов, домен трекера, комментарий)
|
||||
и **дополнять** им контекст транспорта — тем же правилом слияния, что и синтез
|
||||
из полей magnet (пользовательский текст первым; при пустом тексте — только
|
||||
синтез). Обогащённый контекст система SHALL сохранять в `download.Context`.
|
||||
Синтез SHALL выполняться без сетевых запросов.
|
||||
|
||||
`source_ref` у torrent-загрузки SHALL быть человекочитаемым референсом (имя
|
||||
раздачи или файла), а НЕ адресом добавления: добавление в qBittorrent идёт
|
||||
байтами, и трактовать `source_ref` как magnet/URL для добавления система SHALL
|
||||
NOT.
|
||||
|
||||
#### Scenario: Быстрый приём .torrent-файла
|
||||
|
||||
- **GIVEN** валидные байты `.torrent`-файла и (опц.) текст контекста
|
||||
- **WHEN** вызывается приём
|
||||
- **THEN** из файла извлекаются инфохэши и создаётся `download` в состоянии
|
||||
`catched` (`source_type = torrent`) с записями `download_infohash`
|
||||
- **AND** байты файла сохраняются привязанными к загрузке
|
||||
- **AND** ответ транспорту отдан без обращения к qBittorrent
|
||||
|
||||
#### Scenario: Инфохэш из исходных байтов info
|
||||
|
||||
- **WHEN** система разбирает v1/гибридный `.torrent`-файл
|
||||
- **THEN** инфохэш v1 вычисляется как SHA1 исходных байтов info-словаря
|
||||
- **AND** совпадает с хешем, по которому qBittorrent позже сопоставит раздачу
|
||||
|
||||
#### Scenario: v2-only файл записывается под v2-хешем
|
||||
|
||||
- **WHEN** система разбирает `.torrent` только с метаданными v2 (без v1)
|
||||
- **THEN** у загрузки записывается v2-хеш (64-hex), совпадающий с `infohash_v2`
|
||||
qBittorrent
|
||||
- **AND** сопоставление раздачи работает по нему
|
||||
|
||||
#### Scenario: Дубль .torrent по активной задаче
|
||||
|
||||
- **GIVEN** уже есть активная (в т.ч. `catched`) загрузка с тем же infohash
|
||||
- **WHEN** принимается `.torrent` с тем же инфохэшем
|
||||
- **THEN** новая загрузка не создаётся, возвращается существующая
|
||||
- **AND** байты торрента не сохраняются (дубль)
|
||||
|
||||
#### Scenario: Контекст из полей файла
|
||||
|
||||
- **WHEN** принят `.torrent` с именем раздачи, деревом файлов и трекерами
|
||||
- **THEN** в `download.Context` добавляется синтез (имя, размер, сигнал по
|
||||
файлам, домен трекера), дополняющий текст транспорта
|
||||
- **AND** синтез выполнен без сетевых запросов
|
||||
|
||||
#### Scenario: Слишком большой .torrent отклоняется
|
||||
|
||||
- **WHEN** принимаемый `.torrent`-файл превышает ограничение размера
|
||||
- **THEN** приём отклоняется с ошибкой, загрузка не создаётся
|
||||
|
||||
Reference in New Issue
Block a user