refactor: remove annotation handling from engine

This commit is contained in:
Deluan 2020-01-30 22:07:02 -05:00 committed by Deluan Quintão
parent 67ed830a68
commit 72d9ddf532
20 changed files with 151 additions and 386 deletions

View File

@ -81,8 +81,7 @@ func (b *browser) Artist(ctx context.Context, id string) (*DirectoryInfo, error)
for _, al := range albums { for _, al := range albums {
albumIds = append(albumIds, al.ID) albumIds = append(albumIds, al.ID)
} }
annMap, err := b.ds.Annotation(ctx).GetMap(getUserID(ctx), model.AlbumItemType, albumIds) return b.buildArtistDir(a, albums), nil
return b.buildArtistDir(a, albums, annMap), nil
} }
func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error) { func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error) {
@ -96,16 +95,7 @@ func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error)
mfIds = append(mfIds, mf.ID) mfIds = append(mfIds, mf.ID)
} }
userID := getUserID(ctx) return b.buildAlbumDir(al, tracks), nil
trackAnnMap, err := b.ds.Annotation(ctx).GetMap(userID, model.MediaItemType, mfIds)
if err != nil {
return nil, err
}
ann, err := b.ds.Annotation(ctx).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) { func (b *browser) Directory(ctx context.Context, id string) (*DirectoryInfo, error) {
@ -126,13 +116,7 @@ func (b *browser) GetSong(ctx context.Context, id string) (*Entry, error) {
return nil, err return nil, err
} }
userId := getUserID(ctx) entry := FromMediaFile(mf)
ann, err := b.ds.Annotation(ctx).Get(userId, model.MediaItemType, id)
if err != nil {
return nil, err
}
entry := FromMediaFile(mf, ann)
return &entry, nil return &entry, nil
} }
@ -149,7 +133,7 @@ func (b *browser) GetGenres(ctx context.Context) (model.Genres, error) {
return genres, err return genres, err
} }
func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums, albumAnnMap model.AnnotationMap) *DirectoryInfo { func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums) *DirectoryInfo {
dir := &DirectoryInfo{ dir := &DirectoryInfo{
Id: a.ID, Id: a.ID,
Name: a.Name, Name: a.Name,
@ -158,39 +142,31 @@ func (b *browser) buildArtistDir(a *model.Artist, albums model.Albums, albumAnnM
dir.Entries = make(Entries, len(albums)) dir.Entries = make(Entries, len(albums))
for i, al := range albums { for i, al := range albums {
ann := albumAnnMap[al.ID] dir.Entries[i] = FromAlbum(&al)
dir.Entries[i] = FromAlbum(&al, &ann) dir.PlayCount += int32(al.PlayCount)
dir.PlayCount += int32(ann.PlayCount)
} }
return dir return dir
} }
func (b *browser) buildAlbumDir(al *model.Album, albumAnn *model.Annotation, tracks model.MediaFiles, trackAnnMap model.AnnotationMap) *DirectoryInfo { func (b *browser) buildAlbumDir(al *model.Album, tracks model.MediaFiles) *DirectoryInfo {
dir := &DirectoryInfo{ dir := &DirectoryInfo{
Id: al.ID, Id: al.ID,
Name: al.Name, Name: al.Name,
Parent: al.ArtistID, Parent: al.ArtistID,
Artist: al.Artist, Artist: al.Artist,
ArtistId: al.ArtistID, ArtistId: al.ArtistID,
SongCount: al.SongCount, SongCount: al.SongCount,
Duration: al.Duration, Duration: al.Duration,
Created: al.CreatedAt, Created: al.CreatedAt,
Year: al.Year, Year: al.Year,
Genre: al.Genre, Genre: al.Genre,
CoverArt: al.CoverArtId, CoverArt: al.CoverArtId,
} PlayCount: int32(al.PlayCount),
if albumAnn != nil { Starred: al.StarredAt,
dir.PlayCount = int32(albumAnn.PlayCount) UserRating: al.Rating,
dir.Starred = albumAnn.StarredAt
dir.UserRating = albumAnn.Rating
} }
dir.Entries = make(Entries, len(tracks)) dir.Entries = FromMediaFiles(tracks)
for i, mf := range tracks {
mfId := mf.ID
ann := trackAnnMap[mfId]
dir.Entries[i] = FromMediaFile(&mf, &ann)
}
return dir return dir
} }

View File

@ -1,7 +1,6 @@
package engine package engine
import ( import (
"context"
"fmt" "fmt"
"time" "time"
@ -46,19 +45,17 @@ type Entry struct {
type Entries []Entry type Entries []Entry
func FromArtist(ar *model.Artist, ann *model.Annotation) Entry { func FromArtist(ar *model.Artist) Entry {
e := Entry{} e := 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.IsDir = true e.IsDir = true
//if ann != nil {
e.Starred = ar.StarredAt e.Starred = ar.StarredAt
//}
return e return e
} }
func FromAlbum(al *model.Album, ann *model.Annotation) Entry { func FromAlbum(al *model.Album) Entry {
e := Entry{} e := Entry{}
e.Id = al.ID e.Id = al.ID
e.Title = al.Name e.Title = al.Name
@ -74,15 +71,13 @@ func FromAlbum(al *model.Album, ann *model.Annotation) Entry {
e.ArtistId = al.ArtistID e.ArtistId = al.ArtistID
e.Duration = al.Duration e.Duration = al.Duration
e.SongCount = al.SongCount e.SongCount = al.SongCount
//if ann != nil {
e.Starred = al.StarredAt e.Starred = al.StarredAt
e.PlayCount = int32(al.PlayCount) e.PlayCount = int32(al.PlayCount)
e.UserRating = al.Rating e.UserRating = al.Rating
//}
return e return e
} }
func FromMediaFile(mf *model.MediaFile, ann *model.Annotation) Entry { func FromMediaFile(mf *model.MediaFile) Entry {
e := Entry{} e := Entry{}
e.Id = mf.ID e.Id = mf.ID
e.Title = mf.Title e.Title = mf.Title
@ -111,11 +106,9 @@ func FromMediaFile(mf *model.MediaFile, ann *model.Annotation) Entry {
e.AlbumId = mf.AlbumID e.AlbumId = mf.AlbumID
e.ArtistId = mf.ArtistID e.ArtistId = mf.ArtistID
e.Type = "music" // TODO Hardcoded for now e.Type = "music" // TODO Hardcoded for now
//if ann != nil {
e.PlayCount = int32(mf.PlayCount) e.PlayCount = int32(mf.PlayCount)
e.Starred = mf.StarredAt e.Starred = mf.StarredAt
e.UserRating = mf.Rating e.UserRating = mf.Rating
//}
return e return e
} }
@ -130,37 +123,26 @@ func realArtistName(mf *model.MediaFile) string {
return mf.Artist return mf.Artist
} }
func FromAlbums(albums model.Albums, annMap model.AnnotationMap) Entries { func FromAlbums(albums model.Albums) Entries {
entries := make(Entries, len(albums)) entries := make(Entries, len(albums))
for i, al := range albums { for i, al := range albums {
ann := annMap[al.ID] entries[i] = FromAlbum(&al)
entries[i] = FromAlbum(&al, &ann)
} }
return entries return entries
} }
func FromMediaFiles(mfs model.MediaFiles, annMap model.AnnotationMap) Entries { func FromMediaFiles(mfs model.MediaFiles) Entries {
entries := make(Entries, len(mfs)) entries := make(Entries, len(mfs))
for i, mf := range mfs { for i, mf := range mfs {
ann := annMap[mf.ID] entries[i] = FromMediaFile(&mf)
entries[i] = FromMediaFile(&mf, &ann)
} }
return entries return entries
} }
func FromArtists(ars model.Artists, annMap model.AnnotationMap) Entries { func FromArtists(ars model.Artists) Entries {
entries := make(Entries, len(ars)) entries := make(Entries, len(ars))
for i, ar := range ars { for i, ar := range ars {
ann := annMap[ar.ID] entries[i] = FromArtist(&ar)
entries[i] = FromArtist(&ar, &ann)
} }
return entries return entries
} }
func getUserID(ctx context.Context) string {
user, ok := ctx.Value("user").(*model.User)
if ok {
return user.ID
}
return ""
}

View File

@ -40,34 +40,7 @@ func (g *listGenerator) query(ctx context.Context, qo model.QueryOptions) (Entri
for i, al := range albums { for i, al := range albums {
albumIds[i] = al.ID albumIds[i] = al.ID
} }
annMap, err := g.ds.Annotation(ctx).GetMap(getUserID(ctx), model.AlbumItemType, albumIds) return FromAlbums(albums), err
if err != nil {
return nil, err
}
return FromAlbums(albums, annMap), err
}
func (g *listGenerator) queryByAnnotation(ctx context.Context, qo model.QueryOptions) (Entries, error) {
annotations, err := g.ds.Annotation(ctx).GetAll(getUserID(ctx), model.AlbumItemType, qo)
if err != nil {
return nil, err
}
albumIds := make([]string, len(annotations))
for i, ann := range annotations {
albumIds[i] = ann.ItemID
}
albumMap, err := g.ds.Album(ctx).GetMap(albumIds)
if err != nil {
return nil, err
}
var albums Entries
for _, ann := range annotations {
album := albumMap[ann.ItemID]
albums = append(albums, FromAlbum(&album, &ann))
}
return albums, nil
} }
func (g *listGenerator) GetNewest(ctx context.Context, offset int, size int) (Entries, error) { func (g *listGenerator) GetNewest(ctx context.Context, offset int, size int) (Entries, error) {
@ -78,19 +51,19 @@ func (g *listGenerator) GetNewest(ctx context.Context, offset int, size int) (En
func (g *listGenerator) GetRecent(ctx context.Context, 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", Offset: offset, Max: size, qo := model.QueryOptions{Sort: "PlayDate", Order: "desc", Offset: offset, Max: size,
Filters: squirrel.Gt{"play_date": time.Time{}}} Filters: squirrel.Gt{"play_date": time.Time{}}}
return g.queryByAnnotation(ctx, qo) return g.query(ctx, qo)
} }
func (g *listGenerator) GetFrequent(ctx context.Context, 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", Offset: offset, Max: size, qo := model.QueryOptions{Sort: "PlayCount", Order: "desc", Offset: offset, Max: size,
Filters: squirrel.Gt{"play_count": 0}} Filters: squirrel.Gt{"play_count": 0}}
return g.queryByAnnotation(ctx, qo) return g.query(ctx, qo)
} }
func (g *listGenerator) GetHighest(ctx context.Context, 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", Offset: offset, Max: size, qo := model.QueryOptions{Sort: "Rating", Order: "desc", Offset: offset, Max: size,
Filters: squirrel.Gt{"rating__gt": 0}} Filters: squirrel.Gt{"rating": 0}}
return g.queryByAnnotation(ctx, qo) return g.query(ctx, qo)
} }
func (g *listGenerator) GetByName(ctx context.Context, offset int, size int) (Entries, error) { func (g *listGenerator) GetByName(ctx context.Context, offset int, size int) (Entries, error) {
@ -109,19 +82,7 @@ func (g *listGenerator) GetRandom(ctx context.Context, offset int, size int) (En
return nil, err return nil, err
} }
annMap, err := g.getAnnotationsForAlbums(ctx, albums) return FromAlbums(albums), nil
if err != nil {
return nil, err
}
return FromAlbums(albums, annMap), nil
}
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(ctx).GetMap(getUserID(ctx), model.AlbumItemType, albumIds)
} }
func (g *listGenerator) GetRandomSongs(ctx context.Context, size int, genre string) (Entries, error) { func (g *listGenerator) GetRandomSongs(ctx context.Context, size int, genre string) (Entries, error) {
@ -134,45 +95,33 @@ func (g *listGenerator) GetRandomSongs(ctx context.Context, size int, genre stri
return nil, err return nil, err
} }
r := make(Entries, len(mediaFiles)) return FromMediaFiles(mediaFiles), nil
for i, mf := range mediaFiles {
ann, err := g.ds.Annotation(ctx).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(ctx context.Context, 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"} qo := model.QueryOptions{Offset: offset, Max: size, Sort: "starred_at", Order: "desc"}
albums, err := g.ds.Album(ctx).GetStarred(getUserID(ctx), qo) albums, err := g.ds.Album(ctx).GetStarred(qo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
annMap, err := g.getAnnotationsForAlbums(ctx, albums) return FromAlbums(albums), nil
if err != nil {
return nil, err
}
return FromAlbums(albums, annMap), nil
} }
func (g *listGenerator) GetAllStarred(ctx context.Context) (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"} options := model.QueryOptions{Sort: "starred_at", Order: "desc"}
ars, err := g.ds.Artist(ctx).GetStarred(getUserID(ctx), options) ars, err := g.ds.Artist(ctx).GetStarred(options)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
als, err := g.ds.Album(ctx).GetStarred(getUserID(ctx), options) als, err := g.ds.Album(ctx).GetStarred(options)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
mfs, err := g.ds.MediaFile(ctx).GetStarred(getUserID(ctx), options) mfs, err := g.ds.MediaFile(ctx).GetStarred(options)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -181,28 +130,15 @@ func (g *listGenerator) GetAllStarred(ctx context.Context) (artists Entries, alb
for _, mf := range mfs { for _, mf := range mfs {
mfIds = append(mfIds, mf.ID) mfIds = append(mfIds, mf.ID)
} }
trackAnnMap, err := g.ds.Annotation(ctx).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 var artistIds []string
for _, ar := range ars { for _, ar := range ars {
artistIds = append(artistIds, ar.ID) artistIds = append(artistIds, ar.ID)
} }
artistAnnMap, err := g.ds.Annotation(ctx).GetMap(getUserID(ctx), model.MediaItemType, artistIds)
if err != nil {
return nil, nil, nil, err
}
artists = FromArtists(ars, artistAnnMap) artists = FromArtists(ars)
albums = FromAlbums(als, albumAnnMap) albums = FromAlbums(als)
mediaFiles = FromMediaFiles(mfs, trackAnnMap) mediaFiles = FromMediaFiles(mfs)
return return
} }
@ -218,8 +154,7 @@ func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
ann, err := g.ds.Annotation(ctx).Get(getUserID(ctx), model.MediaItemType, mf.ID) entries[i] = FromMediaFile(mf)
entries[i] = FromMediaFile(mf, ann)
entries[i].UserName = np.Username entries[i].UserName = np.Username
entries[i].MinutesAgo = int(time.Now().Sub(np.Start).Minutes()) entries[i].MinutesAgo = int(time.Now().Sub(np.Start).Minutes())
entries[i].PlayerId = np.PlayerId entries[i].PlayerId = np.PlayerId

View File

@ -123,7 +123,7 @@ func (p *playlists) Get(ctx context.Context, id string) (*PlaylistInfo, error) {
} }
// TODO Use model.Playlist when got rid of Entries // TODO Use model.Playlist when got rid of Entries
pinfo := &PlaylistInfo{ plsInfo := &PlaylistInfo{
Id: pl.ID, Id: pl.ID,
Name: pl.Name, Name: pl.Name,
SongCount: len(pl.Tracks), SongCount: len(pl.Tracks),
@ -132,19 +132,7 @@ func (p *playlists) Get(ctx context.Context, id string) (*PlaylistInfo, error) {
Owner: pl.Owner, Owner: pl.Owner,
Comment: pl.Comment, Comment: pl.Comment,
} }
pinfo.Entries = make(Entries, len(pl.Tracks))
var mfIds []string plsInfo.Entries = FromMediaFiles(pl.Tracks)
for _, mf := range pl.Tracks { return plsInfo, nil
mfIds = append(mfIds, mf.ID)
}
annMap, err := p.ds.Annotation(ctx).GetMap(getUserID(ctx), model.MediaItemType, mfIds)
for i, mf := range pl.Tracks {
ann := annMap[mf.ID]
pinfo.Entries[i] = FromMediaFile(&mf, &ann)
}
return pinfo, nil
} }

View File

@ -26,9 +26,9 @@ func (r ratings) SetRating(ctx context.Context, id string, rating int) error {
return err return err
} }
if exist { if exist {
return r.ds.Annotation(ctx).SetRating(rating, getUserID(ctx), model.AlbumItemType, id) return r.ds.Annotation(ctx).SetRating(rating, model.AlbumItemType, id)
} }
return r.ds.Annotation(ctx).SetRating(rating, getUserID(ctx), model.MediaItemType, id) return r.ds.Annotation(ctx).SetRating(rating, model.MediaItemType, id)
} }
func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error { func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
@ -36,7 +36,6 @@ func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
log.Warn(ctx, "Cannot star/unstar an empty list of ids") log.Warn(ctx, "Cannot star/unstar an empty list of ids")
return nil return nil
} }
userId := getUserID(ctx)
return r.ds.WithTx(func(tx model.DataStore) error { return r.ds.WithTx(func(tx model.DataStore) error {
for _, id := range ids { for _, id := range ids {
@ -45,7 +44,7 @@ func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
return err return err
} }
if exist { if exist {
err = tx.Annotation(ctx).SetStar(star, userId, model.AlbumItemType, ids...) err = tx.Annotation(ctx).SetStar(star, model.AlbumItemType, ids...)
if err != nil { if err != nil {
return err return err
} }
@ -56,13 +55,13 @@ func (r ratings) SetStar(ctx context.Context, star bool, ids ...string) error {
return err return err
} }
if exist { if exist {
err = tx.Annotation(ctx).SetStar(star, userId, model.ArtistItemType, ids...) err = tx.Annotation(ctx).SetStar(star, model.ArtistItemType, ids...)
if err != nil { if err != nil {
return err return err
} }
continue continue
} }
err = tx.Annotation(ctx).SetStar(star, userId, model.MediaItemType, ids...) err = tx.Annotation(ctx).SetStar(star, model.MediaItemType, ids...)
if err != nil { if err != nil {
return err return err
} }

View File

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

View File

@ -34,12 +34,7 @@ func (s *search) SearchArtist(ctx context.Context, q string, offset int, size in
for i, al := range artists { for i, al := range artists {
artistIds[i] = al.ID artistIds[i] = al.ID
} }
annMap, err := s.ds.Annotation(ctx).GetMap(getUserID(ctx), model.ArtistItemType, artistIds) return FromArtists(artists), nil
if err != nil {
return nil, nil
}
return FromArtists(artists, annMap), nil
} }
func (s *search) SearchAlbum(ctx context.Context, q string, offset int, size int) (Entries, error) { func (s *search) SearchAlbum(ctx context.Context, q string, offset int, size int) (Entries, error) {
@ -53,12 +48,8 @@ func (s *search) SearchAlbum(ctx context.Context, q string, offset int, size int
for i, al := range albums { for i, al := range albums {
albumIds[i] = al.ID albumIds[i] = al.ID
} }
annMap, err := s.ds.Annotation(ctx).GetMap(getUserID(ctx), model.AlbumItemType, albumIds)
if err != nil {
return nil, nil
}
return FromAlbums(albums, annMap), nil return FromAlbums(albums), nil
} }
func (s *search) SearchSong(ctx context.Context, q string, offset int, size int) (Entries, error) { func (s *search) SearchSong(ctx context.Context, q string, offset int, size int) (Entries, error) {
@ -72,10 +63,6 @@ func (s *search) SearchSong(ctx context.Context, q string, offset int, size int)
for i, mf := range mediaFiles { for i, mf := range mediaFiles {
trackIds[i] = mf.ID trackIds[i] = mf.ID
} }
annMap, err := s.ds.Annotation(ctx).GetMap(getUserID(ctx), model.MediaItemType, trackIds)
if err != nil {
return nil, nil
}
return FromMediaFiles(mediaFiles, annMap), nil return FromMediaFiles(mediaFiles), nil
} }

View File

@ -5,7 +5,7 @@ import "time"
type Album struct { type Album struct {
ID string `json:"id" orm:"column(id)"` ID string `json:"id" orm:"column(id)"`
Name string `json:"name"` Name string `json:"name"`
ArtistID string `json:"artistId"` ArtistID string `json:"artistId" orm:"pk;column(artist_id)"`
CoverArtPath string `json:"-"` CoverArtPath string `json:"-"`
CoverArtId string `json:"-"` CoverArtId string `json:"-"`
Artist string `json:"artist"` Artist string `json:"artist"`
@ -37,7 +37,7 @@ type AlbumRepository interface {
GetAll(...QueryOptions) (Albums, error) GetAll(...QueryOptions) (Albums, error)
GetMap(ids []string) (map[string]Album, error) GetMap(ids []string) (map[string]Album, error)
GetRandom(...QueryOptions) (Albums, error) GetRandom(...QueryOptions) (Albums, error)
GetStarred(userId string, options ...QueryOptions) (Albums, error) GetStarred(options ...QueryOptions) (Albums, 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

View File

@ -9,25 +9,22 @@ const (
) )
type Annotation struct { type Annotation struct {
AnnotationID string AnnID string `json:"annID" orm:"pk;column(ann_id)"`
UserID string UserID string `json:"userID" orm:"pk;column(user_id)"`
ItemID string ItemID string `json:"itemID" orm:"pk;column(item_id)"`
ItemType string ItemType string `json:"itemType"`
PlayCount int PlayCount int `json:"playCount"`
PlayDate time.Time PlayDate time.Time `json:"playDate"`
Rating int Rating int `json:"rating"`
Starred bool Starred bool `json:"starred"`
StarredAt time.Time StarredAt time.Time `json:"starredAt"`
} }
type AnnotationMap map[string]Annotation type AnnotationMap map[string]Annotation
type AnnotationRepository interface { type AnnotationRepository interface {
Get(userID, itemType string, itemID string) (*Annotation, error) Delete(itemType string, itemID ...string) error
GetAll(userID, itemType string, options ...QueryOptions) ([]Annotation, error) IncPlayCount(itemType, itemID string, ts time.Time) error
GetMap(userID, itemType string, itemID []string) (AnnotationMap, error) SetStar(starred bool, itemType string, ids ...string) error
Delete(userID, itemType string, itemID ...string) error SetRating(rating int, itemType, 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

@ -28,7 +28,7 @@ type ArtistRepository interface {
Exists(id string) (bool, error) Exists(id string) (bool, error)
Put(m *Artist) error Put(m *Artist) error
Get(id string) (*Artist, error) Get(id string) (*Artist, error)
GetStarred(userId string, options ...QueryOptions) (Artists, error) GetStarred(options ...QueryOptions) (Artists, 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)

View File

@ -11,9 +11,9 @@ type MediaFile struct {
Title string `json:"title"` Title string `json:"title"`
Album string `json:"album"` Album string `json:"album"`
Artist string `json:"artist"` Artist string `json:"artist"`
ArtistID string `json:"artistId"` ArtistID string `json:"artistId" orm:"pk;column(artist_id)"`
AlbumArtist string `json:"albumArtist"` AlbumArtist string `json:"albumArtist"`
AlbumID string `json:"albumId"` AlbumID string `json:"albumId" orm:"pk;column(album_id)"`
HasCoverArt bool `json:"hasCoverArt"` HasCoverArt bool `json:"hasCoverArt"`
TrackNumber int `json:"trackNumber"` TrackNumber int `json:"trackNumber"`
DiscNumber int `json:"discNumber"` DiscNumber int `json:"discNumber"`
@ -48,8 +48,7 @@ type MediaFileRepository interface {
Get(id string) (*MediaFile, error) Get(id string) (*MediaFile, error)
FindByAlbum(albumId string) (MediaFiles, error) FindByAlbum(albumId string) (MediaFiles, error)
FindByPath(path string) (MediaFiles, error) FindByPath(path string) (MediaFiles, error)
// TODO Remove userId GetStarred(options ...QueryOptions) (MediaFiles, error)
GetStarred(userId string, options ...QueryOptions) (MediaFiles, error)
GetRandom(options ...QueryOptions) (MediaFiles, error) GetRandom(options ...QueryOptions) (MediaFiles, error)
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

View File

@ -101,7 +101,7 @@ func (r *albumRepository) PurgeEmpty() error {
return nil return nil
} }
func (r *albumRepository) GetStarred(userId string, options ...model.QueryOptions) (model.Albums, error) { func (r *albumRepository) GetStarred(options ...model.QueryOptions) (model.Albums, error) {
sq := r.selectAlbum(options...).Where("starred = true") sq := r.selectAlbum(options...).Where("starred = true")
var starred model.Albums var starred model.Albums
err := r.queryAll(sq, &starred) err := r.queryAll(sq, &starred)

View File

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

View File

@ -10,24 +10,6 @@ import (
"github.com/google/uuid" "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:"column(play_count);index;null"`
PlayDate time.Time `orm:"column(play_date);index;null"`
Rating int `orm:"null"`
Starred bool `orm:"index"`
StarredAt time.Time `orm:"column(starred_at);null"`
}
func (u *annotation) TableUnique() [][]string {
return [][]string{
{"UserID", "ItemID", "ItemType"},
}
}
type annotationRepository struct { type annotationRepository struct {
sqlRepository sqlRepository
} }
@ -40,144 +22,70 @@ func NewAnnotationRepository(ctx context.Context, o orm.Ormer) model.AnnotationR
return r return r
} }
func (r *annotationRepository) Get(userID, itemType string, itemID string) (*model.Annotation, error) { func (r *annotationRepository) upsert(values map[string]interface{}, itemType string, itemIDs ...string) error {
q := Select("*").From(r.tableName).Where(And{ upd := Update(r.tableName).Where(r.getId(itemType, itemIDs...))
Eq{"user_id": userId(r.ctx)}, for f, v := range values {
Eq{"item_type": itemType}, upd = upd.Set(f, v)
Eq{"item_id": itemID},
})
var ann annotation
err := r.queryOne(q, &ann)
if err == model.ErrNotFound {
return nil, nil
} }
resp := model.Annotation(ann) c, err := r.executeSQL(upd)
return &resp, nil
}
func (r *annotationRepository) GetMap(userID, itemType string, itemIDs []string) (model.AnnotationMap, error) {
if len(itemIDs) == 0 {
return nil, nil
}
q := Select("*").From(r.tableName).Where(And{
Eq{"user_id": userId(r.ctx)},
Eq{"item_type": itemType},
Eq{"item_id": itemIDs},
})
var res []annotation
err := r.queryAll(q, &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) GetAll(userID, itemType string, options ...model.QueryOptions) ([]model.Annotation, error) {
q := Select("*").From(r.tableName).Where(And{
Eq{"user_id": userId(r.ctx)},
Eq{"item_type": itemType},
})
var res []annotation
err := r.queryAll(q, &res)
if err != nil {
return nil, err
}
all := make([]model.Annotation, len(res))
for i, a := range res {
all[i] = model.Annotation(a)
}
return all, err
}
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 {
uid := userId(r.ctx)
q := Update(r.tableName).
Set("play_count", Expr("play_count + 1")).
Set("play_date", ts).
Where(And{
Eq{"user_id": uid},
Eq{"item_type": itemType},
Eq{"item_id": itemID},
})
c, err := r.executeSQL(q)
if c == 0 || err == orm.ErrNoRows { if c == 0 || err == orm.ErrNoRows {
ann := r.new(uid, itemType, itemID) for _, itemID := range itemIDs {
ann.PlayCount = 1 id, _ := uuid.NewRandom()
ann.PlayDate = ts values["ann_id"] = id.String()
_, err = r.ormer.Insert(ann) values["user_id"] = userId(r.ctx)
} values["item_type"] = itemType
return err values["item_id"] = itemID
} ins := Insert(r.tableName).SetMap(values)
_, err = r.executeSQL(ins)
func (r *annotationRepository) SetStar(starred bool, userID, itemType string, ids ...string) error {
uid := userId(r.ctx)
var starredAt time.Time
if starred {
starredAt = time.Now()
}
q := Update(r.tableName).
Set("starred", starred).
Set("starred_at", starredAt).
Where(And{
Eq{"user_id": uid},
Eq{"item_type": itemType},
Eq{"item_id": ids},
})
c, err := r.executeSQL(q)
if c == 0 || err == orm.ErrNoRows {
for _, id := range ids {
ann := r.new(uid, itemType, id)
ann.Starred = starred
ann.StarredAt = starredAt
_, err = r.ormer.Insert(ann)
if err != nil { if err != nil {
if err.Error() != "LastInsertId is not supported by this driver" { return err
return err
}
} }
} }
} }
return nil return err
} }
func (r *annotationRepository) SetRating(rating int, userID, itemType string, itemID string) error { func (r *annotationRepository) IncPlayCount(itemType, itemID string, ts time.Time) error {
uid := userId(r.ctx) upd := Update(r.tableName).Where(r.getId(itemType, itemID)).
q := Update(r.tableName). Set("play_count", Expr("play_count+1")).
Set("rating", rating). Set("play_date", ts)
Where(And{ c, err := r.executeSQL(upd)
Eq{"user_id": uid},
Eq{"item_type": itemType},
Eq{"item_id": itemID},
})
c, err := r.executeSQL(q)
if c == 0 || err == orm.ErrNoRows { if c == 0 || err == orm.ErrNoRows {
ann := r.new(uid, itemType, itemID) id, _ := uuid.NewRandom()
ann.Rating = rating values := map[string]interface{}{}
_, err = r.ormer.Insert(ann) values["ann_id"] = id.String()
values["user_id"] = userId(r.ctx)
values["item_type"] = itemType
values["item_id"] = itemID
values["play_count"] = 1
values["play_date"] = ts
ins := Insert(r.tableName).SetMap(values)
_, err = r.executeSQL(ins)
if err != nil {
return err
}
} }
return err return err
} }
func (r *annotationRepository) Delete(userID, itemType string, ids ...string) error { func (r *annotationRepository) getId(itemType string, itemID ...string) And {
return r.delete(And{ return And{
Eq{"user_id": userId(r.ctx)}, Eq{"user_id": userId(r.ctx)},
Eq{"item_type": itemType}, Eq{"item_type": itemType},
Eq{"item_id": ids}, Eq{"item_id": itemID},
}) }
}
func (r *annotationRepository) SetStar(starred bool, itemType string, ids ...string) error {
starredAt := time.Now()
return r.upsert(map[string]interface{}{"starred": starred, "starred_at": starredAt}, itemType, ids...)
}
func (r *annotationRepository) SetRating(rating int, itemType, itemID string) error {
return r.upsert(map[string]interface{}{"rating": rating}, itemType, itemID)
}
func (r *annotationRepository) Delete(itemType string, itemIDs ...string) error {
return r.delete(r.getId(itemType, itemIDs...))
} }

View File

@ -108,7 +108,7 @@ func (r *artistRepository) Refresh(ids ...string) error {
return nil return nil
} }
func (r *artistRepository) GetStarred(userId string, options ...model.QueryOptions) (model.Artists, error) { func (r *artistRepository) GetStarred(options ...model.QueryOptions) (model.Artists, error) {
return nil, nil // TODO return nil, nil // TODO
} }
@ -121,7 +121,7 @@ func (r *artistRepository) Search(q string, offset int, size int) (model.Artists
} }
func (r *artistRepository) Count(options ...rest.QueryOptions) (int64, error) { func (r *artistRepository) Count(options ...rest.QueryOptions) (int64, error) {
return r.CountAll(r.parseRestOptions(options...)) return r.CountAll(r.parseRestOptions(options...)) // TODO Don't apply pagination!
} }
func (r *artistRepository) Read(id string) (interface{}, error) { func (r *artistRepository) Read(id string) (interface{}, error) {

View File

@ -78,7 +78,7 @@ func (r mediaFileRepository) FindByPath(path string) (model.MediaFiles, error) {
return res, err return res, err
} }
func (r mediaFileRepository) GetStarred(userId string, options ...model.QueryOptions) (model.MediaFiles, error) { func (r mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.MediaFiles, error) {
sq := r.selectMediaFile(options...).Where("starred = true") sq := r.selectMediaFile(options...).Where("starred = true")
var starred model.MediaFiles var starred model.MediaFiles
err := r.queryAll(sq, &starred) err := r.queryAll(sq, &starred)

View File

@ -44,7 +44,7 @@ var _ = Describe("MediaRepository", func() {
}) })
It("returns empty array when no tracks are found", func() { It("returns empty array when no tracks are found", func() {
Expect(mr.FindByAlbum("67")).To(Equal(model.MediaFiles{})) Expect(mr.FindByAlbum("67")).To(Equal(model.MediaFiles(nil)))
}) })
It("finds tracks by path", func() { It("finds tracks by path", func() {
@ -54,7 +54,7 @@ var _ = Describe("MediaRepository", func() {
}) })
It("returns starred tracks", func() { It("returns starred tracks", func() {
Expect(mr.GetStarred("userid")).To(Equal(model.MediaFiles{ Expect(mr.GetStarred()).To(Equal(model.MediaFiles{
songComeTogether, songComeTogether,
})) }))
}) })

View File

@ -125,17 +125,8 @@ func (db *NewSQLStore) getOrmer() orm.Ormer {
} }
func initORM(dbPath string) error { func initORM(dbPath string) error {
//verbose := conf.Server.LogLevel == "trace"
//orm.Debug = verbose
if strings.Contains(dbPath, "postgres") { if strings.Contains(dbPath, "postgres") {
driver = "postgres" driver = "postgres"
} }
err := orm.RegisterDataBase("default", driver, dbPath) return orm.RegisterDataBase("default", driver, dbPath)
if err != nil {
return err
}
// TODO Remove all RegisterModels (i.e. don't use orm.Insert/Update)
orm.RegisterModel(new(annotation))
return nil
} }

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/Masterminds/squirrel"
"github.com/astaxie/beego/orm" "github.com/astaxie/beego/orm"
"github.com/deluan/navidrome/conf" "github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/db" "github.com/deluan/navidrome/db"
@ -52,8 +53,8 @@ var testSongs = model.MediaFiles{
songAntenna, songAntenna,
} }
var annAlbumRadioactivity = model.Annotation{AnnotationID: "1", UserID: "userid", ItemType: model.AlbumItemType, ItemID: "3", Starred: true} var annAlbumRadioactivity = model.Annotation{AnnID: "1", UserID: "userid", ItemType: model.AlbumItemType, ItemID: "3", Starred: true}
var annSongComeTogether = model.Annotation{AnnotationID: "2", UserID: "userid", ItemType: model.MediaItemType, ItemID: "2", Starred: true} var annSongComeTogether = model.Annotation{AnnID: "2", UserID: "userid", ItemType: model.MediaItemType, ItemID: "2", Starred: true}
var testAnnotations = []model.Annotation{ var testAnnotations = []model.Annotation{
annAlbumRadioactivity, annAlbumRadioactivity,
annSongComeTogether, annSongComeTogether,
@ -81,7 +82,6 @@ var _ = Describe("Initialize test DB", func() {
BeforeSuite(func() { BeforeSuite(func() {
o := orm.NewOrm() o := orm.NewOrm()
mr := NewMediaFileRepository(nil, o) mr := NewMediaFileRepository(nil, o)
for _, s := range testSongs { for _, s := range testSongs {
err := mr.Put(&s) err := mr.Put(&s)
if err != nil { if err != nil {
@ -90,8 +90,13 @@ var _ = Describe("Initialize test DB", func() {
} }
for _, a := range testAnnotations { for _, a := range testAnnotations {
ann := annotation(a) values, _ := toSqlArgs(a)
_, err := o.Insert(&ann) ins := squirrel.Insert("annotation").SetMap(values)
query, args, err := ins.ToSql()
if err != nil {
panic(err)
}
_, err = o.Raw(query, args...).Exec()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -55,9 +55,9 @@ func (r *sqlRepository) applyOptions(sq SelectBuilder, options ...model.QueryOpt
} }
if options[0].Sort != "" { if options[0].Sort != "" {
if options[0].Order == "desc" { if options[0].Order == "desc" {
sq = sq.OrderBy(options[0].Sort + " desc") sq = sq.OrderBy(toSnakeCase(options[0].Sort + " desc"))
} else { } else {
sq = sq.OrderBy(options[0].Sort) sq = sq.OrderBy(toSnakeCase(options[0].Sort))
} }
} }
if options[0].Filters != nil { if options[0].Filters != nil {
@ -151,7 +151,7 @@ func (r sqlRepository) toSql(sq Sqlizer) (string, []interface{}, error) {
func (r sqlRepository) parseRestOptions(options ...rest.QueryOptions) model.QueryOptions { func (r sqlRepository) parseRestOptions(options ...rest.QueryOptions) model.QueryOptions {
qo := model.QueryOptions{} qo := model.QueryOptions{}
if len(options) > 0 { if len(options) > 0 {
qo.Sort = toSnakeCase(options[0].Sort) qo.Sort = options[0].Sort
qo.Order = options[0].Order qo.Order = options[0].Order
qo.Max = options[0].Max qo.Max = options[0].Max
qo.Offset = options[0].Offset qo.Offset = options[0].Offset