Migration 13 adds nullable mbid TEXT columns to artists, release_groups, and recordings with partial indexes. Metadata extraction (tags.go) now reads MusicBrainz IDs from Raw() tags — handles both Vorbis (musicbrainz_artistid) and ID3v2 (MusicBrainz Artist Id) key formats. Scan pipeline (library.go) updates MBIDs after entity upsert via raw SQL UPDATE. Only sets mbid if currently NULL (preserves existing values on rescan). LibraryMBIDIndex (librarymbid.go) provides: - CheckMBIDs: batch lookup for 'In Library' badges - GetArtistMBID: single artist name→MBID lookup - AllArtistMBIDs: full dump for search index Tier 3 MBIDs will be populated on next library rescan. Existing files need a rescan to backfill.
120 lines
2.6 KiB
Go
120 lines
2.6 KiB
Go
package explore
|
|
|
|
import (
|
|
"yellowjacket/backend/database"
|
|
)
|
|
|
|
// LibraryMBIDIndex provides fast MBID lookups against the local
|
|
// music library. Used for "In Library" badges on explore search
|
|
// results and for sharing artist images with local views.
|
|
type LibraryMBIDIndex struct {
|
|
db *database.DB
|
|
}
|
|
|
|
// NewLibraryMBIDIndex creates a library MBID lookup service.
|
|
func NewLibraryMBIDIndex(db *database.DB) *LibraryMBIDIndex {
|
|
return &LibraryMBIDIndex{db: db}
|
|
}
|
|
|
|
// CheckMBIDs returns which of the given MBIDs exist in the local
|
|
// library. The returned map has MBID → entity type ("artist",
|
|
// "release_group", or "recording").
|
|
func (idx *LibraryMBIDIndex) CheckMBIDs(mbids []string) map[string]string {
|
|
if len(mbids) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := make(map[string]string, len(mbids))
|
|
|
|
// Check each table. For a small number of MBIDs this is fine.
|
|
// For bulk checks we'd use a temp table join, but search results
|
|
// are capped at ~30 MBIDs total.
|
|
for _, mbid := range mbids {
|
|
if mbid == "" {
|
|
continue
|
|
}
|
|
|
|
// Check artists.
|
|
if idx.exists("artists", mbid) {
|
|
result[mbid] = "artist"
|
|
|
|
continue
|
|
}
|
|
|
|
// Check release groups.
|
|
if idx.exists("release_groups", mbid) {
|
|
result[mbid] = "release_group"
|
|
|
|
continue
|
|
}
|
|
|
|
// Check recordings.
|
|
if idx.exists("recordings", mbid) {
|
|
result[mbid] = "recording"
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GetArtistMBID returns the MBID for a local artist by name, or "".
|
|
func (idx *LibraryMBIDIndex) GetArtistMBID(artistName string) string {
|
|
rows, err := idx.db.QueryContext(
|
|
"SELECT mbid FROM artists WHERE name = ? AND mbid IS NOT NULL LIMIT 1",
|
|
artistName,
|
|
)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
defer func() { _ = rows.Close() }()
|
|
|
|
if rows.Next() {
|
|
var mbid string
|
|
if err := rows.Scan(&mbid); err == nil {
|
|
return mbid
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// AllArtistMBIDs returns all (name, mbid) pairs for artists that
|
|
// have MBIDs. Used by the search index Tier 3 for direct matching.
|
|
func (idx *LibraryMBIDIndex) AllArtistMBIDs() map[string]string {
|
|
rows, err := idx.db.QueryContext(
|
|
"SELECT name, mbid FROM artists WHERE mbid IS NOT NULL AND mbid != ''",
|
|
)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
defer func() { _ = rows.Close() }()
|
|
|
|
result := make(map[string]string)
|
|
|
|
for rows.Next() {
|
|
var name, mbid string
|
|
if err := rows.Scan(&name, &mbid); err == nil {
|
|
result[name] = mbid
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (idx *LibraryMBIDIndex) exists(table, mbid string) bool {
|
|
//nolint:gosec // table name is hardcoded from internal callers only
|
|
rows, err := idx.db.QueryContext(
|
|
"SELECT 1 FROM "+table+" WHERE mbid = ? LIMIT 1",
|
|
mbid,
|
|
)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
defer func() { _ = rows.Close() }()
|
|
|
|
return rows.Next()
|
|
}
|