feat(subsonic): add support for multi-valued album artist tags

closes #103

a

a

a

r

a

a

a

a

a

a

a

a

a

a
This commit is contained in:
sentriz
2023-07-31 23:07:41 +01:00
parent 908c7cf088
commit 3ac77823c3
27 changed files with 641 additions and 266 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"log"
"path/filepath"
"strings"
"time"
"github.com/jinzhu/gorm"
@@ -55,6 +56,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
construct(ctx, "202211111057", migratePlaylistsQueuesToFullID),
construct(ctx, "202304221528", migratePlaylistsToM3U),
construct(ctx, "202305301718", migratePlayCountToLength),
construct(ctx, "202307281628", migrateAlbumArtistsMany2Many),
}
return gormigrate.
@@ -538,3 +540,52 @@ func migratePlayCountToLength(tx *gorm.DB, _ MigrationContext) error {
return nil
}
func migrateAlbumArtistsMany2Many(tx *gorm.DB, _ MigrationContext) error {
// gorms seems to want to create the table automatically without ON DELETE rules
step := tx.DropTableIfExists(AlbumArtist{})
if err := step.Error; err != nil {
return fmt.Errorf("step drop prev: %w", err)
}
step = tx.AutoMigrate(
AlbumArtist{},
Album{},
Artist{},
)
if err := step.Error; err != nil {
return fmt.Errorf("step auto migrate: %w", err)
}
if tx.Dialect().HasColumn("albums", "tag_artist_id") {
tx = tx.LogMode(false)
step = tx.Exec(`
INSERT INTO album_artists (album_id, artist_id)
SELECT id album_id, tag_artist_id artist_id
FROM albums
WHERE tag_artist_id IS NOT NULL;
`)
if err := step.Error; err != nil && !strings.Contains(err.Error(), "no such column") {
return fmt.Errorf("step insert from albums: %w", err)
}
step = tx.Exec(`DROP INDEX idx_albums_tag_artist_id`)
if err := step.Error; err != nil {
return fmt.Errorf("step drop index: %w", err)
}
step = tx.Exec(`ALTER TABLE albums DROP COLUMN tag_artist_id;`)
if err := step.Error; err != nil {
return fmt.Errorf("step drop albums tag artist id: %w", err)
}
}
if tx.Dialect().HasColumn("tracks", "artist_id") {
step = tx.Exec(`ALTER TABLE tracks DROP COLUMN artist_id;`)
if err := step.Error; err != nil {
return fmt.Errorf("step drop track tag artist: %w", err)
}
}
return nil
}

View File

@@ -9,6 +9,7 @@ package db
import (
"path"
"path/filepath"
"sort"
"strings"
"time"
@@ -46,7 +47,7 @@ type Artist struct {
ID int `gorm:"primary_key"`
Name string `gorm:"not null; unique_index"`
NameUDec string `sql:"default: null"`
Albums []*Album `gorm:"foreignkey:TagArtistID"`
Albums []*Album `gorm:"many2many:album_artists"`
AlbumCount int `sql:"-"`
Cover string `sql:"default: null"`
ArtistStar *ArtistStar
@@ -89,9 +90,7 @@ type Track struct {
Filename string `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null"`
FilenameUDec string `sql:"default: null"`
Album *Album
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Artist *Artist
ArtistID int `gorm:"not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Genres []*Genre `gorm:"many2many:track_genres"`
Size int `sql:"default: null"`
Length int `sql:"default: null"`
@@ -118,10 +117,6 @@ func (t *Track) AlbumSID() *specid.ID {
return &specid.ID{Type: specid.Album, Value: t.AlbumID}
}
func (t *Track) ArtistSID() *specid.ID {
return &specid.ID{Type: specid.Artist, Value: t.ArtistID}
}
func (t *Track) Ext() string {
return filepath.Ext(t.Filename)
}
@@ -190,7 +185,7 @@ type Play struct {
AlbumID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Time time.Time `sql:"default: null"`
Count int
Length int
Length int
}
type Album struct {
@@ -202,16 +197,15 @@ type Album struct {
RightPath string `gorm:"not null; unique_index:idx_album_abs_path" sql:"default: null"`
RightPathUDec string `sql:"default: null"`
Parent *Album
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
RootDir string `gorm:"unique_index:idx_album_abs_path" sql:"default: null"`
Genres []*Genre `gorm:"many2many:album_genres"`
Cover string `sql:"default: null"`
TagArtist *Artist
TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
TagTitle string `sql:"default: null"`
TagTitleUDec string `sql:"default: null"`
TagBrainzID string `sql:"default: null"`
TagYear int `sql:"default: null"`
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
RootDir string `gorm:"unique_index:idx_album_abs_path" sql:"default: null"`
Genres []*Genre `gorm:"many2many:album_genres"`
Cover string `sql:"default: null"`
Artists []*Artist `gorm:"many2many:album_artists"`
TagTitle string `sql:"default: null"`
TagTitleUDec string `sql:"default: null"`
TagBrainzID string `sql:"default: null"`
TagYear int `sql:"default: null"`
Tracks []*Track
ChildCount int `sql:"-"`
Duration int `sql:"-"`
@@ -243,6 +237,18 @@ func (a *Album) GenreStrings() []string {
return strs
}
func (a *Album) ArtistsString() string {
var artists = append([]*Artist(nil), a.Artists...)
sort.Slice(artists, func(i, j int) bool {
return artists[i].ID < artists[j].ID
})
var names []string
for _, artist := range artists {
names = append(names, artist.Name)
}
return strings.Join(names, " & ")
}
type PlayQueue struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
@@ -275,6 +281,13 @@ type TranscodePreference struct {
Profile string `gorm:"not null" sql:"default: null"`
}
type AlbumArtist struct {
Album *Album
AlbumID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
Artist *Artist
ArtistID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
}
type TrackGenre struct {
Track *Track
TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`