From 815623715eeff223dbd004aab045a6997baf3cf0 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 14 Oct 2021 18:39:17 -0400 Subject: [PATCH] Load SmartPlaylists rules from DB --- model/playlist.go | 5 +- persistence/playlist_repository.go | 102 ++++++++++++++++------- persistence/playlist_track_repository.go | 2 +- persistence/sql_smartplaylist.go | 13 ++- 4 files changed, 84 insertions(+), 38 deletions(-) diff --git a/model/playlist.go b/model/playlist.go index 75552b535..7aefc38c5 100644 --- a/model/playlist.go +++ b/model/playlist.go @@ -20,8 +20,8 @@ type Playlist struct { UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"` // SmartPlaylist attributes - //Rules *SmartPlaylist `structs:"rules" json:"rules"` - //EvaluatedAt time.Time `structs:"evaluated_at" json:"evaluatedAt"` + Rules *SmartPlaylist `structs:"-" json:"rules"` + EvaluatedAt time.Time `structs:"evaluated_at" json:"evaluatedAt"` } type Playlists []Playlist @@ -33,6 +33,7 @@ type PlaylistRepository interface { Get(id string) (*Playlist, error) GetAll(options ...QueryOptions) (Playlists, error) FindByPath(path string) (*Playlist, error) + FindByID(id string) (*Playlist, error) Delete(id string) error Tracks(playlistId string) PlaylistTrackRepository } diff --git a/persistence/playlist_repository.go b/persistence/playlist_repository.go index 6a7610265..112dfd3d9 100644 --- a/persistence/playlist_repository.go +++ b/persistence/playlist_repository.go @@ -2,6 +2,8 @@ package persistence import ( "context" + "encoding/json" + "strings" "time" . "github.com/Masterminds/squirrel" @@ -16,6 +18,11 @@ type playlistRepository struct { sqlRestful } +type dbPlaylist struct { + model.Playlist `structs:",flatten"` + RawRules string `structs:"rules"` +} + func NewPlaylistRepository(ctx context.Context, o orm.Ormer) model.PlaylistRepository { r := &playlistRepository{} r.ctx = ctx @@ -59,10 +66,18 @@ func (r *playlistRepository) Delete(id string) error { } func (r *playlistRepository) Put(p *model.Playlist) error { - if p.ID == "" { - p.CreatedAt = time.Now() + pls := dbPlaylist{Playlist: *p} + if p.Rules != nil { + j, err := json.Marshal(p.Rules) + if err != nil { + return err + } + pls.RawRules = string(j) + } + if pls.ID == "" { + pls.CreatedAt = time.Now() } else { - ok, err := r.Exists(p.ID) + ok, err := r.Exists(pls.ID) if err != nil { return err } @@ -70,54 +85,85 @@ func (r *playlistRepository) Put(p *model.Playlist) error { return model.ErrNotAuthorized } } - p.UpdatedAt = time.Now() + pls.UpdatedAt = time.Now() // Save tracks for later and set it to nil, to avoid trying to save it to the DB - tracks := p.Tracks - p.Tracks = nil + tracks := pls.Tracks + pls.Tracks = nil - id, err := r.put(p.ID, p) + id, err := r.put(pls.ID, pls) if err != nil { return err } p.ID = id // Only update tracks if they are specified - if tracks != nil { - err = r.updateTracks(id, tracks) - if err != nil { - return err - } + if tracks == nil { + return nil } - return r.loadTracks(p) + return r.updateTracks(id, tracks) } func (r *playlistRepository) Get(id string) (*model.Playlist, error) { - sel := r.newSelect().Columns("*").Where(And{Eq{"id": id}, r.userFilter()}) - var pls model.Playlist - err := r.queryOne(sel, &pls) - if err != nil { - return nil, err - } - err = r.loadTracks(&pls) - return &pls, err + return r.findBy(And{Eq{"id": id}, r.userFilter()}, true) } func (r *playlistRepository) FindByPath(path string) (*model.Playlist, error) { - sel := r.newSelect().Columns("*").Where(Eq{"path": path}) - var pls model.Playlist - err := r.queryOne(sel, &pls) + return r.findBy(Eq{"path": path}, false) +} + +func (r *playlistRepository) FindByID(id string) (*model.Playlist, error) { + return r.findBy(And{Eq{"id": id}, r.userFilter()}, false) +} + +func (r *playlistRepository) findBy(sql Sqlizer, includeTracks bool) (*model.Playlist, error) { + sel := r.newSelect().Columns("*").Where(sql) + var pls []dbPlaylist + err := r.queryAll(sel, &pls) if err != nil { return nil, err } - return &pls, err + if len(pls) == 0 { + return nil, model.ErrNotFound + } + + return r.toModel(pls[0], includeTracks) +} + +func (r *playlistRepository) toModel(pls dbPlaylist, includeTracks bool) (*model.Playlist, error) { + var err error + if strings.TrimSpace(pls.RawRules) != "" { + r := model.SmartPlaylist{} + err = json.Unmarshal([]byte(pls.RawRules), &r) + if err != nil { + return nil, err + } + pls.Playlist.Rules = &r + } else { + pls.Playlist.Rules = nil + } + if includeTracks { + err = r.loadTracks(&pls) + } + return &pls.Playlist, err } func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playlists, error) { sel := r.newSelect(options...).Columns("*").Where(r.userFilter()) - res := model.Playlists{} + var res []dbPlaylist err := r.queryAll(sel, &res) - return res, err + if err != nil { + return nil, err + } + playlists := make(model.Playlists, len(res)) + for i, p := range res { + pls, err := r.toModel(p, false) + if err != nil { + return nil, err + } + playlists[i] = *pls + } + return playlists, err } func (r *playlistRepository) updateTracks(id string, tracks model.MediaFiles) error { @@ -128,7 +174,7 @@ func (r *playlistRepository) updateTracks(id string, tracks model.MediaFiles) er return r.Tracks(id).Update(ids) } -func (r *playlistRepository) loadTracks(pls *model.Playlist) error { +func (r *playlistRepository) loadTracks(pls *dbPlaylist) error { tracksQuery := Select().From("playlist_tracks"). LeftJoin("annotation on ("+ "annotation.item_id = media_file_id"+ diff --git a/persistence/playlist_track_repository.go b/persistence/playlist_track_repository.go index 612e5e78f..364c7f98f 100644 --- a/persistence/playlist_track_repository.go +++ b/persistence/playlist_track_repository.go @@ -231,7 +231,7 @@ func (r *playlistTrackRepository) isWritable() bool { if usr.IsAdmin { return true } - pls, err := r.playlistRepo.Get(r.playlistId) + pls, err := r.playlistRepo.FindByID(r.playlistId) return err == nil && pls.Owner == usr.UserName } diff --git a/persistence/sql_smartplaylist.go b/persistence/sql_smartplaylist.go index d7b44f9fb..ad1d7f88c 100644 --- a/persistence/sql_smartplaylist.go +++ b/persistence/sql_smartplaylist.go @@ -13,13 +13,12 @@ import ( ) //{ -// "combinator": "and", -// "rules": [ -// {"field": "loved", "operator": "is true"}, -// {"field": "lastPlayed", "operator": "in the last", "value": "90"} -// ], -// "order": "artist asc", -// "limit": 100 +//"combinator": "and", +//"rules": [ +// {"field": "lastPlayed", "operator": "in the last", "value": "30"} +//], +//"order": "lastPlayed desc", +//"limit": 10 //} type SmartPlaylist model.SmartPlaylist