New implementation of NowPlaying
This commit is contained in:
@@ -1,106 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const NowPlayingExpire = 60 * time.Minute
|
||||
|
||||
type NowPlayingInfo struct {
|
||||
TrackID string
|
||||
Start time.Time
|
||||
Username string
|
||||
PlayerId int
|
||||
PlayerName string
|
||||
}
|
||||
|
||||
// This repo must have the semantics of a FIFO queue, for each playerId
|
||||
type NowPlaying interface {
|
||||
// Insert at the head of the queue
|
||||
Enqueue(*NowPlayingInfo) error
|
||||
|
||||
// Returns all heads from all playerIds
|
||||
GetAll() ([]*NowPlayingInfo, error)
|
||||
}
|
||||
|
||||
var playerMap = sync.Map{}
|
||||
|
||||
type nowPlayingRepository struct{}
|
||||
|
||||
func NewNowPlayingRepository() NowPlaying {
|
||||
r := &nowPlayingRepository{}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) Enqueue(info *NowPlayingInfo) error {
|
||||
l := r.getList(info.PlayerId)
|
||||
l.PushFront(info)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) GetAll() ([]*NowPlayingInfo, error) {
|
||||
var all []*NowPlayingInfo
|
||||
playerMap.Range(func(playerId, l interface{}) bool {
|
||||
ll := l.(*list.List)
|
||||
e := checkExpired(ll, ll.Front)
|
||||
if e != nil {
|
||||
all = append(all, e.Value.(*NowPlayingInfo))
|
||||
}
|
||||
return true
|
||||
})
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) getList(id int) *list.List {
|
||||
l, _ := playerMap.LoadOrStore(id, list.New())
|
||||
return l.(*list.List)
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) dequeue(playerId int) (*NowPlayingInfo, error) {
|
||||
l := r.getList(playerId)
|
||||
e := checkExpired(l, l.Back)
|
||||
if e == nil {
|
||||
return nil, nil
|
||||
}
|
||||
l.Remove(e)
|
||||
return e.Value.(*NowPlayingInfo), nil
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) head(playerId int) (*NowPlayingInfo, error) {
|
||||
l := r.getList(playerId)
|
||||
e := checkExpired(l, l.Front)
|
||||
if e == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return e.Value.(*NowPlayingInfo), nil
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) tail(playerId int) (*NowPlayingInfo, error) {
|
||||
l := r.getList(playerId)
|
||||
e := checkExpired(l, l.Back)
|
||||
if e == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return e.Value.(*NowPlayingInfo), nil
|
||||
}
|
||||
|
||||
func (r *nowPlayingRepository) count(playerId int) (int64, error) {
|
||||
l := r.getList(playerId)
|
||||
return int64(l.Len()), nil
|
||||
}
|
||||
|
||||
func checkExpired(l *list.List, f func() *list.Element) *list.Element {
|
||||
for {
|
||||
e := f()
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
start := e.Value.(*NowPlayingInfo).Start
|
||||
if time.Since(start) < NowPlayingExpire {
|
||||
return e
|
||||
}
|
||||
l.Remove(e)
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("NowPlaying", func() {
|
||||
var repo *nowPlayingRepository
|
||||
var now = time.Now()
|
||||
var past = time.Time{}
|
||||
|
||||
BeforeEach(func() {
|
||||
playerMap = sync.Map{}
|
||||
repo = NewNowPlayingRepository().(*nowPlayingRepository)
|
||||
})
|
||||
|
||||
It("enqueues and dequeues records", func() {
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})).To(BeNil())
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "BBB", Start: now})).To(BeNil())
|
||||
|
||||
Expect(repo.tail(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now}))
|
||||
Expect(repo.head(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "BBB", Start: now}))
|
||||
|
||||
Expect(repo.count(1)).To(Equal(int64(2)))
|
||||
|
||||
Expect(repo.dequeue(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now}))
|
||||
Expect(repo.count(1)).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("handles multiple players", func() {
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})).To(BeNil())
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "BBB", Start: now})).To(BeNil())
|
||||
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 2, TrackID: "CCC", Start: now})).To(BeNil())
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 2, TrackID: "DDD", Start: now})).To(BeNil())
|
||||
|
||||
Expect(repo.GetAll()).To(ConsistOf([]*NowPlayingInfo{
|
||||
{PlayerId: 1, TrackID: "BBB", Start: now},
|
||||
{PlayerId: 2, TrackID: "DDD", Start: now},
|
||||
}))
|
||||
|
||||
Expect(repo.count(2)).To(Equal(int64(2)))
|
||||
Expect(repo.count(2)).To(Equal(int64(2)))
|
||||
|
||||
Expect(repo.tail(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now}))
|
||||
Expect(repo.head(2)).To(Equal(&NowPlayingInfo{PlayerId: 2, TrackID: "DDD", Start: now}))
|
||||
})
|
||||
|
||||
It("handles expired items", func() {
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: past})).To(BeNil())
|
||||
Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 2, TrackID: "BBB", Start: now})).To(BeNil())
|
||||
|
||||
Expect(repo.GetAll()).To(ConsistOf([]*NowPlayingInfo{
|
||||
{PlayerId: 2, TrackID: "BBB", Start: now},
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
package scrobbler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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 int
|
||||
PlayerName string
|
||||
}
|
||||
|
||||
type Scrobbler interface {
|
||||
NowPlaying(ctx context.Context, playerId int, 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
|
||||
}
|
||||
|
||||
var playMap = sync.Map{}
|
||||
|
||||
func New(ds model.DataStore) Scrobbler {
|
||||
instance := singleton.Get(scrobbler{}, func() interface{} {
|
||||
return &scrobbler{ds: ds}
|
||||
})
|
||||
return instance.(*scrobbler)
|
||||
}
|
||||
|
||||
func (s *scrobbler) NowPlaying(ctx context.Context, playerId int, playerName string, trackId string) error {
|
||||
username, _ := request.UsernameFrom(ctx)
|
||||
info := NowPlayingInfo{
|
||||
TrackID: trackId,
|
||||
Start: time.Now(),
|
||||
Username: username,
|
||||
PlayerId: playerId,
|
||||
PlayerName: playerName,
|
||||
}
|
||||
playMap.Store(playerId, info)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scrobbler) GetNowPlaying(ctx context.Context) ([]NowPlayingInfo, error) {
|
||||
var res []NowPlayingInfo
|
||||
playMap.Range(func(playerId, value interface{}) bool {
|
||||
info := value.(NowPlayingInfo)
|
||||
if time.Since(info.Start) < nowPlayingExpire {
|
||||
res = append(res, info)
|
||||
}
|
||||
return true
|
||||
})
|
||||
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")
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package core
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
"github.com/navidrome/navidrome/core/scrobbler"
|
||||
"github.com/navidrome/navidrome/core/transcoder"
|
||||
)
|
||||
|
||||
@@ -11,10 +12,10 @@ var Set = wire.NewSet(
|
||||
GetTranscodingCache,
|
||||
GetImageCache,
|
||||
NewArchiver,
|
||||
NewNowPlayingRepository,
|
||||
NewExternalMetadata,
|
||||
NewCacheWarmer,
|
||||
NewPlayers,
|
||||
transcoder.New,
|
||||
scrobbler.New,
|
||||
NewShare,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user