Add Stars to the DB, including Artists! Only if DevUseFolderScanner is true

This commit is contained in:
Deluan 2020-01-18 20:03:52 -05:00
parent a4b75fd69d
commit 128e165aba
18 changed files with 161 additions and 30 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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) {

View File

@ -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"}}

View File

@ -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>

View File

@ -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 -->
*/

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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)