Removed more layers of indirection from the engine package
This commit is contained in:
@@ -1,66 +1,71 @@
|
|||||||
package subsonic
|
package subsonic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/deluan/navidrome/log"
|
"github.com/deluan/navidrome/log"
|
||||||
|
"github.com/deluan/navidrome/model"
|
||||||
"github.com/deluan/navidrome/server/subsonic/engine"
|
"github.com/deluan/navidrome/server/subsonic/engine"
|
||||||
|
"github.com/deluan/navidrome/server/subsonic/filter"
|
||||||
"github.com/deluan/navidrome/server/subsonic/responses"
|
"github.com/deluan/navidrome/server/subsonic/responses"
|
||||||
"github.com/deluan/navidrome/utils"
|
"github.com/deluan/navidrome/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AlbumListController struct {
|
type AlbumListController struct {
|
||||||
|
ds model.DataStore
|
||||||
listGen engine.ListGenerator
|
listGen engine.ListGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlbumListController(listGen engine.ListGenerator) *AlbumListController {
|
func NewAlbumListController(ds model.DataStore, listGen engine.ListGenerator) *AlbumListController {
|
||||||
c := &AlbumListController{
|
c := &AlbumListController{
|
||||||
|
ds: ds,
|
||||||
listGen: listGen,
|
listGen: listGen,
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, error) {
|
func (c *AlbumListController) getAlbumList(r *http.Request) (model.Albums, error) {
|
||||||
typ, err := requiredParamString(r, "type", "Required string parameter 'type' is not present")
|
typ, err := requiredParamString(r, "type", "Required string parameter 'type' is not present")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var filter engine.ListFilter
|
var opts filter.Options
|
||||||
switch typ {
|
switch typ {
|
||||||
case "newest":
|
case "newest":
|
||||||
filter = engine.ByNewest()
|
opts = filter.AlbumsByNewest()
|
||||||
case "recent":
|
case "recent":
|
||||||
filter = engine.ByRecent()
|
opts = filter.AlbumsByRecent()
|
||||||
case "random":
|
case "random":
|
||||||
filter = engine.ByRandom()
|
opts = filter.AlbumsByRandom()
|
||||||
case "alphabeticalByName":
|
case "alphabeticalByName":
|
||||||
filter = engine.ByName()
|
opts = filter.AlbumsByName()
|
||||||
case "alphabeticalByArtist":
|
case "alphabeticalByArtist":
|
||||||
filter = engine.ByArtist()
|
opts = filter.AlbumsByArtist()
|
||||||
case "frequent":
|
case "frequent":
|
||||||
filter = engine.ByFrequent()
|
opts = filter.AlbumsByFrequent()
|
||||||
case "starred":
|
case "starred":
|
||||||
filter = engine.ByStarred()
|
opts = filter.AlbumsByStarred()
|
||||||
case "highest":
|
case "highest":
|
||||||
filter = engine.ByRating()
|
opts = filter.AlbumsByRating()
|
||||||
case "byGenre":
|
case "byGenre":
|
||||||
filter = engine.ByGenre(utils.ParamString(r, "genre"))
|
opts = filter.AlbumsByGenre(utils.ParamString(r, "genre"))
|
||||||
case "byYear":
|
case "byYear":
|
||||||
filter = engine.ByYear(utils.ParamInt(r, "fromYear", 0), utils.ParamInt(r, "toYear", 0))
|
opts = filter.AlbumsByYear(utils.ParamInt(r, "fromYear", 0), utils.ParamInt(r, "toYear", 0))
|
||||||
default:
|
default:
|
||||||
log.Error(r, "albumList type not implemented", "type", typ)
|
log.Error(r, "albumList type not implemented", "type", typ)
|
||||||
return nil, errors.New("Not implemented!")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := utils.ParamInt(r, "offset", 0)
|
opts.Offset = utils.ParamInt(r, "offset", 0)
|
||||||
size := utils.MinInt(utils.ParamInt(r, "size", 10), 500)
|
opts.Max = utils.MinInt(utils.ParamInt(r, "size", 10), 500)
|
||||||
|
albums, err := c.ds.Album(r.Context()).GetAll(model.QueryOptions(opts))
|
||||||
|
|
||||||
albums, err := c.listGen.GetAlbums(r.Context(), offset, size, filter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(r, "Error retrieving albums", "error", err)
|
log.Error(r, "Error retrieving albums", "error", err)
|
||||||
return nil, errors.New("Internal Error")
|
return nil, errors.New("internal Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
return albums, nil
|
return albums, nil
|
||||||
@@ -73,7 +78,7 @@ func (c *AlbumListController) GetAlbumList(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.AlbumList = &responses.AlbumList{Album: toChildren(r.Context(), albums)}
|
response.AlbumList = &responses.AlbumList{Album: childrenFromAlbums(r.Context(), albums)}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,37 +89,45 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.AlbumList2 = &responses.AlbumList{Album: toAlbums(r.Context(), albums)}
|
response.AlbumList2 = &responses.AlbumList{Album: childrenFromAlbums(r.Context(), albums)}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||||
artists, albums, mediaFiles, err := c.listGen.GetAllStarred(r.Context())
|
ctx := r.Context()
|
||||||
|
options := model.QueryOptions{Sort: "starred_at", Order: "desc"}
|
||||||
|
artists, err := c.ds.Artist(ctx).GetStarred(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(r, "Error retrieving starred media", "error", err)
|
log.Error(r, "Error retrieving starred artists", "error", err)
|
||||||
|
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
||||||
|
}
|
||||||
|
albums, err := c.ds.Album(ctx).GetStarred(options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(r, "Error retrieving starred albums", "error", err)
|
||||||
|
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
||||||
|
}
|
||||||
|
mediaFiles, err := c.ds.MediaFile(ctx).GetStarred(options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(r, "Error retrieving starred mediaFiles", "error", err)
|
||||||
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.Starred = &responses.Starred{}
|
response.Starred = &responses.Starred{}
|
||||||
response.Starred.Artist = toArtists(artists)
|
response.Starred.Artist = toArtists(ctx, artists)
|
||||||
response.Starred.Album = toChildren(r.Context(), albums)
|
response.Starred.Album = childrenFromAlbums(r.Context(), albums)
|
||||||
response.Starred.Song = toChildren(r.Context(), mediaFiles)
|
response.Starred.Song = childrenFromMediaFiles(r.Context(), mediaFiles)
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||||
artists, albums, mediaFiles, err := c.listGen.GetAllStarred(r.Context())
|
resp, err := c.GetStarred(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(r, "Error retrieving starred media", "error", err)
|
return nil, err
|
||||||
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.Starred2 = &responses.Starred{}
|
response.Starred2 = resp.Starred
|
||||||
response.Starred2.Artist = toArtists(artists)
|
|
||||||
response.Starred2.Album = toAlbums(r.Context(), albums)
|
|
||||||
response.Starred2.Song = toChildren(r.Context(), mediaFiles)
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +157,7 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ
|
|||||||
fromYear := utils.ParamInt(r, "fromYear", 0)
|
fromYear := utils.ParamInt(r, "fromYear", 0)
|
||||||
toYear := utils.ParamInt(r, "toYear", 0)
|
toYear := utils.ParamInt(r, "toYear", 0)
|
||||||
|
|
||||||
songs, err := c.listGen.GetSongs(r.Context(), 0, size, engine.SongsByRandom(genre, fromYear, toYear))
|
songs, err := c.getSongs(r.Context(), 0, size, filter.SongsByRandom(genre, fromYear, toYear))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(r, "Error retrieving random songs", "error", err)
|
log.Error(r, "Error retrieving random songs", "error", err)
|
||||||
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
||||||
@@ -152,7 +165,7 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.RandomSongs = &responses.Songs{}
|
response.RandomSongs = &responses.Songs{}
|
||||||
response.RandomSongs.Songs = toChildren(r.Context(), songs)
|
response.RandomSongs.Songs = childrenFromMediaFiles(r.Context(), songs)
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +174,7 @@ func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Req
|
|||||||
offset := utils.MinInt(utils.ParamInt(r, "offset", 0), 500)
|
offset := utils.MinInt(utils.ParamInt(r, "offset", 0), 500)
|
||||||
genre := utils.ParamString(r, "genre")
|
genre := utils.ParamString(r, "genre")
|
||||||
|
|
||||||
songs, err := c.listGen.GetSongs(r.Context(), offset, count, engine.SongsByGenre(genre))
|
songs, err := c.getSongs(r.Context(), offset, count, filter.SongsByGenre(genre))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(r, "Error retrieving random songs", "error", err)
|
log.Error(r, "Error retrieving random songs", "error", err)
|
||||||
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
return nil, newError(responses.ErrorGeneric, "Internal Error")
|
||||||
@@ -169,6 +182,12 @@ func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
response.SongsByGenre = &responses.Songs{}
|
response.SongsByGenre = &responses.Songs{}
|
||||||
response.SongsByGenre.Songs = toChildren(r.Context(), songs)
|
response.SongsByGenre.Songs = childrenFromMediaFiles(r.Context(), songs)
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AlbumListController) getSongs(ctx context.Context, offset, size int, opts filter.Options) (model.MediaFiles, error) {
|
||||||
|
opts.Offset = offset
|
||||||
|
opts.Max = size
|
||||||
|
return c.ds.MediaFile(ctx).GetAll(model.QueryOptions(opts))
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,55 +2,40 @@ package subsonic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
"github.com/deluan/navidrome/server/subsonic/engine"
|
"github.com/deluan/navidrome/log"
|
||||||
|
"github.com/deluan/navidrome/model"
|
||||||
|
"github.com/deluan/navidrome/persistence"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeListGen struct {
|
|
||||||
engine.ListGenerator
|
|
||||||
data engine.Entries
|
|
||||||
err error
|
|
||||||
recvOffset int
|
|
||||||
recvSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lg *fakeListGen) GetAlbums(ctx context.Context, offset int, size int, filter engine.ListFilter) (engine.Entries, error) {
|
|
||||||
if lg.err != nil {
|
|
||||||
return nil, lg.err
|
|
||||||
}
|
|
||||||
lg.recvOffset = offset
|
|
||||||
lg.recvSize = size
|
|
||||||
return lg.data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("AlbumListController", func() {
|
var _ = Describe("AlbumListController", func() {
|
||||||
var controller *AlbumListController
|
var controller *AlbumListController
|
||||||
var listGen *fakeListGen
|
var ds model.DataStore
|
||||||
|
var mockRepo *persistence.MockAlbum
|
||||||
var w *httptest.ResponseRecorder
|
var w *httptest.ResponseRecorder
|
||||||
|
ctx := log.NewContext(context.TODO())
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
listGen = &fakeListGen{}
|
ds = &persistence.MockDataStore{}
|
||||||
controller = NewAlbumListController(listGen)
|
mockRepo = ds.Album(ctx).(*persistence.MockAlbum)
|
||||||
|
controller = NewAlbumListController(ds, nil)
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("GetAlbumList", func() {
|
Describe("GetAlbumList", func() {
|
||||||
It("should return list of the type specified", func() {
|
It("should return list of the type specified", func() {
|
||||||
r := newGetRequest("type=newest", "offset=10", "size=20")
|
r := newGetRequest("type=newest", "offset=10", "size=20")
|
||||||
listGen.data = engine.Entries{
|
mockRepo.SetData(`[{"id": "1"},{"id": "2"}]`)
|
||||||
{Id: "1"}, {Id: "2"},
|
|
||||||
}
|
|
||||||
resp, err := controller.GetAlbumList(w, r)
|
resp, err := controller.GetAlbumList(w, r)
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(resp.AlbumList.Album[0].Id).To(Equal("1"))
|
Expect(resp.AlbumList.Album[0].Id).To(Equal("1"))
|
||||||
Expect(resp.AlbumList.Album[1].Id).To(Equal("2"))
|
Expect(resp.AlbumList.Album[1].Id).To(Equal("2"))
|
||||||
Expect(listGen.recvOffset).To(Equal(10))
|
Expect(mockRepo.Options.Offset).To(Equal(10))
|
||||||
Expect(listGen.recvSize).To(Equal(20))
|
Expect(mockRepo.Options.Max).To(Equal(20))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should fail if missing type parameter", func() {
|
It("should fail if missing type parameter", func() {
|
||||||
@@ -61,28 +46,26 @@ var _ = Describe("AlbumListController", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("should return error if call fails", func() {
|
It("should return error if call fails", func() {
|
||||||
listGen.err = errors.New("some issue")
|
mockRepo.SetError(true)
|
||||||
r := newGetRequest("type=newest")
|
r := newGetRequest("type=newest")
|
||||||
|
|
||||||
_, err := controller.GetAlbumList(w, r)
|
_, err := controller.GetAlbumList(w, r)
|
||||||
|
|
||||||
Expect(err).To(MatchError("Internal Error"))
|
Expect(err).ToNot(BeNil())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("GetAlbumList2", func() {
|
Describe("GetAlbumList2", func() {
|
||||||
It("should return list of the type specified", func() {
|
It("should return list of the type specified", func() {
|
||||||
r := newGetRequest("type=newest", "offset=10", "size=20")
|
r := newGetRequest("type=newest", "offset=10", "size=20")
|
||||||
listGen.data = engine.Entries{
|
mockRepo.SetData(`[{"id": "1"},{"id": "2"}]`)
|
||||||
{Id: "1"}, {Id: "2"},
|
|
||||||
}
|
|
||||||
resp, err := controller.GetAlbumList2(w, r)
|
resp, err := controller.GetAlbumList2(w, r)
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(resp.AlbumList2.Album[0].Id).To(Equal("1"))
|
Expect(resp.AlbumList2.Album[0].Id).To(Equal("1"))
|
||||||
Expect(resp.AlbumList2.Album[1].Id).To(Equal("2"))
|
Expect(resp.AlbumList2.Album[1].Id).To(Equal("2"))
|
||||||
Expect(listGen.recvOffset).To(Equal(10))
|
Expect(mockRepo.Options.Offset).To(Equal(10))
|
||||||
Expect(listGen.recvSize).To(Equal(20))
|
Expect(mockRepo.Options.Max).To(Equal(20))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should fail if missing type parameter", func() {
|
It("should fail if missing type parameter", func() {
|
||||||
@@ -93,12 +76,12 @@ var _ = Describe("AlbumListController", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("should return error if call fails", func() {
|
It("should return error if call fails", func() {
|
||||||
listGen.err = errors.New("some issue")
|
mockRepo.SetError(true)
|
||||||
r := newGetRequest("type=newest")
|
r := newGetRequest("type=newest")
|
||||||
|
|
||||||
_, err := controller.GetAlbumList2(w, r)
|
_, err := controller.GetAlbumList2(w, r)
|
||||||
|
|
||||||
Expect(err).To(MatchError("Internal Error"))
|
Expect(err).ToNot(BeNil())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,43 +45,6 @@ type Entry struct {
|
|||||||
|
|
||||||
type Entries []Entry
|
type Entries []Entry
|
||||||
|
|
||||||
func FromArtist(ar *model.Artist) Entry {
|
|
||||||
e := Entry{}
|
|
||||||
e.Id = ar.ID
|
|
||||||
e.Title = ar.Name
|
|
||||||
e.AlbumCount = ar.AlbumCount
|
|
||||||
e.IsDir = true
|
|
||||||
e.UserRating = ar.Rating
|
|
||||||
if ar.Starred {
|
|
||||||
e.Starred = ar.StarredAt
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromAlbum(al *model.Album) Entry {
|
|
||||||
e := Entry{}
|
|
||||||
e.Id = al.ID
|
|
||||||
e.Title = al.Name
|
|
||||||
e.IsDir = true
|
|
||||||
e.Parent = al.AlbumArtistID
|
|
||||||
e.Album = al.Name
|
|
||||||
e.Year = al.MaxYear
|
|
||||||
e.Artist = al.AlbumArtist
|
|
||||||
e.Genre = al.Genre
|
|
||||||
e.CoverArt = al.CoverArtId
|
|
||||||
e.Created = al.CreatedAt
|
|
||||||
e.AlbumId = al.ID
|
|
||||||
e.ArtistId = al.AlbumArtistID
|
|
||||||
e.Duration = int(al.Duration)
|
|
||||||
e.SongCount = al.SongCount
|
|
||||||
if al.Starred {
|
|
||||||
e.Starred = al.StarredAt
|
|
||||||
}
|
|
||||||
e.PlayCount = int32(al.PlayCount)
|
|
||||||
e.UserRating = al.Rating
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromMediaFile(mf *model.MediaFile) Entry {
|
func FromMediaFile(mf *model.MediaFile) Entry {
|
||||||
e := Entry{}
|
e := Entry{}
|
||||||
e.Id = mf.ID
|
e.Id = mf.ID
|
||||||
@@ -133,15 +96,6 @@ func realArtistName(mf *model.MediaFile) string {
|
|||||||
return mf.Artist
|
return mf.Artist
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromAlbums(albums model.Albums) Entries {
|
|
||||||
entries := make(Entries, len(albums))
|
|
||||||
for i := range albums {
|
|
||||||
al := albums[i]
|
|
||||||
entries[i] = FromAlbum(&al)
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromMediaFiles(mfs model.MediaFiles) Entries {
|
func FromMediaFiles(mfs model.MediaFiles) Entries {
|
||||||
entries := make(Entries, len(mfs))
|
entries := make(Entries, len(mfs))
|
||||||
for i := range mfs {
|
for i := range mfs {
|
||||||
@@ -150,12 +104,3 @@ func FromMediaFiles(mfs model.MediaFiles) Entries {
|
|||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromArtists(ars model.Artists) Entries {
|
|
||||||
entries := make(Entries, len(ars))
|
|
||||||
for i := range ars {
|
|
||||||
ar := ars[i]
|
|
||||||
entries[i] = FromArtist(&ar)
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,172 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
|
||||||
"github.com/deluan/navidrome/model"
|
"github.com/deluan/navidrome/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListGenerator interface {
|
type ListGenerator interface {
|
||||||
GetAllStarred(ctx context.Context) (artists Entries, albums Entries, mediaFiles Entries, err error)
|
|
||||||
GetNowPlaying(ctx context.Context) (Entries, error)
|
GetNowPlaying(ctx context.Context) (Entries, error)
|
||||||
GetSongs(ctx context.Context, offset, size int, filter ListFilter) (Entries, error)
|
|
||||||
GetAlbums(ctx context.Context, offset, size int, filter ListFilter) (Entries, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator {
|
func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator {
|
||||||
return &listGenerator{ds, npRepo}
|
return &listGenerator{ds, npRepo}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListFilter model.QueryOptions
|
|
||||||
|
|
||||||
func ByNewest() ListFilter {
|
|
||||||
return ListFilter{Sort: "createdAt", Order: "desc"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByRecent() ListFilter {
|
|
||||||
return ListFilter{Sort: "playDate", Order: "desc", Filters: squirrel.Gt{"play_date": time.Time{}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByFrequent() ListFilter {
|
|
||||||
return ListFilter{Sort: "playCount", Order: "desc", Filters: squirrel.Gt{"play_count": 0}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByRandom() ListFilter {
|
|
||||||
return ListFilter{Sort: "random()"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByName() ListFilter {
|
|
||||||
return ListFilter{Sort: "name"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByArtist() ListFilter {
|
|
||||||
return ListFilter{Sort: "artist"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByStarred() ListFilter {
|
|
||||||
return ListFilter{Sort: "starred_at", Order: "desc", Filters: squirrel.Eq{"starred": true}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByRating() ListFilter {
|
|
||||||
return ListFilter{Sort: "Rating", Order: "desc", Filters: squirrel.Gt{"rating": 0}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByGenre(genre string) ListFilter {
|
|
||||||
return ListFilter{
|
|
||||||
Sort: "genre asc, name asc",
|
|
||||||
Filters: squirrel.Eq{"genre": genre},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ByYear(fromYear, toYear int) ListFilter {
|
|
||||||
sortOption := "max_year, name"
|
|
||||||
if fromYear > toYear {
|
|
||||||
fromYear, toYear = toYear, fromYear
|
|
||||||
sortOption = "max_year desc, name"
|
|
||||||
}
|
|
||||||
return ListFilter{
|
|
||||||
Sort: sortOption,
|
|
||||||
Filters: squirrel.Or{
|
|
||||||
squirrel.And{
|
|
||||||
squirrel.GtOrEq{"min_year": fromYear},
|
|
||||||
squirrel.LtOrEq{"min_year": toYear},
|
|
||||||
},
|
|
||||||
squirrel.And{
|
|
||||||
squirrel.GtOrEq{"max_year": fromYear},
|
|
||||||
squirrel.LtOrEq{"max_year": toYear},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SongsByGenre(genre string) ListFilter {
|
|
||||||
return ListFilter{
|
|
||||||
Sort: "genre asc, title asc",
|
|
||||||
Filters: squirrel.Eq{"genre": genre},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SongsByRandom(genre string, fromYear, toYear int) ListFilter {
|
|
||||||
options := ListFilter{
|
|
||||||
Sort: "random()",
|
|
||||||
}
|
|
||||||
ff := squirrel.And{}
|
|
||||||
if genre != "" {
|
|
||||||
ff = append(ff, squirrel.Eq{"genre": genre})
|
|
||||||
}
|
|
||||||
if fromYear != 0 {
|
|
||||||
ff = append(ff, squirrel.GtOrEq{"year": fromYear})
|
|
||||||
}
|
|
||||||
if toYear != 0 {
|
|
||||||
ff = append(ff, squirrel.LtOrEq{"year": toYear})
|
|
||||||
}
|
|
||||||
options.Filters = ff
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
type listGenerator struct {
|
type listGenerator struct {
|
||||||
ds model.DataStore
|
ds model.DataStore
|
||||||
npRepo NowPlayingRepository
|
npRepo NowPlayingRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetSongs(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) {
|
|
||||||
qo := model.QueryOptions(filter)
|
|
||||||
qo.Offset = offset
|
|
||||||
qo.Max = size
|
|
||||||
mediaFiles, err := g.ds.MediaFile(ctx).GetAll(qo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return FromMediaFiles(mediaFiles), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *listGenerator) GetAlbums(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) {
|
|
||||||
qo := model.QueryOptions(filter)
|
|
||||||
qo.Offset = offset
|
|
||||||
qo.Max = size
|
|
||||||
albums, err := g.ds.Album(ctx).GetAll(qo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return FromAlbums(albums), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *listGenerator) GetStarred(ctx context.Context, offset int, size int) (Entries, error) {
|
|
||||||
qo := model.QueryOptions{Offset: offset, Max: size, Sort: "starred_at", Order: "desc"}
|
|
||||||
albums, err := g.ds.Album(ctx).GetStarred(qo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return FromAlbums(albums), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *listGenerator) GetAllStarred(ctx context.Context) (artists Entries, albums Entries, mediaFiles Entries, err error) {
|
|
||||||
options := model.QueryOptions{Sort: "starred_at", Order: "desc"}
|
|
||||||
|
|
||||||
ars, err := g.ds.Artist(ctx).GetStarred(options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
als, err := g.ds.Album(ctx).GetStarred(options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
mfs, err := g.ds.MediaFile(ctx).GetStarred(options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
artists = FromArtists(ars)
|
|
||||||
albums = FromAlbums(als)
|
|
||||||
mediaFiles = FromMediaFiles(mfs)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) {
|
func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) {
|
||||||
npInfo, err := g.npRepo.GetAll()
|
npInfo, err := g.npRepo.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
|
"github.com/deluan/navidrome/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options model.QueryOptions
|
||||||
|
|
||||||
|
func AlbumsByNewest() Options {
|
||||||
|
return Options{Sort: "createdAt", Order: "desc"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByRecent() Options {
|
||||||
|
return Options{Sort: "playDate", Order: "desc", Filters: squirrel.Gt{"play_date": time.Time{}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByFrequent() Options {
|
||||||
|
return Options{Sort: "playCount", Order: "desc", Filters: squirrel.Gt{"play_count": 0}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByRandom() Options {
|
||||||
|
return Options{Sort: "random()"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByName() Options {
|
||||||
|
return Options{Sort: "name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByArtist() Options {
|
||||||
|
return Options{Sort: "artist"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByStarred() Options {
|
||||||
|
return Options{Sort: "starred_at", Order: "desc", Filters: squirrel.Eq{"starred": true}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByRating() Options {
|
||||||
|
return Options{Sort: "Rating", Order: "desc", Filters: squirrel.Gt{"rating": 0}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByGenre(genre string) Options {
|
||||||
|
return Options{
|
||||||
|
Sort: "genre asc, name asc",
|
||||||
|
Filters: squirrel.Eq{"genre": genre},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AlbumsByYear(fromYear, toYear int) Options {
|
||||||
|
sortOption := "max_year, name"
|
||||||
|
if fromYear > toYear {
|
||||||
|
fromYear, toYear = toYear, fromYear
|
||||||
|
sortOption = "max_year desc, name"
|
||||||
|
}
|
||||||
|
return Options{
|
||||||
|
Sort: sortOption,
|
||||||
|
Filters: squirrel.Or{
|
||||||
|
squirrel.And{
|
||||||
|
squirrel.GtOrEq{"min_year": fromYear},
|
||||||
|
squirrel.LtOrEq{"min_year": toYear},
|
||||||
|
},
|
||||||
|
squirrel.And{
|
||||||
|
squirrel.GtOrEq{"max_year": fromYear},
|
||||||
|
squirrel.LtOrEq{"max_year": toYear},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SongsByGenre(genre string) Options {
|
||||||
|
return Options{
|
||||||
|
Sort: "genre asc, title asc",
|
||||||
|
Filters: squirrel.Eq{"genre": genre},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SongsByRandom(genre string, fromYear, toYear int) Options {
|
||||||
|
options := Options{
|
||||||
|
Sort: "random()",
|
||||||
|
}
|
||||||
|
ff := squirrel.And{}
|
||||||
|
if genre != "" {
|
||||||
|
ff = append(ff, squirrel.Eq{"genre": genre})
|
||||||
|
}
|
||||||
|
if fromYear != 0 {
|
||||||
|
ff = append(ff, squirrel.GtOrEq{"year": fromYear})
|
||||||
|
}
|
||||||
|
if toYear != 0 {
|
||||||
|
ff = append(ff, squirrel.LtOrEq{"year": toYear})
|
||||||
|
}
|
||||||
|
options.Filters = ff
|
||||||
|
return options
|
||||||
|
}
|
||||||
+11
-29
@@ -64,38 +64,20 @@ func (e SubsonicError) Error() string {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAlbums(ctx context.Context, entries engine.Entries) []responses.Child {
|
func toArtists(ctx context.Context, artists model.Artists) []responses.Artist {
|
||||||
children := make([]responses.Child, len(entries))
|
as := make([]responses.Artist, len(artists))
|
||||||
for i, entry := range entries {
|
for i, artist := range artists {
|
||||||
children[i] = toAlbum(ctx, entry)
|
as[i] = responses.Artist{
|
||||||
|
Id: artist.ID,
|
||||||
|
Name: artist.Name,
|
||||||
|
AlbumCount: artist.AlbumCount,
|
||||||
|
UserRating: artist.Rating,
|
||||||
}
|
}
|
||||||
return children
|
if artist.Starred {
|
||||||
}
|
as[i].Starred = &artist.StarredAt
|
||||||
|
|
||||||
func toAlbum(ctx context.Context, entry engine.Entry) responses.Child {
|
|
||||||
album := toChild(ctx, entry)
|
|
||||||
album.Name = album.Title
|
|
||||||
album.Title = ""
|
|
||||||
album.Parent = ""
|
|
||||||
album.Album = ""
|
|
||||||
album.AlbumId = ""
|
|
||||||
return album
|
|
||||||
}
|
|
||||||
|
|
||||||
func toArtists(entries engine.Entries) []responses.Artist {
|
|
||||||
artists := make([]responses.Artist, len(entries))
|
|
||||||
for i, entry := range entries {
|
|
||||||
artists[i] = responses.Artist{
|
|
||||||
Id: entry.Id,
|
|
||||||
Name: entry.Title,
|
|
||||||
AlbumCount: entry.AlbumCount,
|
|
||||||
UserRating: entry.UserRating,
|
|
||||||
}
|
|
||||||
if !entry.Starred.IsZero() {
|
|
||||||
artists[i].Starred = &entry.Starred
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return artists
|
return as
|
||||||
}
|
}
|
||||||
|
|
||||||
func toChildren(ctx context.Context, entries engine.Entries) []responses.Child {
|
func toChildren(ctx context.Context, entries engine.Entries) []responses.Child {
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ func initBrowsingController(router *Router) *BrowsingController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initAlbumListController(router *Router) *AlbumListController {
|
func initAlbumListController(router *Router) *AlbumListController {
|
||||||
|
dataStore := router.DataStore
|
||||||
listGenerator := router.ListGenerator
|
listGenerator := router.ListGenerator
|
||||||
albumListController := NewAlbumListController(listGenerator)
|
albumListController := NewAlbumListController(dataStore, listGenerator)
|
||||||
return albumListController
|
return albumListController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user