Store each client's play queue in the database
The queue lived in memory and was shared by all clients, so a restart or a finished sync cleared it. Every client now has its own queue in the new queue and queue_song tables (added to the undeployed migration 000006). The music endpoints return the queue ID in the X-Queue-Id header and expect it back. A request without an ID, or with an ended one, starts a new queue. /music/reset ends the queue and returns a new ID, and a sync ends all running queues. Ended queues are kept as history. The deprecated legacy routes without the /api/v1 prefix are removed.
This commit is contained in:
1 parent
b5dc42b654
commit
a705d7b476
28 files changed
+1103
-320
No files matched your search
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
// Store is an in-memory implementation of the queries used by the application.
|
||||
@@ -25,6 +26,8 @@ type Store struct {
|
||||
Soundtracks []repository.Soundtrack
|
||||
Songs []repository.Song
|
||||
Sessions map[string]repository.Session
|
||||
Queues []repository.Queue
|
||||
QueueSongs []repository.QueueSong
|
||||
|
||||
// Err, when set, is returned by every implemented query.
|
||||
Err error
|
||||
@@ -373,6 +376,122 @@ func (s *Store) HasSession(token string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *Store) queueIndex(id uuid.UUID) int {
|
||||
return slices.IndexFunc(s.Queues, func(q repository.Queue) bool { return q.Uuid == id })
|
||||
}
|
||||
|
||||
func (s *Store) updateQueue(id uuid.UUID, f func(*repository.Queue)) error {
|
||||
defer s.lock()()
|
||||
if s.Err != nil {
|
||||
return s.Err
|
||||
}
|
||||
if i := s.queueIndex(id); i >= 0 {
|
||||
f(&s.Queues[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateQueue(context.Context) (repository.Queue, error) {
|
||||
defer s.lock()()
|
||||
if s.Err != nil {
|
||||
return repository.Queue{}, s.Err
|
||||
}
|
||||
queue := repository.Queue{Uuid: uuid.New(), CurrentSong: -1, StartedAt: pgtype.Timestamptz{Time: time.Now(), Valid: true}}
|
||||
s.Queues = append(s.Queues, queue)
|
||||
return queue, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetQueue(_ context.Context, id uuid.UUID) (repository.Queue, error) {
|
||||
defer s.lock()()
|
||||
if s.Err != nil {
|
||||
return repository.Queue{}, s.Err
|
||||
}
|
||||
if i := s.queueIndex(id); i >= 0 {
|
||||
return s.Queues[i], nil
|
||||
}
|
||||
return repository.Queue{}, pgx.ErrNoRows
|
||||
}
|
||||
|
||||
func endQueue(q *repository.Queue) {
|
||||
if !q.EndedAt.Valid {
|
||||
q.EndedAt = pgtype.Timestamptz{Time: time.Now(), Valid: true}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) EndQueue(_ context.Context, id uuid.UUID) error {
|
||||
return s.updateQueue(id, endQueue)
|
||||
}
|
||||
|
||||
func (s *Store) EndActiveQueues(context.Context) error {
|
||||
defer s.lock()()
|
||||
for i := range s.Queues {
|
||||
endQueue(&s.Queues[i])
|
||||
}
|
||||
return s.Err
|
||||
}
|
||||
|
||||
func (s *Store) SetQueueLastFetched(_ context.Context, arg repository.SetQueueLastFetchedParams) error {
|
||||
return s.updateQueue(arg.Uuid, func(q *repository.Queue) { q.LastFetchedSongUuid = arg.LastFetchedSongUuid })
|
||||
}
|
||||
|
||||
func (s *Store) SetQueueCurrentSong(_ context.Context, arg repository.SetQueueCurrentSongParams) error {
|
||||
return s.updateQueue(arg.Uuid, func(q *repository.Queue) { q.CurrentSong = arg.CurrentSong })
|
||||
}
|
||||
|
||||
func (s *Store) AddLastFetchedToQueue(_ context.Context, id uuid.UUID) error {
|
||||
defer s.lock()()
|
||||
if s.Err != nil {
|
||||
return s.Err
|
||||
}
|
||||
i := s.queueIndex(id)
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
queue := &s.Queues[i]
|
||||
songIndex := slices.IndexFunc(s.Songs, func(song repository.Song) bool { return song.Uuid == queue.LastFetchedSongUuid })
|
||||
if songIndex < 0 {
|
||||
return nil
|
||||
}
|
||||
song := s.Songs[songIndex]
|
||||
var soundtrackName string
|
||||
if st := s.soundtrackIndex(song.SoundtrackID); st >= 0 {
|
||||
soundtrackName = s.Soundtracks[st].SoundtrackName
|
||||
}
|
||||
songNo := int32(0)
|
||||
for _, qs := range s.QueueSongs {
|
||||
if qs.QueueUuid == id {
|
||||
songNo = max(songNo, qs.SongNo+1)
|
||||
}
|
||||
}
|
||||
s.QueueSongs = append(s.QueueSongs, repository.QueueSong{
|
||||
QueueUuid: id, SongNo: songNo, SongUuid: song.Uuid, SoundtrackName: soundtrackName, SongName: song.SongName,
|
||||
})
|
||||
queue.CurrentSong = songNo
|
||||
queue.LastFetchedSongUuid = uuid.Nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) ListQueueSongs(_ context.Context, id uuid.UUID) ([]repository.ListQueueSongsRow, error) {
|
||||
defer s.lock()()
|
||||
if s.Err != nil {
|
||||
return nil, s.Err
|
||||
}
|
||||
var rows []repository.ListQueueSongsRow
|
||||
for _, qs := range s.QueueSongs {
|
||||
if qs.QueueUuid != id {
|
||||
continue
|
||||
}
|
||||
row := repository.ListQueueSongsRow{SongNo: qs.SongNo, SongUuid: qs.SongUuid, SoundtrackName: qs.SoundtrackName, SongName: qs.SongName}
|
||||
if i := slices.IndexFunc(s.Songs, func(song repository.Song) bool { return song.Uuid == qs.SongUuid }); i >= 0 {
|
||||
song := s.Songs[i]
|
||||
row.Path, row.TimesPlayed, row.SoundtrackUuid = &song.Path, &song.TimesPlayed, song.SoundtrackUuid
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
slices.SortFunc(rows, func(a, b repository.ListQueueSongsRow) int { return cmp.Compare(a.SongNo, b.SongNo) })
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// Statistics queries return empty results; tests that need data use their own fakes.
|
||||
|
||||
func (s *Store) GetMostPlayedSoundtracksWithSongs(context.Context, int32) ([]repository.GetMostPlayedSoundtracksWithSongsRow, error) {
|
||||
|
||||
Reference in new issue
Block a user