store artist album appearances
This commit is contained in:
25
db/db.go
25
db/db.go
@@ -175,15 +175,17 @@ func (db *DB) SetSetting(key SettingKey, value string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Artist struct {
|
type Artist struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
Name string `gorm:"not null; unique_index"`
|
Name string `gorm:"not null; unique_index"`
|
||||||
NameUDec string `sql:"default: null"`
|
NameUDec string `sql:"default: null"`
|
||||||
Albums []*Album `gorm:"many2many:album_artists"`
|
Albums []*Album `gorm:"many2many:album_artists"`
|
||||||
AlbumCount int `sql:"-"`
|
AlbumCount int `sql:"-"`
|
||||||
ArtistStar *ArtistStar
|
Appearances []*Album `gorm:"many2many:artist_appearances"`
|
||||||
ArtistRating *ArtistRating
|
AppearanceCount int `sql:"-"`
|
||||||
AverageRating float64 `sql:"default: null"`
|
ArtistStar *ArtistStar
|
||||||
Info *ArtistInfo `gorm:"foreignkey:id"`
|
ArtistRating *ArtistRating
|
||||||
|
AverageRating float64 `sql:"default: null"`
|
||||||
|
Info *ArtistInfo `gorm:"foreignkey:id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Artist) SID() *specid.ID {
|
func (a *Artist) SID() *specid.ID {
|
||||||
@@ -395,6 +397,11 @@ type TrackArtist struct {
|
|||||||
ArtistID int `gorm:"not null; unique_index:idx_track_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
ArtistID int `gorm:"not null; unique_index:idx_track_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ArtistAppearances struct {
|
||||||
|
ArtistID int `gorm:"not null; unique_index:idx_artist_id_album_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
|
||||||
|
AlbumID int `gorm:"not null; unique_index:idx_artist_id_album_id" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
|
}
|
||||||
|
|
||||||
type TrackGenre struct {
|
type TrackGenre struct {
|
||||||
TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`
|
TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`
|
||||||
GenreID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
|
GenreID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES genres(id) ON DELETE CASCADE"`
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
|
|||||||
construct(ctx, "202309161411", migratePlaylistsPaths),
|
construct(ctx, "202309161411", migratePlaylistsPaths),
|
||||||
construct(ctx, "202310252205", migrateAlbumTagArtistString),
|
construct(ctx, "202310252205", migrateAlbumTagArtistString),
|
||||||
construct(ctx, "202310281803", migrateTrackArtists),
|
construct(ctx, "202310281803", migrateTrackArtists),
|
||||||
|
construct(ctx, "202311062259", migrateArtistAppearances),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gormigrate.
|
return gormigrate.
|
||||||
@@ -744,3 +745,37 @@ func migrateTrackArtists(tx *gorm.DB, _ MigrationContext) error {
|
|||||||
}
|
}
|
||||||
return tx.AutoMigrate(TrackArtist{}).Error
|
return tx.AutoMigrate(TrackArtist{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateArtistAppearances(tx *gorm.DB, _ MigrationContext) error {
|
||||||
|
// gorms seems to want to create the table automatically without ON DELETE rules
|
||||||
|
step := tx.DropTableIfExists(ArtistAppearances{})
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step drop prev: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
step = tx.AutoMigrate(ArtistAppearances{})
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step auto migrate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
step = tx.Exec(`
|
||||||
|
INSERT INTO artist_appearances (artist_id, album_id)
|
||||||
|
SELECT artist_id, album_id
|
||||||
|
FROM album_artists
|
||||||
|
`)
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step transfer album artists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
step = tx.Exec(`
|
||||||
|
INSERT OR IGNORE INTO artist_appearances (artist_id, album_id)
|
||||||
|
SELECT track_artists.artist_id, tracks.album_id
|
||||||
|
FROM track_artists
|
||||||
|
JOIN tracks ON tracks.id=track_artists.track_id
|
||||||
|
`)
|
||||||
|
if err := step.Error; err != nil {
|
||||||
|
return fmt.Errorf("step transfer album artists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -329,6 +329,10 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, c *Context, i int, album *d
|
|||||||
|
|
||||||
// metadata for the album table comes only from the first track's tags
|
// metadata for the album table comes only from the first track's tags
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
if err := tx.Where("album_id=?", album.ID).Delete(db.ArtistAppearances{}).Error; err != nil {
|
||||||
|
return fmt.Errorf("delete artist appearances: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
albumArtistNames := parseMulti(trags, s.multiValueSettings[AlbumArtist], tagcommon.MustAlbumArtists, tagcommon.MustAlbumArtist)
|
albumArtistNames := parseMulti(trags, s.multiValueSettings[AlbumArtist], tagcommon.MustAlbumArtists, tagcommon.MustAlbumArtist)
|
||||||
var albumArtistIDs []int
|
var albumArtistIDs []int
|
||||||
for _, albumArtistName := range albumArtistNames {
|
for _, albumArtistName := range albumArtistNames {
|
||||||
@@ -342,6 +346,10 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, c *Context, i int, album *d
|
|||||||
return fmt.Errorf("populate album artists: %w", err)
|
return fmt.Errorf("populate album artists: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := populateArtistAppearances(tx, album, albumArtistIDs); err != nil {
|
||||||
|
return fmt.Errorf("populate track artists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := populateAlbum(tx, album, trags, stat.ModTime()); err != nil {
|
if err := populateAlbum(tx, album, trags, stat.ModTime()); err != nil {
|
||||||
return fmt.Errorf("populate album: %w", err)
|
return fmt.Errorf("populate album: %w", err)
|
||||||
}
|
}
|
||||||
@@ -371,6 +379,10 @@ func (s *Scanner) populateTrackAndArtists(tx *db.DB, c *Context, i int, album *d
|
|||||||
return fmt.Errorf("populate track artists: %w", err)
|
return fmt.Errorf("populate track artists: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := populateArtistAppearances(tx, album, trackArtistIDs); err != nil {
|
||||||
|
return fmt.Errorf("populate track artists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
c.seenTracks[track.ID] = struct{}{}
|
c.seenTracks[track.ID] = struct{}{}
|
||||||
c.seenTracksNew++
|
c.seenTracksNew++
|
||||||
|
|
||||||
@@ -518,6 +530,13 @@ func populateTrackArtists(tx *db.DB, track *db.Track, trackArtistIDs []int) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func populateArtistAppearances(tx *db.DB, album *db.Album, artistIDs []int) error {
|
||||||
|
if err := tx.InsertBulkLeftMany("artist_appearances", []string{"album_id", "artist_id"}, album.ID, artistIDs); err != nil {
|
||||||
|
return fmt.Errorf("insert bulk track artists: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Scanner) cleanTracks(c *Context) error {
|
func (s *Scanner) cleanTracks(c *Context) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() { log.Printf("finished clean tracks in %s, %d removed", durSince(start), c.TracksMissing()) }()
|
defer func() { log.Printf("finished clean tracks in %s, %d removed", durSince(start), c.TracksMissing()) }()
|
||||||
@@ -573,6 +592,8 @@ func (s *Scanner) cleanArtists(c *Context) error {
|
|||||||
SELECT artist_id FROM track_artists
|
SELECT artist_id FROM track_artists
|
||||||
UNION
|
UNION
|
||||||
SELECT artist_id FROM album_artists
|
SELECT artist_id FROM album_artists
|
||||||
|
UNION
|
||||||
|
SELECT artist_id FROM artist_appearances
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
if err := q.Error; err != nil {
|
if err := q.Error; err != nil {
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ func (c *Controller) ServeGetArtists(r *http.Request) *spec.Response {
|
|||||||
var artists []*db.Artist
|
var artists []*db.Artist
|
||||||
q := c.dbc.
|
q := c.dbc.
|
||||||
Select("*, count(sub.id) album_count").
|
Select("*, count(sub.id) album_count").
|
||||||
Joins("JOIN album_artists ON album_artists.artist_id=artists.id").
|
Joins("JOIN artist_appearances ON artist_appearances.artist_id=artists.id").
|
||||||
Joins("JOIN albums sub ON sub.id=album_artists.album_id").
|
Joins("JOIN albums sub ON sub.id=artist_appearances.album_id").
|
||||||
Preload("ArtistStar", "user_id=?", user.ID).
|
Preload("ArtistStar", "user_id=?", user.ID).
|
||||||
Preload("ArtistRating", "user_id=?", user.ID).
|
Preload("ArtistRating", "user_id=?", user.ID).
|
||||||
Preload("Info").
|
Preload("Info").
|
||||||
@@ -69,15 +69,15 @@ func (c *Controller) ServeGetArtist(r *http.Request) *spec.Response {
|
|||||||
}
|
}
|
||||||
var artist db.Artist
|
var artist db.Artist
|
||||||
c.dbc.
|
c.dbc.
|
||||||
Preload("Albums", func(db *gorm.DB) *gorm.DB {
|
Preload("Appearances", func(db *gorm.DB) *gorm.DB {
|
||||||
return db.
|
return db.
|
||||||
Select("*, count(sub.id) child_count, sum(sub.length) duration").
|
Select("*, count(sub.id) child_count, sum(sub.length) duration").
|
||||||
Joins("LEFT JOIN tracks sub ON albums.id=sub.album_id").
|
Joins("LEFT JOIN tracks sub ON albums.id=sub.album_id").
|
||||||
Order("albums.right_path").
|
Order("albums.right_path").
|
||||||
Group("albums.id")
|
Group("albums.id")
|
||||||
}).
|
}).
|
||||||
Preload("Albums.Artists").
|
Preload("Appearances.Artists").
|
||||||
Preload("Albums.Genres").
|
Preload("Appearances.Genres").
|
||||||
Preload("Info").
|
Preload("Info").
|
||||||
Preload("ArtistStar", "user_id=?", user.ID).
|
Preload("ArtistStar", "user_id=?", user.ID).
|
||||||
Preload("ArtistRating", "user_id=?", user.ID).
|
Preload("ArtistRating", "user_id=?", user.ID).
|
||||||
@@ -85,11 +85,11 @@ func (c *Controller) ServeGetArtist(r *http.Request) *spec.Response {
|
|||||||
|
|
||||||
sub := spec.NewResponse()
|
sub := spec.NewResponse()
|
||||||
sub.Artist = spec.NewArtistByTags(&artist)
|
sub.Artist = spec.NewArtistByTags(&artist)
|
||||||
sub.Artist.Albums = make([]*spec.Album, len(artist.Albums))
|
sub.Artist.Albums = make([]*spec.Album, len(artist.Appearances))
|
||||||
for i, album := range artist.Albums {
|
for i, album := range artist.Appearances {
|
||||||
sub.Artist.Albums[i] = spec.NewAlbumByTags(album, album.Artists)
|
sub.Artist.Albums[i] = spec.NewAlbumByTags(album, album.Artists)
|
||||||
}
|
}
|
||||||
sub.Artist.AlbumCount = len(artist.Albums)
|
sub.Artist.AlbumCount = len(artist.Appearances)
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user