Rename the Fixer to Tune-up
Tab, URL (#/pair/<id>/tune-up; old fixer links still work), job names, History trigger and docs now say Tune-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d8ad42c719
commit
7b180e26db
8 files changed
+21
-21
No files matched your search
@@ -7,12 +7,12 @@ up disk space only once, while both libraries keep looking exactly the same to P
|
|||||||
It ships as a single Go binary with an embedded React web UI and SQLite state, packaged as a small Docker image, in the style of
|
It ships as a single Go binary with an embedded React web UI and SQLite state, packaged as a small Docker image, in the style of
|
||||||
[qui](https://github.com/autobrr/qui).
|
[qui](https://github.com/autobrr/qui).
|
||||||
|
|
||||||
## Workflow: fix, organize, then link
|
## Workflow: tune up, then link
|
||||||
|
|
||||||
Each library pair has a **type**: **Music** (recommended layout `Artist/Album/01 Title.flac`) or **Soundtracks** for video game and film
|
Each library pair has a **type**: **Music** (recommended layout `Artist/Album/01 Title.flac`) or **Soundtracks** for video game and film
|
||||||
soundtracks (`Album/01 Title.flac`, named by game or film). The type sets the default naming; the *Organize* tab can change it.
|
soundtracks (`Album/01 Title.flac`, named by game or film). The type sets the default naming; the *Organize* tab can change it.
|
||||||
|
|
||||||
The **Fixer** tab takes each library through the same steps, the **master first, then the slave**, and then links the two:
|
The **Tune-up** tab takes each library through the same steps, the **master first, then the slave**, and then links the two:
|
||||||
|
|
||||||
1. **Folder structure.** Every album in its own folder at the right depth. Songs outside an album folder move into one named from their
|
1. **Folder structure.** Every album in its own folder at the right depth. Songs outside an album folder move into one named from their
|
||||||
tags; extra folders around an album (a composer folder around a soundtrack, `Metal/Artist/Album`, a `FLAC` folder inside an album) are
|
tags; extra folders around an album (a composer folder around a soundtrack, `Metal/Artist/Album`, a `FLAC` folder inside an album) are
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/cdnutter/playlink/internal/engine"
|
"github.com/cdnutter/playlink/internal/engine"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---- step-by-step fixer ----
|
// ---- tune-up (step-by-step library fixing) ----
|
||||||
|
|
||||||
func (s *Server) handleFixer(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleFixer(w http.ResponseWriter, r *http.Request) {
|
||||||
id, _ := idParam(r)
|
id, _ := idParam(r)
|
||||||
@@ -43,7 +43,7 @@ func (s *Server) handleFixApply(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !readJSON(w, r, &body) {
|
if !readJSON(w, r, &body) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !s.preAction(w, r, id, "fixer") {
|
if !s.preAction(w, r, id, "tune-up") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
j, err := s.eng.StartFixApply(id, body)
|
j, err := s.eng.StartFixApply(id, body)
|
||||||
@@ -51,7 +51,7 @@ func (s *Server) handleFixApply(w http.ResponseWriter, r *http.Request) {
|
|||||||
opErr(w, err)
|
opErr(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.log.Info("fixer apply started", "pair", id, "selected", len(body.IDs))
|
s.log.Info("tune-up apply started", "pair", id, "selected", len(body.IDs))
|
||||||
writeJSON(w, 202, j)
|
writeJSON(w, 202, j)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/cdnutter/playlink/internal/store"
|
"github.com/cdnutter/playlink/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The fixer takes each library through the same steps, the master first and
|
// The tune-up takes each library through the same steps, the master first and
|
||||||
// then the slave:
|
// then the slave:
|
||||||
//
|
//
|
||||||
// 1. structure: every album in its own folder at the right depth (Artist/
|
// 1. structure: every album in its own folder at the right depth (Artist/
|
||||||
@@ -62,7 +62,7 @@ type FixerState struct {
|
|||||||
Link StepStatus `json:"link"`
|
Link StepStatus `json:"link"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixerStateOf reads a pair's fixer progress.
|
// FixerStateOf reads a pair's tune-up progress.
|
||||||
func FixerStateOf(p store.Pair) FixerState {
|
func FixerStateOf(p store.Pair) FixerState {
|
||||||
var s FixerState
|
var s FixerState
|
||||||
if len(p.FixerState) > 0 {
|
if len(p.FixerState) > 0 {
|
||||||
@@ -204,7 +204,7 @@ func (e *Engine) StartFixCheck(pairID int64, c FixCheck) (*Job, error) {
|
|||||||
if c.Step == StepTags && e.meta == nil {
|
if c.Step == StepTags && e.meta == nil {
|
||||||
return nil, errors.New("online lookup is not available")
|
return nil, errors.New("online lookup is not available")
|
||||||
}
|
}
|
||||||
return e.startJob(pairID, "fix-"+c.Step, func(ctx context.Context, j *Job) error {
|
return e.startJob(pairID, "tune-up "+c.Step, func(ctx context.Context, j *Job) error {
|
||||||
l, err := e.loadPair(ctx, pairID)
|
l, err := e.loadPair(ctx, pairID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -292,7 +292,7 @@ func (e *Engine) StartFixApply(pairID int64, req FixApply) (*Job, error) {
|
|||||||
return nil, fmt.Errorf("%w: nothing selected", ErrInvalidBulk)
|
return nil, fmt.Errorf("%w: nothing selected", ErrInvalidBulk)
|
||||||
}
|
}
|
||||||
side, step := plan.Side, plan.Step
|
side, step := plan.Side, plan.Step
|
||||||
return e.startJob(pairID, "fix-"+step+"-apply", func(ctx context.Context, j *Job) error {
|
return e.startJob(pairID, "tune-up "+step+" (apply)", func(ctx context.Context, j *Job) error {
|
||||||
l, err := e.loadPair(ctx, pairID)
|
l, err := e.loadPair(ctx, pairID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -300,7 +300,7 @@ func (e *Engine) StartFixApply(pairID int64, req FixApply) (*Job, error) {
|
|||||||
e.fixMu.Lock()
|
e.fixMu.Lock()
|
||||||
plan.Applied = true // paths and tags change: the plan is stale
|
plan.Applied = true // paths and tags change: the plan is stale
|
||||||
e.fixMu.Unlock()
|
e.fixMu.Unlock()
|
||||||
trigger := "fixer"
|
trigger := "tune-up"
|
||||||
edited := map[string]bool{}
|
edited := map[string]bool{}
|
||||||
if n := len(tagItems) + songsIn(albums); n > 0 {
|
if n := len(tagItems) + songsIn(albums); n > 0 {
|
||||||
j.phase("Updating tags", int64(n))
|
j.phase("Updating tags", int64(n))
|
||||||
@@ -369,7 +369,7 @@ func (e *Engine) StartFixApply(pairID int64, req FixApply) (*Job, error) {
|
|||||||
// Done when every fix was applied and nothing needs a person.
|
// Done when every fix was applied and nothing needs a person.
|
||||||
if !dry && skipped == 0 && applied >= plan.Fixable && plan.Blocked == 0 {
|
if !dry && skipped == 0 && applied >= plan.Fixable && plan.Blocked == 0 {
|
||||||
if err := e.SetStep(ctx, pairID, side, step, "done", fmt.Sprintf("%d fixed", applied)); err != nil {
|
if err := e.SetStep(ctx, pairID, side, step, "done", fmt.Sprintf("%d fixed", applied)); err != nil {
|
||||||
e.log.Warn("fixer step not recorded", "err", err)
|
e.log.Warn("tune-up step not recorded", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ function Shell({ username }: { username: string }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let page;
|
let page;
|
||||||
if (route[0] === "pair" && route[1]) page = <PairPage id={Number(route[1])} tab={route[2] ?? "fixer"} />;
|
if (route[0] === "pair" && route[1]) page = <PairPage id={Number(route[1])} tab={!route[2] || route[2] === "fixer" ? "tune-up" : route[2]} />;
|
||||||
else if (route[0] === "activity") page = <ActivityPage />;
|
else if (route[0] === "activity") page = <ActivityPage />;
|
||||||
else if (route[0] === "settings") page = <SettingsPage />;
|
else if (route[0] === "settings") page = <SettingsPage />;
|
||||||
else page = <Dashboard />;
|
else page = <Dashboard />;
|
||||||
|
|||||||
+1
-1
@@ -107,7 +107,7 @@ export type PairInput = Pick<
|
|||||||
|
|
||||||
export type LibraryType = "music" | "soundtracks";
|
export type LibraryType = "music" | "soundtracks";
|
||||||
|
|
||||||
// ---- step-by-step fixer ----
|
// ---- tune-up (step-by-step library fixing) ----
|
||||||
|
|
||||||
export type FixStep = "structure" | "tags" | "folders" | "files";
|
export type FixStep = "structure" | "tags" | "folders" | "files";
|
||||||
export type StepStatus = { state: "" | "done" | "skipped"; at?: number; note?: string };
|
export type StepStatus = { state: "" | "done" | "skipped"; at?: number; note?: string };
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export function PairForm({ pair, open, onClose }: { pair?: Pair; open: boolean;
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
qc.invalidateQueries({ queryKey: ["pairs"] });
|
qc.invalidateQueries({ queryKey: ["pairs"] });
|
||||||
qc.invalidateQueries({ queryKey: ["pair"] });
|
qc.invalidateQueries({ queryKey: ["pair"] });
|
||||||
toast("ok", pair ? "Library pair updated" : "Library pair created. Start with the Fixer, master library first.");
|
toast("ok", pair ? "Library pair updated" : "Library pair created. Start with the Tune-up, master library first.");
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { OpsTable } from "./Activity";
|
|||||||
import { WarningList } from "./Warnings";
|
import { WarningList } from "./Warnings";
|
||||||
import { TagsTab } from "./Tags";
|
import { TagsTab } from "./Tags";
|
||||||
import { OrganizeTab } from "./Organize";
|
import { OrganizeTab } from "./Organize";
|
||||||
import { FixerTab } from "./Fixer";
|
import { TuneUpTab } from "./TuneUp";
|
||||||
|
|
||||||
export function usePair(id: number) {
|
export function usePair(id: number) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -125,7 +125,7 @@ function LastJob({ job }: { job: Job }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: "fixer", label: "Fixer" },
|
{ id: "tune-up", label: "Tune-up" },
|
||||||
{ id: "review", label: "Review" },
|
{ id: "review", label: "Review" },
|
||||||
{ id: "build", label: "Build" },
|
{ id: "build", label: "Build" },
|
||||||
{ id: "tags", label: "Tags" },
|
{ id: "tags", label: "Tags" },
|
||||||
@@ -208,7 +208,7 @@ export function PairPage({ id, tab }: { id: number; tab: string }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{tab === "fixer" && <FixerTab pair={pair} />}
|
{tab === "tune-up" && <TuneUpTab pair={pair} />}
|
||||||
{tab === "review" && <Review pair={pair} />}
|
{tab === "review" && <Review pair={pair} />}
|
||||||
{tab === "build" && <Build pair={pair} />}
|
{tab === "build" && <Build pair={pair} />}
|
||||||
{tab === "tags" && <TagsTab pair={pair} />}
|
{tab === "tags" && <TagsTab pair={pair} />}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { JobBar, ScanButton } from "./PairPage";
|
|||||||
import { WarningList } from "./Warnings";
|
import { WarningList } from "./Warnings";
|
||||||
import { AlbumEditor } from "./Album";
|
import { AlbumEditor } from "./Album";
|
||||||
|
|
||||||
// The fixer takes each library through the same steps, master first, then
|
// The tune-up takes each library through the same steps, master first, then
|
||||||
// links the two. Each step is checked, reviewed and applied here.
|
// links the two. Each step is checked, reviewed and applied here.
|
||||||
|
|
||||||
type Sel = { side: Side; step: FixStep } | "link";
|
type Sel = { side: Side; step: FixStep } | "link";
|
||||||
@@ -69,12 +69,12 @@ function firstOpen(pair: Pair): Sel {
|
|||||||
return "link";
|
return "link";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FixerTab({ pair }: { pair: Pair }) {
|
export function TuneUpTab({ pair }: { pair: Pair }) {
|
||||||
const info = useInfo();
|
const info = useInfo();
|
||||||
const [sel, setSel] = useState<Sel>(() => firstOpen(pair));
|
const [sel, setSel] = useState<Sel>(() => firstOpen(pair));
|
||||||
const fixer = useQuery({ queryKey: ["fixer", pair.id], queryFn: () => api.get<Fixer>(`/pairs/${pair.id}/fixer`) });
|
const fixer = useQuery({ queryKey: ["fixer", pair.id], queryFn: () => api.get<Fixer>(`/pairs/${pair.id}/fixer`) });
|
||||||
if (info.data && !info.data.tag_editing)
|
if (info.data && !info.data.tag_editing)
|
||||||
return <Alert tone="amber">The fixer reads and writes tags with ffmpeg and ffprobe, which were not found. The Docker image includes both.</Alert>;
|
return <Alert tone="amber">The tune-up reads and writes tags with ffmpeg and ffprobe, which were not found. The Docker image includes both.</Alert>;
|
||||||
const isSel = (s: Side, st: FixStep) => sel !== "link" && sel.side === s && sel.step === st;
|
const isSel = (s: Side, st: FixStep) => sel !== "link" && sel.side === s && sel.step === st;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -217,7 +217,7 @@ function StepPanel({ pair, side, step, fixer, onNext }: { pair: Pair; side: Side
|
|||||||
const status = pair.fixer[side][step];
|
const status = pair.fixer[side][step];
|
||||||
const plan = fixer?.plan && fixer.plan.side === side && fixer.plan.step === step ? fixer.plan : null;
|
const plan = fixer?.plan && fixer.plan.side === side && fixer.plan.step === step ? fixer.plan : null;
|
||||||
const running = pair.job?.state === "running";
|
const running = pair.job?.state === "running";
|
||||||
const ourJob = running && pair.job!.kind.startsWith("fix-");
|
const ourJob = running && pair.job!.kind.startsWith("tune-up");
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
qc.invalidateQueries({ queryKey: ["pair", pair.id] });
|
qc.invalidateQueries({ queryKey: ["pair", pair.id] });
|
||||||
qc.invalidateQueries({ queryKey: ["fixer", pair.id] });
|
qc.invalidateQueries({ queryKey: ["fixer", pair.id] });
|
||||||
Reference in new issue
Block a user