Add ExternalInformation core service (not a great name, I know)

This commit is contained in:
Deluan 2020-10-18 19:10:11 -04:00 committed by Deluan Quintão
parent 19ead8f7e8
commit 07535e1518
14 changed files with 313 additions and 38 deletions

View File

@ -50,7 +50,10 @@ func CreateSubsonicAPIRouter() (*subsonic.Router, error) {
mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache) mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache)
archiver := core.NewArchiver(dataStore) archiver := core.NewArchiver(dataStore)
players := engine.NewPlayers(dataStore) players := engine.NewPlayers(dataStore)
router := subsonic.New(artwork, listGenerator, playlists, mediaStreamer, archiver, players, dataStore) lastFMClient := core.LastFMNewClient()
spotifyClient := core.SpotifyNewClient()
externalInfo := core.NewExternalInfo(dataStore, lastFMClient, spotifyClient)
router := subsonic.New(artwork, listGenerator, playlists, mediaStreamer, archiver, players, externalInfo, dataStore)
return router, nil return router, nil
} }

169
core/external_info.go Normal file
View File

@ -0,0 +1,169 @@
package core
import (
"context"
"sort"
"strings"
"sync"
"time"
"github.com/deluan/navidrome/core/lastfm"
"github.com/deluan/navidrome/core/spotify"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
"github.com/microcosm-cc/bluemonday"
)
const placeholderArtistImageSmallUrl = "https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png"
const placeholderArtistImageMediumUrl = "https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png"
const placeholderArtistImageLargeUrl = "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png"
type ExternalInfo interface {
ArtistInfo(ctx context.Context, artistId string, includeNotPresent bool, count int) (*model.ArtistInfo, error)
}
type LastFMClient interface {
ArtistGetInfo(ctx context.Context, name string) (*lastfm.Artist, error)
}
type SpotifyClient interface {
ArtistImages(ctx context.Context, name string) ([]spotify.Image, error)
}
func NewExternalInfo(ds model.DataStore, lfm LastFMClient, spf SpotifyClient) ExternalInfo {
return &externalInfo{ds: ds, lfm: lfm, spf: spf}
}
type externalInfo struct {
ds model.DataStore
lfm LastFMClient
spf SpotifyClient
}
func (e *externalInfo) ArtistInfo(ctx context.Context, artistId string,
includeNotPresent bool, count int) (*model.ArtistInfo, error) {
info := model.ArtistInfo{ID: artistId}
artist, err := e.ds.Artist(ctx).Get(artistId)
if err != nil {
return nil, err
}
info.Name = artist.Name
// TODO Load from local: artist.jpg/png/webp, artist.json (with the remaining info)
var wg sync.WaitGroup
e.callArtistInfo(ctx, artist, includeNotPresent, &wg, &info)
e.callArtistImages(ctx, artist, &wg, &info)
wg.Wait()
// Use placeholders if could not get from external sources
e.setBio(&info, "Biography not available")
e.setSmallImageUrl(&info, placeholderArtistImageSmallUrl)
e.setMediumImageUrl(&info, placeholderArtistImageMediumUrl)
e.setLargeImageUrl(&info, placeholderArtistImageLargeUrl)
log.Trace(ctx, "ArtistInfo collected", "artist", artist.Name, "info", info)
return &info, nil
}
func (e *externalInfo) callArtistInfo(ctx context.Context, artist *model.Artist, includeNotPresent bool,
wg *sync.WaitGroup, info *model.ArtistInfo) {
if e.lfm != nil {
log.Debug(ctx, "Calling Last.FM ArtistGetInfo", "artist", artist.Name)
wg.Add(1)
go func() {
start := time.Now()
defer wg.Done()
lfmArtist, err := e.lfm.ArtistGetInfo(nil, artist.Name)
if err != nil {
log.Error(ctx, "Error calling Last.FM", "artist", artist.Name, err)
} else {
log.Debug(ctx, "Got info from Last.FM", "artist", artist.Name, "info", lfmArtist.Bio.Summary, "elapsed", time.Since(start))
}
e.setBio(info, lfmArtist.Bio.Summary)
e.setSimilar(ctx, info, lfmArtist.Similar.Artists, includeNotPresent)
}()
}
}
func (e *externalInfo) callArtistImages(ctx context.Context, artist *model.Artist, wg *sync.WaitGroup, info *model.ArtistInfo) {
if e.spf != nil {
log.Debug(ctx, "Calling Spotify ArtistImages", "artist", artist.Name)
wg.Add(1)
go func() {
start := time.Now()
defer wg.Done()
spfImages, err := e.spf.ArtistImages(nil, artist.Name)
if err != nil {
log.Error(ctx, "Error calling Spotify", "artist", artist.Name, err)
} else {
log.Debug(ctx, "Got images from Spotify", "artist", artist.Name, "images", spfImages, "elapsed", time.Since(start))
}
sort.Slice(spfImages, func(i, j int) bool { return spfImages[i].Width > spfImages[j].Width })
if len(spfImages) >= 1 {
e.setLargeImageUrl(info, spfImages[0].URL)
}
if len(spfImages) >= 2 {
e.setMediumImageUrl(info, spfImages[1].URL)
}
if len(spfImages) >= 3 {
e.setSmallImageUrl(info, spfImages[2].URL)
}
}()
}
}
func (e *externalInfo) setBio(info *model.ArtistInfo, bio string) {
policy := bluemonday.UGCPolicy()
if info.Bio == "" {
bio = policy.Sanitize(bio)
bio = strings.ReplaceAll(bio, "\n", " ")
info.Bio = strings.ReplaceAll(bio, "<a ", "<a target='_blank' ")
}
}
func (e *externalInfo) setSmallImageUrl(info *model.ArtistInfo, url string) {
if info.SmallImageUrl == "" {
info.SmallImageUrl = url
}
}
func (e *externalInfo) setMediumImageUrl(info *model.ArtistInfo, url string) {
if info.MediumImageUrl == "" {
info.MediumImageUrl = url
}
}
func (e *externalInfo) setLargeImageUrl(info *model.ArtistInfo, url string) {
if info.LargeImageUrl == "" {
info.LargeImageUrl = url
}
}
func (e *externalInfo) setSimilar(ctx context.Context, info *model.ArtistInfo, artists []lastfm.Artist, includeNotPresent bool) {
if len(info.Similar) == 0 {
var notPresent []string
// First select artists that are present.
for _, s := range artists {
sa, err := e.ds.Artist(ctx).FindByName(s.Name)
if err != nil {
notPresent = append(notPresent, s.Name)
continue
}
info.Similar = append(info.Similar, *sa)
}
// Then fill up with non-present artists
if includeNotPresent {
for _, s := range notPresent {
sa := model.Artist{ID: "-1", Name: s}
info.Similar = append(info.Similar, sa)
}
}
}
}

View File

@ -1,6 +1,7 @@
package lastfm package lastfm
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -27,7 +28,7 @@ type Client struct {
} }
// TODO SimilarArtists() // TODO SimilarArtists()
func (c *Client) ArtistGetInfo(name string) (*Artist, error) { func (c *Client) ArtistGetInfo(ctx context.Context, name string) (*Artist, error) {
params := url.Values{} params := url.Values{}
params.Add("method", "artist.getInfo") params.Add("method", "artist.getInfo")
params.Add("format", "json") params.Add("format", "json")

View File

@ -2,6 +2,7 @@ package lastfm
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -25,7 +26,7 @@ var _ = Describe("Client", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json") f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
httpClient.res = http.Response{Body: f, StatusCode: 200} httpClient.res = http.Response{Body: f, StatusCode: 200}
artist, err := client.ArtistGetInfo("U2") artist, err := client.ArtistGetInfo(context.TODO(), "U2")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(artist.Name).To(Equal("U2")) Expect(artist.Name).To(Equal("U2"))
Expect(httpClient.savedRequest.URL.String()).To(Equal(apiBaseUrl + "?api_key=API_KEY&artist=U2&format=json&lang=pt&method=artist.getInfo")) Expect(httpClient.savedRequest.URL.String()).To(Equal(apiBaseUrl + "?api_key=API_KEY&artist=U2&format=json&lang=pt&method=artist.getInfo"))
@ -37,14 +38,14 @@ var _ = Describe("Client", func() {
StatusCode: 400, StatusCode: 400,
} }
_, err := client.ArtistGetInfo("U2") _, err := client.ArtistGetInfo(context.TODO(), "U2")
Expect(err).To(MatchError("last.fm error(3): Invalid Method - No method with that name in this package")) Expect(err).To(MatchError("last.fm error(3): Invalid Method - No method with that name in this package"))
}) })
It("fails if HttpClient.Do() returns error", func() { It("fails if HttpClient.Do() returns error", func() {
httpClient.err = errors.New("generic error") httpClient.err = errors.New("generic error")
_, err := client.ArtistGetInfo("U2") _, err := client.ArtistGetInfo(context.TODO(), "U2")
Expect(err).To(MatchError("generic error")) Expect(err).To(MatchError("generic error"))
}) })
@ -54,7 +55,7 @@ var _ = Describe("Client", func() {
StatusCode: 200, StatusCode: 200,
} }
_, err := client.ArtistGetInfo("U2") _, err := client.ArtistGetInfo(context.TODO(), "U2")
Expect(err).To(MatchError("invalid character '<' looking for beginning of value")) Expect(err).To(MatchError("invalid character '<' looking for beginning of value"))
}) })

View File

@ -1,6 +1,7 @@
package spotify package spotify
import ( import (
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
@ -34,8 +35,8 @@ type Client struct {
hc HttpClient hc HttpClient
} }
func (c *Client) ArtistImages(name string) ([]Image, error) { func (c *Client) ArtistImages(ctx context.Context, name string) ([]Image, error) {
token, err := c.authorize() token, err := c.authorize(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -58,12 +59,13 @@ func (c *Client) ArtistImages(name string) ([]Image, error) {
if len(results.Artists.Items) == 0 { if len(results.Artists.Items) == 0 {
return nil, ErrNotFound return nil, ErrNotFound
} }
log.Debug(ctx, "Found artist in Spotify", "artist", results.Artists.Items[0].Name)
return results.Artists.Items[0].Images, err return results.Artists.Items[0].Images, err
} }
func (c *Client) authorize() (string, error) { func (c *Client) authorize(ctx context.Context) (string, error) {
payload := url.Values{} payload := url.Values{}
payload.Add("grant_type", "client_credentials.getInfo") payload.Add("grant_type", "client_credentials")
req, _ := http.NewRequest("POST", "https://accounts.spotify.com/api/token", strings.NewReader(payload.Encode())) req, _ := http.NewRequest("POST", "https://accounts.spotify.com/api/token", strings.NewReader(payload.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
@ -80,7 +82,7 @@ func (c *Client) authorize() (string, error) {
if v, ok := response["access_token"]; ok { if v, ok := response["access_token"]; ok {
return v.(string), nil return v.(string), nil
} }
log.Error("Invalid spotify response", "resp", response) log.Error(ctx, "Invalid spotify response", "resp", response)
return "", errors.New("invalid response") return "", errors.New("invalid response")
} }

View File

@ -2,6 +2,7 @@ package spotify
import ( import (
"bytes" "bytes"
"context"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
@ -28,7 +29,7 @@ var _ = Describe("Client", func() {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)),
}) })
images, err := client.ArtistImages("U2") images, err := client.ArtistImages(context.TODO(), "U2")
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(images).To(HaveLen(3)) Expect(images).To(HaveLen(3))
Expect(images[0].Width).To(Equal(640)) Expect(images[0].Width).To(Equal(640))
@ -50,7 +51,7 @@ var _ = Describe("Client", func() {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)),
}) })
_, err := client.ArtistImages("U2") _, err := client.ArtistImages(context.TODO(), "U2")
Expect(err).To(MatchError(ErrNotFound)) Expect(err).To(MatchError(ErrNotFound))
}) })
@ -62,7 +63,7 @@ var _ = Describe("Client", func() {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":"invalid_client","error_description":"Invalid client"}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":"invalid_client","error_description":"Invalid client"}`)),
}) })
_, err := client.ArtistImages("U2") _, err := client.ArtistImages(context.TODO(), "U2")
Expect(err).To(MatchError("spotify error(invalid_client): Invalid client")) Expect(err).To(MatchError("spotify error(invalid_client): Invalid client"))
}) })
}) })
@ -74,7 +75,7 @@ var _ = Describe("Client", func() {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)),
}) })
token, err := client.authorize() token, err := client.authorize(nil)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(token).To(Equal("NEW_ACCESS_TOKEN")) Expect(token).To(Equal("NEW_ACCESS_TOKEN"))
auth := httpClient.lastRequest.Header.Get("Authorization") auth := httpClient.lastRequest.Header.Get("Authorization")
@ -87,7 +88,7 @@ var _ = Describe("Client", func() {
Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":"invalid_client","error_description":"Invalid client"}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":"invalid_client","error_description":"Invalid client"}`)),
}) })
_, err := client.authorize() _, err := client.authorize(nil)
Expect(err).To(MatchError("spotify error(invalid_client): Invalid client")) Expect(err).To(MatchError("spotify error(invalid_client): Invalid client"))
}) })
@ -97,7 +98,7 @@ var _ = Describe("Client", func() {
Body: ioutil.NopCloser(bytes.NewBufferString(`{NOT_VALID}`)), Body: ioutil.NopCloser(bytes.NewBufferString(`{NOT_VALID}`)),
}) })
_, err := client.authorize() _, err := client.authorize(nil)
Expect(err).To(MatchError("invalid character 'N' looking for beginning of object key string")) Expect(err).To(MatchError("invalid character 'N' looking for beginning of object key string"))
}) })
}) })

View File

@ -1,6 +1,11 @@
package core package core
import ( import (
"net/http"
"github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/core/lastfm"
"github.com/deluan/navidrome/core/spotify"
"github.com/deluan/navidrome/core/transcoder" "github.com/deluan/navidrome/core/transcoder"
"github.com/google/wire" "github.com/google/wire"
) )
@ -11,5 +16,24 @@ var Set = wire.NewSet(
NewTranscodingCache, NewTranscodingCache,
NewImageCache, NewImageCache,
NewArchiver, NewArchiver,
NewExternalInfo,
LastFMNewClient,
SpotifyNewClient,
transcoder.New, transcoder.New,
) )
func LastFMNewClient() LastFMClient {
if conf.Server.LastFM.ApiKey == "" {
return nil
}
return lastfm.NewClient(conf.Server.LastFM.ApiKey, conf.Server.LastFM.Language, http.DefaultClient)
}
func SpotifyNewClient() SpotifyClient {
if conf.Server.Spotify.ID == "" || conf.Server.Spotify.Secret == "" {
return nil
}
return spotify.NewClient(conf.Server.Spotify.ID, conf.Server.Spotify.Secret, http.DefaultClient)
}

View File

@ -26,6 +26,7 @@ type ArtistRepository interface {
Exists(id string) (bool, error) Exists(id string) (bool, error)
Put(m *Artist) error Put(m *Artist) error
Get(id string) (*Artist, error) Get(id string) (*Artist, error)
FindByName(name string) (*Artist, error)
GetStarred(options ...QueryOptions) (Artists, error) GetStarred(options ...QueryOptions) (Artists, error)
Search(q string, offset int, size int) (Artists, error) Search(q string, offset int, size int) (Artists, error)
Refresh(ids ...string) error Refresh(ids ...string) error

11
model/artist_info.go Normal file
View File

@ -0,0 +1,11 @@
package model
type ArtistInfo struct {
ID string
Name string
Bio string
Similar []Artist
SmallImageUrl string
MediumImageUrl string
LargeImageUrl string
}

View File

@ -66,6 +66,18 @@ func (r *artistRepository) Get(id string) (*model.Artist, error) {
return &res[0], nil return &res[0], nil
} }
func (r *artistRepository) FindByName(name string) (*model.Artist, error) {
sel := r.selectArtist().Where(Eq{"name": name})
var res model.Artists
if err := r.queryAll(sel, &res); err != nil {
return nil, err
}
if len(res) == 0 {
return nil, model.ErrNotFound
}
return &res[0], nil
}
func (r *artistRepository) GetAll(options ...model.QueryOptions) (model.Artists, error) { func (r *artistRepository) GetAll(options ...model.QueryOptions) (model.Artists, error) {
sel := r.selectArtist(options...) sel := r.selectArtist(options...)
res := model.Artists{} res := model.Artists{}

View File

@ -29,6 +29,7 @@ type Router struct {
Streamer core.MediaStreamer Streamer core.MediaStreamer
Archiver core.Archiver Archiver core.Archiver
Players engine.Players Players engine.Players
ExternalInfo core.ExternalInfo
DataStore model.DataStore DataStore model.DataStore
mux http.Handler mux http.Handler
@ -36,9 +37,9 @@ type Router struct {
func New(artwork core.Artwork, listGenerator engine.ListGenerator, func New(artwork core.Artwork, listGenerator engine.ListGenerator,
playlists engine.Playlists, streamer core.MediaStreamer, playlists engine.Playlists, streamer core.MediaStreamer,
archiver core.Archiver, players engine.Players, ds model.DataStore) *Router { archiver core.Archiver, players engine.Players, externalInfo core.ExternalInfo, ds model.DataStore) *Router {
r := &Router{Artwork: artwork, ListGenerator: listGenerator, Playlists: playlists, r := &Router{Artwork: artwork, ListGenerator: listGenerator, Playlists: playlists,
Streamer: streamer, Archiver: archiver, Players: players, DataStore: ds} Streamer: streamer, Archiver: archiver, Players: players, ExternalInfo: externalInfo, DataStore: ds}
r.mux = r.routes() r.mux = r.routes()
return r return r
} }

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/core"
"github.com/deluan/navidrome/log" "github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model" "github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/server/subsonic/responses"
@ -17,10 +18,11 @@ import (
type BrowsingController struct { type BrowsingController struct {
ds model.DataStore ds model.DataStore
ei core.ExternalInfo
} }
func NewBrowsingController(ds model.DataStore) *BrowsingController { func NewBrowsingController(ds model.DataStore, ei core.ExternalInfo) *BrowsingController {
return &BrowsingController{ds: ds} return &BrowsingController{ds: ds, ei: ei}
} }
func (c *BrowsingController) GetMusicFolders(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetMusicFolders(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
@ -230,29 +232,75 @@ func (c *BrowsingController) GetGenres(w http.ResponseWriter, r *http.Request) (
return response, nil return response, nil
} }
const placeholderArtistImageSmallUrl = "https://lastfm.freetls.fastly.net/i/u/64s/2a96cbd8b46e442fc41c2b86b821562f.png"
const placeholderArtistImageMediumUrl = "https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png"
const placeholderArtistImageLargeUrl = "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png"
// TODO Integrate with Last.FM
func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
id, err := requiredParamString(r, "id", "id parameter required")
if err != nil {
return nil, err
}
count := utils.ParamInt(r, "count", 20)
includeNotPresent := utils.ParamBool(r, "includeNotPresent", false)
entity, err := getEntityByID(ctx, c.ds, id)
if err != nil {
return nil, err
}
switch v := entity.(type) {
case *model.MediaFile:
id = v.ArtistID
case *model.Album:
id = v.AlbumArtistID
case *model.Artist:
id = v.ID
default:
err = model.ErrNotFound
}
if err != nil {
return nil, err
}
info, err := c.ei.ArtistInfo(ctx, id, includeNotPresent, count)
if err != nil {
return nil, err
}
response := newResponse() response := newResponse()
response.ArtistInfo = &responses.ArtistInfo{} response.ArtistInfo = &responses.ArtistInfo{}
response.ArtistInfo.Biography = "Biography not available" response.ArtistInfo.Biography = info.Bio
response.ArtistInfo.SmallImageUrl = placeholderArtistImageSmallUrl response.ArtistInfo.SmallImageUrl = info.SmallImageUrl
response.ArtistInfo.MediumImageUrl = placeholderArtistImageMediumUrl response.ArtistInfo.MediumImageUrl = info.MediumImageUrl
response.ArtistInfo.LargeImageUrl = placeholderArtistImageLargeUrl response.ArtistInfo.LargeImageUrl = info.LargeImageUrl
for _, s := range info.Similar {
similar := responses.Artist{}
similar.Id = s.ID
similar.Name = s.Name
similar.AlbumCount = s.AlbumCount
if s.Starred {
similar.Starred = &s.StarredAt
}
response.ArtistInfo.SimilarArtist = append(response.ArtistInfo.SimilarArtist, similar)
}
return response, nil return response, nil
} }
// TODO Integrate with Last.FM
func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
info, err := c.GetArtistInfo(w, r)
if err != nil {
return nil, err
}
response := newResponse() response := newResponse()
response.ArtistInfo2 = &responses.ArtistInfo2{} response.ArtistInfo2 = &responses.ArtistInfo2{}
response.ArtistInfo2.Biography = "Biography not available" response.ArtistInfo2.ArtistInfoBase = info.ArtistInfo.ArtistInfoBase
response.ArtistInfo2.SmallImageUrl = placeholderArtistImageSmallUrl for _, s := range info.ArtistInfo.SimilarArtist {
response.ArtistInfo2.MediumImageUrl = placeholderArtistImageSmallUrl similar := responses.ArtistID3{}
response.ArtistInfo2.LargeImageUrl = placeholderArtistImageSmallUrl similar.Id = s.Id
similar.Name = s.Name
similar.AlbumCount = s.AlbumCount
similar.Starred = s.Starred
response.ArtistInfo2.SimilarArtist = append(response.ArtistInfo2.SimilarArtist, similar)
}
return response, nil return response, nil
} }

View File

@ -19,7 +19,8 @@ func initSystemController(router *Router) *SystemController {
func initBrowsingController(router *Router) *BrowsingController { func initBrowsingController(router *Router) *BrowsingController {
dataStore := router.DataStore dataStore := router.DataStore
browsingController := NewBrowsingController(dataStore) externalInfo := router.ExternalInfo
browsingController := NewBrowsingController(dataStore, externalInfo)
return browsingController return browsingController
} }
@ -85,5 +86,5 @@ var allProviders = wire.NewSet(
NewUsersController, NewUsersController,
NewMediaRetrievalController, NewMediaRetrievalController,
NewStreamController, NewStreamController,
NewBookmarksController, engine.NewNowPlayingRepository, wire.FieldsOf(new(*Router), "Artwork", "ListGenerator", "Playlists", "Streamer", "Archiver", "DataStore"), NewBookmarksController, engine.NewNowPlayingRepository, wire.FieldsOf(new(*Router), "Artwork", "ListGenerator", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"),
) )

View File

@ -19,7 +19,7 @@ var allProviders = wire.NewSet(
NewStreamController, NewStreamController,
NewBookmarksController, NewBookmarksController,
engine.NewNowPlayingRepository, engine.NewNowPlayingRepository,
wire.FieldsOf(new(*Router), "Artwork", "ListGenerator", "Playlists", "Streamer", "Archiver", "DataStore"), wire.FieldsOf(new(*Router), "Artwork", "ListGenerator", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"),
) )
func initSystemController(router *Router) *SystemController { func initSystemController(router *Router) *SystemController {