fix(scanner): prevent duplicate tracks when multiple missing files match same target (#5183)
In processMissingTracks, matched tracks were not removed from the candidate pool after being consumed by moveMatched. This allowed the same target track to be paired with multiple missing tracks, creating duplicate non-missing records with the same path. Track consumed matches in a usedMatched map so each target is used at most once. Fixes #5169
This commit is contained in:
@@ -114,6 +114,10 @@ func (p *phaseMissingTracks) stages() []ppl.Stage[*missingTracks] {
|
||||
|
||||
func (p *phaseMissingTracks) processMissingTracks(in *missingTracks) (*missingTracks, error) {
|
||||
hasMatches := false
|
||||
// Track which matched entries have already been consumed, so each matched track
|
||||
// is only used once. Without this, the same matched track could be paired with
|
||||
// multiple missing tracks, creating duplicate records with the same path.
|
||||
usedMatched := make(map[string]bool, len(in.matched))
|
||||
|
||||
for _, ms := range in.missing {
|
||||
var exactMatch model.MediaFile
|
||||
@@ -121,6 +125,9 @@ func (p *phaseMissingTracks) processMissingTracks(in *missingTracks) (*missingTr
|
||||
|
||||
// Identify exact and equivalent matches
|
||||
for _, mt := range in.matched {
|
||||
if usedMatched[mt.ID] {
|
||||
continue
|
||||
}
|
||||
if ms.Equals(mt) {
|
||||
exactMatch = mt
|
||||
break // Prioritize exact match
|
||||
@@ -138,13 +145,14 @@ func (p *phaseMissingTracks) processMissingTracks(in *missingTracks) (*missingTr
|
||||
log.Error(p.ctx, "Scanner: Error moving matched track", "missing", ms.Path, "movedTo", exactMatch.Path, "lib", in.lib.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
usedMatched[exactMatch.ID] = true
|
||||
p.totalMatched.Add(1)
|
||||
hasMatches = true
|
||||
continue
|
||||
}
|
||||
|
||||
// If there is only one missing and one matched track, consider them equivalent (same PID)
|
||||
if len(in.missing) == 1 && len(in.matched) == 1 {
|
||||
if len(in.missing) == 1 && len(in.matched) == 1 && !usedMatched[in.matched[0].ID] {
|
||||
singleMatch := in.matched[0]
|
||||
log.Debug(p.ctx, "Scanner: Found track with same persistent ID in a new place", "missing", ms.Path, "movedTo", singleMatch.Path, "lib", in.lib.Name)
|
||||
err := p.moveMatched(singleMatch, ms)
|
||||
@@ -152,6 +160,7 @@ func (p *phaseMissingTracks) processMissingTracks(in *missingTracks) (*missingTr
|
||||
log.Error(p.ctx, "Scanner: Error updating matched track", "missing", ms.Path, "movedTo", singleMatch.Path, "lib", in.lib.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
usedMatched[singleMatch.ID] = true
|
||||
p.totalMatched.Add(1)
|
||||
hasMatches = true
|
||||
continue
|
||||
@@ -165,6 +174,7 @@ func (p *phaseMissingTracks) processMissingTracks(in *missingTracks) (*missingTr
|
||||
log.Error(p.ctx, "Scanner: Error updating matched track", "missing", ms.Path, "movedTo", equivalentMatch.Path, "lib", in.lib.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
usedMatched[equivalentMatch.ID] = true
|
||||
p.totalMatched.Add(1)
|
||||
hasMatches = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user