Раскладка файлов после распознавния

This commit is contained in:
2026-06-14 14:53:40 +03:00
parent 91c501624a
commit 9c1b178e46
19 changed files with 3001 additions and 38 deletions
+63 -10
View File
@@ -4,7 +4,11 @@
// состояние.
//
// Ф1 ведёт задачу downloading → completed, плюс stuck/failed по таймаутам и
// ошибкам qBittorrent. Распознавание и раскладка (completed →) — Ф2+.
// ошибкам qBittorrent. Ф3 продолжает: completed → recognizing (вызов
// recognize) → review; команды ревью (apply/refine/reject/defer/undo,
// переключение типа, пометка «игнор») раскладывают файлы хардлинками через
// layout. Распознавание зовётся в поллинг-цикле, команды — из транспортов;
// всё под per-download блокировкой w.mu.
package worker
import (
@@ -15,7 +19,9 @@ import (
"sync"
"time"
"git.vakhrushev.me/av/jellybit/internal/layout"
"git.vakhrushev.me/av/jellybit/internal/qbt"
"git.vakhrushev.me/av/jellybit/internal/recognize"
"git.vakhrushev.me/av/jellybit/internal/store"
)
@@ -24,12 +30,37 @@ type Store interface {
ListDownloadsByState(ctx context.Context, states ...store.State) ([]store.Download, error)
GetDownload(ctx context.Context, id int64) (*store.Download, error)
SetDownloadState(ctx context.Context, id int64, state store.State, errCode, errMsg string) error
// Ф3: распознавание, ревью, раскладка.
CreateRecognition(ctx context.Context, r *store.Recognition, reasons []string) (int64, error)
GetCurrentRecognition(ctx context.Context, downloadID int64) (*store.Recognition, error)
AddHint(ctx context.Context, downloadID int64, text string) error
ListHints(ctx context.Context, downloadID int64) ([]string, error)
SetOverride(ctx context.Context, downloadID int64, field, value string) error
ListOverrides(ctx context.Context, downloadID int64) (map[string]string, error)
CreateFileLinks(ctx context.Context, links []store.FileLink) error
LatestBatchID(ctx context.Context, downloadID int64) (string, error)
ListFileLinksByBatch(ctx context.Context, batchID string) ([]store.FileLink, error)
DeleteFileLinksByBatch(ctx context.Context, batchID string) error
}
// QBittorrent — нужная worker часть клиента qBittorrent.
type QBittorrent interface {
Torrents(ctx context.Context, category string) ([]qbt.Torrent, error)
Add(ctx context.Context, ar qbt.AddRequest) error
Files(ctx context.Context, hash string) ([]qbt.File, error)
}
// Recognizer — распознаватель (recognize.Recognizer).
type Recognizer interface {
Recognize(ctx context.Context, in recognize.Input) (recognize.Result, error)
}
// Layouter — раскладчик хардлинками (layout.Layouter).
type Layouter interface {
BuildLinks(p layout.Plan) ([]layout.Link, error)
Apply(ctx context.Context, links []layout.Link) ([]layout.Result, error)
Undo(ctx context.Context, links []layout.Link) (int, error)
}
// Config — параметры воркера.
@@ -43,18 +74,36 @@ type Config struct {
// Worker — поллер и владелец переходов.
type Worker struct {
store Store
qbt QBittorrent
cfg Config
log *slog.Logger
store Store
qbt QBittorrent
recognizer Recognizer
layouter Layouter
cfg Config
log *slog.Logger
mu sync.Mutex // сериализует переходы (поллинг + команды)
now func() time.Time // подменяется в тестах
mu sync.Mutex // сериализует переходы (поллинг + команды)
now func() time.Time // подменяется в тестах
newID func() string // генератор apply_batch_id (подменяется в тестах)
}
// New собирает воркер.
func New(st Store, qb QBittorrent, cfg Config, log *slog.Logger) *Worker {
return &Worker{store: st, qbt: qb, cfg: cfg, log: log, now: time.Now}
// New собирает воркер. recognizer/layouter могут быть nil (Ф1 без Ф3-ступеней
// распознавания и раскладки) — тогда completed-задачи не двигаются дальше.
func New(st Store, qb QBittorrent, rec Recognizer, lay Layouter, cfg Config, log *slog.Logger) *Worker {
return &Worker{
store: st,
qbt: qb,
recognizer: rec,
layouter: lay,
cfg: cfg,
log: log,
now: time.Now,
newID: defaultBatchID,
}
}
// defaultBatchID — уникальный идентификатор батча раскладки.
func defaultBatchID() string {
return fmt.Sprintf("b-%d", time.Now().UnixNano())
}
// Run крутит цикл поллинга до отмены ctx.
@@ -79,6 +128,10 @@ func (w *Worker) pollOnce(ctx context.Context) {
if err := w.Poll(ctx); err != nil {
w.log.Warn("poll failed", "err", err)
}
// Ф3: распознаём завершённые загрузки (и перезапускаем по подсказке).
if w.recognizer != nil {
w.recognizePending(ctx)
}
}
// Poll сверяет активные задачи с состоянием qBittorrent и двигает их.