Skip to content

Commit

Permalink
feat(subsonic): add support for multi-valued album artist tags
Browse files Browse the repository at this point in the history
closes #103

a

a

a

r

a

a

a

a

a

a

a

a

a

a
  • Loading branch information
sentriz committed Sep 10, 2023
1 parent 908c7cf commit 3ac7782
Show file tree
Hide file tree
Showing 27 changed files with 600 additions and 225 deletions.
51 changes: 51 additions & 0 deletions db/migrations.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"log"
"path/filepath"
"strings"
"time"

"github.com/jinzhu/gorm"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
51 changes: 32 additions & 19 deletions db/model.go
Expand Up @@ -9,6 +9,7 @@ package db
import (
"path"
"path/filepath"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"`
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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:"-"`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"`
Expand Down
26 changes: 13 additions & 13 deletions go.mod
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/andybalholm/cascadia v1.3.1
github.com/andybalholm/cascadia v1.3.2
github.com/dexterlb/mpvipc v0.0.0-20221227161445-38b9935eae9d
github.com/disintegration/imaging v1.6.2
github.com/dustin/go-humanize v1.0.1
Expand All @@ -16,12 +16,11 @@ require (
github.com/gorilla/mux v1.8.0
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414
github.com/josephburnett/jd v1.5.2
github.com/mattn/go-sqlite3 v1.14.16
github.com/mattn/go-sqlite3 v1.14.17
github.com/mitchellh/mapstructure v1.5.0
github.com/mmcdole/gofeed v1.2.0
github.com/mmcdole/gofeed v1.2.1
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/oklog/run v1.1.0
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
Expand All @@ -31,9 +30,10 @@ require (
github.com/sentriz/audiotags v0.0.0-20230419125925-8886243b2137
github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981
github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
golang.org/x/net v0.7.0
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
golang.org/x/net v0.14.0
gopkg.in/gormigrate.v1 v1.6.0
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
)

require (
Expand All @@ -46,28 +46,28 @@ require (
github.com/go-openapi/swag v0.21.1 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lib/pq v1.3.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mmcdole/goxpp v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/image v0.5.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
)

0 comments on commit 3ac7782

Please sign in to comment.