Files
avandClaude Opus 4.8 14d615a7c2 Приём: добавление загрузки по .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>
2026-07-08 10:52:31 +03:00

6.7 KiB
Raw Permalink Blame History

ADDED Requirements

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 приём отклоняется с ошибкой, загрузка не создаётся