Приём (Ingest) стал быстрым: синхронно только парс magnet, синтез контекста из полей ссылки, атомарный дедуп и запись загрузки в новое состояние `catched` — ответ клиенту сразу. Медленный вывод имени (LLM) и добавление в qBittorrent вынесены в асинхронный шаг машины состояний, который двигает worker. - store: состояние `catched` (нетерминальное, активная группа); атомарный переход PromoteCatched (catched → downloading + display_name) с гардом state='catched' (ре-валидация после сетевых вызовов вне блокировки) - ingest: убраны namer/qbt из пути приёма; пишем `catched`, отвечаем сразу - worker.processCatched: вне w.mu выводит имя и qbt.Add, под w.mu — короткий переход; сбой add оставляет catched (ретрай тиком); предохранитель catch_timeout → failed(qbit_add)+notify; catched исключён из проверок пропажи - config: worker.catch_timeout (дефолт 10m) - веб-UI: бейдж catched, активная группа, самозавершающийся htmx-поллинг карточки/страницы до перехода в downloading; Telegram-текст без сырого catched - OpenSpec: дельты ingest/download-tracking/web-ui влиты в спеки, change заархивирован Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.1 KiB
MODIFIED Requirements
Requirement: Приём источника и заведение загрузки
Приём SHALL быть единым быстрым use-case, общим для всех транспортов (HTTP,
Telegram, CLI): по источнику (Ф1 — magnet) и текстовому контексту система SHALL
синхронно извлечь инфохэши, синтезировать контекст из полей ссылки (без сети),
дедуплицировать по активной задаче и при отсутствии дубля завести загрузку
(download в состоянии catched + записи download_infohash), после чего
сразу вернуть ответ транспорту. Заведение загрузки и запись её хешей SHALL
выполняться атомарно (см. «Атомарность возврата загрузки в активное
состояние»).
Синхронный путь приёма SHALL NOT обращаться к qBittorrent и SHALL NOT выводить
отображаемое имя (потенциально медленный LLM): и добавление источника в
qBittorrent, и вывод имени выполняются отдельным асинхронным шагом машины
состояний (worker) — см. download-tracking «Добавление пойманной загрузки в
qBittorrent».
catched — нетерминальное активное состояние: оно участвует в инварианте «не
более одной активной загрузки на infohash» наравне с прочими активными.
Scenario: Быстрый приём magnet
- GIVEN валидная magnet-ссылка и контекст
- WHEN вызывается приём
- THEN создаётся
downloadв состоянииcatchedс записямиdownload_infohash - AND ответ транспорту отдан без обращения к qBittorrent и без вывода имени
Scenario: Дубль по активной задаче на быстром пути
- GIVEN уже есть активная (в т.ч.
catched) загрузка с тем же infohash - WHEN вызывается приём
- THEN новая загрузка не создаётся, возвращается существующая
Requirement: Отображаемое имя торрента из контекста
На шаге добавления пойманной загрузки в qBittorrent (worker) система SHALL
выводить из контекста загрузки человекочитаемое отображаемое имя и передавать
его в qBittorrent (параметр rename API /torrents/add), чтобы задача в списке
qBit не показывалась безликим dn magnet-ссылки. Это же имя система SHALL
сохранять у загрузки (download.display_name) для последующего показа
заголовком в веб-UI.
Имя SHALL быть коротким читаемым ярлыком (название, опционально режиссёр и год; для сериала — номер сезона, если он определён), а не куском сырого контекста. Имя SHALL очищаться от управляющих символов и переводов строк и SHALL обрезаться по ограничению длины.
Вывод имени SHALL выполняться на шаге добавления, непосредственно перед вызовом
add (параметр rename действует только в момент добавления), а НЕ в
синхронном пути ответа приёма. В состоянии catched (до добавления)
download.display_name ещё пуст — веб-UI берёт заголовок из фолбека.
Отображаемое имя SHALL влиять только на отображение (в qBittorrent и как заголовок в веб-UI) и SHALL NOT влиять на пути файлов на диске, распознавание или раскладку — реальные пути система по-прежнему читает из qBit API.
Scenario: Имя из контекста передаётся в qBittorrent
- WHEN на шаге добавления получен непустой контекст, из которого удалось вывести имя
- THEN система передаёт это имя в qBittorrent в параметре
rename - AND имя — короткий читаемый ярлык вида «название (режиссёр, год)», где режиссёр и год опциональны
Scenario: Имя сохраняется у загрузки
- WHEN на шаге добавления выведено непустое отображаемое имя
- THEN система сохраняет его в
download.display_name(обновлением записи загрузки) - AND веб-UI использует его заголовком карточки и страницы загрузки
Scenario: Контекст пуст или имя не получено
- WHEN контекста нет либо ни один способ вывода не дал непустого имени
- THEN система добавляет загрузку без параметра
rename - AND qBittorrent оставляет собственное имя (из
dn/торрента) - AND
download.display_nameостаётся пустым, а веб-UI берёт заголовок из фолбека (распознанное название или усечённый источник)