mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-03 06:41:01 +00:00
Add Stars to the DB, including Artists! Only if DevUseFolderScanner is true
This commit is contained in:
parent
a4b75fd69d
commit
128e165aba
@ -81,7 +81,7 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||
albums, mediaFiles, err := c.listGen.GetAllStarred()
|
||||
artists, albums, mediaFiles, err := c.listGen.GetAllStarred()
|
||||
if err != nil {
|
||||
log.Error(r, "Error retrieving starred media", "error", err)
|
||||
return nil, NewError(responses.ErrorGeneric, "Internal Error")
|
||||
@ -89,13 +89,14 @@ func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
response := NewResponse()
|
||||
response.Starred = &responses.Starred{}
|
||||
response.Starred.Artist = ToArtists(artists)
|
||||
response.Starred.Album = ToChildren(albums)
|
||||
response.Starred.Song = ToChildren(mediaFiles)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||
albums, mediaFiles, err := c.listGen.GetAllStarred()
|
||||
artists, albums, mediaFiles, err := c.listGen.GetAllStarred()
|
||||
if err != nil {
|
||||
log.Error(r, "Error retrieving starred media", "error", err)
|
||||
return nil, NewError(responses.ErrorGeneric, "Internal Error")
|
||||
@ -103,6 +104,7 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request
|
||||
|
||||
response := NewResponse()
|
||||
response.Starred2 = &responses.Starred{}
|
||||
response.Starred2.Artist = ToArtists(artists)
|
||||
response.Starred2.Album = ToAlbums(albums)
|
||||
response.Starred2.Song = ToChildren(mediaFiles)
|
||||
return response, nil
|
||||
|
||||
@ -145,6 +145,21 @@ func ToAlbum(entry engine.Entry) responses.Child {
|
||||
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,
|
||||
}
|
||||
if !entry.Starred.IsZero() {
|
||||
artists[i].Starred = &entry.Starred
|
||||
}
|
||||
}
|
||||
return artists
|
||||
}
|
||||
|
||||
func ToChildren(entries engine.Entries) []responses.Child {
|
||||
children := make([]responses.Child, len(entries))
|
||||
for i, entry := range entries {
|
||||
|
||||
@ -50,12 +50,15 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req
|
||||
func (c *MediaAnnotationController) getIds(r *http.Request) ([]string, error) {
|
||||
ids := ParamStrings(r, "id")
|
||||
albumIds := ParamStrings(r, "albumId")
|
||||
artistIds := ParamStrings(r, "artistId")
|
||||
|
||||
if len(ids) == 0 && len(albumIds) == 0 {
|
||||
if len(ids)+len(albumIds)+len(artistIds) == 0 {
|
||||
return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing")
|
||||
}
|
||||
|
||||
return append(ids, albumIds...), nil
|
||||
ids = append(ids, albumIds...)
|
||||
ids = append(ids, artistIds...)
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||
|
||||
@ -1 +1 @@
|
||||
{"status":"ok","version":"1.8.0","indexes":{"index":[{"name":"A","artist":[{"id":"111","name":"aaa"}]}],"lastModified":"1","ignoredArticles":"A"}}
|
||||
{"status":"ok","version":"1.8.0","indexes":{"index":[{"name":"A","artist":[{"id":"111","name":"aaa","starred":"2016-03-02T20:30:00Z"}]}],"lastModified":"1","ignoredArticles":"A"}}
|
||||
|
||||
@ -1 +1 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><indexes lastModified="1" ignoredArticles="A"><index name="A"><artist id="111" name="aaa"></artist></index></indexes></subsonic-response>
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.8.0"><indexes lastModified="1" ignoredArticles="A"><index name="A"><artist id="111" name="aaa" starred="2016-03-02T20:30:00Z"></artist></index></indexes></subsonic-response>
|
||||
|
||||
@ -57,11 +57,11 @@ type MusicFolders struct {
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
Id string `xml:"id,attr" json:"id"`
|
||||
Name string `xml:"name,attr" json:"name"`
|
||||
AlbumCount int `xml:"albumCount,attr,omitempty" json:"albumCount,omitempty"`
|
||||
Id string `xml:"id,attr" json:"id"`
|
||||
Name string `xml:"name,attr" json:"name"`
|
||||
AlbumCount int `xml:"albumCount,attr,omitempty" json:"albumCount,omitempty"`
|
||||
Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"`
|
||||
/*
|
||||
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.10.1 -->
|
||||
<xs:attribute name="userRating" type="sub:UserRating" use="optional"/> <!-- Added in 1.13.0 -->
|
||||
<xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.13.0 -->
|
||||
*/
|
||||
|
||||
@ -90,7 +90,8 @@ var _ = Describe("Responses", func() {
|
||||
Context("with data", func() {
|
||||
BeforeEach(func() {
|
||||
artists := make([]Artist, 1)
|
||||
artists[0] = Artist{Id: "111", Name: "aaa"}
|
||||
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
|
||||
artists[0] = Artist{Id: "111", Name: "aaa", Starred: &t}
|
||||
index := make([]Index, 1)
|
||||
index[0] = Index{Name: "A", Artists: artists}
|
||||
response.Indexes.Index = index
|
||||
|
||||
@ -70,10 +70,7 @@ func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (*
|
||||
|
||||
response := NewResponse()
|
||||
searchResult2 := &responses.SearchResult2{}
|
||||
searchResult2.Artist = make([]responses.Artist, len(as))
|
||||
for i, e := range as {
|
||||
searchResult2.Artist[i] = responses.Artist{Id: e.Id, Name: e.Title}
|
||||
}
|
||||
searchResult2.Artist = ToArtists(as)
|
||||
searchResult2.Album = ToChildren(als)
|
||||
searchResult2.Song = ToChildren(mfs)
|
||||
response.SearchResult2 = searchResult2
|
||||
@ -97,6 +94,9 @@ func (c *SearchingController) Search3(w http.ResponseWriter, r *http.Request) (*
|
||||
CoverArt: e.CoverArt,
|
||||
AlbumCount: e.AlbumCount,
|
||||
}
|
||||
if !e.Starred.IsZero() {
|
||||
searchResult3.Artist[i].Starred = &e.Starred
|
||||
}
|
||||
}
|
||||
searchResult3.Album = ToAlbums(als)
|
||||
searchResult3.Song = ToChildren(mfs)
|
||||
|
||||
@ -50,6 +50,7 @@ func FromArtist(ar *model.Artist) Entry {
|
||||
e.Id = ar.ID
|
||||
e.Title = ar.Name
|
||||
e.AlbumCount = ar.AlbumCount
|
||||
e.Starred = ar.StarredAt
|
||||
e.IsDir = true
|
||||
return e
|
||||
}
|
||||
@ -137,3 +138,11 @@ func FromMediaFiles(mfs model.MediaFiles) Entries {
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func FromArtists(ars model.Artists) Entries {
|
||||
entries := make(Entries, len(ars))
|
||||
for i, ar := range ars {
|
||||
entries[i] = FromArtist(&ar)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -17,16 +17,17 @@ type ListGenerator interface {
|
||||
GetByName(offset int, size int) (Entries, error)
|
||||
GetByArtist(offset int, size int) (Entries, error)
|
||||
GetStarred(offset int, size int) (Entries, error)
|
||||
GetAllStarred() (albums Entries, mediaFiles Entries, err error)
|
||||
GetAllStarred() (artists Entries, albums Entries, mediaFiles Entries, err error)
|
||||
GetNowPlaying() (Entries, error)
|
||||
GetRandomSongs(size int) (Entries, error)
|
||||
}
|
||||
|
||||
func NewListGenerator(alr model.AlbumRepository, mfr model.MediaFileRepository, npr model.NowPlayingRepository) ListGenerator {
|
||||
return &listGenerator{alr, mfr, npr}
|
||||
func NewListGenerator(arr model.ArtistRepository, alr model.AlbumRepository, mfr model.MediaFileRepository, npr model.NowPlayingRepository) ListGenerator {
|
||||
return &listGenerator{arr, alr, mfr, npr}
|
||||
}
|
||||
|
||||
type listGenerator struct {
|
||||
artistRepo model.ArtistRepository
|
||||
albumRepo model.AlbumRepository
|
||||
mfRepository model.MediaFileRepository
|
||||
npRepo model.NowPlayingRepository
|
||||
@ -111,7 +112,7 @@ func (g *listGenerator) GetRandomSongs(size int) (Entries, error) {
|
||||
}
|
||||
|
||||
func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
|
||||
qo := model.QueryOptions{Offset: offset, Size: size, Desc: true}
|
||||
qo := model.QueryOptions{Offset: offset, Size: size, SortBy: "starred_at", Desc: true}
|
||||
albums, err := g.albumRepo.GetStarred(qo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -120,15 +121,24 @@ func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
|
||||
return FromAlbums(albums), nil
|
||||
}
|
||||
|
||||
func (g *listGenerator) GetAllStarred() (Entries, Entries, error) {
|
||||
albums, err := g.GetStarred(0, -1)
|
||||
// TODO Return is confusing
|
||||
func (g *listGenerator) GetAllStarred() (Entries, Entries, Entries, error) {
|
||||
artists, err := g.artistRepo.GetStarred(model.QueryOptions{SortBy: "starred_at", Desc: true})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
mediaFiles, err := g.mfRepository.GetStarred(model.QueryOptions{Desc: true})
|
||||
albums, err := g.GetStarred(0, -1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return albums, FromMediaFiles(mediaFiles), err
|
||||
mediaFiles, err := g.mfRepository.GetStarred(model.QueryOptions{SortBy: "starred_at", Desc: true})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return FromArtists(artists), albums, FromMediaFiles(mediaFiles), err
|
||||
}
|
||||
|
||||
func (g *listGenerator) GetNowPlaying() (Entries, error) {
|
||||
|
||||
@ -3,6 +3,7 @@ package engine
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudsonic/sonic-server/conf"
|
||||
"github.com/cloudsonic/sonic-server/itunesbridge"
|
||||
"github.com/cloudsonic/sonic-server/log"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
@ -55,6 +56,19 @@ func (r ratings) SetRating(ctx context.Context, id string, rating int) error {
|
||||
}
|
||||
|
||||
func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
|
||||
if conf.Sonic.DevUseFileScanner {
|
||||
err := r.mfRepo.SetStar(star, ids...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.albumRepo.SetStar(star, ids...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.artistRepo.SetStar(star, ids...)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
isAlbum, _ := r.albumRepo.Exists(id)
|
||||
if isAlbum {
|
||||
|
||||
@ -36,6 +36,7 @@ type AlbumRepository interface {
|
||||
PurgeInactive(active Albums) error
|
||||
GetAllIds() ([]string, error)
|
||||
GetStarred(...QueryOptions) (Albums, error)
|
||||
SetStar(star bool, ids ...string) error
|
||||
Search(q string, offset int, size int) (Albums, error)
|
||||
Refresh(ids ...string) error
|
||||
PurgeEmpty() error
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Artist struct {
|
||||
ID string
|
||||
Name string
|
||||
AlbumCount int
|
||||
Starred bool
|
||||
StarredAt time.Time
|
||||
}
|
||||
type Artists []Artist
|
||||
|
||||
@ -19,6 +23,8 @@ type ArtistRepository interface {
|
||||
Put(m *Artist) error
|
||||
Get(id string) (*Artist, error)
|
||||
PurgeInactive(active Artists) error
|
||||
GetStarred(...QueryOptions) (Artists, error)
|
||||
SetStar(star bool, ids ...string) error
|
||||
Search(q string, offset int, size int) (Artists, error)
|
||||
Refresh(ids ...string) error
|
||||
GetIndex() (ArtistIndexes, error)
|
||||
|
||||
@ -52,4 +52,6 @@ type MediaFileRepository interface {
|
||||
Search(q string, offset int, size int) (MediaFiles, error)
|
||||
Delete(id string) error
|
||||
DeleteByPath(path string) error
|
||||
SetStar(star bool, ids ...string) error
|
||||
SetRating(rating int, ids ...string) error
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ type album struct {
|
||||
Duration int ``
|
||||
Rating int `orm:"index"`
|
||||
Genre string `orm:"index"`
|
||||
StarredAt time.Time `orm:"null"`
|
||||
StarredAt time.Time `orm:"index;null"`
|
||||
CreatedAt time.Time `orm:"null"`
|
||||
UpdatedAt time.Time `orm:"null"`
|
||||
}
|
||||
@ -118,6 +118,9 @@ group by album_id order by f.id`, strings.Join(ids, "','"))
|
||||
if al.Compilation {
|
||||
al.AlbumArtist = "Various Artists"
|
||||
}
|
||||
if al.AlbumArtist == "" {
|
||||
al.AlbumArtist = al.Artist
|
||||
}
|
||||
if al.CurrentId != "" {
|
||||
toUpdate = append(toUpdate, al.album)
|
||||
} else {
|
||||
@ -172,6 +175,21 @@ func (r *albumRepository) GetStarred(options ...model.QueryOptions) (model.Album
|
||||
return r.toAlbums(starred), nil
|
||||
}
|
||||
|
||||
func (r *albumRepository) SetStar(starred bool, ids ...string) error {
|
||||
if len(ids) == 0 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
var starredAt time.Time
|
||||
if starred {
|
||||
starredAt = time.Now()
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{
|
||||
"starred": starred,
|
||||
"starred_at": starredAt,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *albumRepository) Search(q string, offset int, size int) (model.Albums, error) {
|
||||
if len(q) <= 2 {
|
||||
return nil, nil
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/conf"
|
||||
@ -13,9 +14,11 @@ import (
|
||||
)
|
||||
|
||||
type artist struct {
|
||||
ID string `orm:"pk;column(id)"`
|
||||
Name string `orm:"index"`
|
||||
AlbumCount int `orm:"column(album_count)"`
|
||||
ID string `orm:"pk;column(id)"`
|
||||
Name string `orm:"index"`
|
||||
AlbumCount int `orm:"column(album_count)"`
|
||||
Starred bool `orm:"index"`
|
||||
StarredAt time.Time `orm:"index;null"`
|
||||
}
|
||||
|
||||
type artistRepository struct {
|
||||
@ -152,6 +155,30 @@ where f.artist_id in ('%s') group by f.artist_id order by f.id`, strings.Join(id
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *artistRepository) GetStarred(options ...model.QueryOptions) (model.Artists, error) {
|
||||
var starred []artist
|
||||
_, err := r.newQuery(Db(), options...).Filter("starred", true).All(&starred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.toArtists(starred), nil
|
||||
}
|
||||
|
||||
func (r *artistRepository) SetStar(starred bool, ids ...string) error {
|
||||
if len(ids) == 0 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
var starredAt time.Time
|
||||
if starred {
|
||||
starredAt = time.Now()
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{
|
||||
"starred": starred,
|
||||
"starred_at": starredAt,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *artistRepository) PurgeInactive(activeList model.Artists) error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.purgeInactive(o, activeList, func(item interface{}) string {
|
||||
|
||||
@ -32,7 +32,7 @@ type mediaFile struct {
|
||||
PlayDate time.Time `orm:"null"`
|
||||
Rating int `orm:"index"`
|
||||
Starred bool `orm:"index"`
|
||||
StarredAt time.Time `orm:"null"`
|
||||
StarredAt time.Time `orm:"index;null"`
|
||||
CreatedAt time.Time `orm:"null"`
|
||||
UpdatedAt time.Time `orm:"null"`
|
||||
}
|
||||
@ -135,6 +135,29 @@ func (r *mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.M
|
||||
return r.toMediaFiles(starred), nil
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) SetStar(starred bool, ids ...string) error {
|
||||
if len(ids) == 0 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
var starredAt time.Time
|
||||
if starred {
|
||||
starredAt = time.Now()
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{
|
||||
"starred": starred,
|
||||
"starred_at": starredAt,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) SetRating(rating int, ids ...string) error {
|
||||
if len(ids) == 0 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{"rating": rating})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) PurgeInactive(activeList model.MediaFiles) error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.purgeInactive(o, activeList, func(item interface{}) string {
|
||||
|
||||
@ -43,7 +43,7 @@ func CreateSubsonicAPIRouter() *api.Router {
|
||||
browser := engine.NewBrowser(propertyRepository, mediaFolderRepository, artistRepository, albumRepository, mediaFileRepository, genreRepository)
|
||||
cover := engine.NewCover(mediaFileRepository, albumRepository)
|
||||
nowPlayingRepository := persistence.NewNowPlayingRepository()
|
||||
listGenerator := engine.NewListGenerator(albumRepository, mediaFileRepository, nowPlayingRepository)
|
||||
listGenerator := engine.NewListGenerator(artistRepository, albumRepository, mediaFileRepository, nowPlayingRepository)
|
||||
itunesControl := itunesbridge.NewItunesControl()
|
||||
playlistRepository := persistence.NewPlaylistRepository()
|
||||
playlists := engine.NewPlaylists(itunesControl, playlistRepository, mediaFileRepository)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user