Only import playlists from configured paths in option PlaylistsPath. Closes #1181
Syntax is Ant-style Globs, with support for '**' (any subfolder). Default: '.:**' (or '.;**' in Windows`, meaning all folders and subfolders under `MusicFolder`
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-zglob"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
@@ -17,14 +19,18 @@ import (
|
||||
)
|
||||
|
||||
type playlistSync struct {
|
||||
ds model.DataStore
|
||||
ds model.DataStore
|
||||
rootFolder string
|
||||
}
|
||||
|
||||
func newPlaylistSync(ds model.DataStore) *playlistSync {
|
||||
return &playlistSync{ds: ds}
|
||||
func newPlaylistSync(ds model.DataStore, rootFolder string) *playlistSync {
|
||||
return &playlistSync{ds: ds, rootFolder: rootFolder}
|
||||
}
|
||||
|
||||
func (s *playlistSync) processPlaylists(ctx context.Context, dir string) int64 {
|
||||
if !s.inPlaylistsPath(dir) {
|
||||
return 0
|
||||
}
|
||||
var count int64
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
@@ -127,6 +133,16 @@ func (s *playlistSync) updatePlaylist(ctx context.Context, newPls *model.Playlis
|
||||
return s.ds.Playlist(ctx).Put(newPls)
|
||||
}
|
||||
|
||||
func (s *playlistSync) inPlaylistsPath(dir string) bool {
|
||||
rel, _ := filepath.Rel(s.rootFolder, dir)
|
||||
for _, path := range strings.Split(conf.Server.PlaylistsPath, string(filepath.ListSeparator)) {
|
||||
if match, _ := zglob.Match(path, rel); match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/41433698
|
||||
func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
|
||||
@@ -3,6 +3,8 @@ package scanner
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -10,15 +12,20 @@ import (
|
||||
)
|
||||
|
||||
var _ = Describe("playlistSync", func() {
|
||||
var ds model.DataStore
|
||||
var ps *playlistSync
|
||||
ctx := context.Background()
|
||||
|
||||
BeforeEach(func() {
|
||||
ds = &tests.MockDataStore{
|
||||
MockedMediaFile: &mockedMediaFile{},
|
||||
MockedPlaylist: &mockedPlaylist{},
|
||||
}
|
||||
})
|
||||
|
||||
Describe("parsePlaylist", func() {
|
||||
var ds model.DataStore
|
||||
var ps *playlistSync
|
||||
ctx := context.TODO()
|
||||
BeforeEach(func() {
|
||||
ds = &tests.MockDataStore{
|
||||
MockedMediaFile: &mockedMediaFile{},
|
||||
}
|
||||
ps = newPlaylistSync(ds)
|
||||
ps = newPlaylistSync(ds, "tests/")
|
||||
})
|
||||
|
||||
It("parses well-formed playlists", func() {
|
||||
@@ -41,6 +48,41 @@ var _ = Describe("playlistSync", func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(pls.Tracks).To(HaveLen(2))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Describe("processPlaylists", func() {
|
||||
Context("Default PlaylistsPath", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.PlaylistsPath = consts.DefaultPlaylistsPath
|
||||
})
|
||||
It("finds and import playlists at the top level", func() {
|
||||
ps = newPlaylistSync(ds, "tests/fixtures/playlists/subfolder1")
|
||||
Expect(ps.processPlaylists(ctx, "tests/fixtures/playlists/subfolder1")).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("finds and import playlists at any subfolder level", func() {
|
||||
ps = newPlaylistSync(ds, "tests")
|
||||
Expect(ps.processPlaylists(ctx, "tests/fixtures/playlists/subfolder1")).To(Equal(int64(1)))
|
||||
})
|
||||
})
|
||||
|
||||
It("ignores playlists not in the PlaylistsPath", func() {
|
||||
conf.Server.PlaylistsPath = "subfolder1"
|
||||
ps = newPlaylistSync(ds, "tests/fixtures/playlists")
|
||||
|
||||
Expect(ps.processPlaylists(ctx, "tests/fixtures/playlists/subfolder1")).To(Equal(int64(1)))
|
||||
Expect(ps.processPlaylists(ctx, "tests/fixtures/playlists/subfolder2")).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("only imports playlists from the root of MusicFolder if PlaylistsPath is '.'", func() {
|
||||
conf.Server.PlaylistsPath = "."
|
||||
ps = newPlaylistSync(ds, "tests/fixtures/playlists")
|
||||
|
||||
Expect(ps.processPlaylists(ctx, "tests/fixtures/playlists")).To(Equal(int64(3)))
|
||||
Expect(ps.processPlaylists(ctx, "tests/fixtures/playlists/subfolder1")).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -54,3 +96,15 @@ func (r *mockedMediaFile) FindByPath(s string) (*model.MediaFile, error) {
|
||||
Path: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type mockedPlaylist struct {
|
||||
model.PlaylistRepository
|
||||
}
|
||||
|
||||
func (r *mockedPlaylist) FindByPath(path string) (*model.Playlist, error) {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
|
||||
func (r *mockedPlaylist) Put(pls *model.Playlist) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ type TagScanner struct {
|
||||
func NewTagScanner(rootFolder string, ds model.DataStore, cacheWarmer core.CacheWarmer) *TagScanner {
|
||||
return &TagScanner{
|
||||
rootFolder: rootFolder,
|
||||
plsSync: newPlaylistSync(ds),
|
||||
plsSync: newPlaylistSync(ds, rootFolder),
|
||||
ds: ds,
|
||||
cacheWarmer: cacheWarmer,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user