Files

7.7 KiB
Raw Permalink Blame History

Жизненный цикл загрузки и машина состояний

Как загрузка проходит путь от приёма источника до разложенных файлов: состояния, переходы и то, что их вызывает. Кто владеет переходами и общее устройство — в architecture.md; детали распознавания — в recognition.md; действия человека в ревью — в review-ux.md.

Граф состояний

stateDiagram-v2
    [*] --> downloading: ingest (источник отдан в qBittorrent)

    downloading --> completed: файлы на месте
    downloading --> stuck: stalledDL дольше stuck_after
    downloading --> failed: metaDL дольше magnet_timeout / error

    completed --> recognizing

    recognizing --> linking: авто (матч в базе + валидация)
    recognizing --> review: нужно подтверждение / ответ LLM не разобран

    review --> linking: Применить
    review --> recognizing: Уточнить / Распознать заново
    review --> deferred: Позже
    review --> cancelled: Отклонить
    deferred --> review: любое действие (та же поверхность)

    linking --> done
    linking --> review: коллизия цели
    linking --> failed: ошибка ФС

    done --> reverted: Undo

    reverted --> recognizing: Привязать заново
    cancelled --> recognizing: Привязать заново

    stuck --> downloading: Retry
    failed --> downloading: Retry

    done --> [*]
    cancelled --> [*]
    reverted --> [*]

    note right of cancelled
        «Отклонить» доступно из любого
        нетерминального состояния
    end note

Условно-терминальные состояния — done, cancelled, failed, reverted: задача в них останавливается, но из failed/stuck есть Retry, а из reverted/cancelledПривязать заново. stuck восстановимо ретраем.

Состояния и переходы

  • ingest → downloading — приняли источник + контекст, отдали в qBittorrent (категория qbittorrent.category), записали в БД с ключом идемпотентности. См. architecture.md → «Транспорты».
  • downloading / completedworker поллит qBittorrent (worker.poll_interval, 5 с). Готовность — только когда файлы на месте (не moving/checking*), см. «Завершение в qBittorrent» ниже.
  • recognizingrecognize строит план и оценку уверенности (recognition.md). Невалидный/непарсящийся ответ LLM → review (не failed).
  • review — план уходит человеку (review-ux.md); цикл review ⇄ recognizing — перераспознавание по подсказке. «Уточнить» — подсказка + перераспознавание; «Распознать заново» — повторный прогон без новой подсказки, по уже накопленному контексту и подсказкам.
  • deferred — «Позже» паркует задачу; принимает те же команды, что и review, и возвращается в поверхность ревью по любому действию.
  • linkinglayout создаёт хардлинки; идемпотентно, батчем. Коллизия цели возвращает в review, ошибка ФС → failed. См. architecture.md → «Раскладка файлов».
  • done — при входе неблокирующе дёргаем пересканирование Jellyfin (опц., см. architecture.md → «Пересканирование Jellyfin»); доступен Undoreverted (убрать созданные ссылки).
  • stuck / failed / cancelled — не качается дольше таймаута; ошибка (ретраибельна); «Отклонить».
  • reverted / cancelled → recognizing — «Привязать заново»: после отката или отклонения можно перезапустить распознавание для той же раздачи. Перепривязка всегда идёт через review с ручным подтверждением (авто-раскладку не делаем) и требует, чтобы раздача всё ещё была в qBittorrent.

Все переходы и команды идут через worker под per-download блокировкой — два транспорта не гонятся за одно состояние. Состояние персистентно в SQLite; worker периодически сверяет qBittorrent с БД и усыновляет раздачи с нашей категорией (qbittorrent.category) или тегом (qbittorrent.tag), которых ещё нет в БД, заводя для них задачу в состоянии downloading. Категория ставится на добавляемые нами раздачи (push, задаёт savepath); тег позволяет подхватить уже существующую раздачу, не трогая её категорию и файлы (pull).

Завершение в qBittorrent

worker опрашивает qBittorrent и сопоставляет его состояния с нашими:

  • готово к раскладке: uploading/stalledUP/pausedUP/stoppedUP/ queuedUP/forcedUP (имена paused*/stopped* различаются между qBit v4 и v5 — поддержаны оба).
  • переходное, ждём: moving/checkingUP/checkingResumeData/ allocating — остаёмся в downloading, пока qBit не закончит перенос/ проверку (готовность не объявляем, даже если флаги «UP»).
  • ещё качается: downloading/stalledDL/metaDL/forcedMetaDL/ queuedDL/checkingDL/forcedDL/pausedDL/stoppedDL.
  • застряло/ошибка по таймауту: metaDL/forcedMetaDL дольше magnet_timeoutfailed; stalledDL дольше stuck_afterstuck (восстановимо ретраем). Возраст считаем от создания задачи.
  • ошибка: error/missingFilesfailed.

Пути файлов берём из API (save_path + относительные имена из /torrents/files, уже включающие корневую папку торрента), не из константы (обычно это уже хост-путь). «Incomplete»-каталог в qBittorrent включён (/srv/media/incomplete): пока качается — файлы там, по завершении qBit переносит их в /srv/media/downloads (состояние moving — дожидаемся окончания переноса и только потом берём финальный путь). Подробнее о путях и песочнице — architecture.md → «Пути и контейнеры».