Skip to content

Commit 3ac7782

Browse files
committed
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
1 parent 908c7cf commit 3ac7782

27 files changed

+600
-225
lines changed

db/migrations.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"log"
88
"path/filepath"
9+
"strings"
910
"time"
1011

1112
"github.com/jinzhu/gorm"
@@ -55,6 +56,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
5556
construct(ctx, "202211111057", migratePlaylistsQueuesToFullID),
5657
construct(ctx, "202304221528", migratePlaylistsToM3U),
5758
construct(ctx, "202305301718", migratePlayCountToLength),
59+
construct(ctx, "202307281628", migrateAlbumArtistsMany2Many),
5860
}
5961

6062
return gormigrate.
@@ -538,3 +540,52 @@ func migratePlayCountToLength(tx *gorm.DB, _ MigrationContext) error {
538540

539541
return nil
540542
}
543+
544+
func migrateAlbumArtistsMany2Many(tx *gorm.DB, _ MigrationContext) error {
545+
// gorms seems to want to create the table automatically without ON DELETE rules
546+
step := tx.DropTableIfExists(AlbumArtist{})
547+
if err := step.Error; err != nil {
548+
return fmt.Errorf("step drop prev: %w", err)
549+
}
550+
551+
step = tx.AutoMigrate(
552+
AlbumArtist{},
553+
Album{},
554+
Artist{},
555+
)
556+
if err := step.Error; err != nil {
557+
return fmt.Errorf("step auto migrate: %w", err)
558+
}
559+
560+
if tx.Dialect().HasColumn("albums", "tag_artist_id") {
561+
tx = tx.LogMode(false)
562+
step = tx.Exec(`
563+
INSERT INTO album_artists (album_id, artist_id)
564+
SELECT id album_id, tag_artist_id artist_id
565+
FROM albums
566+
WHERE tag_artist_id IS NOT NULL;
567+
`)
568+
if err := step.Error; err != nil && !strings.Contains(err.Error(), "no such column") {
569+
return fmt.Errorf("step insert from albums: %w", err)
570+
}
571+
572+
step = tx.Exec(`DROP INDEX idx_albums_tag_artist_id`)
573+
if err := step.Error; err != nil {
574+
return fmt.Errorf("step drop index: %w", err)
575+
}
576+
577+
step = tx.Exec(`ALTER TABLE albums DROP COLUMN tag_artist_id;`)
578+
if err := step.Error; err != nil {
579+
return fmt.Errorf("step drop albums tag artist id: %w", err)
580+
}
581+
}
582+
583+
if tx.Dialect().HasColumn("tracks", "artist_id") {
584+
step = tx.Exec(`ALTER TABLE tracks DROP COLUMN artist_id;`)
585+
if err := step.Error; err != nil {
586+
return fmt.Errorf("step drop track tag artist: %w", err)
587+
}
588+
}
589+
590+
return nil
591+
}

db/model.go

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package db
99
import (
1010
"path"
1111
"path/filepath"
12+
"sort"
1213
"strings"
1314
"time"
1415

@@ -46,7 +47,7 @@ type Artist struct {
4647
ID int `gorm:"primary_key"`
4748
Name string `gorm:"not null; unique_index"`
4849
NameUDec string `sql:"default: null"`
49-
Albums []*Album `gorm:"foreignkey:TagArtistID"`
50+
Albums []*Album `gorm:"many2many:album_artists"`
5051
AlbumCount int `sql:"-"`
5152
Cover string `sql:"default: null"`
5253
ArtistStar *ArtistStar
@@ -89,9 +90,7 @@ type Track struct {
8990
Filename string `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null"`
9091
FilenameUDec string `sql:"default: null"`
9192
Album *Album
92-
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
93-
Artist *Artist
94-
ArtistID int `gorm:"not null" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
93+
AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
9594
Genres []*Genre `gorm:"many2many:track_genres"`
9695
Size int `sql:"default: null"`
9796
Length int `sql:"default: null"`
@@ -118,10 +117,6 @@ func (t *Track) AlbumSID() *specid.ID {
118117
return &specid.ID{Type: specid.Album, Value: t.AlbumID}
119118
}
120119

121-
func (t *Track) ArtistSID() *specid.ID {
122-
return &specid.ID{Type: specid.Artist, Value: t.ArtistID}
123-
}
124-
125120
func (t *Track) Ext() string {
126121
return filepath.Ext(t.Filename)
127122
}
@@ -190,7 +185,7 @@ type Play struct {
190185
AlbumID int `gorm:"not null; index" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
191186
Time time.Time `sql:"default: null"`
192187
Count int
193-
Length int
188+
Length int
194189
}
195190

196191
type Album struct {
@@ -202,16 +197,15 @@ type Album struct {
202197
RightPath string `gorm:"not null; unique_index:idx_album_abs_path" sql:"default: null"`
203198
RightPathUDec string `sql:"default: null"`
204199
Parent *Album
205-
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
206-
RootDir string `gorm:"unique_index:idx_album_abs_path" sql:"default: null"`
207-
Genres []*Genre `gorm:"many2many:album_genres"`
208-
Cover string `sql:"default: null"`
209-
TagArtist *Artist
210-
TagArtistID int `gorm:"index" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
211-
TagTitle string `sql:"default: null"`
212-
TagTitleUDec string `sql:"default: null"`
213-
TagBrainzID string `sql:"default: null"`
214-
TagYear int `sql:"default: null"`
200+
ParentID int `sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
201+
RootDir string `gorm:"unique_index:idx_album_abs_path" sql:"default: null"`
202+
Genres []*Genre `gorm:"many2many:album_genres"`
203+
Cover string `sql:"default: null"`
204+
Artists []*Artist `gorm:"many2many:album_artists"`
205+
TagTitle string `sql:"default: null"`
206+
TagTitleUDec string `sql:"default: null"`
207+
TagBrainzID string `sql:"default: null"`
208+
TagYear int `sql:"default: null"`
215209
Tracks []*Track
216210
ChildCount int `sql:"-"`
217211
Duration int `sql:"-"`
@@ -243,6 +237,18 @@ func (a *Album) GenreStrings() []string {
243237
return strs
244238
}
245239

240+
func (a *Album) ArtistsString() string {
241+
var artists = append([]*Artist(nil), a.Artists...)
242+
sort.Slice(artists, func(i, j int) bool {
243+
return artists[i].ID < artists[j].ID
244+
})
245+
var names []string
246+
for _, artist := range artists {
247+
names = append(names, artist.Name)
248+
}
249+
return strings.Join(names, " & ")
250+
}
251+
246252
type PlayQueue struct {
247253
ID int `gorm:"primary_key"`
248254
CreatedAt time.Time
@@ -275,6 +281,13 @@ type TranscodePreference struct {
275281
Profile string `gorm:"not null" sql:"default: null"`
276282
}
277283

284+
type AlbumArtist struct {
285+
Album *Album
286+
AlbumID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"`
287+
Artist *Artist
288+
ArtistID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"`
289+
}
290+
278291
type TrackGenre struct {
279292
Track *Track
280293
TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"`

go.mod

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.19
44

55
require (
66
github.com/Masterminds/sprig v2.22.0+incompatible
7-
github.com/andybalholm/cascadia v1.3.1
7+
github.com/andybalholm/cascadia v1.3.2
88
github.com/dexterlb/mpvipc v0.0.0-20221227161445-38b9935eae9d
99
github.com/disintegration/imaging v1.6.2
1010
github.com/dustin/go-humanize v1.0.1
@@ -16,12 +16,11 @@ require (
1616
github.com/gorilla/mux v1.8.0
1717
github.com/gorilla/securecookie v1.1.1
1818
github.com/gorilla/sessions v1.2.1
19-
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
2019
github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414
2120
github.com/josephburnett/jd v1.5.2
22-
github.com/mattn/go-sqlite3 v1.14.16
21+
github.com/mattn/go-sqlite3 v1.14.17
2322
github.com/mitchellh/mapstructure v1.5.0
24-
github.com/mmcdole/gofeed v1.2.0
23+
github.com/mmcdole/gofeed v1.2.1
2524
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
2625
github.com/oklog/run v1.1.0
2726
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
@@ -31,9 +30,10 @@ require (
3130
github.com/sentriz/audiotags v0.0.0-20230419125925-8886243b2137
3231
github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981
3332
github.com/stretchr/testify v1.8.1
34-
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
35-
golang.org/x/net v0.7.0
33+
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
34+
golang.org/x/net v0.14.0
3635
gopkg.in/gormigrate.v1 v1.6.0
36+
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
3737
)
3838

3939
require (
@@ -46,28 +46,28 @@ require (
4646
github.com/go-openapi/swag v0.21.1 // indirect
4747
github.com/gorilla/context v1.1.1 // indirect
4848
github.com/huandu/xstrings v1.4.0 // indirect
49-
github.com/imdario/mergo v0.3.13 // indirect
49+
github.com/imdario/mergo v0.3.16 // indirect
5050
github.com/jinzhu/inflection v1.0.0 // indirect
5151
github.com/jinzhu/now v1.1.2 // indirect
5252
github.com/josharian/intern v1.0.0 // indirect
5353
github.com/json-iterator/go v1.1.12 // indirect
5454
github.com/lib/pq v1.3.0 // indirect
5555
github.com/mailru/easyjson v0.7.7 // indirect
56-
github.com/mattn/go-runewidth v0.0.9 // indirect
56+
github.com/mattn/go-runewidth v0.0.15 // indirect
5757
github.com/mitchellh/copystructure v1.2.0 // indirect
5858
github.com/mitchellh/reflectwalk v1.0.2 // indirect
5959
github.com/mmcdole/goxpp v1.1.0 // indirect
6060
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6161
github.com/modern-go/reflect2 v1.0.2 // indirect
6262
github.com/olekukonko/tablewriter v0.0.5 // indirect
6363
github.com/pmezard/go-difflib v1.0.0 // indirect
64+
github.com/rivo/uniseg v0.4.4 // indirect
6465
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
65-
golang.org/x/crypto v0.6.0 // indirect
66-
golang.org/x/image v0.5.0 // indirect
67-
golang.org/x/sys v0.5.0 // indirect
68-
golang.org/x/text v0.7.0 // indirect
66+
golang.org/x/crypto v0.12.0 // indirect
67+
golang.org/x/image v0.11.0 // indirect
68+
golang.org/x/sys v0.11.0 // indirect
69+
golang.org/x/text v0.12.0 // indirect
6970
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
7071
gopkg.in/yaml.v2 v2.4.0 // indirect
7172
gopkg.in/yaml.v3 v3.0.1 // indirect
72-
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
7373
)

0 commit comments

Comments
 (0)