149 lines
3.3 KiB
Go
149 lines
3.3 KiB
Go
package explore
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"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))
|
|
|
|
// Batch check all MBIDs against each table with a single IN query.
|
|
type tableEntity struct {
|
|
table string
|
|
entityType string
|
|
}
|
|
|
|
tables := []tableEntity{
|
|
{"artists", "artist"},
|
|
{"release_groups", "release_group"},
|
|
{"recordings", "recording"},
|
|
}
|
|
|
|
// Build a set of MBIDs still unresolved.
|
|
remaining := make(map[string]bool, len(mbids))
|
|
for _, m := range mbids {
|
|
if m != "" {
|
|
remaining[m] = true
|
|
}
|
|
}
|
|
|
|
for _, te := range tables {
|
|
if len(remaining) == 0 {
|
|
break
|
|
}
|
|
|
|
// Build IN clause from remaining MBIDs.
|
|
placeholders := make([]string, 0, len(remaining))
|
|
args := make([]any, 0, len(remaining))
|
|
|
|
for m := range remaining {
|
|
placeholders = append(placeholders, "?")
|
|
args = append(args, m)
|
|
}
|
|
|
|
//nolint:gosec // table name is hardcoded from the tables slice above
|
|
query := "SELECT mbid FROM " + te.table + " WHERE mbid IN (" +
|
|
strings.Join(placeholders, ",") + ")"
|
|
|
|
rows, err := idx.db.QueryContext(query, args...)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for rows.Next() {
|
|
var mbid string
|
|
if err := rows.Scan(&mbid); err == nil {
|
|
result[mbid] = te.entityType
|
|
delete(remaining, mbid)
|
|
}
|
|
}
|
|
|
|
_ = rows.Close()
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
//nolint:unused // utility kept for future per-MBID existence checks.
|
|
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()
|
|
}
|