feat: duplicates resolve themselves without Lidarr (M502 #5517)
release / govulncheck (push) Successful in 37s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m46s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 6m48s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 37s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m46s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 6m48s
release / Verify release artifacts (tag releases only) (push) Skipped
Lidarr is off, and with it the resolver had stopped merging: it only merged when Lidarr had said which copies it mapped. Minstrel is now the only program writing the library, so the rule is about the copies: - A song held twice on one album: the extra copies merge into the one whose tags fit best (then lossless, larger, older). - Across releases or under another title: when every copy but one is a video rip (held back, or named like one and not marked fine), the rips merge into the one that is not. - Clean copies on different releases are linked as one song, as before. - Anything else waits for the operator. Gone with Lidarr: release switches and their settle window, the unmapped-file read, the rip search, the merge guard and its 409/503, lidarr.Client's release methods, and duplicate_group_members.lidarr_state (migration 0078). The duplicates page drops the per-copy Lidarr badge and the blocked-merge notice; past release changes stay in its history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,6 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/audit"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/lidarr"
|
||||
syncpkg "git.fabledsword.com/bvandeusen/minstrel/internal/sync"
|
||||
)
|
||||
|
||||
@@ -41,22 +40,6 @@ import (
|
||||
// same pointer.
|
||||
var ErrNotFound = apierror.ErrNotFound
|
||||
|
||||
// ErrLidarrDisabled is what a LidarrUnmonitorer that also lists Lidarr's
|
||||
// unmapped files returns when Lidarr is turned off: there is then no Lidarr
|
||||
// record a removal could break, and a merge goes ahead unguarded.
|
||||
var ErrLidarrDisabled = errors.New("tracks: lidarr disabled")
|
||||
|
||||
// ErrLidarrUnavailable means Lidarr is on but did not answer, so a merge cannot
|
||||
// confirm the copies it would remove are ones Lidarr does not need (M498), and
|
||||
// refuses rather than guess.
|
||||
var ErrLidarrUnavailable = errors.New("tracks: lidarr did not answer")
|
||||
|
||||
// lidarrFileLister is the optional part of the Lidarr dependency a merge uses to
|
||||
// learn which files Lidarr holds without mapping to any track.
|
||||
type lidarrFileLister interface {
|
||||
ListUnmappedTrackFiles(ctx context.Context) ([]lidarr.TrackFile, error)
|
||||
}
|
||||
|
||||
// LidarrUnmonitorer is the subset of *lidarr.Client RemoveTrack uses.
|
||||
// Defined as an interface so tests can stub without spinning up a Lidarr
|
||||
// httptest server. UnmonitorTrack failures are non-fatal at the service
|
||||
@@ -168,9 +151,7 @@ func (s *Service) RemoveTrack(
|
||||
}
|
||||
|
||||
// MergeDuplicates merges a duplicate group into the copy to keep
|
||||
// (library.MergeDuplicateGroupGuarded, refusing to remove a copy Lidarr maps:
|
||||
// library.ErrCopyTrackedByLidarr, or ErrLidarrUnavailable when Lidarr cannot
|
||||
// say), then — when asked — tells Lidarr to stop
|
||||
// (library.MergeDuplicateGroup), then — when asked — tells Lidarr to stop
|
||||
// monitoring the removed copies so it does not download them again, and records
|
||||
// the merge in the audit log.
|
||||
//
|
||||
@@ -180,11 +161,7 @@ func (s *Service) RemoveTrack(
|
||||
func (s *Service) MergeDuplicates(
|
||||
ctx context.Context, groupID, survivorID, actorID pgtype.UUID, unmonitor bool,
|
||||
) (res library.MergeResult, lidarrUnmonitorFailed bool, err error) {
|
||||
removable, err := s.lidarrRemovable(ctx)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
res, err = library.MergeDuplicateGroupGuarded(ctx, s.pool, s.logger, s.dataDir, groupID, survivorID, removable)
|
||||
res, err = library.MergeDuplicateGroup(ctx, s.pool, s.logger, s.dataDir, groupID, survivorID)
|
||||
if err != nil {
|
||||
return res, false, err
|
||||
}
|
||||
@@ -226,29 +203,6 @@ func (s *Service) MergeDuplicates(
|
||||
return res, lidarrUnmonitorFailed, nil
|
||||
}
|
||||
|
||||
// lidarrRemovable is the merge's guard (M498): only a copy Lidarr holds without
|
||||
// mapping it to a track may be removed, because removing a mapped one makes
|
||||
// Lidarr download it again. Nil, allowing everything, when Lidarr is disabled
|
||||
// or the dependency cannot list files.
|
||||
func (s *Service) lidarrRemovable(ctx context.Context) (library.RemovableFunc, error) {
|
||||
lister, ok := s.lidarr.(lidarrFileLister)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
files, err := lister.ListUnmappedTrackFiles(ctx)
|
||||
if errors.Is(err, ErrLidarrDisabled) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrLidarrUnavailable, err)
|
||||
}
|
||||
unmapped := make(map[string]bool, len(files))
|
||||
for _, f := range files {
|
||||
unmapped[library.LidarrPathKey(f.Path)] = true
|
||||
}
|
||||
return func(p string) bool { return unmapped[library.LidarrPathKey(p)] }, nil
|
||||
}
|
||||
|
||||
// sameLidarrTrack reports whether two copies are the same Lidarr track: one
|
||||
// recording on one album. Lidarr monitors per album track, so when the removed
|
||||
// copy is a second file of the kept copy's own album track — the #3885 case —
|
||||
|
||||
Reference in New Issue
Block a user