feat: the library's albums go on the wanted list as held, once; after that the list is the truth (M502 #5567)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m31s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m24s
release / go (push) Successful in 1m44s
release / integration (push) Successful in 5m19s
release / android (push) Successful in 5m31s
release / Build signed APK (releases and dev) (push) Successful in 5m35s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
A one-time job in the wanted worker lists every album in the library as a held want, one per release group, with a record that it happened (acquisition_settings.library_listed_at). Albums whose tags name only a release are named on MusicBrainz first, 120 a pass; what nothing names is counted and shown on the Held tab. Re-acquisition now wants back only albums on the list. Taking a held album off the list leaves its files, and it is never fetched back; albums put in the library folder by hand afterwards are not added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package wanted
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/tags"
|
||||
)
|
||||
|
||||
// ReasonLibrary: on the list because the library held it when the list
|
||||
// began (#5567).
|
||||
const ReasonLibrary = "library"
|
||||
|
||||
// libraryNamesPerPass bounds the MusicBrainz lookups one pass makes for
|
||||
// albums whose tags named a release but not its group: about two minutes at
|
||||
// MusicBrainz's one request a second, so the pass still settles its wants.
|
||||
const libraryNamesPerPass = 120
|
||||
|
||||
// listLibrary puts the library's albums on the wanted list as held, once
|
||||
// (operator, 2026-10-10: "make a record saying that the import has
|
||||
// happened so later changes like unwanting hold and dont auto import from
|
||||
// the file directory"). It runs a step each pass until done:
|
||||
//
|
||||
// - the first step stamps when the listing began, which is its cutoff:
|
||||
// an album the library gains afterwards, dropped in by hand, is never
|
||||
// listed;
|
||||
// - every album whose release group is known goes on the list at once;
|
||||
// - albums that name a release but not its group are looked up on
|
||||
// MusicBrainz, a bounded number a pass, and listed as each is named;
|
||||
// - once none is left to name, the listing is stamped done, with a count
|
||||
// of the albums nothing names to MusicBrainz.
|
||||
//
|
||||
// After that it never runs again, so an album taken off the list stays off.
|
||||
func (s *Service) listLibrary(ctx context.Context, res *PassResult) error {
|
||||
q := dbq.New(s.pool)
|
||||
state, err := q.GetLibraryListing(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wanted: read the library listing: %w", err)
|
||||
}
|
||||
if state.LibraryListedAt.Valid {
|
||||
return nil
|
||||
}
|
||||
cutoff := state.LibraryListingStartedAt
|
||||
if !cutoff.Valid {
|
||||
if cutoff, err = q.StartLibraryListing(ctx); err != nil {
|
||||
return fmt.Errorf("wanted: start the library listing: %w", err)
|
||||
}
|
||||
n, err := q.ListLibraryAlbums(ctx, dbq.ListLibraryAlbumsParams{Cutoff: cutoff})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wanted: list the library: %w", err)
|
||||
}
|
||||
res.Listed += int(n)
|
||||
s.logger.Info("wanted: listing the library's albums as held", "listed", n)
|
||||
}
|
||||
|
||||
for {
|
||||
albums, err := q.ListLibraryAlbumsToName(ctx, dbq.ListLibraryAlbumsToNameParams{Cutoff: cutoff, MaxRows: libraryNamesPerPass})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wanted: albums to name: %w", err)
|
||||
}
|
||||
if len(albums) == 0 {
|
||||
break
|
||||
}
|
||||
for _, a := range albums {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
group, err := s.releaseGroup(ctx, deref(a.Mbid))
|
||||
switch {
|
||||
case errors.Is(err, tags.ErrSwitchedOff):
|
||||
// Nothing can be named while MusicBrainz is off: the rest are
|
||||
// counted as unlisted rather than left waiting for ever.
|
||||
return s.finishLibrary(ctx, q, res)
|
||||
case errors.Is(err, tags.ErrNotFound):
|
||||
group = "" // MusicBrainz has none: asked once
|
||||
case err != nil:
|
||||
// Unreachable or refusing for now: the next pass goes on.
|
||||
s.logger.Info("wanted: naming the library's albums paused", "err", err)
|
||||
return nil
|
||||
}
|
||||
if err := q.SetAlbumReleaseGroup(ctx, dbq.SetAlbumReleaseGroupParams{ID: a.ID, ReleaseGroupMbid: &group}); err != nil {
|
||||
return fmt.Errorf("wanted: name album %s: %w", a.Title, err)
|
||||
}
|
||||
if group == "" {
|
||||
continue
|
||||
}
|
||||
n, err := q.ListLibraryAlbums(ctx, dbq.ListLibraryAlbumsParams{Cutoff: cutoff, AlbumID: a.ID})
|
||||
if err != nil {
|
||||
return fmt.Errorf("wanted: list album %s: %w", a.Title, err)
|
||||
}
|
||||
res.Listed += int(n)
|
||||
}
|
||||
if len(albums) == libraryNamesPerPass {
|
||||
return nil // the rest next pass
|
||||
}
|
||||
}
|
||||
return s.finishLibrary(ctx, q, res)
|
||||
}
|
||||
|
||||
func (s *Service) finishLibrary(ctx context.Context, q *dbq.Queries, res *PassResult) error {
|
||||
done, err := q.FinishLibraryListing(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wanted: finish the library listing: %w", err)
|
||||
}
|
||||
s.logger.Info("wanted: the library's albums are on the list", "listed_this_pass", res.Listed,
|
||||
"unlisted", done.LibraryUnlisted)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deref(p *string) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
return *p
|
||||
}
|
||||
Reference in New Issue
Block a user