package metadata import ( "context" "fmt" "github.com/rs/zerolog/log" metadataPb "homelab.lan/music-agregator/gen/metadata/v1" "homelab.lan/music-agregator/internal/database" ) type MetadataService struct { client metadataPb.MetadataServiceClient artists *database.ArtistRepository albums *database.AlbumRepository tracks *database.TrackRepository } func NewMetadataService(client metadataPb.MetadataServiceClient, db *database.DB) *MetadataService { return &MetadataService{ client: client, artists: database.NewArtistRepository(db.Pool), albums: database.NewAlbumRepository(db.Pool), tracks: database.NewTrackRepository(db.Pool), } } func (s *MetadataService) GetAlbum(ctx context.Context, albumID string) (*metadataPb.Album, error) { resp, err := s.client.GetAlbum(ctx, &metadataPb.GetAlbumRequest{ Identifier: &metadataPb.GetAlbumRequest_Id{Id: albumID}, }) if err != nil { return nil, fmt.Errorf("fetching album: %w", err) } album := resp.GetAlbum() if _, err := s.albums.GetByExternalID(ctx, album.GetId()); err != nil { s.PersistArtist(ctx, album, database.Monitored) s.PersistAlbum(ctx, album, database.Monitored) } return album, nil } func (s *MetadataService) GetArtistAlbums(ctx context.Context, artistExternalID string) ([]*metadataPb.Album, error) { resp, err := s.client.GetArtistAlbums(ctx, &metadataPb.GetArtistAlbumsRequest{ ArtistId: artistExternalID, }) if err != nil { return nil, fmt.Errorf("fetching artist albums: %w", err) } return resp.GetAlbums(), nil } func (s *MetadataService) SearchArtists(ctx context.Context, query string, limit, offset int32) (*metadataPb.SearchArtistsResponse, error) { resp, err := s.client.SearchArtists(ctx, &metadataPb.SearchArtistsRequest{ Query: query, Limit: limit, Offset: offset, }) if err != nil { return nil, fmt.Errorf("searching artists: %w", err) } return resp, nil } func (s *MetadataService) GetArtistAlbumsWithPagination(ctx context.Context, artistID string, limit, offset int32) (*metadataPb.GetArtistAlbumsResponse, error) { resp, err := s.client.GetArtistAlbums(ctx, &metadataPb.GetArtistAlbumsRequest{ ArtistId: artistID, Limit: limit, Offset: offset, }) if err != nil { return nil, fmt.Errorf("fetching artist albums: %w", err) } return resp, nil } func (s *MetadataService) GetAlbumTracks(ctx context.Context, albumExternalID string) ([]*metadataPb.Track, error) { resp, err := s.client.GetAlbumTracks(ctx, &metadataPb.GetAlbumTracksRequest{ AlbumId: albumExternalID, }) if err != nil { return nil, fmt.Errorf("fetching album tracks: %w", err) } return resp.GetTracks(), nil } func (s *MetadataService) GetArtistByExternalID(ctx context.Context, externalID string) (*database.Artist, error) { return s.artists.GetByExternalID(ctx, externalID) } func (s *MetadataService) GetAlbumByID(ctx context.Context, id string) (*database.Album, error) { return s.albums.GetByID(ctx, id) } func (s *MetadataService) GetAlbumByExternalID(ctx context.Context, externalID string) (*database.Album, error) { return s.albums.GetByExternalID(ctx, externalID) } func (s *MetadataService) GetAlbumsByArtistID(ctx context.Context, artistID string) ([]*database.Album, error) { return s.albums.GetByArtistID(ctx, artistID) } func (s *MetadataService) GetTracksByAlbumID(ctx context.Context, albumID string) ([]*database.Track, error) { return s.tracks.GetByAlbumID(ctx, albumID) } func (s *MetadataService) SetAlbumMonitorState(ctx context.Context, id string, state database.MonitorState) error { return s.albums.SetMonitorState(ctx, id, state) } func (s *MetadataService) PersistArtist(ctx context.Context, album *metadataPb.Album, state database.MonitorState) { if len(album.GetArtists()) == 0 { return } artist := album.GetArtists()[0].GetArtist() var genres []string for _, g := range artist.GetGenres() { genres = append(genres, g.GetName()) } err := s.artists.Create(ctx, &database.Artist{ ExternalID: artist.GetId(), Name: artist.GetName(), ArtistType: artist.GetArtistType(), Country: artist.GetCountry(), Genres: genres, ImageURL: artist.GetImageUrl(), MonitorState: state, }) if err != nil { log.Warn().Err(err).Str("name", artist.GetName()).Msg("failed to persist artist") } } func (s *MetadataService) PersistAlbum(ctx context.Context, album *metadataPb.Album, state database.MonitorState) { s.PersistAlbumForArtist(ctx, album, "", state) } func (s *MetadataService) PersistAlbumsForArtist(ctx context.Context, metadataAlbums []*metadataPb.Album, artistDBID string, state database.MonitorState) { if len(metadataAlbums) == 0 || artistDBID == "" { return } dbAlbums := make([]*database.Album, 0, len(metadataAlbums)) for _, album := range metadataAlbums { var genres []string for _, g := range album.GetGenres() { genres = append(genres, g.GetName()) } labelName := "" if album.GetLabel() != nil { labelName = album.GetLabel().GetName() } dbAlbums = append(dbAlbums, &database.Album{ ExternalID: album.GetId(), ArtistID: artistDBID, Title: album.GetTitle(), AlbumType: album.GetAlbumType(), TotalTracks: int(album.GetTotalTracks()), TotalDiscs: int(album.GetTotalDiscs()), Label: labelName, Genres: genres, CoverURL: album.GetCoverUrl(), MonitorState: state, }) } if err := s.albums.CreateBatch(ctx, dbAlbums); err != nil { log.Warn().Err(err).Int("count", len(dbAlbums)).Msg("failed to batch persist albums") } } func (s *MetadataService) PersistAlbumForArtist(ctx context.Context, album *metadataPb.Album, artistDBID string, state database.MonitorState) { if artistDBID == "" { if len(album.GetArtists()) > 0 { a, err := s.artists.GetByExternalID(ctx, album.GetArtists()[0].GetArtist().GetId()) if err == nil { artistDBID = a.ID } } } if artistDBID == "" { log.Trace().Str("album", album.GetTitle()).Msg("skipping album persist, no artist in DB") return } var genres []string for _, g := range album.GetGenres() { genres = append(genres, g.GetName()) } labelName := "" if album.GetLabel() != nil { labelName = album.GetLabel().GetName() } err := s.albums.Create(ctx, &database.Album{ ExternalID: album.GetId(), ArtistID: artistDBID, Title: album.GetTitle(), AlbumType: album.GetAlbumType(), TotalTracks: int(album.GetTotalTracks()), TotalDiscs: int(album.GetTotalDiscs()), Label: labelName, Genres: genres, CoverURL: album.GetCoverUrl(), MonitorState: state, }) if err != nil { log.Warn().Err(err).Str("title", album.GetTitle()).Msg("failed to persist album") } } func (s *MetadataService) PersistTracks(ctx context.Context, albumDBID string, tracks []*metadataPb.Track) { for _, t := range tracks { err := s.tracks.Create(ctx, &database.Track{ ExternalID: t.GetId(), AlbumID: albumDBID, Title: t.GetTitle(), DurationMS: int(t.GetDurationMs()), ISRC: t.GetIsrc(), DiscNumber: int(t.GetDiscNumber()), TrackNumber: int(t.GetTrackNumber()), }) if err != nil { log.Warn().Err(err).Str("title", t.GetTitle()).Msg("failed to persist track") } } }