diff --git a/api/album_lists.go b/api/album_lists.go
index dd56d0b0..0f8d1975 100644
--- a/api/album_lists.go
+++ b/api/album_lists.go
@@ -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
diff --git a/api/helpers.go b/api/helpers.go
index a8bdfaba..f607d814 100644
--- a/api/helpers.go
+++ b/api/helpers.go
@@ -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 {
diff --git a/api/media_annotation.go b/api/media_annotation.go
index d54edb0e..52c2f332 100644
--- a/api/media_annotation.go
+++ b/api/media_annotation.go
@@ -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) {
diff --git a/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .JSON b/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .JSON
index b1f56886..5eac46ab 100644
--- a/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .JSON
+++ b/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .JSON
@@ -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"}}
diff --git a/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .XML b/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .XML
index b5493f87..f89fb21a 100644
--- a/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .XML
+++ b/api/responses/.snapshots/responses-snapshotMatcher-Match-Responses Indexes with data should match .XML
@@ -1 +1 @@
-
+
diff --git a/api/responses/responses.go b/api/responses/responses.go
index 50e79e78..901297ce 100644
--- a/api/responses/responses.go
+++ b/api/responses/responses.go
@@ -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"`
/*
-
*/
diff --git a/api/responses/responses_test.go b/api/responses/responses_test.go
index af17701f..9b7998b7 100644
--- a/api/responses/responses_test.go
+++ b/api/responses/responses_test.go
@@ -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
diff --git a/api/searching.go b/api/searching.go
index 19c0641a..2d5f89f0 100644
--- a/api/searching.go
+++ b/api/searching.go
@@ -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)
diff --git a/engine/common.go b/engine/common.go
index 336e8787..3b865208 100644
--- a/engine/common.go
+++ b/engine/common.go
@@ -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
+}
diff --git a/engine/list_generator.go b/engine/list_generator.go
index 4a07f86f..f9eec81f 100644
--- a/engine/list_generator.go
+++ b/engine/list_generator.go
@@ -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) {
diff --git a/engine/ratings.go b/engine/ratings.go
index 465fea05..2018cffe 100644
--- a/engine/ratings.go
+++ b/engine/ratings.go
@@ -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 {
diff --git a/model/album.go b/model/album.go
index b452caa4..b96cd009 100644
--- a/model/album.go
+++ b/model/album.go
@@ -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
diff --git a/model/artist.go b/model/artist.go
index a2af148b..ef4b1615 100644
--- a/model/artist.go
+++ b/model/artist.go
@@ -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)
diff --git a/model/mediafile.go b/model/mediafile.go
index 118c0d77..ea2a7a53 100644
--- a/model/mediafile.go
+++ b/model/mediafile.go
@@ -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
}
diff --git a/persistence/album_repository.go b/persistence/album_repository.go
index aa584e82..50fd12c7 100644
--- a/persistence/album_repository.go
+++ b/persistence/album_repository.go
@@ -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
diff --git a/persistence/artist_repository.go b/persistence/artist_repository.go
index abe9d88c..345edba7 100644
--- a/persistence/artist_repository.go
+++ b/persistence/artist_repository.go
@@ -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 {
diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go
index ee72537a..d342be95 100644
--- a/persistence/mediafile_repository.go
+++ b/persistence/mediafile_repository.go
@@ -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 {
diff --git a/wire_gen.go b/wire_gen.go
index 60f05167..cf601d31 100644
--- a/wire_gen.go
+++ b/wire_gen.go
@@ -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)