Implement annotations per user

This commit is contained in:
Deluan 2020-01-21 23:01:43 -05:00
parent e03304650d
commit d7116eebd4
26 changed files with 572 additions and 262 deletions

View file

@ -19,7 +19,7 @@ type Browser interface {
Directory(ctx context.Context, id string) (*DirectoryInfo, error)
Artist(ctx context.Context, id string) (*DirectoryInfo, error)
Album(ctx context.Context, id string) (*DirectoryInfo, error)
GetSong(id string) (*Entry, error)
GetSong(ctx context.Context, id string) (*Entry, error)
GetGenres() (model.Genres, error)
}
@ -77,7 +77,12 @@ func (b *browser) Artist(ctx context.Context, id string) (*DirectoryInfo, error)
return nil, err
}
log.Debug(ctx, "Found Artist", "id", id, "name", a.Name)
return b.buildArtistDir(a, albums), nil
var albumIds []string
for _, al := range albums {
albumIds = append(albumIds, al.ID)
}
annMap, err := b.ds.Annotation().GetMap(getUserID(ctx), model.AlbumItemType, albumIds)
return b.buildArtistDir(a, albums, annMap), nil
}
func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error) {
@ -86,7 +91,21 @@ func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error)
return nil, err
}
log.Debug(ctx, "Found Album", "id", id, "name", al.Name)
return b.buildAlbumDir(al, tracks), nil
var mfIds []string
for _, mf := range tracks {
mfIds = append(mfIds, mf.ID)
}
userID := getUserID(ctx)
trackAnnMap, err := b.ds.Annotation().GetMap(userID, model.MediaItemType, mfIds)
if err != nil {
return nil, err
}
ann, err := b.ds.Annotation().Get(userID, model.AlbumItemType, al.ID)
if err != nil {
return nil, err
}
return b.buildAlbumDir(al, ann, tracks, trackAnnMap), nil
}
func (b *browser) Directory(ctx context.Context, id string) (*DirectoryInfo, error) {
@ -101,13 +120,19 @@ func (b *browser) Directory(ctx context.Context, id string) (*DirectoryInfo, err
}
}
func (b *browser) GetSong(id string) (*Entry, error) {
func (b *browser) GetSong(ctx context.Context, id string) (*Entry, error) {
mf, err := b.ds.MediaFile().Get(id)
if err != nil {
return nil, err
}
entry := FromMediaFile(mf)
userId := getUserID(ctx)
ann, err := b.ds.Annotation().Get(userId, model.MediaItemType, id)
if err != nil {
return nil, err
}
entry := FromMediaFile(mf, ann)
return &entry, nil
}
@ -124,7 +149,7 @@ func (b *browser) GetGenres() (model.Genres, error) {
return genres, err
}
func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums) *DirectoryInfo {
func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums, albumAnnMap model.AnnotationMap) *DirectoryInfo {
dir := &DirectoryInfo{
Id: a.ID,
Name: a.Name,
@ -133,33 +158,38 @@ func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums) *Director
dir.Entries = make(Entries, len(albums))
for i, al := range albums {
dir.Entries[i] = FromAlbum(&al)
dir.PlayCount += int32(al.PlayCount)
ann := albumAnnMap[al.ID]
dir.Entries[i] = FromAlbum(&al, &ann)
dir.PlayCount += int32(ann.PlayCount)
}
return dir
}
func (b *browser) buildAlbumDir(al *model.Album, tracks model.MediaFiles) *DirectoryInfo {
func (b *browser) buildAlbumDir(al *model.Album, albumAnn *model.Annotation, tracks model.MediaFiles, trackAnnMap model.AnnotationMap) *DirectoryInfo {
dir := &DirectoryInfo{
Id: al.ID,
Name: al.Name,
Parent: al.ArtistID,
PlayCount: int32(al.PlayCount),
UserRating: al.Rating,
Starred: al.StarredAt,
Artist: al.Artist,
ArtistId: al.ArtistID,
SongCount: al.SongCount,
Duration: al.Duration,
Created: al.CreatedAt,
Year: al.Year,
Genre: al.Genre,
CoverArt: al.CoverArtId,
Id: al.ID,
Name: al.Name,
Parent: al.ArtistID,
Artist: al.Artist,
ArtistId: al.ArtistID,
SongCount: al.SongCount,
Duration: al.Duration,
Created: al.CreatedAt,
Year: al.Year,
Genre: al.Genre,
CoverArt: al.CoverArtId,
}
if albumAnn != nil {
dir.PlayCount = int32(albumAnn.PlayCount)
dir.Starred = albumAnn.StarredAt
dir.UserRating = albumAnn.Rating
}
dir.Entries = make(Entries, len(tracks))
for i, mf := range tracks {
dir.Entries[i] = FromMediaFile(&mf)
mfId := mf.ID
ann := trackAnnMap[mfId]
dir.Entries[i] = FromMediaFile(&mf, &ann)
}
return dir
}

View file

@ -1,6 +1,7 @@
package engine
import (
"context"
"fmt"
"time"
@ -45,17 +46,19 @@ type Entry struct {
type Entries []Entry
func FromArtist(ar *model.Artist) Entry {
func FromArtist(ar *model.Artist, ann *model.Annotation) Entry {
e := Entry{}
e.Id = ar.ID
e.Title = ar.Name
e.AlbumCount = ar.AlbumCount
e.Starred = ar.StarredAt
e.IsDir = true
if ann != nil {
e.Starred = ann.StarredAt
}
return e
}
func FromAlbum(al *model.Album) Entry {
func FromAlbum(al *model.Album, ann *model.Annotation) Entry {
e := Entry{}
e.Id = al.ID
e.Title = al.Name
@ -66,18 +69,20 @@ func FromAlbum(al *model.Album) Entry {
e.Artist = al.AlbumArtist
e.Genre = al.Genre
e.CoverArt = al.CoverArtId
e.Starred = al.StarredAt
e.PlayCount = int32(al.PlayCount)
e.Created = al.CreatedAt
e.AlbumId = al.ID
e.ArtistId = al.ArtistID
e.UserRating = al.Rating
e.Duration = al.Duration
e.SongCount = al.SongCount
if ann != nil {
e.Starred = ann.StarredAt
e.PlayCount = int32(ann.PlayCount)
e.UserRating = ann.Rating
}
return e
}
func FromMediaFile(mf *model.MediaFile) Entry {
func FromMediaFile(mf *model.MediaFile, ann *model.Annotation) Entry {
e := Entry{}
e.Id = mf.ID
e.Title = mf.Title
@ -92,7 +97,6 @@ func FromMediaFile(mf *model.MediaFile) Entry {
e.Size = mf.Size
e.Suffix = mf.Suffix
e.BitRate = mf.BitRate
e.Starred = mf.StarredAt
if mf.HasCoverArt {
e.CoverArt = mf.ID
}
@ -102,13 +106,16 @@ func FromMediaFile(mf *model.MediaFile) Entry {
if mf.Path != "" {
e.Path = fmt.Sprintf("%s/%s/%s.%s", realArtistName(mf), mf.Album, mf.Title, mf.Suffix)
}
e.PlayCount = int32(mf.PlayCount)
e.DiscNumber = mf.DiscNumber
e.Created = mf.CreatedAt
e.AlbumId = mf.AlbumID
e.ArtistId = mf.ArtistID
e.Type = "music" // TODO Hardcoded for now
e.UserRating = mf.Rating
if ann != nil {
e.PlayCount = int32(ann.PlayCount)
e.Starred = ann.StarredAt
e.UserRating = ann.Rating
}
return e
}
@ -123,26 +130,37 @@ func realArtistName(mf *model.MediaFile) string {
return mf.Artist
}
func FromAlbums(albums model.Albums) Entries {
func FromAlbums(albums model.Albums, annMap model.AnnotationMap) Entries {
entries := make(Entries, len(albums))
for i, al := range albums {
entries[i] = FromAlbum(&al)
ann := annMap[al.ID]
entries[i] = FromAlbum(&al, &ann)
}
return entries
}
func FromMediaFiles(mfs model.MediaFiles) Entries {
func FromMediaFiles(mfs model.MediaFiles, annMap model.AnnotationMap) Entries {
entries := make(Entries, len(mfs))
for i, mf := range mfs {
entries[i] = FromMediaFile(&mf)
ann := annMap[mf.ID]
entries[i] = FromMediaFile(&mf, &ann)
}
return entries
}
func FromArtists(ars model.Artists) Entries {
func FromArtists(ars model.Artists, annMap model.AnnotationMap) Entries {
entries := make(Entries, len(ars))
for i, ar := range ars {
entries[i] = FromArtist(&ar)
ann := annMap[ar.ID]
entries[i] = FromArtist(&ar, &ann)
}
return entries
}
func getUserID(ctx context.Context) string {
user, ok := ctx.Value("user").(*model.User)
if ok {
return user.ID
}
return ""
}

View file

@ -1,23 +1,24 @@
package engine
import (
"context"
"time"
"github.com/cloudsonic/sonic-server/model"
)
type ListGenerator interface {
GetNewest(offset int, size int) (Entries, error)
GetRecent(offset int, size int) (Entries, error)
GetFrequent(offset int, size int) (Entries, error)
GetHighest(offset int, size int) (Entries, error)
GetRandom(offset int, size int) (Entries, error)
GetByName(offset int, size int) (Entries, error)
GetByArtist(offset int, size int) (Entries, error)
GetStarred(offset int, size int) (Entries, error)
GetAllStarred() (artists Entries, albums Entries, mediaFiles Entries, err error)
GetNowPlaying() (Entries, error)
GetRandomSongs(size int, genre string) (Entries, error)
GetNewest(ctx context.Context, offset int, size int) (Entries, error)
GetRecent(ctx context.Context, offset int, size int) (Entries, error)
GetFrequent(ctx context.Context, offset int, size int) (Entries, error)
GetHighest(ctx context.Context, offset int, size int) (Entries, error)
GetRandom(ctx context.Context, offset int, size int) (Entries, error)
GetByName(ctx context.Context, offset int, size int) (Entries, error)
GetByArtist(ctx context.Context, offset int, size int) (Entries, error)
GetStarred(ctx context.Context, offset int, size int) (Entries, error)
GetAllStarred(ctx context.Context) (artists Entries, albums Entries, mediaFiles Entries, err error)
GetNowPlaying(ctx context.Context) (Entries, error)
GetRandomSongs(ctx context.Context, size int, genre string) (Entries, error)
}
func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator {
@ -30,58 +31,76 @@ type listGenerator struct {
}
// TODO: Only return albums that have the Sort field != empty
func (g *listGenerator) query(qo model.QueryOptions, offset int, size int) (Entries, error) {
func (g *listGenerator) query(ctx context.Context, qo model.QueryOptions, offset int, size int) (Entries, error) {
qo.Offset = offset
qo.Max = size
albums, err := g.ds.Album().GetAll(qo)
return FromAlbums(albums), err
if err != nil {
return nil, err
}
albumIds := make([]string, len(albums))
for i, al := range albums {
albumIds[i] = al.ID
}
annMap, err := g.ds.Annotation().GetMap(getUserID(ctx), model.AlbumItemType, albumIds)
if err != nil {
return nil, err
}
return FromAlbums(albums, annMap), err
}
func (g *listGenerator) GetNewest(offset int, size int) (Entries, error) {
func (g *listGenerator) GetNewest(ctx context.Context, offset int, size int) (Entries, error) {
qo := model.QueryOptions{Sort: "CreatedAt", Order: "desc"}
return g.query(qo, offset, size)
return g.query(ctx, qo, offset, size)
}
func (g *listGenerator) GetRecent(offset int, size int) (Entries, error) {
func (g *listGenerator) GetRecent(ctx context.Context, offset int, size int) (Entries, error) {
qo := model.QueryOptions{Sort: "PlayDate", Order: "desc"}
return g.query(qo, offset, size)
return g.query(ctx, qo, offset, size)
}
func (g *listGenerator) GetFrequent(offset int, size int) (Entries, error) {
func (g *listGenerator) GetFrequent(ctx context.Context, offset int, size int) (Entries, error) {
qo := model.QueryOptions{Sort: "PlayCount", Order: "desc"}
return g.query(qo, offset, size)
return g.query(ctx, qo, offset, size)
}
func (g *listGenerator) GetHighest(offset int, size int) (Entries, error) {
func (g *listGenerator) GetHighest(ctx context.Context, offset int, size int) (Entries, error) {
qo := model.QueryOptions{Sort: "Rating", Order: "desc"}
return g.query(qo, offset, size)
return g.query(ctx, qo, offset, size)
}
func (g *listGenerator) GetByName(offset int, size int) (Entries, error) {
func (g *listGenerator) GetByName(ctx context.Context, offset int, size int) (Entries, error) {
qo := model.QueryOptions{Sort: "Name"}
return g.query(qo, offset, size)
return g.query(ctx, qo, offset, size)
}
func (g *listGenerator) GetByArtist(offset int, size int) (Entries, error) {
func (g *listGenerator) GetByArtist(ctx context.Context, offset int, size int) (Entries, error) {
qo := model.QueryOptions{Sort: "Artist"}
return g.query(qo, offset, size)
return g.query(ctx, qo, offset, size)
}
func (g *listGenerator) GetRandom(offset int, size int) (Entries, error) {
func (g *listGenerator) GetRandom(ctx context.Context, offset int, size int) (Entries, error) {
albums, err := g.ds.Album().GetRandom(model.QueryOptions{Max: size, Offset: offset})
if err != nil {
return nil, err
}
r := make(Entries, len(albums))
for i, al := range albums {
r[i] = FromAlbum(&al)
annMap, err := g.getAnnotationsForAlbums(ctx, albums)
if err != nil {
return nil, err
}
return r, nil
return FromAlbums(albums, annMap), nil
}
func (g *listGenerator) GetRandomSongs(size int, genre string) (Entries, error) {
func (g *listGenerator) getAnnotationsForAlbums(ctx context.Context, albums model.Albums) (model.AnnotationMap, error) {
albumIds := make([]string, len(albums))
for i, al := range albums {
albumIds[i] = al.ID
}
return g.ds.Annotation().GetMap(getUserID(ctx), model.AlbumItemType, albumIds)
}
func (g *listGenerator) GetRandomSongs(ctx context.Context, size int, genre string) (Entries, error) {
options := model.QueryOptions{Max: size}
if genre != "" {
options.Filters = map[string]interface{}{"genre": genre}
@ -93,47 +112,78 @@ func (g *listGenerator) GetRandomSongs(size int, genre string) (Entries, error)
r := make(Entries, len(mediaFiles))
for i, mf := range mediaFiles {
r[i] = FromMediaFile(&mf)
ann, err := g.ds.Annotation().Get(getUserID(ctx), model.MediaItemType, mf.ID)
if err != nil {
return nil, err
}
r[i] = FromMediaFile(&mf, ann)
}
return r, nil
}
func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) {
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().GetStarred(qo)
albums, err := g.ds.Album().GetStarred(getUserID(ctx), qo)
if err != nil {
return nil, err
}
return FromAlbums(albums), nil
annMap, err := g.getAnnotationsForAlbums(ctx, albums)
if err != nil {
return nil, err
}
return FromAlbums(albums, annMap), nil
}
func (g *listGenerator) GetAllStarred() (artists Entries, albums Entries, mediaFiles Entries, err error) {
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().GetStarred(options)
ars, err := g.ds.Artist().GetStarred(getUserID(ctx), options)
if err != nil {
return nil, nil, nil, err
}
als, err := g.ds.Album().GetStarred(options)
als, err := g.ds.Album().GetStarred(getUserID(ctx), options)
if err != nil {
return nil, nil, nil, err
}
mfs, err := g.ds.MediaFile().GetStarred(options)
mfs, err := g.ds.MediaFile().GetStarred(getUserID(ctx), options)
if err != nil {
return nil, nil, nil, err
}
artists = FromArtists(ars)
albums = FromAlbums(als)
mediaFiles = FromMediaFiles(mfs)
var mfIds []string
for _, mf := range mfs {
mfIds = append(mfIds, mf.ID)
}
trackAnnMap, err := g.ds.Annotation().GetMap(getUserID(ctx), model.MediaItemType, mfIds)
if err != nil {
return nil, nil, nil, err
}
albumAnnMap, err := g.getAnnotationsForAlbums(ctx, als)
if err != nil {
return nil, nil, nil, err
}
var artistIds []string
for _, ar := range ars {
artistIds = append(artistIds, ar.ID)
}
artistAnnMap, err := g.ds.Annotation().GetMap(getUserID(ctx), model.MediaItemType, artistIds)
if err != nil {
return nil, nil, nil, err
}
artists = FromArtists(ars, artistAnnMap)
albums = FromAlbums(als, albumAnnMap)
mediaFiles = FromMediaFiles(mfs, trackAnnMap)
return
}
func (g *listGenerator) GetNowPlaying() (Entries, error) {
func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) {
npInfo, err := g.npRepo.GetAll()
if err != nil {
return nil, err
@ -144,7 +194,8 @@ func (g *listGenerator) GetNowPlaying() (Entries, error) {
if err != nil {
return nil, err
}
entries[i] = FromMediaFile(mf)
ann, err := g.ds.Annotation().Get(getUserID(ctx), model.MediaItemType, mf.ID)
entries[i] = FromMediaFile(mf, ann)
entries[i].UserName = np.Username
entries[i].MinutesAgo = int(time.Now().Sub(np.Start).Minutes())
entries[i].PlayerId = np.PlayerId

View file

@ -10,7 +10,7 @@ import (
type Playlists interface {
GetAll() (model.Playlists, error)
Get(id string) (*PlaylistInfo, error)
Get(ctx context.Context, id string) (*PlaylistInfo, error)
Create(ctx context.Context, playlistId, name string, ids []string) error
Delete(ctx context.Context, playlistId string) error
Update(ctx context.Context, playlistId string, name *string, idsToAdd []string, idxToRemove []int) error
@ -118,7 +118,7 @@ type PlaylistInfo struct {
Comment string
}
func (p *playlists) Get(id string) (*PlaylistInfo, error) {
func (p *playlists) Get(ctx context.Context, id string) (*PlaylistInfo, error) {
pl, err := p.ds.Playlist().GetWithTracks(id)
if err != nil {
return nil, err
@ -136,8 +136,16 @@ func (p *playlists) Get(id string) (*PlaylistInfo, error) {
}
pinfo.Entries = make(Entries, len(pl.Tracks))
var mfIds []string
for _, mf := range pl.Tracks {
mfIds = append(mfIds, mf.ID)
}
annMap, err := p.ds.Annotation().GetMap(getUserID(ctx), model.MediaItemType, mfIds)
for i, mf := range pl.Tracks {
pinfo.Entries[i] = FromMediaFile(&mf)
ann := annMap[mf.ID]
pinfo.Entries[i] = FromMediaFile(&mf, &ann)
}
return pinfo, nil

View file

@ -3,6 +3,7 @@ package engine
import (
"context"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
)
@ -20,21 +21,52 @@ type ratings struct {
}
func (r ratings) SetRating(ctx context.Context, id string, rating int) error {
// TODO
return model.ErrNotFound
exist, err := r.ds.Album().Exists(id)
if err != nil {
return err
}
if exist {
return r.ds.Annotation().SetRating(rating, getUserID(ctx), model.AlbumItemType, id)
}
return r.ds.Annotation().SetRating(rating, getUserID(ctx), model.MediaItemType, id)
}
func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
if len(ids) == 0 {
log.Warn(ctx, "Cannot star/unstar an empty list of ids")
return nil
}
userId := getUserID(ctx)
return r.ds.WithTx(func(tx model.DataStore) error {
err := tx.MediaFile().SetStar(star, ids...)
if err != nil {
return err
for _, id := range ids {
exist, err := r.ds.Album().Exists(id)
if err != nil {
return err
}
if exist {
err = tx.Annotation().SetStar(star, userId, model.AlbumItemType, ids...)
if err != nil {
return err
}
continue
}
exist, err = r.ds.Artist().Exists(id)
if err != nil {
return err
}
if exist {
err = tx.Annotation().SetStar(star, userId, model.ArtistItemType, ids...)
if err != nil {
return err
}
continue
}
err = tx.Annotation().SetStar(star, userId, model.MediaItemType, ids...)
if err != nil {
return err
}
}
err = tx.Album().SetStar(star, ids...)
if err != nil {
return err
}
err = tx.Artist().SetStar(star, ids...)
return err
return nil
})
}

View file

@ -24,6 +24,8 @@ type scrobbler struct {
}
func (s *scrobbler) Register(ctx context.Context, playerId int, trackId string, playTime time.Time) (*model.MediaFile, error) {
userId := getUserID(ctx)
var mf *model.MediaFile
var err error
err = s.ds.WithTx(func(tx model.DataStore) error {
@ -31,11 +33,11 @@ func (s *scrobbler) Register(ctx context.Context, playerId int, trackId string,
if err != nil {
return err
}
err = s.ds.MediaFile().MarkAsPlayed(trackId, playTime)
err = s.ds.Annotation().IncPlayCount(userId, model.MediaItemType, trackId, playTime)
if err != nil {
return err
}
err = s.ds.Album().MarkAsPlayed(mf.AlbumID, playTime)
err = s.ds.Annotation().IncPlayCount(userId, model.AlbumItemType, mf.AlbumID, playTime)
return err
})
return mf, err

View file

@ -25,39 +25,57 @@ func NewSearch(ds model.DataStore) Search {
func (s *search) SearchArtist(ctx context.Context, q string, offset int, size int) (Entries, error) {
q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*")))
resp, err := s.ds.Artist().Search(q, offset, size)
artists, err := s.ds.Artist().Search(q, offset, size)
if len(artists) == 0 || err != nil {
return nil, nil
}
artistIds := make([]string, len(artists))
for i, al := range artists {
artistIds[i] = al.ID
}
annMap, err := s.ds.Annotation().GetMap(getUserID(ctx), model.ArtistItemType, artistIds)
if err != nil {
return nil, nil
}
res := make(Entries, 0, len(resp))
for _, ar := range resp {
res = append(res, FromArtist(&ar))
}
return res, nil
return FromArtists(artists, annMap), nil
}
func (s *search) SearchAlbum(ctx context.Context, q string, offset int, size int) (Entries, error) {
q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*")))
resp, err := s.ds.Album().Search(q, offset, size)
albums, err := s.ds.Album().Search(q, offset, size)
if len(albums) == 0 || err != nil {
return nil, nil
}
albumIds := make([]string, len(albums))
for i, al := range albums {
albumIds[i] = al.ID
}
annMap, err := s.ds.Annotation().GetMap(getUserID(ctx), model.AlbumItemType, albumIds)
if err != nil {
return nil, nil
}
res := make(Entries, 0, len(resp))
for _, al := range resp {
res = append(res, FromAlbum(&al))
}
return res, nil
return FromAlbums(albums, annMap), nil
}
func (s *search) SearchSong(ctx context.Context, q string, offset int, size int) (Entries, error) {
q = sanitize.Accents(strings.ToLower(strings.TrimSuffix(q, "*")))
resp, err := s.ds.MediaFile().Search(q, offset, size)
mediaFiles, err := s.ds.MediaFile().Search(q, offset, size)
if len(mediaFiles) == 0 || err != nil {
return nil, nil
}
trackIds := make([]string, len(mediaFiles))
for i, mf := range mediaFiles {
trackIds[i] = mf.ID
}
annMap, err := s.ds.Annotation().GetMap(getUserID(ctx), model.MediaItemType, trackIds)
if err != nil {
return nil, nil
}
res := make(Entries, 0, len(resp))
for _, mf := range resp {
res = append(res, FromMediaFile(&mf))
}
return res, nil
return FromMediaFiles(mediaFiles, annMap), nil
}

View file

@ -12,14 +12,9 @@ type Album struct {
AlbumArtist string
Year int
Compilation bool
Starred bool
PlayCount int
PlayDate time.Time
SongCount int
Duration int
Rating int
Genre string
StarredAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
@ -34,10 +29,8 @@ type AlbumRepository interface {
FindByArtist(artistId string) (Albums, error)
GetAll(...QueryOptions) (Albums, error)
GetRandom(...QueryOptions) (Albums, error)
GetStarred(...QueryOptions) (Albums, error)
GetStarred(userId string, options ...QueryOptions) (Albums, error)
Search(q string, offset int, size int) (Albums, error)
Refresh(ids ...string) error
PurgeEmpty() error
SetStar(star bool, ids ...string) error
MarkAsPlayed(id string, playDate time.Time) error
}

32
model/annotation.go Normal file
View file

@ -0,0 +1,32 @@
package model
import "time"
const (
ArtistItemType = "artist"
AlbumItemType = "album"
MediaItemType = "mediaFile"
)
type Annotation struct {
AnnotationID string
UserID string
ItemID string
ItemType string
PlayCount int
PlayDate time.Time
Rating int
Starred bool
StarredAt time.Time
}
type AnnotationMap map[string]Annotation
type AnnotationRepository interface {
Get(userID, itemType string, itemID string) (*Annotation, error)
GetMap(userID, itemType string, itemID []string) (AnnotationMap, error)
Delete(userID, itemType string, itemID ...string) error
IncPlayCount(userID, itemType string, itemID string, ts time.Time) error
SetStar(starred bool, userID, itemType string, ids ...string) error
SetRating(rating int, userID, itemType string, itemID string) error
}

View file

@ -1,13 +1,9 @@
package model
import "time"
type Artist struct {
ID string
Name string
AlbumCount int
Starred bool
StarredAt time.Time
}
type Artists []Artist
@ -22,7 +18,7 @@ type ArtistRepository interface {
Exists(id string) (bool, error)
Put(m *Artist) error
Get(id string) (*Artist, error)
GetStarred(...QueryOptions) (Artists, error)
GetStarred(userId string, options ...QueryOptions) (Artists, error)
SetStar(star bool, ids ...string) error
Search(q string, offset int, size int) (Artists, error)
Refresh(ids ...string) error

View file

@ -30,6 +30,7 @@ type DataStore interface {
Playlist() PlaylistRepository
Property() PropertyRepository
User() UserRepository
Annotation() AnnotationRepository
Resource(model interface{}) ResourceRepository

View file

@ -24,11 +24,6 @@ type MediaFile struct {
BitRate int
Genre string
Compilation bool
PlayCount int
PlayDate time.Time
Rating int
Starred bool
StarredAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
@ -46,12 +41,9 @@ type MediaFileRepository interface {
Get(id string) (*MediaFile, error)
FindByAlbum(albumId string) (MediaFiles, error)
FindByPath(path string) (MediaFiles, error)
GetStarred(options ...QueryOptions) (MediaFiles, error)
GetStarred(userId string, options ...QueryOptions) (MediaFiles, error)
GetRandom(options ...QueryOptions) (MediaFiles, error)
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
MarkAsPlayed(id string, playTime time.Time) error
}

View file

@ -5,6 +5,7 @@ import (
"strings"
"time"
"github.com/Masterminds/squirrel"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
@ -20,14 +21,9 @@ type album struct {
AlbumArtist string ``
Year int `orm:"index"`
Compilation bool ``
Starred bool `orm:"index"`
PlayCount int `orm:"index"`
PlayDate time.Time `orm:"null;index"`
SongCount int ``
Duration int ``
Rating int `orm:"index"`
Genre string `orm:"index"`
StarredAt time.Time `orm:"index;null"`
CreatedAt time.Time `orm:"null"`
UpdatedAt time.Time `orm:"null"`
}
@ -115,9 +111,9 @@ func (r *albumRepository) Refresh(ids ...string) error {
o := r.ormer
sql := fmt.Sprintf(`
select album_id as id, album as name, f.artist, f.album_artist, f.artist_id, f.compilation, f.genre,
max(f.year) as year, sum(f.play_count) as play_count, max(f.play_date) as play_date, sum(f.duration) as duration,
max(f.updated_at) as updated_at, min(f.created_at) as created_at, count(*) as song_count,
a.id as current_id, f.id as cover_art_id, f.path as cover_art_path, f.has_cover_art
max(f.year) as year, sum(f.duration) as duration, max(f.updated_at) as updated_at,
min(f.created_at) as created_at, count(*) as song_count, a.id as current_id, f.id as cover_art_id,
f.path as cover_art_path, f.has_cover_art
from media_file f left outer join album a on f.album_id = a.id
where f.album_id in ('%s')
group by album_id order by f.id`, strings.Join(ids, "','"))
@ -157,9 +153,8 @@ group by album_id order by f.id`, strings.Join(ids, "','"))
}
if len(toUpdate) > 0 {
for _, al := range toUpdate {
// Don't update Starred/Rating
_, err := o.Update(&al, "name", "artist_id", "cover_art_path", "cover_art_id", "artist", "album_artist",
"year", "compilation", "play_count", "play_date", "song_count", "duration", "updated_at", "created_at")
"year", "compilation", "song_count", "duration", "updated_at", "created_at")
if err != nil {
return err
}
@ -174,45 +169,28 @@ func (r *albumRepository) PurgeEmpty() error {
return err
}
func (r *albumRepository) GetStarred(options ...model.QueryOptions) (model.Albums, error) {
func (r *albumRepository) GetStarred(userId string, options ...model.QueryOptions) (model.Albums, error) {
var starred []album
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
sq := r.newRawQuery(options...).Join("annotation").Where("annotation.item_id = " + r.tableName + ".id")
sq = sq.Where(squirrel.Eq{"annotation.user_id": userId})
sql, args, err := sq.ToSql()
if err != nil {
return nil, err
}
_, err = r.ormer.Raw(sql, args...).QueryRows(&starred)
if err != nil {
return nil, err
}
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().Filter("id__in", ids).Update(orm.Params{
"starred": starred,
"starred_at": starredAt,
})
return err
}
func (r *albumRepository) MarkAsPlayed(id string, playDate time.Time) error {
_, err := r.newQuery().Filter("id", id).Update(orm.Params{
"play_count": orm.ColValue(orm.ColAdd, 1),
"play_date": playDate,
})
return err
}
func (r *albumRepository) Search(q string, offset int, size int) (model.Albums, error) {
if len(q) <= 2 {
return nil, nil
}
var results []album
err := r.doSearch(r.tableName, q, offset, size, &results, "rating desc", "starred desc", "play_count desc", "name")
err := r.doSearch(r.tableName, q, offset, size, &results, "name")
if err != nil {
return nil, err
}

View file

@ -44,7 +44,7 @@ var _ = Describe("AlbumRepository", func() {
Describe("GetStarred", func() {
It("returns all starred records", func() {
Expect(repo.GetStarred(model.QueryOptions{})).To(Equal(model.Albums{
Expect(repo.GetStarred("userid", model.QueryOptions{})).To(Equal(model.Albums{
albumRadioactivity,
}))
})

View file

@ -0,0 +1,154 @@
package persistence
import (
"time"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/model"
"github.com/google/uuid"
)
type annotation struct {
AnnotationID string `orm:"pk;column(ann_id)"`
UserID string `orm:"column(user_id)"`
ItemID string `orm:"column(item_id)"`
ItemType string `orm:"column(item_type)"`
PlayCount int `orm:"index;null"`
PlayDate time.Time `orm:"index;null"`
Rating int `orm:"index;null"`
Starred bool `orm:"index"`
StarredAt time.Time `orm:"null"`
}
func (u *annotation) TableUnique() [][]string {
return [][]string{
[]string{"UserID", "ItemID", "ItemType"},
}
}
type annotationRepository struct {
sqlRepository
}
func NewAnnotationRepository(o orm.Ormer) model.AnnotationRepository {
r := &annotationRepository{}
r.ormer = o
r.tableName = "annotation"
return r
}
func (r *annotationRepository) Get(userID, itemType string, itemID string) (*model.Annotation, error) {
if userID == "" {
return nil, model.ErrInvalidAuth
}
q := r.newQuery().Filter("user_id", userID).Filter("item_type", itemType).Filter("item_id", itemID)
var ann annotation
err := q.One(&ann)
if err == orm.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, err
}
resp := model.Annotation(ann)
return &resp, nil
}
func (r *annotationRepository) GetMap(userID, itemType string, itemID []string) (model.AnnotationMap, error) {
if userID == "" {
return nil, model.ErrInvalidAuth
}
if len(itemID) == 0 {
return nil, nil
}
q := r.newQuery().Filter("user_id", userID).Filter("item_type", itemType).Filter("item_id__in", itemID)
var res []annotation
_, err := q.All(&res)
if err != nil {
return nil, err
}
m := make(model.AnnotationMap)
for _, a := range res {
m[a.ItemID] = model.Annotation(a)
}
return m, nil
}
func (r *annotationRepository) new(userID, itemType string, itemID string) *annotation {
id, _ := uuid.NewRandom()
return &annotation{
AnnotationID: id.String(),
UserID: userID,
ItemID: itemID,
ItemType: itemType,
}
}
func (r *annotationRepository) IncPlayCount(userID, itemType string, itemID string, ts time.Time) error {
if userID == "" {
return model.ErrInvalidAuth
}
q := r.newQuery().Filter("user_id", userID).Filter("item_type", itemType).Filter("item_id", itemID)
c, err := q.Update(orm.Params{
"play_count": orm.ColValue(orm.ColAdd, 1),
"play_date": ts,
})
if c == 0 || err == orm.ErrNoRows {
ann := r.new(userID, itemType, itemID)
ann.PlayCount = 1
ann.PlayDate = ts
_, err = r.ormer.Insert(ann)
}
return err
}
func (r *annotationRepository) SetStar(starred bool, userID, itemType string, ids ...string) error {
if userID == "" {
return model.ErrInvalidAuth
}
q := r.newQuery().Filter("user_id", userID).Filter("item_type", itemType).Filter("item_id__in", ids)
var starredAt time.Time
if starred {
starredAt = time.Now()
}
c, err := q.Update(orm.Params{
"starred": starred,
"starred_at": starredAt,
})
if c == 0 || err == orm.ErrNoRows {
for _, id := range ids {
ann := r.new(userID, itemType, id)
ann.Starred = starred
ann.StarredAt = starredAt
_, err = r.ormer.Insert(ann)
if err != nil {
return err
}
}
}
return nil
}
func (r *annotationRepository) SetRating(rating int, userID, itemType string, itemID string) error {
if userID == "" {
return model.ErrInvalidAuth
}
q := r.newQuery().Filter("user_id", userID).Filter("item_type", itemType).Filter("item_id", itemID)
c, err := q.Update(orm.Params{
"rating": rating,
})
if c == 0 || err == orm.ErrNoRows {
ann := r.new(userID, itemType, itemID)
ann.Rating = rating
_, err = r.ormer.Insert(ann)
}
return err
}
func (r *annotationRepository) Delete(userID, itemType string, itemID ...string) error {
q := r.newQuery().Filter("user_id", userID).Filter("item_type", itemType).Filter("item_id__in", itemID)
_, err := q.Delete()
return err
}

View file

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/Masterminds/squirrel"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
@ -14,11 +15,9 @@ import (
)
type artist struct {
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"`
ID string `orm:"pk;column(id)"`
Name string `orm:"index"`
AlbumCount int `orm:"column(album_count)"`
}
type artistRepository struct {
@ -155,9 +154,15 @@ 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) {
func (r *artistRepository) GetStarred(userId string, options ...model.QueryOptions) (model.Artists, error) {
var starred []artist
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
sq := r.newRawQuery(options...).Join("annotation").Where("annotation.item_id = " + r.tableName + ".id")
sq = sq.Where(squirrel.Eq{"annotation.user_id": userId})
sql, args, err := sq.ToSql()
if err != nil {
return nil, err
}
_, err = r.ormer.Raw(sql, args...).QueryRows(&starred)
if err != nil {
return nil, err
}

View file

@ -5,6 +5,7 @@ import (
"strings"
"time"
"github.com/Masterminds/squirrel"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/model"
)
@ -28,11 +29,6 @@ type mediaFile struct {
BitRate int ``
Genre string `orm:"index"`
Compilation bool ``
PlayCount int `orm:"index"`
PlayDate time.Time `orm:"null"`
Rating int `orm:"index"`
Starred bool `orm:"index"`
StarredAt time.Time `orm:"index;null"`
CreatedAt time.Time `orm:"null"`
UpdatedAt time.Time `orm:"null"`
}
@ -51,6 +47,7 @@ func NewMediaFileRepository(o orm.Ormer) model.MediaFileRepository {
func (r *mediaFileRepository) Put(m *model.MediaFile) error {
tm := mediaFile(*m)
// Don't update media annotation fields (playcount, starred, etc..)
// TODO Validate if this is still necessary, now that we don't have annotations in the mediafile model
return r.put(m.ID, m.Title, &tm, "path", "title", "album", "artist", "artist_id", "album_artist",
"album_id", "has_cover_art", "track_number", "disc_number", "year", "size", "suffix", "duration",
"bit_rate", "genre", "compilation", "updated_at")
@ -144,53 +141,28 @@ func (r *mediaFileRepository) GetRandom(options ...model.QueryOptions) (model.Me
return r.toMediaFiles(results), err
}
func (r *mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.MediaFiles, error) {
func (r *mediaFileRepository) GetStarred(userId string, options ...model.QueryOptions) (model.MediaFiles, error) {
var starred []mediaFile
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
sq := r.newRawQuery(options...).Join("annotation").Where("annotation.item_id = " + r.tableName + ".id")
sq = sq.Where(squirrel.Eq{"annotation.user_id": userId})
sql, args, err := sq.ToSql()
if err != nil {
return nil, err
}
_, err = r.ormer.Raw(sql, args...).QueryRows(&starred)
if err != nil {
return nil, err
}
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().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().Filter("id__in", ids).Update(orm.Params{"rating": rating})
return err
}
func (r *mediaFileRepository) MarkAsPlayed(id string, playDate time.Time) error {
_, err := r.newQuery().Filter("id", id).Update(orm.Params{
"play_count": orm.ColValue(orm.ColAdd, 1),
"play_date": playDate,
})
return err
}
func (r *mediaFileRepository) Search(q string, offset int, size int) (model.MediaFiles, error) {
if len(q) <= 2 {
return nil, nil
}
var results []mediaFile
err := r.doSearch(r.tableName, q, offset, size, &results, "rating desc", "starred desc", "play_count desc", "title")
err := r.doSearch(r.tableName, q, offset, size, &results, "title")
if err != nil {
return nil, err
}

View file

@ -57,6 +57,10 @@ func (db *MockDataStore) User() model.UserRepository {
return db.MockedUser
}
func (db *MockDataStore) Annotation() model.AnnotationRepository {
return struct{ model.AnnotationRepository }{}
}
func (db *MockDataStore) WithTx(block func(db model.DataStore) error) error {
return block(db)
}

View file

@ -73,6 +73,10 @@ func (db *SQLStore) User() model.UserRepository {
return NewUserRepository(db.getOrmer())
}
func (db *SQLStore) Annotation() model.AnnotationRepository {
return NewAnnotationRepository(db.getOrmer())
}
func (db *SQLStore) Resource(model interface{}) model.ResourceRepository {
return NewResource(db.getOrmer(), model, getMappedModel(model))
}
@ -159,6 +163,7 @@ func init() {
registerModel(model.Property{}, new(property))
registerModel(model.Playlist{}, new(playlist))
registerModel(model.User{}, new(user))
registerModel(model.Annotation{}, new(annotation))
orm.RegisterModel(new(checksum))
orm.RegisterModel(new(search))

View file

@ -5,6 +5,7 @@ import (
"strings"
"testing"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/conf"
"github.com/cloudsonic/sonic-server/log"
"github.com/cloudsonic/sonic-server/model"
@ -29,13 +30,18 @@ var testArtists = model.Artists{
var albumSgtPeppers = model.Album{ID: "1", Name: "Sgt Peppers", Artist: "The Beatles", ArtistID: "1", Genre: "Rock"}
var albumAbbeyRoad = model.Album{ID: "2", Name: "Abbey Road", Artist: "The Beatles", ArtistID: "1", Genre: "Rock"}
var albumRadioactivity = model.Album{ID: "3", Name: "Radioactivity", Artist: "Kraftwerk", ArtistID: "2", Starred: true, Genre: "Electronic"}
var albumRadioactivity = model.Album{ID: "3", Name: "Radioactivity", Artist: "Kraftwerk", ArtistID: "2", Genre: "Electronic"}
var testAlbums = model.Albums{
albumSgtPeppers,
albumAbbeyRoad,
albumRadioactivity,
}
var annRadioactivity = model.Annotation{AnnotationID: "1", UserID: "userid", ItemType: model.AlbumItemType, ItemID: "3", Starred: true}
var testAnnotations = []model.Annotation{
annRadioactivity,
}
var songDayInALife = model.MediaFile{ID: "1", Title: "A Day In A Life", ArtistID: "3", AlbumID: "1", Genre: "Rock", Path: P("/beatles/1/sgt/a day.mp3")}
var songComeTogether = model.MediaFile{ID: "2", Title: "Come Together", ArtistID: "3", AlbumID: "2", Genre: "Rock", Path: P("/beatles/1/come together.mp3")}
var songRadioactivity = model.MediaFile{ID: "3", Title: "Radioactivity", ArtistID: "2", AlbumID: "3", Genre: "Electronic", Path: P("/kraft/radio/radio.mp3")}
@ -76,5 +82,14 @@ var _ = Describe("Initialize test DB", func() {
panic(err)
}
}
o := orm.NewOrm()
for _, a := range testAnnotations {
ann := annotation(a)
_, err := o.Insert(&ann)
if err != nil {
panic(err)
}
}
})
})

View file

@ -1,6 +1,7 @@
package subsonic
import (
"context"
"errors"
"net/http"
@ -32,7 +33,7 @@ func NewAlbumListController(listGen engine.ListGenerator) *AlbumListController {
return c
}
type strategy func(offset int, size int) (engine.Entries, error)
type strategy func(ctx context.Context, offset int, size int) (engine.Entries, error)
func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, error) {
typ, err := RequiredParamString(r, "type", "Required string parameter 'type' is not present")
@ -49,7 +50,7 @@ func (c *AlbumListController) getAlbumList(r *http.Request) (engine.Entries, err
offset := ParamInt(r, "offset", 0)
size := utils.MinInt(ParamInt(r, "size", 10), 500)
albums, err := listFunc(offset, size)
albums, err := listFunc(r.Context(), offset, size)
if err != nil {
log.Error(r, "Error retrieving albums", "error", err)
return nil, errors.New("Internal Error")
@ -81,7 +82,7 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque
}
func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
artists, albums, mediaFiles, err := c.listGen.GetAllStarred()
artists, albums, mediaFiles, err := c.listGen.GetAllStarred(r.Context())
if err != nil {
log.Error(r, "Error retrieving starred media", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")
@ -96,7 +97,7 @@ func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request)
}
func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
artists, albums, mediaFiles, err := c.listGen.GetAllStarred()
artists, albums, mediaFiles, err := c.listGen.GetAllStarred(r.Context())
if err != nil {
log.Error(r, "Error retrieving starred media", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")
@ -111,7 +112,7 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request
}
func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
npInfos, err := c.listGen.GetNowPlaying()
npInfos, err := c.listGen.GetNowPlaying(r.Context())
if err != nil {
log.Error(r, "Error retrieving now playing list", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")
@ -134,7 +135,7 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ
size := utils.MinInt(ParamInt(r, "size", 10), 500)
genre := ParamString(r, "genre")
songs, err := c.listGen.GetRandomSongs(size, genre)
songs, err := c.listGen.GetRandomSongs(r.Context(), size, genre)
if err != nil {
log.Error(r, "Error retrieving random songs", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")

View file

@ -1,6 +1,7 @@
package subsonic
import (
"context"
"errors"
"net/http/httptest"
@ -17,7 +18,7 @@ type fakeListGen struct {
recvSize int
}
func (lg *fakeListGen) GetNewest(offset int, size int) (engine.Entries, error) {
func (lg *fakeListGen) GetNewest(ctx context.Context, offset int, size int) (engine.Entries, error) {
if lg.err != nil {
return nil, lg.err
}

View file

@ -135,7 +135,7 @@ func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (*
func (c *BrowsingController) GetSong(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
id := ParamString(r, "id")
song, err := c.browser.GetSong(id)
song, err := c.browser.GetSong(r.Context(), id)
switch {
case err == model.ErrNotFound:
log.Error(r, "Requested ID not found ", "id", id)

View file

@ -1,6 +1,7 @@
package subsonic
import (
"context"
"net/http"
"time"
@ -47,53 +48,54 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req
return NewResponse(), nil
}
func (c *MediaAnnotationController) getIds(r *http.Request) ([]string, error) {
func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ids := ParamStrings(r, "id")
albumIds := ParamStrings(r, "albumId")
artistIds := ParamStrings(r, "artistId")
if len(ids)+len(albumIds)+len(artistIds) == 0 {
return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing")
}
ids = append(ids, albumIds...)
ids = append(ids, artistIds...)
return ids, nil
}
func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ids, err := c.getIds(r)
err := c.star(r.Context(), true, ids...)
if err != nil {
return nil, err
}
log.Debug(r, "Starring items", "ids", ids)
err = c.ratings.SetStar(r.Context(), true, ids...)
switch {
case err == model.ErrNotFound:
log.Error(r, err)
return nil, NewError(responses.ErrorDataNotFound, "ID not found")
case err != nil:
log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")
}
return NewResponse(), nil
}
func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ids, err := c.getIds(r)
if err != nil {
return nil, err
func (c *MediaAnnotationController) star(ctx context.Context, starred bool, ids ...string) error {
if len(ids) == 0 {
return nil
}
log.Debug(r, "Unstarring items", "ids", ids)
err = c.ratings.SetStar(r.Context(), false, ids...)
log.Debug(ctx, "Changing starred", "ids", ids, "starred", starred)
err := c.ratings.SetStar(ctx, starred, ids...)
switch {
case err == model.ErrNotFound:
log.Error(r, err)
return nil, NewError(responses.ErrorDataNotFound, "Directory not found")
log.Error(ctx, err)
return NewError(responses.ErrorDataNotFound, "ID not found")
case err != nil:
log.Error(r, err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")
log.Error(ctx, err)
return NewError(responses.ErrorGeneric, "Internal Error")
}
return nil
}
func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
ids := ParamStrings(r, "id")
albumIds := ParamStrings(r, "albumId")
artistIds := ParamStrings(r, "artistId")
if len(ids)+len(albumIds)+len(artistIds) == 0 {
return nil, NewError(responses.ErrorMissingParameter, "Required id parameter is missing")
}
ids = append(ids, albumIds...)
ids = append(ids, artistIds...)
err := c.star(r.Context(), false, ids...)
if err != nil {
return nil, err
}
return NewResponse(), nil

View file

@ -45,7 +45,7 @@ func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request
if err != nil {
return nil, err
}
pinfo, err := c.pls.Get(id)
pinfo, err := c.pls.Get(r.Context(), id)
switch {
case err == model.ErrNotFound:
log.Error(r, err.Error(), "id", id)

View file

@ -24,7 +24,7 @@ func (c *StreamController) getMediaFile(r *http.Request) (mf *engine.Entry, err
return nil, err
}
mf, err = c.browser.GetSong(id)
mf, err = c.browser.GetSong(r.Context(), id)
switch {
case err == model.ErrNotFound:
log.Error(r, "Mediafile not found", "id", id)