mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-15 03:30:39 +03:00
Add methods to Playlist model
Also, don't load genres for Playlists tracks (not necessary for now)
This commit is contained in:
parent
d200933b68
commit
c72add516a
@ -59,7 +59,7 @@ func (a *archiver) ZipPlaylist(ctx context.Context, id string, out io.Writer) er
|
|||||||
log.Error(ctx, "Error loading mediafiles from playlist", "id", id, err)
|
log.Error(ctx, "Error loading mediafiles from playlist", "id", id, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return a.zipTracks(ctx, id, out, pls.Tracks, a.createPlaylistHeader)
|
return a.zipTracks(ctx, id, out, pls.MediaFiles(), a.createPlaylistHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles, ch createHeader) error {
|
func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles, ch createHeader) error {
|
||||||
|
@ -1,32 +1,87 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Playlist struct {
|
type Playlist struct {
|
||||||
ID string `structs:"id" json:"id" orm:"column(id)"`
|
ID string `structs:"id" json:"id" orm:"column(id)"`
|
||||||
Name string `structs:"name" json:"name"`
|
Name string `structs:"name" json:"name"`
|
||||||
Comment string `structs:"comment" json:"comment"`
|
Comment string `structs:"comment" json:"comment"`
|
||||||
Duration float32 `structs:"duration" json:"duration"`
|
Duration float32 `structs:"duration" json:"duration"`
|
||||||
Size int64 `structs:"size" json:"size"`
|
Size int64 `structs:"size" json:"size"`
|
||||||
SongCount int `structs:"song_count" json:"songCount"`
|
SongCount int `structs:"song_count" json:"songCount"`
|
||||||
Owner string `structs:"owner" json:"owner"`
|
Owner string `structs:"owner" json:"owner"`
|
||||||
Public bool `structs:"public" json:"public"`
|
Public bool `structs:"public" json:"public"`
|
||||||
Tracks MediaFiles `structs:"-" json:"tracks,omitempty"`
|
Tracks PlaylistTracks `structs:"-" json:"tracks,omitempty"`
|
||||||
Path string `structs:"path" json:"path"`
|
Path string `structs:"path" json:"path"`
|
||||||
Sync bool `structs:"sync" json:"sync"`
|
Sync bool `structs:"sync" json:"sync"`
|
||||||
CreatedAt time.Time `structs:"created_at" json:"createdAt"`
|
CreatedAt time.Time `structs:"created_at" json:"createdAt"`
|
||||||
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"`
|
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"`
|
||||||
|
|
||||||
// SmartPlaylist attributes
|
// SmartPlaylist attributes
|
||||||
Rules *SmartPlaylist `structs:"-" json:"rules"`
|
Rules *SmartPlaylist `structs:"-" json:"rules"`
|
||||||
EvaluatedAt time.Time `structs:"evaluated_at" json:"evaluatedAt"`
|
EvaluatedAt time.Time `structs:"evaluated_at" json:"evaluatedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pls Playlist) IsSmartPlaylist() bool {
|
||||||
|
return pls.Rules != nil && pls.Rules.Combinator != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pls Playlist) MediaFiles() MediaFiles {
|
||||||
|
mfs := make(MediaFiles, len(pls.Tracks))
|
||||||
|
for i, t := range pls.Tracks {
|
||||||
|
mfs[i] = t.MediaFile
|
||||||
|
}
|
||||||
|
return mfs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pls *Playlist) RemoveTracks(idxToRemove []int) {
|
||||||
|
var newTracks PlaylistTracks
|
||||||
|
for i, t := range pls.Tracks {
|
||||||
|
if utils.IntInSlice(i, idxToRemove) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newTracks = append(newTracks, t)
|
||||||
|
}
|
||||||
|
pls.Tracks = newTracks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pls *Playlist) AddTracks(mediaFileIds []string) {
|
||||||
|
pos := len(pls.Tracks)
|
||||||
|
for _, mfId := range mediaFileIds {
|
||||||
|
pos++
|
||||||
|
t := PlaylistTrack{
|
||||||
|
ID: strconv.Itoa(pos),
|
||||||
|
MediaFileID: mfId,
|
||||||
|
MediaFile: MediaFile{ID: mfId},
|
||||||
|
PlaylistID: pls.ID,
|
||||||
|
}
|
||||||
|
pls.Tracks = append(pls.Tracks, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pls *Playlist) AddMediaFiles(mfs MediaFiles) {
|
||||||
|
pos := len(pls.Tracks)
|
||||||
|
for _, mf := range mfs {
|
||||||
|
pos++
|
||||||
|
t := PlaylistTrack{
|
||||||
|
ID: strconv.Itoa(pos),
|
||||||
|
MediaFileID: mf.ID,
|
||||||
|
MediaFile: mf,
|
||||||
|
PlaylistID: pls.ID,
|
||||||
|
}
|
||||||
|
pls.Tracks = append(pls.Tracks, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Playlists []Playlist
|
type Playlists []Playlist
|
||||||
|
|
||||||
type PlaylistRepository interface {
|
type PlaylistRepository interface {
|
||||||
|
ResourceRepository
|
||||||
CountAll(options ...QueryOptions) (int64, error)
|
CountAll(options ...QueryOptions) (int64, error)
|
||||||
Exists(id string) (bool, error)
|
Exists(id string) (bool, error)
|
||||||
Put(pls *Playlist) error
|
Put(pls *Playlist) error
|
||||||
|
@ -70,16 +70,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
plsBest = model.Playlist{
|
plsBest model.Playlist
|
||||||
Name: "Best",
|
plsCool model.Playlist
|
||||||
Comment: "No Comments",
|
testPlaylists []*model.Playlist
|
||||||
Owner: "userid",
|
|
||||||
Public: true,
|
|
||||||
SongCount: 2,
|
|
||||||
Tracks: model.MediaFiles{{ID: "1001"}, {ID: "1003"}},
|
|
||||||
}
|
|
||||||
plsCool = model.Playlist{Name: "Cool", Owner: "userid", Tracks: model.MediaFiles{{ID: "1004"}}}
|
|
||||||
testPlaylists = []*model.Playlist{&plsBest, &plsCool}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func P(path string) string {
|
func P(path string) string {
|
||||||
@ -130,6 +123,18 @@ var _ = Describe("Initialize test DB", func() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plsBest = model.Playlist{
|
||||||
|
Name: "Best",
|
||||||
|
Comment: "No Comments",
|
||||||
|
Owner: "userid",
|
||||||
|
Public: true,
|
||||||
|
SongCount: 2,
|
||||||
|
}
|
||||||
|
plsBest.AddTracks([]string{"1001", "1003"})
|
||||||
|
plsCool = model.Playlist{Name: "Cool", Owner: "userid"}
|
||||||
|
plsCool.AddTracks([]string{"1004"})
|
||||||
|
testPlaylists = []*model.Playlist{&plsBest, &plsCool}
|
||||||
|
|
||||||
pr := NewPlaylistRepository(ctx, o)
|
pr := NewPlaylistRepository(ctx, o)
|
||||||
for i := range testPlaylists {
|
for i := range testPlaylists {
|
||||||
err := pr.Put(testPlaylists[i])
|
err := pr.Put(testPlaylists[i])
|
||||||
@ -162,6 +167,5 @@ var _ = Describe("Initialize test DB", func() {
|
|||||||
songComeTogether.Starred = true
|
songComeTogether.Starred = true
|
||||||
songComeTogether.StarredAt = mf.StarredAt
|
songComeTogether.StarredAt = mf.StarredAt
|
||||||
testSongs[1] = songComeTogether
|
testSongs[1] = songComeTogether
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -101,7 +101,7 @@ func (r *playlistRepository) Put(p *model.Playlist) error {
|
|||||||
if tracks == nil {
|
if tracks == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return r.updateTracks(id, tracks)
|
return r.updateTracks(id, p.MediaFiles())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
|
func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
|
||||||
@ -185,9 +185,8 @@ func (r *playlistRepository) loadTracks(pls *dbPlaylist) error {
|
|||||||
Where(Eq{"playlist_id": pls.ID}).OrderBy("playlist_tracks.id")
|
Where(Eq{"playlist_id": pls.ID}).OrderBy("playlist_tracks.id")
|
||||||
err := r.queryAll(tracksQuery, &pls.Tracks)
|
err := r.queryAll(tracksQuery, &pls.Tracks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error loading playlist tracks", "playlist", pls.Name, "id", pls.ID)
|
log.Error(r.ctx, "Error loading playlist tracks", "playlist", pls.Name, "id", pls.ID, err)
|
||||||
}
|
}
|
||||||
err = r.loadMediaFileGenres(&pls.Tracks)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,23 +58,23 @@ var _ = Describe("PlaylistRepository", func() {
|
|||||||
pls, err := repo.GetWithTracks(plsBest.ID)
|
pls, err := repo.GetWithTracks(plsBest.ID)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(pls.Name).To(Equal(plsBest.Name))
|
Expect(pls.Name).To(Equal(plsBest.Name))
|
||||||
Expect(pls.Tracks).To(Equal(model.MediaFiles{
|
mfs := pls.MediaFiles()
|
||||||
songDayInALife,
|
Expect(mfs).To(HaveLen(2))
|
||||||
songRadioactivity,
|
Expect(mfs[0].ID).To(Equal(songDayInALife.ID))
|
||||||
}))
|
Expect(mfs[1].ID).To(Equal(songRadioactivity.ID))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Put/Exists/Delete", func() {
|
It("Put/Exists/Delete", func() {
|
||||||
By("saves the playlist to the DB")
|
By("saves the playlist to the DB")
|
||||||
newPls := model.Playlist{Name: "Great!", Owner: "userid",
|
newPls := model.Playlist{Name: "Great!", Owner: "userid"}
|
||||||
Tracks: model.MediaFiles{{ID: "1004"}, {ID: "1003"}}}
|
newPls.AddTracks([]string{"1004", "1003"})
|
||||||
|
|
||||||
By("saves the playlist to the DB")
|
By("saves the playlist to the DB")
|
||||||
Expect(repo.Put(&newPls)).To(BeNil())
|
Expect(repo.Put(&newPls)).To(BeNil())
|
||||||
|
|
||||||
By("adds repeated songs to a playlist and keeps the order")
|
By("adds repeated songs to a playlist and keeps the order")
|
||||||
newPls.Tracks = append(newPls.Tracks, model.MediaFile{ID: "1004"})
|
newPls.AddTracks([]string{"1004"})
|
||||||
Expect(repo.Put(&newPls)).To(BeNil())
|
Expect(repo.Put(&newPls)).To(BeNil())
|
||||||
saved, _ := repo.GetWithTracks(newPls.ID)
|
saved, _ := repo.GetWithTracks(newPls.ID)
|
||||||
Expect(saved.Tracks).To(HaveLen(3))
|
Expect(saved.Tracks).To(HaveLen(3))
|
||||||
|
@ -83,6 +83,7 @@ func (s *playlistSync) parsePlaylist(ctx context.Context, playlistFile string, b
|
|||||||
mediaFileRepository := s.ds.MediaFile(ctx)
|
mediaFileRepository := s.ds.MediaFile(ctx)
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(scanLines)
|
scanner.Split(scanLines)
|
||||||
|
var mfs model.MediaFiles
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
path := scanner.Text()
|
path := scanner.Text()
|
||||||
// Skip extended info
|
// Skip extended info
|
||||||
@ -101,8 +102,10 @@ func (s *playlistSync) parsePlaylist(ctx context.Context, playlistFile string, b
|
|||||||
log.Warn(ctx, "Path in playlist not found", "playlist", playlistFile, "path", path, err)
|
log.Warn(ctx, "Path in playlist not found", "playlist", playlistFile, "path", path, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pls.Tracks = append(pls.Tracks, *mf)
|
mfs = append(mfs, *mf)
|
||||||
}
|
}
|
||||||
|
pls.Tracks = nil
|
||||||
|
pls.AddMediaFiles(mfs)
|
||||||
|
|
||||||
return pls, scanner.Err()
|
return pls, scanner.Err()
|
||||||
}
|
}
|
||||||
|
@ -76,16 +76,14 @@ func (c *PlaylistsController) create(ctx context.Context, playlistId, name strin
|
|||||||
if owner != pls.Owner {
|
if owner != pls.Owner {
|
||||||
return model.ErrNotAuthorized
|
return model.ErrNotAuthorized
|
||||||
}
|
}
|
||||||
pls.Tracks = nil
|
|
||||||
} else {
|
} else {
|
||||||
pls = &model.Playlist{
|
pls = &model.Playlist{
|
||||||
Name: name,
|
Name: name,
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, id := range ids {
|
pls.Tracks = nil
|
||||||
pls.Tracks = append(pls.Tracks, model.MediaFile{ID: id})
|
pls.AddTracks(ids)
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Playlist(ctx).Put(pls)
|
err = tx.Playlist(ctx).Put(pls)
|
||||||
playlistId = pls.ID
|
playlistId = pls.ID
|
||||||
@ -143,18 +141,8 @@ func (c *PlaylistsController) update(ctx context.Context, playlistId string, nam
|
|||||||
pls.Public = *public
|
pls.Public = *public
|
||||||
}
|
}
|
||||||
|
|
||||||
newTracks := model.MediaFiles{}
|
pls.RemoveTracks(idxToRemove)
|
||||||
for i, t := range pls.Tracks {
|
pls.AddTracks(idsToAdd)
|
||||||
if utils.IntInSlice(i, idxToRemove) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newTracks = append(newTracks, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range idsToAdd {
|
|
||||||
newTracks = append(newTracks, model.MediaFile{ID: id})
|
|
||||||
}
|
|
||||||
pls.Tracks = newTracks
|
|
||||||
|
|
||||||
return tx.Playlist(ctx).Put(pls)
|
return tx.Playlist(ctx).Put(pls)
|
||||||
})
|
})
|
||||||
@ -203,7 +191,7 @@ func (c *PlaylistsController) buildPlaylistWithSongs(ctx context.Context, p *mod
|
|||||||
pls := &responses.PlaylistWithSongs{
|
pls := &responses.PlaylistWithSongs{
|
||||||
Playlist: *c.buildPlaylist(*p),
|
Playlist: *c.buildPlaylist(*p),
|
||||||
}
|
}
|
||||||
pls.Entry = childrenFromMediaFiles(ctx, p.Tracks)
|
pls.Entry = childrenFromMediaFiles(ctx, p.MediaFiles())
|
||||||
return pls
|
return pls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user