Приём: добавление загрузки по .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:
@@ -0,0 +1,95 @@
|
||||
## Why
|
||||
|
||||
Приём сейчас умеет только magnet-ссылку (`ingest.Request.Source` — строка;
|
||||
`magnet.Parse` — единственный парсер; при неудаче — ошибка «.torrent/url —
|
||||
следующий заход»). Но пользователи часто получают с трекера именно `.torrent`-
|
||||
файл, а не magnet, и у файла есть два преимущества, которых нет у голого
|
||||
magnet:
|
||||
|
||||
- **Работает там, где magnet не резолвится.** Раздачи закрытых трекеров и
|
||||
торренты без DHT по magnet-хешу метаданные не докачают (`metaDL` навсегда).
|
||||
`.torrent` несёт полный info-словарь — qBittorrent стартует сразу, без
|
||||
докачки метаданных.
|
||||
- **Максимум контекста без сети.** В файле уже лежат реальное имя раздачи,
|
||||
дерево файлов с размерами, суммарный размер, трекеры, комментарий — гораздо
|
||||
богаче полей magnet. Это прямой сигнал для recognition ещё до старта
|
||||
скачивания.
|
||||
|
||||
Инфраструктура почти готова: `qbt.AddRequest.Torrents [][]byte` и
|
||||
`qbt.Client.Add` уже умеют слать `.torrent` байтами (multipart-upload),
|
||||
константа `store.SourceTorrent` уже объявлена. Не хватает трёх вещей: парсера
|
||||
байтов в инфохэш+контекст, персистентности байтов между быстрым приёмом
|
||||
(`catched`) и добавлением воркером, и путей приёма файла в транспортах
|
||||
(Telegram-документ, файл-пикер в вебе).
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Новый парсер `internal/torrent`** поверх `github.com/anacrolix/torrent/
|
||||
metainfo`: `Parse(data []byte) (Info, error)` вытаскивает инфохэш(и) (v1
|
||||
обязательно, v2 при наличии), имя раздачи, список файлов с размерами,
|
||||
суммарный размер, трекеры, комментарий. Инфохэш v1 — SHA1 **исходных** байтов
|
||||
info-словаря (без переэнкода). `func (Info) Context() string` синтезирует
|
||||
человекочитаемый контекст распознавания из этих полей — в том же стиле, что
|
||||
`magnet.Info.Context()`.
|
||||
- **Приём (`ingest`) принимает байты `.torrent`.** `ingest.Request` получает
|
||||
путь для байтов торрент-файла; при их наличии источник парсится
|
||||
`torrent.Parse` (иначе — как сейчас, `magnet.Parse`). Загрузка заводится с
|
||||
`SourceType = SourceTorrent`, инфохэши и дедуп — тем же атомарным путём
|
||||
(`CreateDownloadIfNoActive`), контекст — синтез из полей файла, слитый с
|
||||
текстом транспорта.
|
||||
- **Персистентность байтов.** Байты `.torrent` нужны воркеру на шаге добавления
|
||||
(быстрый приём лишь сохраняет `catched`). Заводим таблицу-спутник
|
||||
`download_torrent(download_id, data)` — байты живут и удаляются вместе с
|
||||
загрузкой, не раздувая выборки `download`.
|
||||
- **Воркер добавляет по типу источника.** `processCatched` ветвится по
|
||||
`SourceType`: magnet/url — как сейчас (`URLs`, hint из `magnet.Parse`);
|
||||
torrent — грузит байты из `download_torrent`, hint из имени раздачи,
|
||||
добавляет через `qbt.AddRequest.Torrents`.
|
||||
- **Веб-UI: файл-пикер.** Рядом со строкой ввода источника — `<input
|
||||
type="file" accept=".torrent">` и `enctype="multipart/form-data"` на форме.
|
||||
Выбран файл — приём по байтам; иначе — по тексту. Деградация без JS
|
||||
сохраняется (обычный multipart-POST).
|
||||
- **Telegram: приём документа.** `handleMessage` распознаёт `.torrent`-
|
||||
документ (mime `application/x-bittorrent` / расширение), скачивает его байты
|
||||
через Bot API и подаёт в приём; подпись сообщения идёт контекстом.
|
||||
|
||||
Вне объёма (сознательно):
|
||||
|
||||
- **Торрент по http(s)-ссылке** (URL на `.torrent`) — отдельная задача: фетч
|
||||
тянет сеть в fast-path приёма, требует SSRF-гарда, таймаутов и обработки
|
||||
ошибок загрузки.
|
||||
- Изменения механизма вывода отображаемого имени и распознавания — не
|
||||
трогаем; они лишь получают более богатый контекст.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет._ Изменение укладывается в существующие capability.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `ingest`: добавляется приём источника из байтов `.torrent` (парс метаданных,
|
||||
извлечение инфохэшей, синтез контекста из полей файла, `SourceType =
|
||||
torrent`, персистентность байтов до добавления). Существующий magnet-путь и
|
||||
инварианты дедупа/инфохэшей — без изменений.
|
||||
- `download-tracking`: шаг «добавление пойманной загрузки в qBittorrent»
|
||||
ветвится по типу источника — для torrent источник добавляется байтами файла,
|
||||
hint отображаемого имени берётся из метаданных торрента.
|
||||
- `web-ui`: форма добавления получает файловый ввод `.torrent`
|
||||
(`multipart/form-data`), с деградацией без JS.
|
||||
|
||||
## Impact
|
||||
|
||||
- Код: новый `internal/torrent` (парсер + `Context()`); `internal/ingest`
|
||||
(ветка байтов, `SourceTorrent`, запись байтов); `internal/store` (таблица
|
||||
`download_torrent`, чтение/запись байтов, миграция); `internal/worker`
|
||||
(`processCatched` — диспетч по `SourceType`); `internal/httpapi` (multipart в
|
||||
`handleUIAdd`, размерный лимит); `internal/tgbot` (обработка `m.Document`,
|
||||
скачивание файла).
|
||||
- Зависимости: `+ github.com/anacrolix/torrent` (пакет `metainfo`) в go.mod.
|
||||
- Данные: новая таблица `download_torrent` (миграция goose) + обновление
|
||||
ER-схемы `docs/specs/database.md`. Таблица `download` не меняется.
|
||||
- Конфиг: размерный лимит `.torrent` — константа с разумным дефолтом (вынос в
|
||||
конфиг — при необходимости, отдельно).
|
||||
- Совместимость: аддитивно; существующие magnet-загрузки не затронуты.
|
||||
Reference in New Issue
Block a user