feat(subsonic): add getAlbumInfo with cache

Release-As: 0.16.1
This commit is contained in:
sentriz
2023-11-07 23:43:11 +00:00
parent 3f5cf56c88
commit cc1a99f033
14 changed files with 268 additions and 48 deletions

View File

@@ -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)
}

View File

@@ -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{}

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View 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
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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"`

View File

@@ -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)))

View File

@@ -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.

View File

@@ -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"

View File

@@ -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"`
}