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
+4 -2
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) { 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 { if err != nil {
log.Error(r, "Error retrieving starred media", "error", err) log.Error(r, "Error retrieving starred media", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
@@ -89,13 +89,14 @@ func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request)
response := NewResponse() response := NewResponse()
response.Starred = &responses.Starred{} response.Starred = &responses.Starred{}
response.Starred.Artist = ToArtists(artists)
response.Starred.Album = ToChildren(albums) response.Starred.Album = ToChildren(albums)
response.Starred.Song = ToChildren(mediaFiles) response.Starred.Song = ToChildren(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) {
albums, mediaFiles, err := c.listGen.GetAllStarred() artists, albums, mediaFiles, err := c.listGen.GetAllStarred()
if err != nil { if err != nil {
log.Error(r, "Error retrieving starred media", "error", err) log.Error(r, "Error retrieving starred media", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error") return nil, NewError(responses.ErrorGeneric, "Internal Error")
@@ -103,6 +104,7 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request
response := NewResponse() response := NewResponse()
response.Starred2 = &responses.Starred{} response.Starred2 = &responses.Starred{}
response.Starred2.Artist = ToArtists(artists)
response.Starred2.Album = ToAlbums(albums) response.Starred2.Album = ToAlbums(albums)
response.Starred2.Song = ToChildren(mediaFiles) response.Starred2.Song = ToChildren(mediaFiles)
return response, nil return response, nil
+15
View File
@@ -145,6 +145,21 @@ func ToAlbum(entry engine.Entry) responses.Child {
return album 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 { func ToChildren(entries engine.Entries) []responses.Child {
children := make([]responses.Child, len(entries)) children := make([]responses.Child, len(entries))
for i, entry := range entries { for i, entry := range entries {
+5 -2
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) { func (c *MediaAnnotationController) getIds(r *http.Request) ([]string, error) {
ids := ParamStrings(r, "id") ids := ParamStrings(r, "id")
albumIds := ParamStrings(r, "albumId") 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 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) { 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>
+4 -4
View File
@@ -57,11 +57,11 @@ type MusicFolders struct {
} }
type Artist struct { type Artist struct {
Id string `xml:"id,attr" json:"id"` Id string `xml:"id,attr" json:"id"`
Name string `xml:"name,attr" json:"name"` Name string `xml:"name,attr" json:"name"`
AlbumCount int `xml:"albumCount,attr,omitempty" json:"albumCount,omitempty"` 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="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 --> <xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.13.0 -->
*/ */
+2 -1
View File
@@ -90,7 +90,8 @@ var _ = Describe("Responses", func() {
Context("with data", func() { Context("with data", func() {
BeforeEach(func() { BeforeEach(func() {
artists := make([]Artist, 1) 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 := make([]Index, 1)
index[0] = Index{Name: "A", Artists: artists} index[0] = Index{Name: "A", Artists: artists}
response.Indexes.Index = index response.Indexes.Index = index
+4 -4
View File
@@ -70,10 +70,7 @@ func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (*
response := NewResponse() response := NewResponse()
searchResult2 := &responses.SearchResult2{} searchResult2 := &responses.SearchResult2{}
searchResult2.Artist = make([]responses.Artist, len(as)) searchResult2.Artist = ToArtists(as)
for i, e := range as {
searchResult2.Artist[i] = responses.Artist{Id: e.Id, Name: e.Title}
}
searchResult2.Album = ToChildren(als) searchResult2.Album = ToChildren(als)
searchResult2.Song = ToChildren(mfs) searchResult2.Song = ToChildren(mfs)
response.SearchResult2 = searchResult2 response.SearchResult2 = searchResult2
@@ -97,6 +94,9 @@ func (c *SearchingController) Search3(w http.ResponseWriter, r *http.Request) (*
CoverArt: e.CoverArt, CoverArt: e.CoverArt,
AlbumCount: e.AlbumCount, AlbumCount: e.AlbumCount,
} }
if !e.Starred.IsZero() {
searchResult3.Artist[i].Starred = &e.Starred
}
} }
searchResult3.Album = ToAlbums(als) searchResult3.Album = ToAlbums(als)
searchResult3.Song = ToChildren(mfs) searchResult3.Song = ToChildren(mfs)
+9
View File
@@ -50,6 +50,7 @@ func FromArtist(ar *model.Artist) Entry {
e.Id = ar.ID e.Id = ar.ID
e.Title = ar.Name e.Title = ar.Name
e.AlbumCount = ar.AlbumCount e.AlbumCount = ar.AlbumCount
e.Starred = ar.StarredAt
e.IsDir = true e.IsDir = true
return e return e
} }
@@ -137,3 +138,11 @@ func FromMediaFiles(mfs model.MediaFiles) Entries {
} }
return 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
}
+19 -9
View File
@@ -17,16 +17,17 @@ type ListGenerator interface {
GetByName(offset int, size int) (Entries, error) GetByName(offset int, size int) (Entries, error)
GetByArtist(offset int, size int) (Entries, error) GetByArtist(offset int, size int) (Entries, error)
GetStarred(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) GetNowPlaying() (Entries, error)
GetRandomSongs(size int) (Entries, error) GetRandomSongs(size int) (Entries, error)
} }
func NewListGenerator(alr model.AlbumRepository, mfr model.MediaFileRepository, npr model.NowPlayingRepository) ListGenerator { func NewListGenerator(arr model.ArtistRepository, alr model.AlbumRepository, mfr model.MediaFileRepository, npr model.NowPlayingRepository) ListGenerator {
return &listGenerator{alr, mfr, npr} return &listGenerator{arr, alr, mfr, npr}
} }
type listGenerator struct { type listGenerator struct {
artistRepo model.ArtistRepository
albumRepo model.AlbumRepository albumRepo model.AlbumRepository
mfRepository model.MediaFileRepository mfRepository model.MediaFileRepository
npRepo model.NowPlayingRepository 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) { 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) albums, err := g.albumRepo.GetStarred(qo)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -120,15 +121,24 @@ func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
return FromAlbums(albums), nil return FromAlbums(albums), nil
} }
func (g *listGenerator) GetAllStarred() (Entries, Entries, error) { // TODO Return is confusing
albums, err := g.GetStarred(0, -1) func (g *listGenerator) GetAllStarred() (Entries, Entries, Entries, error) {
artists, err := g.artistRepo.GetStarred(model.QueryOptions{SortBy: "starred_at", Desc: true})
if err != nil { 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) { func (g *listGenerator) GetNowPlaying() (Entries, error) {
+14
View File
@@ -3,6 +3,7 @@ package engine
import ( import (
"context" "context"
"github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/itunesbridge" "github.com/cloudsonic/sonic-server/itunesbridge"
"github.com/cloudsonic/sonic-server/log" "github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model" "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 { 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 { for _, id := range ids {
isAlbum, _ := r.albumRepo.Exists(id) isAlbum, _ := r.albumRepo.Exists(id)
if isAlbum { if isAlbum {
+1
View File
@@ -36,6 +36,7 @@ type AlbumRepository interface {
PurgeInactive(active Albums) error PurgeInactive(active Albums) error
GetAllIds() ([]string, error) GetAllIds() ([]string, error)
GetStarred(...QueryOptions) (Albums, error) GetStarred(...QueryOptions) (Albums, error)
SetStar(star bool, ids ...string) error
Search(q string, offset int, size int) (Albums, error) Search(q string, offset int, size int) (Albums, error)
Refresh(ids ...string) error Refresh(ids ...string) error
PurgeEmpty() error PurgeEmpty() error
+6
View File
@@ -1,9 +1,13 @@
package model package model
import "time"
type Artist struct { type Artist struct {
ID string ID string
Name string Name string
AlbumCount int AlbumCount int
Starred bool
StarredAt time.Time
} }
type Artists []Artist type Artists []Artist
@@ -19,6 +23,8 @@ type ArtistRepository interface {
Put(m *Artist) error Put(m *Artist) error
Get(id string) (*Artist, error) Get(id string) (*Artist, error)
PurgeInactive(active Artists) error PurgeInactive(active Artists) error
GetStarred(...QueryOptions) (Artists, error)
SetStar(star bool, ids ...string) error
Search(q string, offset int, size int) (Artists, error) Search(q string, offset int, size int) (Artists, error)
Refresh(ids ...string) error Refresh(ids ...string) error
GetIndex() (ArtistIndexes, error) GetIndex() (ArtistIndexes, error)
+2
View File
@@ -52,4 +52,6 @@ type MediaFileRepository interface {
Search(q string, offset int, size int) (MediaFiles, error) Search(q string, offset int, size int) (MediaFiles, error)
Delete(id string) error Delete(id string) error
DeleteByPath(path string) error DeleteByPath(path string) error
SetStar(star bool, ids ...string) error
SetRating(rating int, ids ...string) error
} }
+19 -1
View File
@@ -27,7 +27,7 @@ type album struct {
Duration int `` Duration int ``
Rating int `orm:"index"` Rating int `orm:"index"`
Genre string `orm:"index"` Genre string `orm:"index"`
StarredAt time.Time `orm:"null"` StarredAt time.Time `orm:"index;null"`
CreatedAt time.Time `orm:"null"` CreatedAt time.Time `orm:"null"`
UpdatedAt 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 { if al.Compilation {
al.AlbumArtist = "Various Artists" al.AlbumArtist = "Various Artists"
} }
if al.AlbumArtist == "" {
al.AlbumArtist = al.Artist
}
if al.CurrentId != "" { if al.CurrentId != "" {
toUpdate = append(toUpdate, al.album) toUpdate = append(toUpdate, al.album)
} else { } else {
@@ -172,6 +175,21 @@ func (r *albumRepository) GetStarred(options ...model.QueryOptions) (model.Album
return r.toAlbums(starred), nil 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) { func (r *albumRepository) Search(q string, offset int, size int) (model.Albums, error) {
if len(q) <= 2 { if len(q) <= 2 {
return nil, nil return nil, nil
+30 -3
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"time"
"github.com/astaxie/beego/orm" "github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/conf" "github.com/cloudsonic/sonic-server/conf"
@@ -13,9 +14,11 @@ import (
) )
type artist struct { type artist struct {
ID string `orm:"pk;column(id)"` ID string `orm:"pk;column(id)"`
Name string `orm:"index"` Name string `orm:"index"`
AlbumCount int `orm:"column(album_count)"` AlbumCount int `orm:"column(album_count)"`
Starred bool `orm:"index"`
StarredAt time.Time `orm:"index;null"`
} }
type artistRepository struct { 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 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 { func (r *artistRepository) PurgeInactive(activeList model.Artists) error {
return withTx(func(o orm.Ormer) error { return withTx(func(o orm.Ormer) error {
_, err := r.purgeInactive(o, activeList, func(item interface{}) string { _, err := r.purgeInactive(o, activeList, func(item interface{}) string {
+24 -1
View File
@@ -32,7 +32,7 @@ type mediaFile struct {
PlayDate time.Time `orm:"null"` PlayDate time.Time `orm:"null"`
Rating int `orm:"index"` Rating int `orm:"index"`
Starred bool `orm:"index"` Starred bool `orm:"index"`
StarredAt time.Time `orm:"null"` StarredAt time.Time `orm:"index;null"`
CreatedAt time.Time `orm:"null"` CreatedAt time.Time `orm:"null"`
UpdatedAt 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 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 { func (r *mediaFileRepository) PurgeInactive(activeList model.MediaFiles) error {
return withTx(func(o orm.Ormer) error { return withTx(func(o orm.Ormer) error {
_, err := r.purgeInactive(o, activeList, func(item interface{}) string { _, err := r.purgeInactive(o, activeList, func(item interface{}) string {
+1 -1
View File
@@ -43,7 +43,7 @@ func CreateSubsonicAPIRouter() *api.Router {
browser := engine.NewBrowser(propertyRepository, mediaFolderRepository, artistRepository, albumRepository, mediaFileRepository, genreRepository) browser := engine.NewBrowser(propertyRepository, mediaFolderRepository, artistRepository, albumRepository, mediaFileRepository, genreRepository)
cover := engine.NewCover(mediaFileRepository, albumRepository) cover := engine.NewCover(mediaFileRepository, albumRepository)
nowPlayingRepository := persistence.NewNowPlayingRepository() nowPlayingRepository := persistence.NewNowPlayingRepository()
listGenerator := engine.NewListGenerator(albumRepository, mediaFileRepository, nowPlayingRepository) listGenerator := engine.NewListGenerator(artistRepository, albumRepository, mediaFileRepository, nowPlayingRepository)
itunesControl := itunesbridge.NewItunesControl() itunesControl := itunesbridge.NewItunesControl()
playlistRepository := persistence.NewPlaylistRepository() playlistRepository := persistence.NewPlaylistRepository()
playlists := engine.NewPlaylists(itunesControl, playlistRepository, mediaFileRepository) playlists := engine.NewPlaylists(itunesControl, playlistRepository, mediaFileRepository)