package database import ( "database/sql" "errors" "git.noroutes.net/NoRoutes/vibetunes/internal/idgen" ) type CoverArt struct { ID string AlbumID sql.NullString MediaFileID sql.NullString Source string SourcePath string MimeType string ContentHash string Width int Height int } const coverArtSelect = `SELECT id, album_id, media_file_id, source, source_path, mime_type, content_hash, width, height FROM cover_art` func GetCoverArt(db *sql.DB, id string) (*CoverArt, error) { return scanCoverArt(db.QueryRow(coverArtSelect+` WHERE id = ?`, id)) } func GetCoverArtForAlbum(db *sql.DB, albumID string) (*CoverArt, error) { return scanCoverArt(db.QueryRow(coverArtSelect+` WHERE album_id = ? ORDER BY updated_at DESC LIMIT 1`, albumID)) } func scanCoverArt(row *sql.Row) (*CoverArt, error) { var c CoverArt err := row.Scan(&c.ID, &c.AlbumID, &c.MediaFileID, &c.Source, &c.SourcePath, &c.MimeType, &c.ContentHash, &c.Width, &c.Height) if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } if err != nil { return nil, err } return &c, nil } // UpsertAlbumCoverArt records (or replaces) the discovered cover art // source for an album and links it as the album's cover. mediaFileID is // set (with sourcePath empty) for embedded artwork, which is extracted // on demand from the media file rather than at scan time. func UpsertAlbumCoverArt(db *sql.DB, albumID, source, sourcePath, mediaFileID, mimeType, contentHash string, width, height int) (*CoverArt, error) { var id string err := db.QueryRow(`SELECT id FROM cover_art WHERE album_id = ?`, albumID).Scan(&id) switch { case err == nil: _, err = db.Exec(`UPDATE cover_art SET source=?, source_path=?, media_file_id=?, mime_type=?, content_hash=?, width=?, height=?, updated_at=strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE id=?`, source, sourcePath, nullEmptyStr(mediaFileID), mimeType, contentHash, width, height, id) if err != nil { return nil, err } case errors.Is(err, sql.ErrNoRows): id = idgen.New("ca") _, err = db.Exec(`INSERT INTO cover_art (id, album_id, media_file_id, source, source_path, mime_type, content_hash, width, height) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, id, albumID, nullEmptyStr(mediaFileID), source, sourcePath, mimeType, contentHash, width, height) if err != nil { return nil, err } default: return nil, err } if err := SetAlbumCoverArt(db, albumID, id); err != nil { return nil, err } return GetCoverArt(db, id) } func nullEmptyStr(s string) any { if s == "" { return nil } return s }