feat(subsonic): add getAlbumInfo with cache
Release-As: 0.16.1
This commit is contained in:
@@ -30,9 +30,10 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"go.senan.xyz/gonic"
|
"go.senan.xyz/gonic"
|
||||||
"go.senan.xyz/gonic/artistinfocache"
|
|
||||||
"go.senan.xyz/gonic/db"
|
"go.senan.xyz/gonic/db"
|
||||||
"go.senan.xyz/gonic/handlerutil"
|
"go.senan.xyz/gonic/handlerutil"
|
||||||
|
"go.senan.xyz/gonic/infocache/albuminfocache"
|
||||||
|
"go.senan.xyz/gonic/infocache/artistinfocache"
|
||||||
"go.senan.xyz/gonic/jukebox"
|
"go.senan.xyz/gonic/jukebox"
|
||||||
"go.senan.xyz/gonic/lastfm"
|
"go.senan.xyz/gonic/lastfm"
|
||||||
"go.senan.xyz/gonic/listenbrainz"
|
"go.senan.xyz/gonic/listenbrainz"
|
||||||
@@ -240,6 +241,7 @@ func main() {
|
|||||||
sessDB.SessionOpts.SameSite = http.SameSiteLaxMode
|
sessDB.SessionOpts.SameSite = http.SameSiteLaxMode
|
||||||
|
|
||||||
artistInfoCache := artistinfocache.New(dbc, lastfmClient)
|
artistInfoCache := artistinfocache.New(dbc, lastfmClient)
|
||||||
|
albumInfoCache := albuminfocache.New(dbc, lastfmClient)
|
||||||
|
|
||||||
scrobblers := []scrobble.Scrobbler{lastfmClient, listenbrainzClient}
|
scrobblers := []scrobble.Scrobbler{lastfmClient, listenbrainzClient}
|
||||||
|
|
||||||
@@ -251,7 +253,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("error creating admin controller: %v\n", err)
|
log.Panicf("error creating admin controller: %v\n", err)
|
||||||
}
|
}
|
||||||
ctrlSubsonic, err := ctrlsubsonic.New(dbc, scannr, musicPaths, *confPodcastPath, cacheDirAudio, cacheDirCovers, jukebx, playlistStore, scrobblers, podcast, transcoder, lastfmClient, artistInfoCache, resolveProxyPath)
|
ctrlSubsonic, err := ctrlsubsonic.New(dbc, scannr, musicPaths, *confPodcastPath, cacheDirAudio, cacheDirCovers, jukebx, playlistStore, scrobblers, podcast, transcoder, lastfmClient, artistInfoCache, albumInfoCache, resolveProxyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("error creating subsonic controller: %v\n", err)
|
log.Panicf("error creating subsonic controller: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|||||||
9
db/db.go
9
db/db.go
@@ -574,6 +574,15 @@ func (p *ArtistInfo) SetSimilarArtists(items []string) { p.SimilarArtists = stri
|
|||||||
func (p *ArtistInfo) GetTopTracks() []string { return strings.Split(p.TopTracks, ";") }
|
func (p *ArtistInfo) GetTopTracks() []string { return strings.Split(p.TopTracks, ";") }
|
||||||
func (p *ArtistInfo) SetTopTracks(items []string) { p.TopTracks = strings.Join(items, ";") }
|
func (p *ArtistInfo) SetTopTracks(items []string) { p.TopTracks = strings.Join(items, ";") }
|
||||||
|
|
||||||
|
type AlbumInfo struct {
|
||||||
|
ID int `gorm:"primary_key" sql:"type:int REFERENCES albums(id) ON DELETE CASCADE"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time `gorm:"index"`
|
||||||
|
Notes string
|
||||||
|
MusicBrainzID string
|
||||||
|
LastFMURL string
|
||||||
|
}
|
||||||
|
|
||||||
func splitIDs(in, sep string) []specid.ID {
|
func splitIDs(in, sep string) []specid.ID {
|
||||||
if in == "" {
|
if in == "" {
|
||||||
return []specid.ID{}
|
return []specid.ID{}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
|
|||||||
construct(ctx, "202310252205", migrateAlbumTagArtistString),
|
construct(ctx, "202310252205", migrateAlbumTagArtistString),
|
||||||
construct(ctx, "202310281803", migrateTrackArtists),
|
construct(ctx, "202310281803", migrateTrackArtists),
|
||||||
construct(ctx, "202311062259", migrateArtistAppearances),
|
construct(ctx, "202311062259", migrateArtistAppearances),
|
||||||
|
construct(ctx, "202311072309", migrateAlbumInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
return gormigrate.
|
return gormigrate.
|
||||||
@@ -779,3 +780,10 @@ func migrateArtistAppearances(tx *gorm.DB, _ MigrationContext) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migrateAlbumInfo(tx *gorm.DB, _ MigrationContext) error {
|
||||||
|
return tx.AutoMigrate(
|
||||||
|
AlbumInfo{},
|
||||||
|
).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|||||||
16
go.mod
16
go.mod
@@ -11,12 +11,12 @@ require (
|
|||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.4.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.2
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414
|
github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414
|
||||||
github.com/josephburnett/jd v1.5.2
|
github.com/josephburnett/jd v1.5.2
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.18
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mmcdole/gofeed v1.2.1
|
github.com/mmcdole/gofeed v1.2.1
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
@@ -28,7 +28,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||||
golang.org/x/net v0.17.0
|
golang.org/x/net v0.17.0
|
||||||
golang.org/x/sync v0.4.0
|
golang.org/x/sync v0.5.0
|
||||||
gopkg.in/gormigrate.v1 v1.6.0
|
gopkg.in/gormigrate.v1 v1.6.0
|
||||||
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
|
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
|
||||||
)
|
)
|
||||||
@@ -40,7 +40,7 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
github.com/go-openapi/swag v0.21.1 // indirect
|
github.com/go-openapi/swag v0.21.1 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/huandu/xstrings v1.4.0 // indirect
|
github.com/huandu/xstrings v1.4.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.16 // indirect
|
github.com/imdario/mergo v0.3.16 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
@@ -61,8 +61,8 @@ require (
|
|||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
golang.org/x/crypto v0.14.0 // indirect
|
||||||
golang.org/x/image v0.13.0 // indirect
|
golang.org/x/image v0.13.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.14.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
31
go.sum
31
go.sum
@@ -46,16 +46,21 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2V
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||||
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||||
@@ -97,8 +102,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
|||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
@@ -180,8 +185,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -192,8 +197,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@@ -204,8 +209,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
|||||||
81
infocache/albuminfocache/albuminfocache.go
Normal file
81
infocache/albuminfocache/albuminfocache.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//nolint:revive
|
||||||
|
package albuminfocache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"go.senan.xyz/gonic/db"
|
||||||
|
"go.senan.xyz/gonic/lastfm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keepFor = 30 * time.Hour * 24
|
||||||
|
|
||||||
|
type AlbumInfoCache struct {
|
||||||
|
db *db.DB
|
||||||
|
lastfmClient *lastfm.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *db.DB, lastfmClient *lastfm.Client) *AlbumInfoCache {
|
||||||
|
return &AlbumInfoCache{db: db, lastfmClient: lastfmClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AlbumInfoCache) GetOrLookup(ctx context.Context, albumID int) (*db.AlbumInfo, error) {
|
||||||
|
var album db.Album
|
||||||
|
if err := a.db.Find(&album, "id=?", albumID).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("find album in db: %w", err)
|
||||||
|
}
|
||||||
|
if album.TagAlbumArtist == "" || album.TagTitle == "" {
|
||||||
|
return nil, fmt.Errorf("no metadata to look up")
|
||||||
|
}
|
||||||
|
|
||||||
|
var albumInfo db.AlbumInfo
|
||||||
|
if err := a.db.Find(&albumInfo, "id=?", albumID).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fmt.Errorf("find album info in db: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if albumInfo.ID == 0 || time.Since(albumInfo.UpdatedAt) > keepFor {
|
||||||
|
return a.Lookup(ctx, &album)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &albumInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AlbumInfoCache) Get(ctx context.Context, albumID int) (*db.AlbumInfo, error) {
|
||||||
|
var albumInfo db.AlbumInfo
|
||||||
|
if err := a.db.Find(&albumInfo, "id=?", albumID).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("find album info in db: %w", err)
|
||||||
|
}
|
||||||
|
return &albumInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AlbumInfoCache) Lookup(ctx context.Context, album *db.Album) (*db.AlbumInfo, error) {
|
||||||
|
var albumInfo db.AlbumInfo
|
||||||
|
albumInfo.ID = album.ID
|
||||||
|
|
||||||
|
if err := a.db.FirstOrCreate(&albumInfo, "id=?", albumInfo.ID).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("first or create album info: %w", err)
|
||||||
|
}
|
||||||
|
if err := a.db.Save(&albumInfo).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("bump updated_at time: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := a.lastfmClient.AlbumGetInfo(album.TagAlbumArtist, album.TagTitle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get upstream info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
albumInfo.ID = album.ID
|
||||||
|
albumInfo.Notes = info.Wiki.Content
|
||||||
|
albumInfo.MusicBrainzID = info.MBID
|
||||||
|
albumInfo.LastFMURL = info.URL
|
||||||
|
|
||||||
|
if err := a.db.Save(&albumInfo).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("save upstream info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &albumInfo, nil
|
||||||
|
}
|
||||||
@@ -29,6 +29,9 @@ func (a *ArtistInfoCache) GetOrLookup(ctx context.Context, artistID int) (*db.Ar
|
|||||||
if err := a.db.Find(&artist, "id=?", artistID).Error; err != nil {
|
if err := a.db.Find(&artist, "id=?", artistID).Error; err != nil {
|
||||||
return nil, fmt.Errorf("find artist in db: %w", err)
|
return nil, fmt.Errorf("find artist in db: %w", err)
|
||||||
}
|
}
|
||||||
|
if artist.Name == "" {
|
||||||
|
return nil, fmt.Errorf("no metadata to look up")
|
||||||
|
}
|
||||||
|
|
||||||
var artistInfo db.ArtistInfo
|
var artistInfo db.ArtistInfo
|
||||||
if err := a.db.Find(&artistInfo, "id=?", artistID).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err := a.db.Find(&artistInfo, "id=?", artistID).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -60,6 +60,26 @@ func (c *Client) ArtistGetInfo(artistName string) (Artist, error) {
|
|||||||
return resp.Artist, nil
|
return resp.Artist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) AlbumGetInfo(artistName, albumName string) (Album, error) {
|
||||||
|
apiKey, _, err := c.keySecret()
|
||||||
|
if err != nil {
|
||||||
|
return Album{}, fmt.Errorf("get key and secret: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("method", "album.getInfo")
|
||||||
|
params.Add("api_key", apiKey)
|
||||||
|
params.Add("artist", artistName)
|
||||||
|
params.Add("album", albumName)
|
||||||
|
|
||||||
|
resp, err := c.makeRequest(http.MethodGet, params)
|
||||||
|
if err != nil {
|
||||||
|
return Album{}, fmt.Errorf("make request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Album, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ArtistGetTopTracks(artistName string) (TopTracks, error) {
|
func (c *Client) ArtistGetTopTracks(artistName string) (TopTracks, error) {
|
||||||
apiKey, _, err := c.keySecret()
|
apiKey, _, err := c.keySecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type (
|
|||||||
Session Session `xml:"session"`
|
Session Session `xml:"session"`
|
||||||
Error Error `xml:"error"`
|
Error Error `xml:"error"`
|
||||||
Artist Artist `xml:"artist"`
|
Artist Artist `xml:"artist"`
|
||||||
|
Album Album `xml:"album"`
|
||||||
TopTracks TopTracks `xml:"toptracks"`
|
TopTracks TopTracks `xml:"toptracks"`
|
||||||
SimilarTracks SimilarTracks `xml:"similartracks"`
|
SimilarTracks SimilarTracks `xml:"similartracks"`
|
||||||
SimilarArtists SimilarArtists `xml:"similarartists"`
|
SimilarArtists SimilarArtists `xml:"similarartists"`
|
||||||
@@ -61,6 +62,54 @@ type (
|
|||||||
Bio ArtistBio `xml:"bio"`
|
Bio ArtistBio `xml:"bio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Album struct {
|
||||||
|
XMLName xml.Name `xml:"album"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Artist string `xml:"artist"`
|
||||||
|
MBID string `xml:"mbid"`
|
||||||
|
URL string `xml:"url"`
|
||||||
|
Image []struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Size string `xml:"size,attr"`
|
||||||
|
} `xml:"image"`
|
||||||
|
Listeners string `xml:"listeners"`
|
||||||
|
Playcount string `xml:"playcount"`
|
||||||
|
Tracks struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Track []struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Rank string `xml:"rank,attr"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
URL string `xml:"url"`
|
||||||
|
Duration string `xml:"duration"`
|
||||||
|
Streamable struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Fulltrack string `xml:"fulltrack,attr"`
|
||||||
|
} `xml:"streamable"`
|
||||||
|
Artist struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Mbid string `xml:"mbid"`
|
||||||
|
URL string `xml:"url"`
|
||||||
|
} `xml:"artist"`
|
||||||
|
} `xml:"track"`
|
||||||
|
} `xml:"tracks"`
|
||||||
|
Tags struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Tag []struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
URL string `xml:"url"`
|
||||||
|
} `xml:"tag"`
|
||||||
|
} `xml:"tags"`
|
||||||
|
Wiki struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Published string `xml:"published"`
|
||||||
|
Summary string `xml:"summary"`
|
||||||
|
Content string `xml:"content"`
|
||||||
|
} `xml:"wiki"`
|
||||||
|
}
|
||||||
|
|
||||||
ArtistTag struct {
|
ArtistTag struct {
|
||||||
Name string `xml:"name"`
|
Name string `xml:"name"`
|
||||||
URL string `xml:"url"`
|
URL string `xml:"url"`
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.senan.xyz/gonic/artistinfocache"
|
|
||||||
"go.senan.xyz/gonic/db"
|
"go.senan.xyz/gonic/db"
|
||||||
"go.senan.xyz/gonic/handlerutil"
|
"go.senan.xyz/gonic/handlerutil"
|
||||||
|
"go.senan.xyz/gonic/infocache/albuminfocache"
|
||||||
|
"go.senan.xyz/gonic/infocache/artistinfocache"
|
||||||
"go.senan.xyz/gonic/jukebox"
|
"go.senan.xyz/gonic/jukebox"
|
||||||
"go.senan.xyz/gonic/lastfm"
|
"go.senan.xyz/gonic/lastfm"
|
||||||
"go.senan.xyz/gonic/playlist"
|
"go.senan.xyz/gonic/playlist"
|
||||||
@@ -63,10 +64,11 @@ type Controller struct {
|
|||||||
transcoder transcode.Transcoder
|
transcoder transcode.Transcoder
|
||||||
lastFMClient *lastfm.Client
|
lastFMClient *lastfm.Client
|
||||||
artistInfoCache *artistinfocache.ArtistInfoCache
|
artistInfoCache *artistinfocache.ArtistInfoCache
|
||||||
|
albumInfoCache *albuminfocache.AlbumInfoCache
|
||||||
resolveProxyPath ProxyPathResolver
|
resolveProxyPath ProxyPathResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(dbc *db.DB, scannr *scanner.Scanner, musicPaths []MusicPath, podcastsPath string, cacheAudioPath string, cacheCoverPath string, jukebox *jukebox.Jukebox, playlistStore *playlist.Store, scrobblers []scrobble.Scrobbler, podcasts *podcast.Podcasts, transcoder transcode.Transcoder, lastFMClient *lastfm.Client, artistInfoCache *artistinfocache.ArtistInfoCache, resolveProxyPath ProxyPathResolver) (*Controller, error) {
|
func New(dbc *db.DB, scannr *scanner.Scanner, musicPaths []MusicPath, podcastsPath string, cacheAudioPath string, cacheCoverPath string, jukebox *jukebox.Jukebox, playlistStore *playlist.Store, scrobblers []scrobble.Scrobbler, podcasts *podcast.Podcasts, transcoder transcode.Transcoder, lastFMClient *lastfm.Client, artistInfoCache *artistinfocache.ArtistInfoCache, albumInfoCache *albuminfocache.AlbumInfoCache, resolveProxyPath ProxyPathResolver) (*Controller, error) {
|
||||||
c := Controller{
|
c := Controller{
|
||||||
ServeMux: http.NewServeMux(),
|
ServeMux: http.NewServeMux(),
|
||||||
|
|
||||||
@@ -83,6 +85,7 @@ func New(dbc *db.DB, scannr *scanner.Scanner, musicPaths []MusicPath, podcastsPa
|
|||||||
transcoder: transcoder,
|
transcoder: transcoder,
|
||||||
lastFMClient: lastFMClient,
|
lastFMClient: lastFMClient,
|
||||||
artistInfoCache: artistInfoCache,
|
artistInfoCache: artistInfoCache,
|
||||||
|
albumInfoCache: albumInfoCache,
|
||||||
resolveProxyPath: resolveProxyPath,
|
resolveProxyPath: resolveProxyPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +135,9 @@ func New(dbc *db.DB, scannr *scanner.Scanner, musicPaths []MusicPath, podcastsPa
|
|||||||
c.Handle("/getArtist", chain(resp(c.ServeGetArtist)))
|
c.Handle("/getArtist", chain(resp(c.ServeGetArtist)))
|
||||||
c.Handle("/getArtists", chain(resp(c.ServeGetArtists)))
|
c.Handle("/getArtists", chain(resp(c.ServeGetArtists)))
|
||||||
c.Handle("/search3", chain(resp(c.ServeSearchThree)))
|
c.Handle("/search3", chain(resp(c.ServeSearchThree)))
|
||||||
c.Handle("/getArtistInfo2", chain(resp(c.ServeGetArtistInfoTwo)))
|
|
||||||
c.Handle("/getStarred2", chain(resp(c.ServeGetStarredTwo)))
|
c.Handle("/getStarred2", chain(resp(c.ServeGetStarredTwo)))
|
||||||
|
c.Handle("/getArtistInfo2", chain(resp(c.ServeGetArtistInfoTwo)))
|
||||||
|
c.Handle("/getAlbumInfo2", chain(resp(c.ServeGetAlbumInfoTwo)))
|
||||||
|
|
||||||
// browse by folder
|
// browse by folder
|
||||||
c.Handle("/getIndexes", chain(resp(c.ServeGetIndexes)))
|
c.Handle("/getIndexes", chain(resp(c.ServeGetIndexes)))
|
||||||
|
|||||||
@@ -380,6 +380,38 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ServeGetAlbumInfoTwo(r *http.Request) *spec.Response {
|
||||||
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
|
id, err := params.GetID("id")
|
||||||
|
if err != nil {
|
||||||
|
return spec.NewError(10, "please provide an `id` parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
var album db.Album
|
||||||
|
err = c.dbc.
|
||||||
|
Where("id=?", id.Value).
|
||||||
|
Find(&album).
|
||||||
|
Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return spec.NewError(70, "album with id %q not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub := spec.NewResponse()
|
||||||
|
sub.AlbumInfo = &spec.AlbumInfo{}
|
||||||
|
|
||||||
|
info, err := c.albumInfoCache.GetOrLookup(r.Context(), album.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error fetching album info from lastfm: %v", err)
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
sub.AlbumInfo.Notes = info.Notes
|
||||||
|
sub.AlbumInfo.MusicBrainzID = info.MusicBrainzID
|
||||||
|
sub.AlbumInfo.LastFMURL = info.LastFMURL
|
||||||
|
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) ServeGetGenres(_ *http.Request) *spec.Response {
|
func (c *Controller) ServeGetGenres(_ *http.Request) *spec.Response {
|
||||||
var genres []*db.Genre
|
var genres []*db.Genre
|
||||||
c.dbc.
|
c.dbc.
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
"go.senan.xyz/gonic/artistinfocache"
|
|
||||||
"go.senan.xyz/gonic/db"
|
"go.senan.xyz/gonic/db"
|
||||||
|
"go.senan.xyz/gonic/infocache/artistinfocache"
|
||||||
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
|
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
|
||||||
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
|
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
|
||||||
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type Response struct {
|
|||||||
Playlist *Playlist `xml:"playlist" json:"playlist,omitempty"`
|
Playlist *Playlist `xml:"playlist" json:"playlist,omitempty"`
|
||||||
ArtistInfo *ArtistInfo `xml:"artistInfo" json:"artistInfo,omitempty"`
|
ArtistInfo *ArtistInfo `xml:"artistInfo" json:"artistInfo,omitempty"`
|
||||||
ArtistInfoTwo *ArtistInfo `xml:"artistInfo2" json:"artistInfo2,omitempty"`
|
ArtistInfoTwo *ArtistInfo `xml:"artistInfo2" json:"artistInfo2,omitempty"`
|
||||||
|
AlbumInfo *AlbumInfo `xml:"albumInfo" json:"albumInfo,omitempty"`
|
||||||
Genres *Genres `xml:"genres" json:"genres,omitempty"`
|
Genres *Genres `xml:"genres" json:"genres,omitempty"`
|
||||||
PlayQueue *PlayQueue `xml:"playQueue" json:"playQueue,omitempty"`
|
PlayQueue *PlayQueue `xml:"playQueue" json:"playQueue,omitempty"`
|
||||||
JukeboxStatus *JukeboxStatus `xml:"jukeboxStatus" json:"jukeboxStatus,omitempty"`
|
JukeboxStatus *JukeboxStatus `xml:"jukeboxStatus" json:"jukeboxStatus,omitempty"`
|
||||||
@@ -305,6 +306,12 @@ type ArtistInfo struct {
|
|||||||
Similar []*Artist `xml:"similarArtist,omitempty" json:"similarArtist,omitempty"`
|
Similar []*Artist `xml:"similarArtist,omitempty" json:"similarArtist,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AlbumInfo struct {
|
||||||
|
Notes string `xml:"notes" json:"notes"`
|
||||||
|
MusicBrainzID string `xml:"musicBrainzId" json:"musicBrainzId"`
|
||||||
|
LastFMURL string `xml:"lastFmUrl" json:"lastFmUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
type Genres struct {
|
type Genres struct {
|
||||||
List []*Genre `xml:"genre" json:"genre"`
|
List []*Genre `xml:"genre" json:"genre"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user