Koito/internal/catalog/associate_track.go
Gabe Farrell d87ed2eb97
fix: ensure listen activity correctly sums listen activity in step (#139)
* remove impossible nil check

* fix listen activity not correctly aggregating step

* remove stray log

* fix test
2026-01-14 21:35:01 -05:00

128 lines
4.2 KiB
Go

package catalog
import (
"context"
"errors"
"fmt"
"github.com/gabehf/koito/internal/db"
"github.com/gabehf/koito/internal/logger"
"github.com/gabehf/koito/internal/mbz"
"github.com/gabehf/koito/internal/models"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
type AssociateTrackOpts struct {
ArtistIDs []int32
AlbumID int32
TrackMbzID uuid.UUID
TrackName string
Duration int32
Mbzc mbz.MusicBrainzCaller
}
func AssociateTrack(ctx context.Context, d db.DB, opts AssociateTrackOpts) (*models.Track, error) {
l := logger.FromContext(ctx)
if opts.TrackName == "" {
return nil, errors.New("AssociateTrack: missing required parameter 'opts.TrackName'")
}
if len(opts.ArtistIDs) < 1 {
return nil, errors.New("AssociateTrack: at least one artist id must be specified")
}
if opts.AlbumID == 0 {
return nil, errors.New("AssociateTrack: release group id must be specified")
}
// first, try to match track Mbz ID
if opts.TrackMbzID != uuid.Nil {
l.Debug().Msgf("Associating track '%s' by MusicBrainz recording ID", opts.TrackName)
return matchTrackByMbzID(ctx, d, opts)
} else {
l.Debug().Msgf("Associating track '%s' by title and artist", opts.TrackName)
return matchTrackByTrackInfo(ctx, d, opts)
}
}
// If no match is found, will call matchTrackByTitleAndArtist and associate the Mbz ID with the result
func matchTrackByMbzID(ctx context.Context, d db.DB, opts AssociateTrackOpts) (*models.Track, error) {
l := logger.FromContext(ctx)
track, err := d.GetTrack(ctx, db.GetTrackOpts{
MusicBrainzID: opts.TrackMbzID,
})
if err == nil {
l.Debug().Msgf("Found track '%s' by MusicBrainz ID", track.Title)
return track, nil
} else if !errors.Is(err, pgx.ErrNoRows) {
return nil, fmt.Errorf("matchTrackByMbzID: %w", err)
} else {
l.Debug().Msgf("Track '%s' could not be found by MusicBrainz ID", opts.TrackName)
track, err := matchTrackByTrackInfo(ctx, d, opts)
if err != nil {
return nil, fmt.Errorf("matchTrackByMbzID: %w", err)
}
l.Debug().Msgf("Updating track '%s' with MusicBrainz ID %s", opts.TrackName, opts.TrackMbzID)
if track.MbzID == nil || *track.MbzID == uuid.Nil {
err := d.UpdateTrack(ctx, db.UpdateTrackOpts{
ID: track.ID,
MusicBrainzID: opts.TrackMbzID,
})
if err != nil {
l.Err(err).Msg("matchArtistsByMBIDMappings: failed to update track with MusicBrainz ID")
return nil, fmt.Errorf("matchArtistsByMBIDMappings: %w", err)
}
l.Debug().Msgf("Updated track '%s' with MusicBrainz ID", track.Title)
} else {
l.Warn().Msgf("Attempted to update track %s with MusicBrainz ID, but an existing ID was already found", track.Title)
}
track.MbzID = &opts.TrackMbzID
return track, nil
}
}
func matchTrackByTrackInfo(ctx context.Context, d db.DB, opts AssociateTrackOpts) (*models.Track, error) {
l := logger.FromContext(ctx)
// try provided track title
track, err := d.GetTrack(ctx, db.GetTrackOpts{
Title: opts.TrackName,
ReleaseID: opts.AlbumID,
ArtistIDs: opts.ArtistIDs,
})
if err == nil {
l.Debug().Msgf("Track '%s' found by title, release and artist match", track.Title)
return track, nil
} else if !errors.Is(err, pgx.ErrNoRows) {
return nil, fmt.Errorf("matchTrackByTrackInfo: %w", err)
} else {
if opts.TrackMbzID != uuid.Nil {
mbzTrack, err := opts.Mbzc.GetTrack(ctx, opts.TrackMbzID)
if err == nil {
track, err := d.GetTrack(ctx, db.GetTrackOpts{
Title: mbzTrack.Title,
ReleaseID: opts.AlbumID,
ArtistIDs: opts.ArtistIDs,
})
if err == nil {
l.Debug().Msgf("Track '%s' found by MusicBrainz title, release and artist match", opts.TrackName)
return track, nil
}
}
}
l.Debug().Msgf("Track '%s' could not be found by title and artist match", opts.TrackName)
t, err := d.SaveTrack(ctx, db.SaveTrackOpts{
RecordingMbzID: opts.TrackMbzID,
AlbumID: opts.AlbumID,
Title: opts.TrackName,
ArtistIDs: opts.ArtistIDs,
Duration: opts.Duration,
})
if err != nil {
return nil, fmt.Errorf("matchTrackByTrackInfo: %w", err)
}
if opts.TrackMbzID == uuid.Nil {
l.Info().Msgf("Created track '%s' with title and artist", opts.TrackName)
} else {
l.Info().Msgf("Created track '%s' with MusicBrainz Recording ID", opts.TrackName)
}
return t, nil
}
}