Rename host library to master throughout
The two libraries are now "master" and "slave" everywhere: UI text, API JSON fields (master_root, rel_master, master_size, ...), Go identifiers, database columns, quality verdicts, quarantine subfolders and docs. Existing databases are upgraded by a new migration that renames the columns in place and rewrites stored "host" values, so pairs, history, cached verdicts and restorable quarantine entries carry over. Network "host" usages (SplitHostPort, the Origin/Host check) are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d06b6d546d
commit
5b088bb4a6
19 files changed
+339
-326
No files matched your search
+45
-45
@@ -97,17 +97,17 @@ func firstComponent(rel string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// hostIndex supports the fallback lookups for slave files without an exact
|
||||
// masterIndex supports the fallback lookups for slave files without an exact
|
||||
// path counterpart.
|
||||
type hostIndex struct {
|
||||
type masterIndex struct {
|
||||
files map[string]fsops.Info
|
||||
lower map[string]string // lower(rel) -> rel
|
||||
byDir map[string][]string // lower(dir) -> rels
|
||||
byTitle map[string][]string // lower(top folder)|normTitle -> rels
|
||||
}
|
||||
|
||||
func buildIndex(files map[string]fsops.Info) *hostIndex {
|
||||
ix := &hostIndex{files: files, lower: map[string]string{}, byDir: map[string][]string{}, byTitle: map[string][]string{}}
|
||||
func buildIndex(files map[string]fsops.Info) *masterIndex {
|
||||
ix := &masterIndex{files: files, lower: map[string]string{}, byDir: map[string][]string{}, byTitle: map[string][]string{}}
|
||||
for _, rel := range sortedKeys(files) {
|
||||
ix.lower[strings.ToLower(rel)] = rel
|
||||
d := strings.ToLower(filepath.Dir(rel))
|
||||
@@ -118,7 +118,7 @@ func buildIndex(files map[string]fsops.Info) *hostIndex {
|
||||
return ix
|
||||
}
|
||||
|
||||
// losslessRank orders extension candidates so a lossless host file is
|
||||
// losslessRank orders extension candidates so a lossless master file is
|
||||
// preferred when several share the same title.
|
||||
func extRank(rel string) int {
|
||||
switch strings.ToLower(filepath.Ext(rel)) {
|
||||
@@ -130,8 +130,8 @@ func extRank(rel string) int {
|
||||
return 2
|
||||
}
|
||||
|
||||
// find locates the host counterpart of a slave file.
|
||||
func (ix *hostIndex) find(relSlave string) (string, string) {
|
||||
// find locates the master counterpart of a slave file.
|
||||
func (ix *masterIndex) find(relSlave string) (string, string) {
|
||||
if _, ok := ix.files[relSlave]; ok {
|
||||
return relSlave, store.KindPath
|
||||
}
|
||||
@@ -173,40 +173,40 @@ func (ix *hostIndex) find(relSlave string) (string, string) {
|
||||
|
||||
// classify fills in status, probes and verdict for a match, reusing cached
|
||||
// results from prev when neither file changed.
|
||||
func (e *Engine) classify(ctx context.Context, l *libs, m *store.Match, hi, si fsops.Info, prev *store.Match) {
|
||||
func (e *Engine) classify(ctx context.Context, l *libs, m *store.Match, mi, si fsops.Info, prev *store.Match) {
|
||||
m.SlaveIno, m.SlaveSize, m.SlaveMtime = si.Ino, si.Size, si.MtimeNs
|
||||
m.HostIno, m.HostSize, m.HostMtime = 0, 0, 0
|
||||
m.HostProbe, m.SlaveProbe, m.Verdict, m.Note = nil, nil, nil, ""
|
||||
m.MasterIno, m.MasterSize, m.MasterMtime = 0, 0, 0
|
||||
m.MasterProbe, m.SlaveProbe, m.Verdict, m.Note = nil, nil, nil, ""
|
||||
if m.Kind == store.KindNone {
|
||||
m.Status = store.StatusUnmatched
|
||||
return
|
||||
}
|
||||
m.HostIno, m.HostSize, m.HostMtime = hi.Ino, hi.Size, hi.MtimeNs
|
||||
m.MasterIno, m.MasterSize, m.MasterMtime = mi.Ino, mi.Size, mi.MtimeNs
|
||||
|
||||
unchanged := prev != nil && prev.RelHost == m.RelHost &&
|
||||
prev.HostIno == hi.Ino && prev.HostSize == hi.Size && prev.HostMtime == hi.MtimeNs &&
|
||||
unchanged := prev != nil && prev.RelMaster == m.RelMaster &&
|
||||
prev.MasterIno == mi.Ino && prev.MasterSize == mi.Size && prev.MasterMtime == mi.MtimeNs &&
|
||||
prev.SlaveIno == si.Ino && prev.SlaveSize == si.Size && prev.SlaveMtime == si.MtimeNs
|
||||
if prev != nil && prev.RelHost == m.RelHost {
|
||||
if prev != nil && prev.RelMaster == m.RelMaster {
|
||||
m.Ignored = prev.Ignored
|
||||
}
|
||||
|
||||
switch {
|
||||
case hi.Dev != si.Dev:
|
||||
case mi.Dev != si.Dev:
|
||||
m.Status, m.Note = store.StatusError, "files are on different filesystems"
|
||||
return
|
||||
case hi.SameInode(si):
|
||||
case mi.SameInode(si):
|
||||
m.Status = store.StatusLinked
|
||||
return
|
||||
}
|
||||
|
||||
pathKind := m.Kind == store.KindPath || m.Kind == store.KindPathCase
|
||||
if pathKind && hi.Size == si.Size {
|
||||
if pathKind && mi.Size == si.Size {
|
||||
if unchanged && (prev.Status == store.StatusIdentical || prev.Status == store.StatusDifferent) {
|
||||
m.Status = prev.Status
|
||||
} else {
|
||||
hostAbs := filepath.Join(l.host.Path, m.RelHost)
|
||||
masterAbs := filepath.Join(l.master.Path, m.RelMaster)
|
||||
slaveAbs := filepath.Join(l.slave.Path, m.RelSlave)
|
||||
same, err := fsops.SameContent(hostAbs, slaveAbs)
|
||||
same, err := fsops.SameContent(masterAbs, slaveAbs)
|
||||
switch {
|
||||
case err != nil:
|
||||
m.Status, m.Note = store.StatusError, "compare failed: "+err.Error()
|
||||
@@ -227,14 +227,14 @@ func (e *Engine) classify(ctx context.Context, l *libs, m *store.Match, hi, si f
|
||||
}
|
||||
|
||||
// Manual review: gather codec details for both sides.
|
||||
if unchanged && len(prev.HostProbe) > 0 && len(prev.SlaveProbe) > 0 {
|
||||
m.HostProbe, m.SlaveProbe, m.Verdict = prev.HostProbe, prev.SlaveProbe, prev.Verdict
|
||||
if unchanged && len(prev.MasterProbe) > 0 && len(prev.SlaveProbe) > 0 {
|
||||
m.MasterProbe, m.SlaveProbe, m.Verdict = prev.MasterProbe, prev.SlaveProbe, prev.Verdict
|
||||
return
|
||||
}
|
||||
hp := e.prober.Probe(ctx, filepath.Join(l.host.Path, m.RelHost))
|
||||
mp := e.prober.Probe(ctx, filepath.Join(l.master.Path, m.RelMaster))
|
||||
sp := e.prober.Probe(ctx, filepath.Join(l.slave.Path, m.RelSlave))
|
||||
v := probe.Compare(hp, sp)
|
||||
m.HostProbe, _ = json.Marshal(hp)
|
||||
v := probe.Compare(mp, sp)
|
||||
m.MasterProbe, _ = json.Marshal(mp)
|
||||
m.SlaveProbe, _ = json.Marshal(sp)
|
||||
m.Verdict, _ = json.Marshal(v)
|
||||
}
|
||||
@@ -255,9 +255,9 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
|
||||
}
|
||||
warns := layoutWarnings(l)
|
||||
|
||||
j.phase("Indexing host library", 0)
|
||||
hostFiles := map[string]fsops.Info{}
|
||||
hs, err := walk(ctx, l, l.host, hostFiles)
|
||||
j.phase("Indexing master library", 0)
|
||||
masterFiles := map[string]fsops.Info{}
|
||||
ms, err := walk(ctx, l, l.master, masterFiles)
|
||||
if err != nil {
|
||||
_ = e.st.FinishScan(ctx, pairID, started.Unix(), err.Error(), warns)
|
||||
return err
|
||||
@@ -269,7 +269,7 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
|
||||
_ = e.st.FinishScan(ctx, pairID, started.Unix(), err.Error(), warns)
|
||||
return err
|
||||
}
|
||||
ix := buildIndex(hostFiles)
|
||||
ix := buildIndex(masterFiles)
|
||||
prev, err := e.st.AllMatches(ctx, pairID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -284,12 +284,12 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
|
||||
}
|
||||
si := slaveFiles[rel]
|
||||
m := store.Match{PairID: pairID, RelSlave: rel}
|
||||
m.RelHost, m.Kind = ix.find(rel)
|
||||
m.RelMaster, m.Kind = ix.find(rel)
|
||||
var pm *store.Match
|
||||
if p, ok := prev[rel]; ok {
|
||||
pm = &p
|
||||
}
|
||||
e.classify(ctx, l, &m, hostFiles[m.RelHost], si, pm)
|
||||
e.classify(ctx, l, &m, masterFiles[m.RelMaster], si, pm)
|
||||
if err := e.st.UpsertMatch(ctx, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -305,12 +305,12 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
|
||||
// Layout warnings based on what was actually found.
|
||||
if ss.files > 0 {
|
||||
if n := kinds[store.KindElsewhere] + kinds[store.KindNone]; n*4 > ss.files {
|
||||
warns = append(warns, fmt.Sprintf("%d of %d slave files have no counterpart at the same path in the host library. "+
|
||||
warns = append(warns, fmt.Sprintf("%d of %d slave files have no counterpart at the same path in the master library. "+
|
||||
"Check that both libraries use the same folder layout.", n, ss.files))
|
||||
}
|
||||
}
|
||||
if hs.symlinks+ss.symlinks > 0 {
|
||||
warns = append(warns, fmt.Sprintf("Ignored %d symlinks (playlink never follows or replaces symlinks).", hs.symlinks+ss.symlinks))
|
||||
if ms.symlinks+ss.symlinks > 0 {
|
||||
warns = append(warns, fmt.Sprintf("Ignored %d symlinks (playlink never follows or replaces symlinks).", ms.symlinks+ss.symlinks))
|
||||
}
|
||||
if e.prober.Bin == "" {
|
||||
warns = append(warns, "ffprobe was not found, so codec and quality details are unavailable for manual review.")
|
||||
@@ -319,7 +319,7 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
|
||||
return err
|
||||
}
|
||||
j.update(func(j *Job) {
|
||||
j.Message = fmt.Sprintf("Scanned %d host and %d slave files in %s", hs.files, ss.files, time.Since(started).Round(time.Second))
|
||||
j.Message = fmt.Sprintf("Scanned %d master and %d slave files in %s", ms.files, ss.files, time.Since(started).Round(time.Second))
|
||||
})
|
||||
|
||||
if l.pair.AutoLinkIdentical {
|
||||
@@ -367,7 +367,7 @@ func (e *Engine) linkIDs(ctx context.Context, j *Job, pairID int64, ids []int64,
|
||||
j.tick()
|
||||
continue
|
||||
}
|
||||
if settle > 0 && (m.SlaveMtime > cutoff || m.HostMtime > cutoff) {
|
||||
if settle > 0 && (m.SlaveMtime > cutoff || m.MasterMtime > cutoff) {
|
||||
j.skip(m.RelSlave + ": modified recently, waiting for it to settle")
|
||||
j.tick()
|
||||
continue
|
||||
@@ -415,24 +415,24 @@ func (e *Engine) refresh(ctx context.Context, l *libs, relSlave string) {
|
||||
if p, err := e.st.MatchBySlave(ctx, l.pair.ID, relSlave); err == nil {
|
||||
pm = &p
|
||||
}
|
||||
var hi fsops.Info
|
||||
if hp, err := l.host.Resolve(relSlave); err == nil {
|
||||
if in, err := fsops.RegularFile(hp); err == nil {
|
||||
hi = in
|
||||
m.RelHost, m.Kind = relSlave, store.KindPath
|
||||
var mi fsops.Info
|
||||
if mp, err := l.master.Resolve(relSlave); err == nil {
|
||||
if in, err := fsops.RegularFile(mp); err == nil {
|
||||
mi = in
|
||||
m.RelMaster, m.Kind = relSlave, store.KindPath
|
||||
}
|
||||
}
|
||||
if m.Kind == "" {
|
||||
if pm != nil && pm.RelHost != "" {
|
||||
if in, err := fsops.RegularFile(filepath.Join(l.host.Path, pm.RelHost)); err == nil {
|
||||
hi = in
|
||||
m.RelHost, m.Kind = pm.RelHost, pm.Kind
|
||||
if pm != nil && pm.RelMaster != "" {
|
||||
if in, err := fsops.RegularFile(filepath.Join(l.master.Path, pm.RelMaster)); err == nil {
|
||||
mi = in
|
||||
m.RelMaster, m.Kind = pm.RelMaster, pm.Kind
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.Kind == "" {
|
||||
m.Kind = store.KindNone
|
||||
}
|
||||
e.classify(ctx, l, &m, hi, si, pm)
|
||||
e.classify(ctx, l, &m, mi, si, pm)
|
||||
_ = e.st.UpsertMatch(ctx, &m)
|
||||
}
|
||||
Reference in new issue
Block a user