navidrome/persistence/playlist_track_repository.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

239 lines
6.5 KiB
Go
Raw Normal View History

2020-05-16 02:47:15 +02:00
package persistence
import (
"time"
2020-05-16 02:47:15 +02:00
. "github.com/Masterminds/squirrel"
"github.com/deluan/rest"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
2020-06-05 01:05:41 +02:00
"github.com/navidrome/navidrome/utils"
2020-05-16 02:47:15 +02:00
)
type playlistTrackRepository struct {
sqlRepository
sqlRestful
playlistId string
playlistRepo model.PlaylistRepository
2020-05-16 02:47:15 +02:00
}
func (r *playlistRepository) Tracks(playlistId string) model.PlaylistTrackRepository {
p := &playlistTrackRepository{}
p.playlistRepo = NewPlaylistRepository(r.ctx, r.ormer)
2020-05-16 02:47:15 +02:00
p.playlistId = playlistId
p.ctx = r.ctx
p.ormer = r.ormer
p.tableName = "playlist_tracks"
p.sortMappings = map[string]string{
"id": "playlist_tracks.id",
}
return p
}
func (r *playlistTrackRepository) Count(options ...rest.QueryOptions) (int64, error) {
return r.count(Select().Where(Eq{"playlist_id": r.playlistId}), r.parseRestOptions(options...))
}
func (r *playlistTrackRepository) Read(id string) (interface{}, error) {
sel := r.newSelect().
LeftJoin("annotation on ("+
"annotation.item_id = media_file_id"+
" AND annotation.item_type = 'media_file'"+
" AND annotation.user_id = '"+userId(r.ctx)+"')").
Columns("starred", "starred_at", "play_count", "play_date", "rating", "f.*", "playlist_tracks.*").
Join("media_file f on f.id = media_file_id").
Where(And{Eq{"playlist_id": r.playlistId}, Eq{"id": id}})
var trk model.PlaylistTrack
err := r.queryOne(sel, &trk)
return &trk, err
}
func (r *playlistTrackRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
sel := r.newSelect(r.parseRestOptions(options...)).
LeftJoin("annotation on ("+
"annotation.item_id = media_file_id"+
" AND annotation.item_type = 'media_file'"+
" AND annotation.user_id = '"+userId(r.ctx)+"')").
Columns("starred", "starred_at", "play_count", "play_date", "rating", "f.*", "playlist_tracks.*").
Join("media_file f on f.id = media_file_id").
Where(Eq{"playlist_id": r.playlistId})
res := model.PlaylistTracks{}
err := r.queryAll(sel, &res)
return res, err
}
func (r *playlistTrackRepository) EntityName() string {
return "playlist_tracks"
}
func (r *playlistTrackRepository) NewInstance() interface{} {
return &model.PlaylistTrack{}
}
func (r *playlistTrackRepository) Add(mediaFileIds []string) (int, error) {
if !r.isWritable() {
return 0, rest.ErrPermissionDenied
}
if len(mediaFileIds) > 0 {
log.Debug(r.ctx, "Adding songs to playlist", "playlistId", r.playlistId, "mediaFileIds", mediaFileIds)
}
2020-05-16 02:47:15 +02:00
2020-06-05 01:05:41 +02:00
ids, err := r.getTracks()
if err != nil {
return 0, err
2020-06-05 01:05:41 +02:00
}
// Append new tracks
ids = append(ids, mediaFileIds...)
// Update tracks and playlist
return len(mediaFileIds), r.Update(ids)
}
func (r *playlistTrackRepository) AddAlbums(albumIds []string) (int, error) {
sq := Select("id").From("media_file").Where(Eq{"album_id": albumIds})
return r.addMediaFileIds(sq)
}
func (r *playlistTrackRepository) AddArtists(artistIds []string) (int, error) {
sq := Select("id").From("media_file").Where(Eq{"album_artist_id": artistIds})
return r.addMediaFileIds(sq)
}
func (r *playlistTrackRepository) AddDiscs(discs []model.DiscID) (int, error) {
sq := Select("id").From("media_file")
if len(discs) == 0 {
return 0, nil
}
var clauses []Sqlizer
for _, d := range discs {
clauses = append(clauses, And{Eq{"album_id": d.AlbumID}, Eq{"disc_number": d.DiscNumber}})
}
sq = sq.Where(Or(clauses))
return r.addMediaFileIds(sq)
}
func (r *playlistTrackRepository) addMediaFileIds(sq SelectBuilder) (int, error) {
var res []struct{ Id string }
sq = sq.OrderBy("album_artist, album, disc_number, track_number")
err := r.queryAll(sq, &res)
if err != nil {
log.Error(r.ctx, "Error getting tracks to add to playlist", err)
return 0, err
}
if len(res) == 0 {
return 0, nil
}
var ids []string
for _, r := range res {
ids = append(ids, r.Id)
}
return r.Add(ids)
2020-06-05 01:05:41 +02:00
}
func (r *playlistTrackRepository) getTracks() ([]string, error) {
2020-05-16 02:47:15 +02:00
// Get all current tracks
all := r.newSelect().Columns("media_file_id").Where(Eq{"playlist_id": r.playlistId}).OrderBy("id")
var tracks model.PlaylistTracks
err := r.queryAll(all, &tracks)
if err != nil {
log.Error("Error querying current tracks from playlist", "playlistId", r.playlistId, err)
2020-06-05 01:05:41 +02:00
return nil, err
2020-05-16 02:47:15 +02:00
}
ids := make([]string, len(tracks))
for i := range tracks {
ids[i] = tracks[i].MediaFileID
}
2020-06-05 01:05:41 +02:00
return ids, nil
2020-05-16 02:47:15 +02:00
}
func (r *playlistTrackRepository) Update(mediaFileIds []string) error {
if !r.isWritable() {
return rest.ErrPermissionDenied
}
2020-05-16 02:47:15 +02:00
// Remove old tracks
del := Delete(r.tableName).Where(Eq{"playlist_id": r.playlistId})
_, err := r.executeSQL(del)
if err != nil {
return err
}
// Break the track list in chunks to avoid hitting SQLITE_MAX_FUNCTION_ARG limit
2020-06-11 23:36:09 +02:00
chunks := utils.BreakUpStringSlice(mediaFileIds, 50)
2020-05-16 02:47:15 +02:00
// Add new tracks, chunk by chunk
pos := 1
2020-05-16 02:47:15 +02:00
for i := range chunks {
ins := Insert(r.tableName).Columns("playlist_id", "media_file_id", "id")
for _, t := range chunks[i] {
ins = ins.Values(r.playlistId, t, pos)
pos++
}
_, err = r.executeSQL(ins)
if err != nil {
return err
}
}
2020-05-17 00:10:08 +02:00
return r.updateStats()
}
func (r *playlistTrackRepository) updateStats() error {
2020-10-13 03:28:59 +02:00
// Get total playlist duration, size and count
statsSql := Select("sum(duration) as duration", "sum(size) as size", "count(*) as count").
From("media_file").
2020-05-16 02:47:15 +02:00
Join("playlist_tracks f on f.media_file_id = media_file.id").
Where(Eq{"playlist_id": r.playlistId})
2020-10-13 03:28:59 +02:00
var res struct{ Duration, Size, Count float32 }
2020-05-17 00:10:08 +02:00
err := r.queryOne(statsSql, &res)
2020-05-16 02:47:15 +02:00
if err != nil {
return err
}
2020-10-13 03:28:59 +02:00
// Update playlist's total duration, size and count
2020-05-16 02:47:15 +02:00
upd := Update("playlist").
Set("duration", res.Duration).
2020-10-13 03:28:59 +02:00
Set("size", res.Size).
2020-05-16 02:47:15 +02:00
Set("song_count", res.Count).
Set("updated_at", time.Now()).
2020-05-16 02:47:15 +02:00
Where(Eq{"id": r.playlistId})
_, err = r.executeSQL(upd)
return err
}
2020-05-17 00:10:08 +02:00
func (r *playlistTrackRepository) Delete(id string) error {
if !r.isWritable() {
return rest.ErrPermissionDenied
}
2020-05-17 00:10:08 +02:00
err := r.delete(And{Eq{"playlist_id": r.playlistId}, Eq{"id": id}})
if err != nil {
return err
}
return r.updateStats()
}
2020-06-05 01:05:41 +02:00
func (r *playlistTrackRepository) Reorder(pos int, newPos int) error {
if !r.isWritable() {
return rest.ErrPermissionDenied
}
2020-06-05 01:05:41 +02:00
ids, err := r.getTracks()
if err != nil {
return err
}
newOrder := utils.MoveString(ids, pos-1, newPos-1)
return r.Update(newOrder)
}
func (r *playlistTrackRepository) isWritable() bool {
usr := loggedUser(r.ctx)
if usr.IsAdmin {
return true
}
2021-10-15 00:39:17 +02:00
pls, err := r.playlistRepo.FindByID(r.playlistId)
return err == nil && pls.Owner == usr.UserName
}
2020-05-16 02:47:15 +02:00
var _ model.PlaylistTrackRepository = (*playlistTrackRepository)(nil)