package playlists
import (
"context"
"reflect"
"strings"
"github.com/deluan/rest"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/criteria"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/utils/slice"
)
func (s *playlists) Repository() rest.Repository[model.Playlist] {
return s.repo
}
type playlistRepositoryWrapper struct {
model.PlaylistRepository
service *playlists
}
var _ rest.Persistable[model.Playlist] = (*playlistRepositoryWrapper)(nil)
func (r *playlistRepositoryWrapper) Save(ctx context.Context, entity *model.Playlist) (string, error) {
return r.service.savePlaylist(ctx, entity)
}
func (r *playlistRepositoryWrapper) Update(ctx context.Context, id string, entity model.Playlist, cols ...string) error {
return r.service.updatePlaylistEntity(ctx, id, &entity, cols...)
}
func (r *playlistRepositoryWrapper) Delete(ctx context.Context, ids ...string) error {
for _, id := range ids {
if err := r.service.Delete(ctx, id); err != nil {
return err
}
}
return nil
}
func (s *playlists) TracksRepository(ctx context.Context, playlistId string, refreshSmartPlaylist bool) rest.Repository[model.PlaylistTrack] {
repo := s.ds.Playlist()
tracks := repo.Tracks(ctx, playlistId, refreshSmartPlaylist)
if tracks == nil {
return nil
}
return tracks
}
func (s *playlists) savePlaylist(ctx context.Context, pls *model.Playlist) (string, error) {
usr, _ := request.UserFrom(ctx)
pls.OwnerID = usr.ID
pls.ID = ""
pls.Path = ""
pls.Sync = false
pls.UploadedImage = ""
pls.ExternalImageURL = ""
pls.EvaluatedAt = nil
err := s.ds.Playlist().Put(ctx, pls)
if err != nil {
return "", err
}
return pls.ID, nil
}
func (s *playlists) updatePlaylistEntity(ctx context.Context, id string, entity *model.Playlist, cols ...string) error {
current, err := s.checkWritable(ctx, id)
if err != nil {
return err
}
sent := sentFields(cols)
usr, _ := request.UserFrom(ctx)
ownerChanged := sent("ownerId") && entity.OwnerID != "" && entity.OwnerID != current.OwnerID
if !usr.IsAdmin && ownerChanged {
return rest.ErrPermissionDenied
}
nameChanged := sent("name") && entity.Name != current.Name
commentChanged := sent("comment") && entity.Comment != current.Comment
rulesChanged := sent("rules") && !rulesEqual(current.Rules, entity.Rules)
if nameChanged || commentChanged || ownerChanged || rulesChanged {
return s.applyContentUpdate(ctx, current, entity, sent,
nameChanged, commentChanged, ownerChanged, rulesChanged)
}
return s.applyFlagsOnly(ctx, current, entity, sent)
}
func (s *playlists) applyContentUpdate(ctx context.Context, current, entity *model.Playlist,
sent func(string) bool, nameChanged, commentChanged, ownerChanged, rulesChanged bool,
) error {
if ownerChanged {
current.OwnerID = entity.OwnerID
}
if rulesChanged {
current.Rules = entity.Rules
current.EvaluatedAt = nil
current.ImportedHash = ""
}
if sent("sync") && current.Path != "" && current.Sync != entity.Sync {
current.Sync = entity.Sync
}
var namePtr, commentPtr *string
var publicPtr *bool
if nameChanged {
namePtr = &entity.Name
}
if commentChanged {
commentPtr = &entity.Comment
}
if sent("public") {
publicPtr = &entity.Public
}
return s.updateMetadata(ctx, s.ds, current, namePtr, commentPtr, publicPtr)
}
func (s *playlists) applyFlagsOnly(ctx context.Context, current, entity *model.Playlist,
sent func(string) bool,
) error {
var updateCols []string
if sent("sync") && current.Path != "" && current.Sync != entity.Sync {
current.Sync = entity.Sync
updateCols = append(updateCols, "sync")
}
if sent("public") && current.Public != entity.Public {
current.Public = entity.Public
updateCols = append(updateCols, "public")
}
if len(updateCols) == 0 {
return nil
}
return s.ds.Playlist().Put(ctx, current, updateCols...)
}
func sentFields(cols []string) func(string) bool {
if len(cols) == 0 {
return func(string) bool { return true }
}
set := slice.ToMap(cols, func(c string) (string, struct{}) { return strings.ToLower(c), struct{}{} })
return func(field string) bool {
_, ok := set[strings.ToLower(field)]
return ok
}
}
func rulesEqual(a, b *criteria.Criteria) bool {
if a == b {
return true
}
if a == nil || b == nil {
return false
}
return reflect.DeepEqual(a, b)
}