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>
118 lines
4.0 KiB
Go
118 lines
4.0 KiB
Go
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
|
|
}
|