Refactor Agents to be singleton

Initial work for Last.fm scrobbler
This commit is contained in:
Deluan 2021-06-22 11:15:51 -04:00 committed by Deluan Quintão
parent f9fa9667a3
commit d5461d0ae9
17 changed files with 289 additions and 212 deletions

View File

@ -8,6 +8,7 @@ package cmd
import (
"github.com/google/wire"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/agents/lastfm"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/core/transcoder"
@ -45,11 +46,12 @@ func CreateSubsonicAPIRouter() *subsonic.Router {
mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache)
archiver := core.NewArchiver(dataStore)
players := core.NewPlayers(dataStore)
externalMetadata := core.NewExternalMetadata(dataStore)
agentsAgents := agents.New(dataStore)
externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents)
scanner := GetScanner()
broker := events.GetBroker()
scrobblerScrobbler := scrobbler.GetInstance(dataStore)
router := subsonic.New(dataStore, artwork, mediaStreamer, archiver, players, externalMetadata, scanner, broker, scrobblerScrobbler)
scrobblerBroker := scrobbler.GetBroker(dataStore)
router := subsonic.New(dataStore, artwork, mediaStreamer, archiver, players, externalMetadata, scanner, broker, scrobblerBroker)
return router
}

View File

@ -5,145 +5,147 @@ import (
"strings"
"time"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/utils"
)
type Agents struct {
ctx context.Context
ds model.DataStore
agents []Interface
}
func NewAgents(ctx context.Context) *Agents {
func New(ds model.DataStore) *Agents {
order := strings.Split(conf.Server.Agents, ",")
order = append(order, PlaceholderAgentName)
var res []Interface
for _, name := range order {
init, ok := Map[name]
if !ok {
log.Error(ctx, "Agent not available. Check configuration", "name", name)
log.Error("Agent not available. Check configuration", "name", name)
continue
}
res = append(res, init(ctx))
res = append(res, init(ds))
}
return &Agents{ctx: ctx, agents: res}
return &Agents{ds: ds, agents: res}
}
func (a *Agents) AgentName() string {
return "agents"
}
func (a *Agents) GetMBID(id string, name string) (string, error) {
func (a *Agents) GetMBID(ctx context.Context, id string, name string) (string, error) {
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(a.ctx) {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistMBIDRetriever)
if !ok {
continue
}
mbid, err := agent.GetMBID(id, name)
mbid, err := agent.GetMBID(ctx, id, name)
if mbid != "" && err == nil {
log.Debug(a.ctx, "Got MBID", "agent", ag.AgentName(), "artist", name, "mbid", mbid, "elapsed", time.Since(start))
log.Debug(ctx, "Got MBID", "agent", ag.AgentName(), "artist", name, "mbid", mbid, "elapsed", time.Since(start))
return mbid, err
}
}
return "", ErrNotFound
}
func (a *Agents) GetURL(id, name, mbid string) (string, error) {
func (a *Agents) GetURL(ctx context.Context, id, name, mbid string) (string, error) {
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(a.ctx) {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistURLRetriever)
if !ok {
continue
}
url, err := agent.GetURL(id, name, mbid)
url, err := agent.GetURL(ctx, id, name, mbid)
if url != "" && err == nil {
log.Debug(a.ctx, "Got External Url", "agent", ag.AgentName(), "artist", name, "url", url, "elapsed", time.Since(start))
log.Debug(ctx, "Got External Url", "agent", ag.AgentName(), "artist", name, "url", url, "elapsed", time.Since(start))
return url, err
}
}
return "", ErrNotFound
}
func (a *Agents) GetBiography(id, name, mbid string) (string, error) {
func (a *Agents) GetBiography(ctx context.Context, id, name, mbid string) (string, error) {
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(a.ctx) {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistBiographyRetriever)
if !ok {
continue
}
bio, err := agent.GetBiography(id, name, mbid)
bio, err := agent.GetBiography(ctx, id, name, mbid)
if bio != "" && err == nil {
log.Debug(a.ctx, "Got Biography", "agent", ag.AgentName(), "artist", name, "len", len(bio), "elapsed", time.Since(start))
log.Debug(ctx, "Got Biography", "agent", ag.AgentName(), "artist", name, "len", len(bio), "elapsed", time.Since(start))
return bio, err
}
}
return "", ErrNotFound
}
func (a *Agents) GetSimilar(id, name, mbid string, limit int) ([]Artist, error) {
func (a *Agents) GetSimilar(ctx context.Context, id, name, mbid string, limit int) ([]Artist, error) {
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(a.ctx) {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistSimilarRetriever)
if !ok {
continue
}
similar, err := agent.GetSimilar(id, name, mbid, limit)
similar, err := agent.GetSimilar(ctx, id, name, mbid, limit)
if len(similar) >= 0 && err == nil {
log.Debug(a.ctx, "Got Similar Artists", "agent", ag.AgentName(), "artist", name, "similar", similar, "elapsed", time.Since(start))
log.Debug(ctx, "Got Similar Artists", "agent", ag.AgentName(), "artist", name, "similar", similar, "elapsed", time.Since(start))
return similar, err
}
}
return nil, ErrNotFound
}
func (a *Agents) GetImages(id, name, mbid string) ([]ArtistImage, error) {
func (a *Agents) GetImages(ctx context.Context, id, name, mbid string) ([]ArtistImage, error) {
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(a.ctx) {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistImageRetriever)
if !ok {
continue
}
images, err := agent.GetImages(id, name, mbid)
images, err := agent.GetImages(ctx, id, name, mbid)
if len(images) > 0 && err == nil {
log.Debug(a.ctx, "Got Images", "agent", ag.AgentName(), "artist", name, "images", images, "elapsed", time.Since(start))
log.Debug(ctx, "Got Images", "agent", ag.AgentName(), "artist", name, "images", images, "elapsed", time.Since(start))
return images, err
}
}
return nil, ErrNotFound
}
func (a *Agents) GetTopSongs(id, artistName, mbid string, count int) ([]Song, error) {
func (a *Agents) GetTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error) {
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(a.ctx) {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistTopSongsRetriever)
if !ok {
continue
}
songs, err := agent.GetTopSongs(id, artistName, mbid, count)
songs, err := agent.GetTopSongs(ctx, id, artistName, mbid, count)
if len(songs) > 0 && err == nil {
log.Debug(a.ctx, "Got Top Songs", "agent", ag.AgentName(), "artist", artistName, "songs", songs, "elapsed", time.Since(start))
log.Debug(ctx, "Got Top Songs", "agent", ag.AgentName(), "artist", artistName, "songs", songs, "elapsed", time.Since(start))
return songs, err
}
}

View File

@ -20,14 +20,14 @@ var _ = Describe("Agents", func() {
var ag *Agents
BeforeEach(func() {
conf.Server.Agents = ""
ag = NewAgents(ctx)
ag = New(ctx)
})
It("calls the placeholder GetBiography", func() {
Expect(ag.GetBiography("123", "John Doe", "mb123")).To(Equal(placeholderBiography))
Expect(ag.GetBiography(ctx, "123", "John Doe", "mb123")).To(Equal(placeholderBiography))
})
It("calls the placeholder GetImages", func() {
images, err := ag.GetImages("123", "John Doe", "mb123")
images, err := ag.GetImages(ctx, "123", "John Doe", "mb123")
Expect(err).ToNot(HaveOccurred())
Expect(images).To(HaveLen(3))
for _, i := range images {
@ -50,24 +50,24 @@ var _ = Describe("Agents", func() {
}{}
})
conf.Server.Agents = "empty,fake"
ag = NewAgents(ctx)
ag = New(ctx)
Expect(ag.AgentName()).To(Equal("agents"))
})
Describe("GetMBID", func() {
It("returns on first match", func() {
Expect(ag.GetMBID("123", "test")).To(Equal("mbid"))
Expect(ag.GetMBID(ctx, "123", "test")).To(Equal("mbid"))
Expect(mock.Args).To(ConsistOf("123", "test"))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetMBID("123", "test")
_, err := ag.GetMBID(ctx, "123", "test")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetMBID("123", "test")
_, err := ag.GetMBID(ctx, "123", "test")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
@ -75,18 +75,18 @@ var _ = Describe("Agents", func() {
Describe("GetURL", func() {
It("returns on first match", func() {
Expect(ag.GetURL("123", "test", "mb123")).To(Equal("url"))
Expect(ag.GetURL(ctx, "123", "test", "mb123")).To(Equal("url"))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetURL("123", "test", "mb123")
_, err := ag.GetURL(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetURL("123", "test", "mb123")
_, err := ag.GetURL(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
@ -94,17 +94,17 @@ var _ = Describe("Agents", func() {
Describe("GetBiography", func() {
It("returns on first match", func() {
Expect(ag.GetBiography("123", "test", "mb123")).To(Equal("bio"))
Expect(ag.GetBiography(ctx, "123", "test", "mb123")).To(Equal("bio"))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
Expect(ag.GetBiography("123", "test", "mb123")).To(Equal(placeholderBiography))
Expect(ag.GetBiography(ctx, "123", "test", "mb123")).To(Equal(placeholderBiography))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetBiography("123", "test", "mb123")
_, err := ag.GetBiography(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
@ -112,7 +112,7 @@ var _ = Describe("Agents", func() {
Describe("GetImages", func() {
It("returns on first match", func() {
Expect(ag.GetImages("123", "test", "mb123")).To(Equal([]ArtistImage{{
Expect(ag.GetImages(ctx, "123", "test", "mb123")).To(Equal([]ArtistImage{{
URL: "imageUrl",
Size: 100,
}}))
@ -120,12 +120,12 @@ var _ = Describe("Agents", func() {
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
Expect(ag.GetImages("123", "test", "mb123")).To(HaveLen(3))
Expect(ag.GetImages(ctx, "123", "test", "mb123")).To(HaveLen(3))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetImages("123", "test", "mb123")
_, err := ag.GetImages(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
@ -133,7 +133,7 @@ var _ = Describe("Agents", func() {
Describe("GetSimilar", func() {
It("returns on first match", func() {
Expect(ag.GetSimilar("123", "test", "mb123", 1)).To(Equal([]Artist{{
Expect(ag.GetSimilar(ctx, "123", "test", "mb123", 1)).To(Equal([]Artist{{
Name: "Joe Dohn",
MBID: "mbid321",
}}))
@ -141,13 +141,13 @@ var _ = Describe("Agents", func() {
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetSimilar("123", "test", "mb123", 1)
_, err := ag.GetSimilar(ctx, "123", "test", "mb123", 1)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123", 1))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetSimilar("123", "test", "mb123", 1)
_, err := ag.GetSimilar(ctx, "123", "test", "mb123", 1)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
@ -155,7 +155,7 @@ var _ = Describe("Agents", func() {
Describe("GetTopSongs", func() {
It("returns on first match", func() {
Expect(ag.GetTopSongs("123", "test", "mb123", 2)).To(Equal([]Song{{
Expect(ag.GetTopSongs(ctx, "123", "test", "mb123", 2)).To(Equal([]Song{{
Name: "A Song",
MBID: "mbid444",
}}))
@ -163,13 +163,13 @@ var _ = Describe("Agents", func() {
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetTopSongs("123", "test", "mb123", 2)
_, err := ag.GetTopSongs(ctx, "123", "test", "mb123", 2)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123", 2))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetTopSongs("123", "test", "mb123", 2)
_, err := ag.GetTopSongs(ctx, "123", "test", "mb123", 2)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
@ -186,7 +186,7 @@ func (a *mockAgent) AgentName() string {
return "fake"
}
func (a *mockAgent) GetMBID(id string, name string) (string, error) {
func (a *mockAgent) GetMBID(ctx context.Context, id string, name string) (string, error) {
a.Args = []interface{}{id, name}
if a.Err != nil {
return "", a.Err
@ -194,7 +194,7 @@ func (a *mockAgent) GetMBID(id string, name string) (string, error) {
return "mbid", nil
}
func (a *mockAgent) GetURL(id, name, mbid string) (string, error) {
func (a *mockAgent) GetURL(ctx context.Context, id, name, mbid string) (string, error) {
a.Args = []interface{}{id, name, mbid}
if a.Err != nil {
return "", a.Err
@ -202,7 +202,7 @@ func (a *mockAgent) GetURL(id, name, mbid string) (string, error) {
return "url", nil
}
func (a *mockAgent) GetBiography(id, name, mbid string) (string, error) {
func (a *mockAgent) GetBiography(ctx context.Context, id, name, mbid string) (string, error) {
a.Args = []interface{}{id, name, mbid}
if a.Err != nil {
return "", a.Err
@ -210,7 +210,7 @@ func (a *mockAgent) GetBiography(id, name, mbid string) (string, error) {
return "bio", nil
}
func (a *mockAgent) GetImages(id, name, mbid string) ([]ArtistImage, error) {
func (a *mockAgent) GetImages(ctx context.Context, id, name, mbid string) ([]ArtistImage, error) {
a.Args = []interface{}{id, name, mbid}
if a.Err != nil {
return nil, a.Err
@ -221,7 +221,7 @@ func (a *mockAgent) GetImages(id, name, mbid string) ([]ArtistImage, error) {
}}, nil
}
func (a *mockAgent) GetSimilar(id, name, mbid string, limit int) ([]Artist, error) {
func (a *mockAgent) GetSimilar(ctx context.Context, id, name, mbid string, limit int) ([]Artist, error) {
a.Args = []interface{}{id, name, mbid, limit}
if a.Err != nil {
return nil, a.Err
@ -232,7 +232,7 @@ func (a *mockAgent) GetSimilar(id, name, mbid string, limit int) ([]Artist, erro
}}, nil
}
func (a *mockAgent) GetTopSongs(id, artistName, mbid string, count int) ([]Song, error) {
func (a *mockAgent) GetTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error) {
a.Args = []interface{}{id, artistName, mbid, count}
if a.Err != nil {
return nil, a.Err

View File

@ -3,9 +3,11 @@ package agents
import (
"context"
"errors"
"github.com/navidrome/navidrome/model"
)
type Constructor func(ctx context.Context) Interface
type Constructor func(ds model.DataStore) Interface
type Interface interface {
AgentName() string
@ -31,27 +33,27 @@ var (
)
type ArtistMBIDRetriever interface {
GetMBID(id string, name string) (string, error)
GetMBID(ctx context.Context, id string, name string) (string, error)
}
type ArtistURLRetriever interface {
GetURL(id, name, mbid string) (string, error)
GetURL(ctx context.Context, id, name, mbid string) (string, error)
}
type ArtistBiographyRetriever interface {
GetBiography(id, name, mbid string) (string, error)
GetBiography(ctx context.Context, id, name, mbid string) (string, error)
}
type ArtistSimilarRetriever interface {
GetSimilar(id, name, mbid string, limit int) ([]Artist, error)
GetSimilar(ctx context.Context, id, name, mbid string, limit int) ([]Artist, error)
}
type ArtistImageRetriever interface {
GetImages(id, name, mbid string) ([]ArtistImage, error)
GetImages(ctx context.Context, id, name, mbid string) ([]ArtistImage, error)
}
type ArtistTopSongsRetriever interface {
GetTopSongs(id, artistName, mbid string, count int) ([]Song, error)
GetTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error)
}
var Map map[string]Constructor

View File

@ -7,7 +7,9 @@ import (
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils"
)
@ -17,15 +19,16 @@ const (
type lastfmAgent struct {
ctx context.Context
ds model.DataStore
apiKey string
secret string
lang string
client *Client
}
func lastFMConstructor(ctx context.Context) agents.Interface {
func lastFMConstructor(ds model.DataStore) *lastfmAgent {
l := &lastfmAgent{
ctx: ctx,
ds: ds,
lang: conf.Server.LastFM.Language,
apiKey: conf.Server.LastFM.ApiKey,
secret: conf.Server.LastFM.Secret,
@ -39,7 +42,7 @@ func (l *lastfmAgent) AgentName() string {
return lastFMAgentName
}
func (l *lastfmAgent) GetMBID(id string, name string) (string, error) {
func (l *lastfmAgent) GetMBID(ctx context.Context, id string, name string) (string, error) {
a, err := l.callArtistGetInfo(name, "")
if err != nil {
return "", err
@ -50,7 +53,7 @@ func (l *lastfmAgent) GetMBID(id string, name string) (string, error) {
return a.MBID, nil
}
func (l *lastfmAgent) GetURL(id, name, mbid string) (string, error) {
func (l *lastfmAgent) GetURL(ctx context.Context, id, name, mbid string) (string, error) {
a, err := l.callArtistGetInfo(name, mbid)
if err != nil {
return "", err
@ -61,7 +64,7 @@ func (l *lastfmAgent) GetURL(id, name, mbid string) (string, error) {
return a.URL, nil
}
func (l *lastfmAgent) GetBiography(id, name, mbid string) (string, error) {
func (l *lastfmAgent) GetBiography(ctx context.Context, id, name, mbid string) (string, error) {
a, err := l.callArtistGetInfo(name, mbid)
if err != nil {
return "", err
@ -72,7 +75,7 @@ func (l *lastfmAgent) GetBiography(id, name, mbid string) (string, error) {
return a.Bio.Summary, nil
}
func (l *lastfmAgent) GetSimilar(id, name, mbid string, limit int) ([]agents.Artist, error) {
func (l *lastfmAgent) GetSimilar(ctx context.Context, id, name, mbid string, limit int) ([]agents.Artist, error) {
resp, err := l.callArtistGetSimilar(name, mbid, limit)
if err != nil {
return nil, err
@ -90,7 +93,7 @@ func (l *lastfmAgent) GetSimilar(id, name, mbid string, limit int) ([]agents.Art
return res, nil
}
func (l *lastfmAgent) GetTopSongs(id, artistName, mbid string, count int) ([]agents.Song, error) {
func (l *lastfmAgent) GetTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]agents.Song, error) {
resp, err := l.callArtistGetTopTracks(artistName, mbid, count)
if err != nil {
return nil, err
@ -151,10 +154,23 @@ func (l *lastfmAgent) callArtistGetTopTracks(artistName, mbid string, count int)
return t.Track, nil
}
func (l *lastfmAgent) NowPlaying(c context.Context, track *model.MediaFile) error {
return nil
}
func (l *lastfmAgent) Scrobble(ctx context.Context, scrobbles []scrobbler.Scrobble) error {
return nil
}
func init() {
conf.AddHook(func() {
if conf.Server.LastFM.Enabled {
agents.Register(lastFMAgentName, lastFMConstructor)
agents.Register(lastFMAgentName, func(ds model.DataStore) agents.Interface {
return lastFMConstructor(ds)
})
scrobbler.Register(lastFMAgentName, func(ds model.DataStore) scrobbler.Scrobbler {
return lastFMConstructor(ds)
})
}
})
}

View File

@ -46,14 +46,14 @@ var _ = Describe("lastfmAgent", func() {
It("returns the biography", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetBiography("123", "U2", "mbid-1234")).To(Equal("U2 é uma das mais importantes bandas de rock de todos os tempos. Formada em 1976 em Dublin, composta por Bono (vocalista e guitarrista), The Edge (guitarrista, pianista e backing vocal), Adam Clayton (baixista), Larry Mullen, Jr. (baterista e percussionista).\n\nDesde a década de 80, U2 é uma das bandas mais populares no mundo. Seus shows são únicos e um verdadeiro festival de efeitos especiais, além de serem um dos que mais arrecadam anualmente. <a href=\"https://www.last.fm/music/U2\">Read more on Last.fm</a>"))
Expect(agent.GetBiography(ctx, "123", "U2", "mbid-1234")).To(Equal("U2 é uma das mais importantes bandas de rock de todos os tempos. Formada em 1976 em Dublin, composta por Bono (vocalista e guitarrista), The Edge (guitarrista, pianista e backing vocal), Adam Clayton (baixista), Larry Mullen, Jr. (baterista e percussionista).\n\nDesde a década de 80, U2 é uma das bandas mais populares no mundo. Seus shows são únicos e um verdadeiro festival de efeitos especiais, além de serem um dos que mais arrecadam anualmente. <a href=\"https://www.last.fm/music/U2\">Read more on Last.fm</a>"))
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.FM call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetBiography("123", "U2", "mbid-1234")
_, err := agent.GetBiography(ctx, "123", "U2", "mbid-1234")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
@ -61,7 +61,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call returns an error", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetBiography("123", "U2", "mbid-1234")
_, err := agent.GetBiography(ctx, "123", "U2", "mbid-1234")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
@ -69,7 +69,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetBiography("123", "U2", "")
_, err := agent.GetBiography(ctx, "123", "U2", "")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
@ -78,13 +78,13 @@ var _ = Describe("lastfmAgent", func() {
It("calls again when the response is artist == [unknown]", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.unknown.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
_, _ = agent.GetBiography("123", "U2", "mbid-1234")
_, _ = agent.GetBiography(ctx, "123", "U2", "mbid-1234")
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetBiography("123", "U2", "mbid-1234")
_, _ = agent.GetBiography(ctx, "123", "U2", "mbid-1234")
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
@ -104,7 +104,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns similar artists", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getsimilar.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetSimilar("123", "U2", "mbid-1234", 2)).To(Equal([]agents.Artist{
Expect(agent.GetSimilar(ctx, "123", "U2", "mbid-1234", 2)).To(Equal([]agents.Artist{
{Name: "Passengers", MBID: "e110c11f-1c94-4471-a350-c38f46b29389"},
{Name: "INXS", MBID: "481bf5f9-2e7c-4c44-b08a-05b32bc7c00d"},
}))
@ -114,7 +114,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetSimilar("123", "U2", "mbid-1234", 2)
_, err := agent.GetSimilar(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
@ -122,7 +122,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call returns an error", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetSimilar("123", "U2", "mbid-1234", 2)
_, err := agent.GetSimilar(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
@ -130,7 +130,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetSimilar("123", "U2", "", 2)
_, err := agent.GetSimilar(ctx, "123", "U2", "", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
@ -139,13 +139,13 @@ var _ = Describe("lastfmAgent", func() {
It("calls again when the response is artist == [unknown]", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getsimilar.unknown.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
_, _ = agent.GetSimilar("123", "U2", "mbid-1234", 2)
_, _ = agent.GetSimilar(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetSimilar("123", "U2", "mbid-1234", 2)
_, _ = agent.GetSimilar(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
@ -165,7 +165,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns top songs", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetTopSongs("123", "U2", "mbid-1234", 2)).To(Equal([]agents.Song{
Expect(agent.GetTopSongs(ctx, "123", "U2", "mbid-1234", 2)).To(Equal([]agents.Song{
{Name: "Beautiful Day", MBID: "f7f264d0-a89b-4682-9cd7-a4e7c37637af"},
{Name: "With or Without You", MBID: "6b9a509f-6907-4a6e-9345-2f12da09ba4b"},
}))
@ -175,7 +175,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetTopSongs("123", "U2", "mbid-1234", 2)
_, err := agent.GetTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
@ -183,7 +183,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call returns an error", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetTopSongs("123", "U2", "mbid-1234", 2)
_, err := agent.GetTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
@ -191,7 +191,7 @@ var _ = Describe("lastfmAgent", func() {
It("returns an error if Last.FM call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetTopSongs("123", "U2", "", 2)
_, err := agent.GetTopSongs(ctx, "123", "U2", "", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
@ -200,13 +200,13 @@ var _ = Describe("lastfmAgent", func() {
It("calls again when the response is artist == [unknown]", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.unknown.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
_, _ = agent.GetTopSongs("123", "U2", "mbid-1234", 2)
_, _ = agent.GetTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: ioutil.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetTopSongs("123", "U2", "mbid-1234", 2)
_, _ = agent.GetTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})

View File

@ -2,6 +2,8 @@ package agents
import (
"context"
"github.com/navidrome/navidrome/model"
)
const PlaceholderAgentName = "placeholder"
@ -15,7 +17,7 @@ const (
type placeholderAgent struct{}
func placeholdersConstructor(ctx context.Context) Interface {
func placeholdersConstructor(ds model.DataStore) Interface {
return &placeholderAgent{}
}
@ -23,11 +25,11 @@ func (p *placeholderAgent) AgentName() string {
return PlaceholderAgentName
}
func (p *placeholderAgent) GetBiography(id, name, mbid string) (string, error) {
func (p *placeholderAgent) GetBiography(ctx context.Context, id, name, mbid string) (string, error) {
return placeholderBiography, nil
}
func (p *placeholderAgent) GetImages(id, name, mbid string) ([]ArtistImage, error) {
func (p *placeholderAgent) GetImages(ctx context.Context, id, name, mbid string) ([]ArtistImage, error) {
return []ArtistImage{
{placeholderArtistImageLargeUrl, 300},
{placeholderArtistImageMediumUrl, 174},

View File

@ -19,15 +19,15 @@ import (
const spotifyAgentName = "spotify"
type spotifyAgent struct {
ctx context.Context
ds model.DataStore
id string
secret string
client *Client
}
func spotifyConstructor(ctx context.Context) agents.Interface {
func spotifyConstructor(ds model.DataStore) agents.Interface {
l := &spotifyAgent{
ctx: ctx,
ds: ds,
id: conf.Server.Spotify.ID,
secret: conf.Server.Spotify.Secret,
}
@ -40,13 +40,13 @@ func (s *spotifyAgent) AgentName() string {
return spotifyAgentName
}
func (s *spotifyAgent) GetImages(id, name, mbid string) ([]agents.ArtistImage, error) {
a, err := s.searchArtist(name)
func (s *spotifyAgent) GetImages(ctx context.Context, id, name, mbid string) ([]agents.ArtistImage, error) {
a, err := s.searchArtist(ctx, name)
if err != nil {
if err == model.ErrNotFound {
log.Warn(s.ctx, "Artist not found in Spotify", "artist", name)
log.Warn(ctx, "Artist not found in Spotify", "artist", name)
} else {
log.Error(s.ctx, "Error calling Spotify", "artist", name, err)
log.Error(ctx, "Error calling Spotify", "artist", name, err)
}
return nil, err
}
@ -61,8 +61,8 @@ func (s *spotifyAgent) GetImages(id, name, mbid string) ([]agents.ArtistImage, e
return res, nil
}
func (s *spotifyAgent) searchArtist(name string) (*Artist, error) {
artists, err := s.client.SearchArtists(s.ctx, name, 40)
func (s *spotifyAgent) searchArtist(ctx context.Context, name string) (*Artist, error) {
artists, err := s.client.SearchArtists(ctx, name, 40)
if err != nil || len(artists) == 0 {
return nil, model.ErrNotFound
}

View File

@ -31,6 +31,7 @@ type ExternalMetadata interface {
type externalMetadata struct {
ds model.DataStore
ag *agents.Agents
}
type auxArtist struct {
@ -38,8 +39,8 @@ type auxArtist struct {
Name string
}
func NewExternalMetadata(ds model.DataStore) ExternalMetadata {
return &externalMetadata{ds: ds}
func NewExternalMetadata(ds model.DataStore, agents *agents.Agents) ExternalMetadata {
return &externalMetadata{ds: ds, ag: agents}
}
func (e *externalMetadata) getArtist(ctx context.Context, id string) (*auxArtist, error) {
@ -106,11 +107,9 @@ func (e *externalMetadata) UpdateArtistInfo(ctx context.Context, id string, simi
}
func (e *externalMetadata) refreshArtistInfo(ctx context.Context, artist *auxArtist) error {
ag := agents.NewAgents(ctx)
// Get MBID first, if it is not yet available
if artist.MbzArtistID == "" {
mbid, err := ag.GetMBID(artist.ID, artist.Name)
mbid, err := e.ag.GetMBID(ctx, artist.ID, artist.Name)
if mbid != "" && err == nil {
artist.MbzArtistID = mbid
}
@ -118,10 +117,10 @@ func (e *externalMetadata) refreshArtistInfo(ctx context.Context, artist *auxArt
// Call all registered agents and collect information
callParallel([]func(){
func() { e.callGetBiography(ctx, ag, artist) },
func() { e.callGetURL(ctx, ag, artist) },
func() { e.callGetImage(ctx, ag, artist) },
func() { e.callGetSimilar(ctx, ag, artist, maxSimilarArtists, true) },
func() { e.callGetBiography(ctx, e.ag, artist) },
func() { e.callGetURL(ctx, e.ag, artist) },
func() { e.callGetImage(ctx, e.ag, artist) },
func() { e.callGetSimilar(ctx, e.ag, artist, maxSimilarArtists, true) },
})
if utils.IsCtxDone(ctx) {
@ -152,13 +151,12 @@ func callParallel(fs []func()) {
}
func (e *externalMetadata) SimilarSongs(ctx context.Context, id string, count int) (model.MediaFiles, error) {
ag := agents.NewAgents(ctx)
artist, err := e.getArtist(ctx, id)
if err != nil {
return nil, err
}
e.callGetSimilar(ctx, ag, artist, 15, false)
e.callGetSimilar(ctx, e.ag, artist, 15, false)
if utils.IsCtxDone(ctx) {
log.Warn(ctx, "SimilarSongs call canceled", ctx.Err())
return nil, ctx.Err()
@ -175,7 +173,7 @@ func (e *externalMetadata) SimilarSongs(ctx context.Context, id string, count in
}
topCount := utils.MaxInt(count, 20)
topSongs, err := e.getMatchingTopSongs(ctx, ag, &auxArtist{Name: a.Name, Artist: a}, topCount)
topSongs, err := e.getMatchingTopSongs(ctx, e.ag, &auxArtist{Name: a.Name, Artist: a}, topCount)
if err != nil {
log.Warn(ctx, "Error getting artist's top songs", "artist", a.Name, err)
continue
@ -202,18 +200,17 @@ func (e *externalMetadata) SimilarSongs(ctx context.Context, id string, count in
}
func (e *externalMetadata) TopSongs(ctx context.Context, artistName string, count int) (model.MediaFiles, error) {
ag := agents.NewAgents(ctx)
artist, err := e.findArtistByName(ctx, artistName)
if err != nil {
log.Error(ctx, "Artist not found", "name", artistName, err)
return nil, nil
}
return e.getMatchingTopSongs(ctx, ag, artist, count)
return e.getMatchingTopSongs(ctx, e.ag, artist, count)
}
func (e *externalMetadata) getMatchingTopSongs(ctx context.Context, agent agents.ArtistTopSongsRetriever, artist *auxArtist, count int) (model.MediaFiles, error) {
songs, err := agent.GetTopSongs(artist.ID, artist.Name, artist.MbzArtistID, count)
songs, err := agent.GetTopSongs(ctx, artist.ID, artist.Name, artist.MbzArtistID, count)
if err != nil {
return nil, err
}
@ -258,7 +255,7 @@ func (e *externalMetadata) findMatchingTrack(ctx context.Context, mbid string, a
}
func (e *externalMetadata) callGetURL(ctx context.Context, agent agents.ArtistURLRetriever, artist *auxArtist) {
url, err := agent.GetURL(artist.ID, artist.Name, artist.MbzArtistID)
url, err := agent.GetURL(ctx, artist.ID, artist.Name, artist.MbzArtistID)
if url == "" || err != nil {
return
}
@ -266,7 +263,7 @@ func (e *externalMetadata) callGetURL(ctx context.Context, agent agents.ArtistUR
}
func (e *externalMetadata) callGetBiography(ctx context.Context, agent agents.ArtistBiographyRetriever, artist *auxArtist) {
bio, err := agent.GetBiography(artist.ID, clearName(artist.Name), artist.MbzArtistID)
bio, err := agent.GetBiography(ctx, artist.ID, clearName(artist.Name), artist.MbzArtistID)
if bio == "" || err != nil {
return
}
@ -277,7 +274,7 @@ func (e *externalMetadata) callGetBiography(ctx context.Context, agent agents.Ar
}
func (e *externalMetadata) callGetImage(ctx context.Context, agent agents.ArtistImageRetriever, artist *auxArtist) {
images, err := agent.GetImages(artist.ID, artist.Name, artist.MbzArtistID)
images, err := agent.GetImages(ctx, artist.ID, artist.Name, artist.MbzArtistID)
if len(images) == 0 || err != nil {
return
}
@ -296,7 +293,7 @@ func (e *externalMetadata) callGetImage(ctx context.Context, agent agents.Artist
func (e *externalMetadata) callGetSimilar(ctx context.Context, agent agents.ArtistSimilarRetriever, artist *auxArtist,
limit int, includeNotPresent bool) {
similar, err := agent.GetSimilar(artist.ID, artist.Name, artist.MbzArtistID, limit)
similar, err := agent.GetSimilar(ctx, artist.ID, artist.Name, artist.MbzArtistID, limit)
if len(similar) == 0 || err != nil {
return
}

110
core/scrobbler/broker.go Normal file
View File

@ -0,0 +1,110 @@
package scrobbler
import (
"context"
"sort"
"time"
"github.com/navidrome/navidrome/log"
"github.com/ReneKroon/ttlcache/v2"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/utils/singleton"
)
const nowPlayingExpire = 60 * time.Minute
type NowPlayingInfo struct {
TrackID string
Start time.Time
Username string
PlayerId string
PlayerName string
}
type Broker interface {
NowPlaying(ctx context.Context, playerId string, playerName string, trackId string) error
GetNowPlaying(ctx context.Context) ([]NowPlayingInfo, error)
Submit(ctx context.Context, playerId int, trackId string, playTime time.Time) error
}
type broker struct {
ds model.DataStore
playMap *ttlcache.Cache
}
func GetBroker(ds model.DataStore) Broker {
instance := singleton.Get(broker{}, func() interface{} {
m := ttlcache.NewCache()
m.SkipTTLExtensionOnHit(true)
_ = m.SetTTL(nowPlayingExpire)
return &broker{ds: ds, playMap: m}
})
return instance.(*broker)
}
func (s *broker) NowPlaying(ctx context.Context, playerId string, playerName string, trackId string) error {
username, _ := request.UsernameFrom(ctx)
info := NowPlayingInfo{
TrackID: trackId,
Start: time.Now(),
Username: username,
PlayerId: playerId,
PlayerName: playerName,
}
_ = s.playMap.Set(playerId, info)
s.dispatchNowPlaying(ctx, trackId)
return nil
}
func (s *broker) dispatchNowPlaying(ctx context.Context, trackId string) {
t, err := s.ds.MediaFile(ctx).Get(trackId)
if err != nil {
log.Error(ctx, "Error retrieving mediaFile", "id", trackId, err)
return
}
// TODO Parallelize
for name, constructor := range scrobblers {
log.Debug(ctx, "Sending NowPlaying info", "scrobbler", name, "track", t.Title, "artist", t.Artist)
err := func() error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
s := constructor(s.ds)
return s.NowPlaying(ctx, t)
}()
if err != nil {
log.Error(ctx, "Error sending NowPlayingInfo", "scrobbler", name, "track", t.Title, "artist", t.Artist, err)
return
}
}
}
func (s *broker) GetNowPlaying(ctx context.Context) ([]NowPlayingInfo, error) {
var res []NowPlayingInfo
for _, playerId := range s.playMap.GetKeys() {
value, err := s.playMap.Get(playerId)
if err != nil {
continue
}
info := value.(NowPlayingInfo)
res = append(res, info)
}
sort.Slice(res, func(i, j int) bool {
return res[i].Start.After(res[j].Start)
})
return res, nil
}
func (s *broker) Submit(ctx context.Context, playerId int, trackId string, playTime time.Time) error {
panic("implement me")
}
var scrobblers map[string]Constructor
func Register(name string, init Constructor) {
if scrobblers == nil {
scrobblers = make(map[string]Constructor)
}
scrobblers[name] = init
}

View File

@ -0,0 +1,20 @@
package scrobbler
import (
"context"
"time"
"github.com/navidrome/navidrome/model"
)
type Scrobble struct {
Track *model.MediaFile
TimeStamp *time.Time
}
type Scrobbler interface {
NowPlaying(context.Context, *model.MediaFile) error
Scrobble(context.Context, []Scrobble) error
}
type Constructor func(ds model.DataStore) Scrobbler

View File

@ -1,76 +0,0 @@
package scrobbler
import (
"context"
"sort"
"time"
"github.com/ReneKroon/ttlcache/v2"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/utils/singleton"
)
const nowPlayingExpire = 60 * time.Minute
type NowPlayingInfo struct {
TrackID string
Start time.Time
Username string
PlayerId string
PlayerName string
}
type Scrobbler interface {
NowPlaying(ctx context.Context, playerId string, playerName string, trackId string) error
GetNowPlaying(ctx context.Context) ([]NowPlayingInfo, error)
Submit(ctx context.Context, playerId int, trackId string, playTime time.Time) error
}
type scrobbler struct {
ds model.DataStore
playMap *ttlcache.Cache
}
func GetInstance(ds model.DataStore) Scrobbler {
instance := singleton.Get(scrobbler{}, func() interface{} {
m := ttlcache.NewCache()
m.SkipTTLExtensionOnHit(true)
_ = m.SetTTL(nowPlayingExpire)
return &scrobbler{ds: ds, playMap: m}
})
return instance.(*scrobbler)
}
func (s *scrobbler) NowPlaying(ctx context.Context, playerId string, playerName string, trackId string) error {
username, _ := request.UsernameFrom(ctx)
info := NowPlayingInfo{
TrackID: trackId,
Start: time.Now(),
Username: username,
PlayerId: playerId,
PlayerName: playerName,
}
_ = s.playMap.Set(playerId, info)
return nil
}
func (s *scrobbler) GetNowPlaying(ctx context.Context) ([]NowPlayingInfo, error) {
var res []NowPlayingInfo
for _, playerId := range s.playMap.GetKeys() {
value, err := s.playMap.Get(playerId)
if err != nil {
continue
}
info := value.(NowPlayingInfo)
res = append(res, info)
}
sort.Slice(res, func(i, j int) bool {
return res[i].Start.After(res[j].Start)
})
return res, nil
}
func (s *scrobbler) Submit(ctx context.Context, playerId int, trackId string, playTime time.Time) error {
panic("implement me")
}

View File

@ -2,6 +2,7 @@ package core
import (
"github.com/google/wire"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/core/transcoder"
)
@ -15,7 +16,8 @@ var Set = wire.NewSet(
NewExternalMetadata,
NewCacheWarmer,
NewPlayers,
agents.New,
transcoder.New,
scrobbler.GetInstance,
scrobbler.GetBroker,
NewShare,
)

View File

@ -16,10 +16,10 @@ import (
type AlbumListController struct {
ds model.DataStore
scrobbler scrobbler.Scrobbler
scrobbler scrobbler.Broker
}
func NewAlbumListController(ds model.DataStore, scrobbler scrobbler.Scrobbler) *AlbumListController {
func NewAlbumListController(ds model.DataStore, scrobbler scrobbler.Broker) *AlbumListController {
c := &AlbumListController{
ds: ds,
scrobbler: scrobbler,

View File

@ -34,11 +34,11 @@ type Router struct {
ExternalMetadata core.ExternalMetadata
Scanner scanner.Scanner
Broker events.Broker
Scrobbler scrobbler.Scrobbler
Scrobbler scrobbler.Broker
}
func New(ds model.DataStore, artwork core.Artwork, streamer core.MediaStreamer, archiver core.Archiver, players core.Players,
externalMetadata core.ExternalMetadata, scanner scanner.Scanner, broker events.Broker, scrobbler scrobbler.Scrobbler) *Router {
externalMetadata core.ExternalMetadata, scanner scanner.Scanner, broker events.Broker, scrobbler scrobbler.Broker) *Router {
r := &Router{
DataStore: ds,
Artwork: artwork,

View File

@ -18,11 +18,11 @@ import (
type MediaAnnotationController struct {
ds model.DataStore
scrobbler scrobbler.Scrobbler
scrobbler scrobbler.Broker
broker events.Broker
}
func NewMediaAnnotationController(ds model.DataStore, scrobbler scrobbler.Scrobbler, broker events.Broker) *MediaAnnotationController {
func NewMediaAnnotationController(ds model.DataStore, scrobbler scrobbler.Broker, broker events.Broker) *MediaAnnotationController {
return &MediaAnnotationController{ds: ds, scrobbler: scrobbler, broker: broker}
}

View File

@ -25,16 +25,16 @@ func initBrowsingController(router *Router) *BrowsingController {
func initAlbumListController(router *Router) *AlbumListController {
dataStore := router.DataStore
scrobbler := router.Scrobbler
albumListController := NewAlbumListController(dataStore, scrobbler)
broker := router.Scrobbler
albumListController := NewAlbumListController(dataStore, broker)
return albumListController
}
func initMediaAnnotationController(router *Router) *MediaAnnotationController {
dataStore := router.DataStore
scrobbler := router.Scrobbler
broker := router.Broker
mediaAnnotationController := NewMediaAnnotationController(dataStore, scrobbler, broker)
broker := router.Scrobbler
eventsBroker := router.Broker
mediaAnnotationController := NewMediaAnnotationController(dataStore, broker, eventsBroker)
return mediaAnnotationController
}