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"
|
||||
|
||||
"go.senan.xyz/gonic"
|
||||
"go.senan.xyz/gonic/artistinfocache"
|
||||
"go.senan.xyz/gonic/db"
|
||||
"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/lastfm"
|
||||
"go.senan.xyz/gonic/listenbrainz"
|
||||
@@ -240,6 +241,7 @@ func main() {
|
||||
sessDB.SessionOpts.SameSite = http.SameSiteLaxMode
|
||||
|
||||
artistInfoCache := artistinfocache.New(dbc, lastfmClient)
|
||||
albumInfoCache := albuminfocache.New(dbc, lastfmClient)
|
||||
|
||||
scrobblers := []scrobble.Scrobbler{lastfmClient, listenbrainzClient}
|
||||
|
||||
@@ -251,7 +253,7 @@ func main() {
|
||||
if err != nil {
|
||||
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 {
|
||||
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) 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 {
|
||||
if in == "" {
|
||||
return []specid.ID{}
|
||||
|
||||
@@ -69,6 +69,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
|
||||
construct(ctx, "202310252205", migrateAlbumTagArtistString),
|
||||
construct(ctx, "202310281803", migrateTrackArtists),
|
||||
construct(ctx, "202311062259", migrateArtistAppearances),
|
||||
construct(ctx, "202311072309", migrateAlbumInfo),
|
||||
}
|
||||
|
||||
return gormigrate.
|
||||
@@ -779,3 +780,10 @@ func migrateArtistAppearances(tx *gorm.DB, _ MigrationContext) error {
|
||||
|
||||
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/fsnotify/fsnotify v1.7.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/jinzhu/gorm v1.9.17-0.20211120011537-5c235b72a414
|
||||
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/mmcdole/gofeed v1.2.1
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
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
|
||||
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056
|
||||
)
|
||||
@@ -40,7 +40,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // 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/imdario/mergo v0.3.16 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
@@ -61,8 +61,8 @@ require (
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.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
|
||||
|
||||
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/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.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/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/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.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/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
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-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.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
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/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
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-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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
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-20190412213103-97732733099d/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.5.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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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 {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
apiKey, _, err := c.keySecret()
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ type (
|
||||
Session Session `xml:"session"`
|
||||
Error Error `xml:"error"`
|
||||
Artist Artist `xml:"artist"`
|
||||
Album Album `xml:"album"`
|
||||
TopTracks TopTracks `xml:"toptracks"`
|
||||
SimilarTracks SimilarTracks `xml:"similartracks"`
|
||||
SimilarArtists SimilarArtists `xml:"similarartists"`
|
||||
@@ -61,6 +62,54 @@ type (
|
||||
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 {
|
||||
Name string `xml:"name"`
|
||||
URL string `xml:"url"`
|
||||
|
||||
@@ -11,9 +11,10 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"go.senan.xyz/gonic/artistinfocache"
|
||||
"go.senan.xyz/gonic/db"
|
||||
"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/lastfm"
|
||||
"go.senan.xyz/gonic/playlist"
|
||||
@@ -63,10 +64,11 @@ type Controller struct {
|
||||
transcoder transcode.Transcoder
|
||||
lastFMClient *lastfm.Client
|
||||
artistInfoCache *artistinfocache.ArtistInfoCache
|
||||
albumInfoCache *albuminfocache.AlbumInfoCache
|
||||
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{
|
||||
ServeMux: http.NewServeMux(),
|
||||
|
||||
@@ -83,6 +85,7 @@ func New(dbc *db.DB, scannr *scanner.Scanner, musicPaths []MusicPath, podcastsPa
|
||||
transcoder: transcoder,
|
||||
lastFMClient: lastFMClient,
|
||||
artistInfoCache: artistInfoCache,
|
||||
albumInfoCache: albumInfoCache,
|
||||
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("/getArtists", chain(resp(c.ServeGetArtists)))
|
||||
c.Handle("/search3", chain(resp(c.ServeSearchThree)))
|
||||
c.Handle("/getArtistInfo2", chain(resp(c.ServeGetArtistInfoTwo)))
|
||||
c.Handle("/getStarred2", chain(resp(c.ServeGetStarredTwo)))
|
||||
c.Handle("/getArtistInfo2", chain(resp(c.ServeGetArtistInfoTwo)))
|
||||
c.Handle("/getAlbumInfo2", chain(resp(c.ServeGetAlbumInfoTwo)))
|
||||
|
||||
// browse by folder
|
||||
c.Handle("/getIndexes", chain(resp(c.ServeGetIndexes)))
|
||||
|
||||
@@ -380,6 +380,38 @@ func (c *Controller) ServeGetArtistInfoTwo(r *http.Request) *spec.Response {
|
||||
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 {
|
||||
var genres []*db.Genre
|
||||
c.dbc.
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"go.senan.xyz/gonic/artistinfocache"
|
||||
"go.senan.xyz/gonic/db"
|
||||
"go.senan.xyz/gonic/infocache/artistinfocache"
|
||||
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
|
||||
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
|
||||
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
|
||||
|
||||
@@ -45,27 +45,28 @@ type Response struct {
|
||||
MusicFolders *MusicFolders `xml:"musicFolders" json:"musicFolders,omitempty"`
|
||||
ScanStatus *ScanStatus `xml:"scanStatus" json:"scanStatus,omitempty"`
|
||||
Licence *Licence `xml:"license" json:"license,omitempty"`
|
||||
SearchResultTwo *SearchResultTwo `xml:"searchResult2" json:"searchResult2,omitempty"`
|
||||
SearchResultThree *SearchResultThree `xml:"searchResult3" json:"searchResult3,omitempty"`
|
||||
User *User `xml:"user" json:"user,omitempty"`
|
||||
Playlists *Playlists `xml:"playlists" json:"playlists,omitempty"`
|
||||
Playlist *Playlist `xml:"playlist" json:"playlist,omitempty"`
|
||||
ArtistInfo *ArtistInfo `xml:"artistInfo" json:"artistInfo,omitempty"`
|
||||
ArtistInfoTwo *ArtistInfo `xml:"artistInfo2" json:"artistInfo2,omitempty"`
|
||||
Genres *Genres `xml:"genres" json:"genres,omitempty"`
|
||||
PlayQueue *PlayQueue `xml:"playQueue" json:"playQueue,omitempty"`
|
||||
JukeboxStatus *JukeboxStatus `xml:"jukeboxStatus" json:"jukeboxStatus,omitempty"`
|
||||
JukeboxPlaylist *JukeboxPlaylist `xml:"jukeboxPlaylist" json:"jukeboxPlaylist,omitempty"`
|
||||
Podcasts *Podcasts `xml:"podcasts" json:"podcasts,omitempty"`
|
||||
NewestPodcasts *NewestPodcasts `xml:"newestPodcasts" json:"newestPodcasts,omitempty"`
|
||||
Bookmarks *Bookmarks `xml:"bookmarks" json:"bookmarks,omitempty"`
|
||||
Starred *Starred `xml:"starred" json:"starred,omitempty"`
|
||||
StarredTwo *StarredTwo `xml:"starred2" json:"starred2,omitempty"`
|
||||
TopSongs *TopSongs `xml:"topSongs" json:"topSongs,omitempty"`
|
||||
SimilarSongs *SimilarSongs `xml:"similarSongs" json:"similarSongs,omitempty"`
|
||||
SimilarSongsTwo *SimilarSongsTwo `xml:"similarSongs2" json:"similarSongs2,omitempty"`
|
||||
InternetRadioStations *InternetRadioStations `xml:"internetRadioStations" json:"internetRadioStations,omitempty"`
|
||||
Lyrics *Lyrics `xml:"lyrics" json:"lyrics,omitempty"`
|
||||
SearchResultTwo *SearchResultTwo `xml:"searchResult2" json:"searchResult2,omitempty"`
|
||||
SearchResultThree *SearchResultThree `xml:"searchResult3" json:"searchResult3,omitempty"`
|
||||
User *User `xml:"user" json:"user,omitempty"`
|
||||
Playlists *Playlists `xml:"playlists" json:"playlists,omitempty"`
|
||||
Playlist *Playlist `xml:"playlist" json:"playlist,omitempty"`
|
||||
ArtistInfo *ArtistInfo `xml:"artistInfo" json:"artistInfo,omitempty"`
|
||||
ArtistInfoTwo *ArtistInfo `xml:"artistInfo2" json:"artistInfo2,omitempty"`
|
||||
AlbumInfo *AlbumInfo `xml:"albumInfo" json:"albumInfo,omitempty"`
|
||||
Genres *Genres `xml:"genres" json:"genres,omitempty"`
|
||||
PlayQueue *PlayQueue `xml:"playQueue" json:"playQueue,omitempty"`
|
||||
JukeboxStatus *JukeboxStatus `xml:"jukeboxStatus" json:"jukeboxStatus,omitempty"`
|
||||
JukeboxPlaylist *JukeboxPlaylist `xml:"jukeboxPlaylist" json:"jukeboxPlaylist,omitempty"`
|
||||
Podcasts *Podcasts `xml:"podcasts" json:"podcasts,omitempty"`
|
||||
NewestPodcasts *NewestPodcasts `xml:"newestPodcasts" json:"newestPodcasts,omitempty"`
|
||||
Bookmarks *Bookmarks `xml:"bookmarks" json:"bookmarks,omitempty"`
|
||||
Starred *Starred `xml:"starred" json:"starred,omitempty"`
|
||||
StarredTwo *StarredTwo `xml:"starred2" json:"starred2,omitempty"`
|
||||
TopSongs *TopSongs `xml:"topSongs" json:"topSongs,omitempty"`
|
||||
SimilarSongs *SimilarSongs `xml:"similarSongs" json:"similarSongs,omitempty"`
|
||||
SimilarSongsTwo *SimilarSongsTwo `xml:"similarSongs2" json:"similarSongs2,omitempty"`
|
||||
InternetRadioStations *InternetRadioStations `xml:"internetRadioStations" json:"internetRadioStations,omitempty"`
|
||||
Lyrics *Lyrics `xml:"lyrics" json:"lyrics,omitempty"`
|
||||
}
|
||||
|
||||
func NewResponse() *Response {
|
||||
@@ -305,6 +306,12 @@ type ArtistInfo struct {
|
||||
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 {
|
||||
List []*Genre `xml:"genre" json:"genre"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user