Добавил ручную перепривязку

This commit is contained in:
2026-06-15 07:42:50 +03:00
parent 093211c9c7
commit 16a82572e7
10 changed files with 206 additions and 10 deletions
+55 -7
View File
@@ -21,10 +21,11 @@ import (
const (
ovrMediaType = "media_type"
ovrIgnoredFiles = "ignored_files"
ovrProvider = "provider" // выбранная база ("none" = без базы)
ovrProviderID = "provider_id" // id в выбранной базе
ovrTitle = "title" // запиненное каноническое название
ovrYear = "year" // запиненный год
ovrProvider = "provider" // выбранная база ("none" = без базы)
ovrProviderID = "provider_id" // id в выбранной базе
ovrTitle = "title" // запиненное каноническое название
ovrYear = "year" // запиненный год
ovrForceReview = "force_review" // ручная перепривязка: не авто-раскладывать
)
// recognizePending распознаёт завершённые загрузки и перезапускает те, что
@@ -178,9 +179,13 @@ func (w *Worker) finishRecognition(ctx context.Context, id int64, res recognize.
}
// Авто-раскладка при подтверждённом матче и чистой валидации (Ф4);
// иначе — review. Раскладчик может быть не сконфигурирован.
if res.Decision.Auto && w.layouter != nil {
plan := applyOverrides(res.Plan, w.overridesOrNil(ctx, id))
// иначе — review. Раскладчик может быть не сконфигурирован. При ручной
// перепривязке (force_review) авто-раскладку не делаем — нужно явное
// подтверждение человеком.
overrides := w.overridesOrNil(ctx, id)
forceReview := overrides[ovrForceReview] == "1"
if res.Decision.Auto && !forceReview && w.layouter != nil {
plan := applyOverrides(res.Plan, overrides)
w.transition(ctx, *d, store.StateLinking, "", "")
if err := w.linkPlan(ctx, d, plan, tag, savePath); err != nil {
w.log.Warn("recognize: auto-apply failed, left for review",
@@ -281,6 +286,49 @@ func (w *Worker) linkPlan(ctx context.Context, d *store.Download, plan recognize
return nil
}
// Relink повторно привязывает откатанную задачу (reverted): возвращает её на
// распознавание, и поллинг-цикл перезапустит recognize. Авто-раскладку при
// этом не делаем — ручная перепривязка всегда проходит через ревью с
// подтверждением (force_review). Источник (раздача в qBittorrent) для этого
// должен быть на месте.
func (w *Worker) Relink(ctx context.Context, id int64) error {
w.mu.Lock()
defer w.mu.Unlock()
d, err := w.store.GetDownload(ctx, id)
if err != nil {
return fmt.Errorf("relink: %w", err)
}
if d.State != store.StateReverted {
return fmt.Errorf("relink: download %d is in state %s (expected reverted)", id, d.State)
}
if !d.Infohash.Valid {
return fmt.Errorf("relink: download %d has no infohash", id)
}
// Раздача должна ещё быть в qBittorrent — без неё распознавать нечего.
if _, ok, terr := w.torrentByInfohash(ctx, d.Infohash.String); terr != nil {
return fmt.Errorf("relink: %w", terr)
} else if !ok {
return fmt.Errorf("relink: торрент не найден в qBittorrent")
}
// Вернуть задачу в активную обработку можно, только если другой активной
// задачи на этот infohash нет (partial unique index по idempotency_key).
active, err := w.store.FindActiveByInfohash(ctx, d.Infohash.String)
if err != nil {
return fmt.Errorf("relink: %w", err)
}
if active != nil {
return fmt.Errorf("relink: для этого торрента уже есть активная задача #%d", active.ID)
}
// Ручная перепривязка — всегда с подтверждением, без авто-раскладки.
if err := w.store.SetOverride(ctx, id, ovrForceReview, "1"); err != nil {
return fmt.Errorf("relink: %w", err)
}
w.transition(ctx, *d, store.StateRecognizing, "", "")
w.log.Info("relink: re-recognizing reverted download", "download_id", id)
return nil
}
// Refine добавляет подсказку и отправляет задачу на перераспознавание.
func (w *Worker) Refine(ctx context.Context, id int64, hint string) error {
hint = strings.TrimSpace(hint)