Раскладка файлов после распознавния
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/worker"
|
||||
)
|
||||
|
||||
// Reviewer — операции ревью и раскладки (worker.Worker).
|
||||
type Reviewer interface {
|
||||
ReviewData(ctx context.Context, id int64) (*worker.ReviewData, error)
|
||||
Apply(ctx context.Context, id int64) error
|
||||
Refine(ctx context.Context, id int64, hint string) error
|
||||
SetType(ctx context.Context, id int64, mediaType string) error
|
||||
IgnoreFile(ctx context.Context, id int64, src string) error
|
||||
Defer(ctx context.Context, id int64) error
|
||||
Undo(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
// --- Представление страницы ревью ---
|
||||
|
||||
type reviewView struct {
|
||||
ID int64
|
||||
Source string
|
||||
Context string
|
||||
State string
|
||||
Error string // из ?err=
|
||||
StateError string // error_msg загрузки (напр. причина коллизии)
|
||||
MediaType string
|
||||
IsSeries bool
|
||||
Title string
|
||||
OriginalTitle string
|
||||
Year int
|
||||
Confidence string
|
||||
Reasons []string
|
||||
Hints []string
|
||||
Files []reviewFileView
|
||||
Preview []string
|
||||
HasPlan bool
|
||||
}
|
||||
|
||||
type reviewFileView struct {
|
||||
Src string
|
||||
Role string
|
||||
Season string
|
||||
Episode string
|
||||
Ignored bool
|
||||
}
|
||||
|
||||
func (s *server) handleReview(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
http.Error(w, "некорректный id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
rd, err := s.deps.Reviewer.ReviewData(r.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
http.Error(w, "задача не найдена", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s.deps.Logger.Error("review data", "id", id, "err", err)
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
view := reviewView{
|
||||
ID: id,
|
||||
Source: shorten(rd.Download.SourceRef, 80),
|
||||
Context: rd.Download.Context,
|
||||
State: string(rd.Download.State),
|
||||
Error: r.URL.Query().Get("err"),
|
||||
StateError: rd.Download.ErrorMsg.String,
|
||||
Hints: rd.Hints,
|
||||
}
|
||||
if rec := rd.Recognition; rec != nil {
|
||||
view.MediaType = string(rd.Plan.Type)
|
||||
view.IsSeries = rd.Plan.Type == "series"
|
||||
view.Title = rd.Plan.Title
|
||||
view.OriginalTitle = rd.Plan.OriginalTitle
|
||||
view.Year = rd.Plan.Year
|
||||
view.Reasons = rec.ReasonList()
|
||||
if rec.Confidence.Valid {
|
||||
view.Confidence = strconv.FormatFloat(rec.Confidence.Float64, 'f', 2, 64)
|
||||
}
|
||||
for _, f := range rd.Plan.Files {
|
||||
view.Files = append(view.Files, reviewFileView{
|
||||
Src: f.Src,
|
||||
Role: string(f.Role),
|
||||
Season: intPtrStr(f.Season),
|
||||
Episode: intPtrStr(f.Episode),
|
||||
Ignored: f.Role == "ignore",
|
||||
})
|
||||
}
|
||||
view.HasPlan = len(rd.Plan.Files) > 0
|
||||
}
|
||||
for _, l := range rd.Preview {
|
||||
view.Preview = append(view.Preview, l.Dst)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := s.review.Execute(w, view); err != nil {
|
||||
s.deps.Logger.Error("render review", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Действия ревью (POST → redirect) ---
|
||||
|
||||
func (s *server) handleApply(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
redirectErr(w, r, "некорректный id")
|
||||
return
|
||||
}
|
||||
if err := s.deps.Reviewer.Apply(r.Context(), id); err != nil {
|
||||
redirectReview(w, r, id, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *server) handleRefine(w http.ResponseWriter, r *http.Request) {
|
||||
s.reviewAction(w, r, func(ctx context.Context, id int64) error {
|
||||
_ = r.ParseForm()
|
||||
return s.deps.Reviewer.Refine(ctx, id, r.PostForm.Get("hint"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) handleSetType(w http.ResponseWriter, r *http.Request) {
|
||||
s.reviewAction(w, r, func(ctx context.Context, id int64) error {
|
||||
_ = r.ParseForm()
|
||||
return s.deps.Reviewer.SetType(ctx, id, r.PostForm.Get("type"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) handleIgnore(w http.ResponseWriter, r *http.Request) {
|
||||
s.reviewAction(w, r, func(ctx context.Context, id int64) error {
|
||||
_ = r.ParseForm()
|
||||
return s.deps.Reviewer.IgnoreFile(ctx, id, r.PostForm.Get("src"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) handleDefer(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
redirectErr(w, r, "некорректный id")
|
||||
return
|
||||
}
|
||||
if err := s.deps.Reviewer.Defer(r.Context(), id); err != nil {
|
||||
redirectReview(w, r, id, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *server) handleUndo(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
redirectErr(w, r, "некорректный id")
|
||||
return
|
||||
}
|
||||
if err := s.deps.Reviewer.Undo(r.Context(), id); err != nil {
|
||||
redirectErr(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// reviewAction — общий помощник: выполнить действие и вернуться на страницу
|
||||
// ревью (с ошибкой в ?err при неудаче).
|
||||
func (s *server) reviewAction(w http.ResponseWriter, r *http.Request, fn func(context.Context, int64) error) {
|
||||
id, err := pathID(r)
|
||||
if err != nil {
|
||||
redirectErr(w, r, "некорректный id")
|
||||
return
|
||||
}
|
||||
if err := fn(r.Context(), id); err != nil {
|
||||
redirectReview(w, r, id, err.Error())
|
||||
return
|
||||
}
|
||||
redirectReview(w, r, id, "")
|
||||
}
|
||||
|
||||
func redirectReview(w http.ResponseWriter, r *http.Request, id int64, msg string) {
|
||||
u := "/review/" + strconv.FormatInt(id, 10)
|
||||
if msg != "" {
|
||||
u += "?err=" + url.QueryEscape(msg)
|
||||
}
|
||||
http.Redirect(w, r, u, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func intPtrStr(p *int) string {
|
||||
if p == nil {
|
||||
return "—"
|
||||
}
|
||||
return strconv.Itoa(*p)
|
||||
}
|
||||
Reference in New Issue
Block a user