Ревью: preflight готовности источника для команд ревью (MAJOR-5)
Команды ревью проверяли только наличие раздачи в qBittorrent, но не её готовность. Недокачанную задачу можно припарковать в deferred, затем «Распознать заново» → recognizing → авто-раскладка (Rerecognize/Refine/ SetType не ставят force_review) → хардлинки на неполные файлы. Даже ручной Apply не имел preflight завершённости. Вводим ensureSourceReady (classify(t.State)==classReady) вместо ensureSourcePresent во всех командах, которым нужен источник (Relink/ Rerecognize/Refine/SetType), и inline-проверку класса в Apply — последний рубеж перед хардлинками. Недокачанный источник → отдельный sentinel ErrNotReady (409) с actionable-текстом «торрент ещё качается» в web и Telegram, без reconcile (состояние deferred/review легитимно). Change review-readiness-preflight заархивирован, дельта влита в openspec/specs/review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -245,23 +245,33 @@ func recoveredState(state string) store.State {
|
||||
|
||||
// --- Синхронный preflight перед действием (не доверяем state в БД) ---
|
||||
|
||||
// ensureSourcePresent синхронно (без дебаунса) проверяет, что раздача есть в
|
||||
// qBittorrent прямо сейчас. При отсутствии приводит состояние к реальности и
|
||||
// возвращает ErrConflict. Недоступность qBittorrent — честный отказ операции.
|
||||
func (w *Worker) ensureSourcePresent(ctx context.Context, d *store.Download, op string) error {
|
||||
// ensureSourceReady синхронно (без дебаунса) проверяет, что раздача есть в
|
||||
// qBittorrent прямо сейчас И докачана (класс classReady). Команды ревью, ведущие
|
||||
// к распознаванию или раскладке, работают только с готовым источником — иначе
|
||||
// хардлинки легли бы на неполные файлы (qBittorrent отдаёт имена до завершения).
|
||||
// - источник исчез → приводим состояние к реальности (orphaned/deleted) и
|
||||
// ErrConflict;
|
||||
// - источник есть, но ещё качается → ErrNotReady БЕЗ reconcile: нахождение
|
||||
// задачи в review/deferred/… легитимно, приводить нечего.
|
||||
//
|
||||
// Недоступность qBittorrent — честный отказ операции.
|
||||
func (w *Worker) ensureSourceReady(ctx context.Context, d *store.Download, op string) error {
|
||||
if len(d.Infohashes) == 0 {
|
||||
return fmt.Errorf("%s: download %s has no infohash", op, d.ID)
|
||||
}
|
||||
_, ok, err := w.torrentByInfohash(ctx, d.HashList())
|
||||
t, ok, err := w.torrentByInfohash(ctx, d.HashList())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
if !ok {
|
||||
// Источник пропал — немедленно приводим состояние к реальности.
|
||||
w.reconcileToReality(ctx, *d, false)
|
||||
return fmt.Errorf("%s: источник удалён из qBittorrent: %w", op, ErrConflict)
|
||||
}
|
||||
// Источник пропал — немедленно приводим состояние к реальности.
|
||||
w.reconcileToReality(ctx, *d, false)
|
||||
return fmt.Errorf("%s: источник удалён из qBittorrent: %w", op, ErrConflict)
|
||||
if classify(t.State) != classReady {
|
||||
return fmt.Errorf("%s: торрент ещё качается: %w", op, ErrNotReady)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reconcileToReality выводит и проставляет состояние по уже известному факту об
|
||||
|
||||
Reference in New Issue
Block a user