mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-14 19:20:37 +03:00
Give warning when playlists are not imported due to not having an admin user
This commit is contained in:
parent
41138bd665
commit
feca030c6d
@ -12,10 +12,16 @@ import (
|
|||||||
"github.com/deluan/navidrome/utils"
|
"github.com/deluan/navidrome/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dirMap = map[string]time.Time
|
type (
|
||||||
|
dirMapValue struct {
|
||||||
|
modTime time.Time
|
||||||
|
hasPlaylist bool
|
||||||
|
}
|
||||||
|
dirMap = map[string]dirMapValue
|
||||||
|
)
|
||||||
|
|
||||||
func loadDirTree(ctx context.Context, rootFolder string) (dirMap, error) {
|
func loadDirTree(ctx context.Context, rootFolder string) (dirMap, error) {
|
||||||
newMap := make(map[string]time.Time)
|
newMap := make(dirMap)
|
||||||
err := loadMap(ctx, rootFolder, rootFolder, newMap)
|
err := loadMap(ctx, rootFolder, rootFolder, newMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error loading directory tree", err)
|
log.Error(ctx, "Error loading directory tree", err)
|
||||||
@ -24,7 +30,7 @@ func loadDirTree(ctx context.Context, rootFolder string) (dirMap, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadMap(ctx context.Context, rootPath string, currentFolder string, dirMap dirMap) error {
|
func loadMap(ctx context.Context, rootPath string, currentFolder string, dirMap dirMap) error {
|
||||||
children, lastUpdated, err := loadDir(ctx, currentFolder)
|
children, dirMapValue, err := loadDir(ctx, currentFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -36,18 +42,18 @@ func loadMap(ctx context.Context, rootPath string, currentFolder string, dirMap
|
|||||||
}
|
}
|
||||||
|
|
||||||
dir := filepath.Clean(currentFolder)
|
dir := filepath.Clean(currentFolder)
|
||||||
dirMap[dir] = lastUpdated
|
dirMap[dir] = dirMapValue
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDir(ctx context.Context, dirPath string) (children []string, lastUpdated time.Time, err error) {
|
func loadDir(ctx context.Context, dirPath string) (children []string, info dirMapValue, err error) {
|
||||||
dirInfo, err := os.Stat(dirPath)
|
dirInfo, err := os.Stat(dirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error stating dir", "path", dirPath, err)
|
log.Error(ctx, "Error stating dir", "path", dirPath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastUpdated = dirInfo.ModTime()
|
info.modTime = dirInfo.ModTime()
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(dirPath)
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,9 +69,10 @@ func loadDir(ctx context.Context, dirPath string) (children []string, lastUpdate
|
|||||||
if isDir && !isDirIgnored(dirPath, f) && isDirReadable(dirPath, f) {
|
if isDir && !isDirIgnored(dirPath, f) && isDirReadable(dirPath, f) {
|
||||||
children = append(children, filepath.Join(dirPath, f.Name()))
|
children = append(children, filepath.Join(dirPath, f.Name()))
|
||||||
} else {
|
} else {
|
||||||
if f.ModTime().After(lastUpdated) {
|
if f.ModTime().After(info.modTime) {
|
||||||
lastUpdated = f.ModTime()
|
info.modTime = f.ModTime()
|
||||||
}
|
}
|
||||||
|
info.hasPlaylist = info.hasPlaylist || utils.IsPlaylist(f.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/deluan/navidrome/log"
|
"github.com/deluan/navidrome/log"
|
||||||
"github.com/deluan/navidrome/model"
|
"github.com/deluan/navidrome/model"
|
||||||
"github.com/deluan/navidrome/model/request"
|
"github.com/deluan/navidrome/model/request"
|
||||||
|
"github.com/deluan/navidrome/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type playlistSync struct {
|
type playlistSync struct {
|
||||||
@ -22,15 +23,15 @@ func newPlaylistSync(ds model.DataStore) *playlistSync {
|
|||||||
return &playlistSync{ds: ds}
|
return &playlistSync{ds: ds}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playlistSync) processPlaylists(ctx context.Context, dir string) error {
|
func (s *playlistSync) processPlaylists(ctx context.Context, dir string) int {
|
||||||
|
count := 0
|
||||||
files, err := ioutil.ReadDir(dir)
|
files, err := ioutil.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error reading files", "dir", dir, err)
|
log.Error(ctx, "Error reading files", "dir", dir, err)
|
||||||
return err
|
return count
|
||||||
}
|
}
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
match, _ := filepath.Match("*.m3u", strings.ToLower(f.Name()))
|
if !utils.IsPlaylist(f.Name()) {
|
||||||
if !match {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pls, err := s.parsePlaylist(ctx, f.Name(), dir)
|
pls, err := s.parsePlaylist(ctx, f.Name(), dir)
|
||||||
@ -43,8 +44,9 @@ func (s *playlistSync) processPlaylists(ctx context.Context, dir string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error updating playlist", "playlist", f.Name(), err)
|
log.Error(ctx, "Error updating playlist", "playlist", f.Name(), err)
|
||||||
}
|
}
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
return nil
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playlistSync) parsePlaylist(ctx context.Context, playlistFile string, baseDir string) (*model.Playlist, error) {
|
func (s *playlistSync) parsePlaylist(ctx context.Context, playlistFile string, baseDir string) (*model.Playlist, error) {
|
||||||
|
@ -33,19 +33,22 @@ func NewTagScanner2(rootFolder string, ds model.DataStore) *TagScanner2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scan algorithm overview:
|
// Scan algorithm overview:
|
||||||
// Load all directories under the music folder, with their ModTime (self or any non-dir children)
|
// Load all directories under the music folder, with their ModTime (self or any non-dir children, whichever is newer)
|
||||||
// Find changed folders (based on lastModifiedSince) and deletes folders (comparing to the DB)
|
// Find changed folders (based on lastModifiedSince) and deleted folders (comparing to the DB)
|
||||||
// For each deleted folder: delete all files from DB whose path starts with the delete folder path
|
// For each deleted folder: delete all files from DB whose path starts with the delete folder path (non-recursively)
|
||||||
// For each changed folder: Get all files from DB whose path starts with the changed folder, scan each file:
|
// For each changed folder: get all files from DB whose path starts with the changed folder (non-recursively), check each file:
|
||||||
// if file in folder is newer, update the one in DB
|
// if file in folder is newer, update the one in DB
|
||||||
// if file in folder does not exists in DB, add
|
// if file in folder does not exists in DB, add it
|
||||||
// for each file in the DB that is not found in the folder, delete from DB
|
// for each file in the DB that is not found in the folder, delete it from DB
|
||||||
// Create new albums/artists, update counters:
|
// Create new albums/artists, update counters:
|
||||||
// collect all albumIDs and artistIDs from previous steps
|
// collect all albumIDs and artistIDs from previous steps
|
||||||
// refresh the collected albums and artists with the metadata from the mediafiles
|
// refresh the collected albums and artists with the metadata from the mediafiles
|
||||||
// Delete all empty albums, delete all empty Artists
|
// For each changed folder, process playlists:
|
||||||
|
// If the playlist is not in the DB, import it, setting sync = true
|
||||||
|
// If the playlist is in the DB and sync == true, import it, or else skip it
|
||||||
|
// Delete all empty albums, delete all empty artists, clean-up playlists
|
||||||
func (s *TagScanner2) Scan(ctx context.Context, lastModifiedSince time.Time) error {
|
func (s *TagScanner2) Scan(ctx context.Context, lastModifiedSince time.Time) error {
|
||||||
ctx = s.setAdminUser(ctx)
|
ctx = s.withAdminUser(ctx)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
allDirs, err := s.getDirTree(ctx)
|
allDirs, err := s.getDirTree(ctx)
|
||||||
@ -88,13 +91,23 @@ func (s *TagScanner2) Scan(ctx context.Context, lastModifiedSince time.Time) err
|
|||||||
_ = s.artistMap.flush()
|
_ = s.artistMap.flush()
|
||||||
|
|
||||||
// Now that all mediafiles are imported/updated, search for and import playlists
|
// Now that all mediafiles are imported/updated, search for and import playlists
|
||||||
|
u, _ := request.UserFrom(ctx)
|
||||||
|
plsCount := 0
|
||||||
for _, dir := range changedDirs {
|
for _, dir := range changedDirs {
|
||||||
_ = s.plsSync.processPlaylists(ctx, dir)
|
info := allDirs[dir]
|
||||||
|
if info.hasPlaylist {
|
||||||
|
if !u.IsAdmin {
|
||||||
|
log.Warn("Playlists will not be imported, as there are no admin users yet, "+
|
||||||
|
"Please create an admin user first, and then update the playlists for them to be imported", "dir", dir)
|
||||||
|
} else {
|
||||||
|
plsCount = s.plsSync.processPlaylists(ctx, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.ds.GC(log.NewContext(ctx))
|
err = s.ds.GC(log.NewContext(ctx))
|
||||||
log.Info("Finished processing Music Folder", "folder", s.rootFolder, "elapsed", time.Since(start),
|
log.Info("Finished processing Music Folder", "folder", s.rootFolder, "elapsed", time.Since(start),
|
||||||
"added", s.cnt.added, "updated", s.cnt.updated, "deleted", s.cnt.deleted)
|
"added", s.cnt.added, "updated", s.cnt.updated, "deleted", s.cnt.deleted, "playlistsImported", plsCount)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -114,8 +127,8 @@ func (s *TagScanner2) getChangedDirs(ctx context.Context, dirs dirMap, lastModif
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
log.Trace(ctx, "Checking for changed folders")
|
log.Trace(ctx, "Checking for changed folders")
|
||||||
var changed []string
|
var changed []string
|
||||||
for d, t := range dirs {
|
for d, info := range dirs {
|
||||||
if t.After(lastModified) {
|
if info.modTime.After(lastModified) {
|
||||||
changed = append(changed, d)
|
changed = append(changed, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,7 +219,7 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If track from folder is newer than the one in DB, select for update/insert in DB and delete from the current tracks
|
// If track from folder is newer than the one in DB, select for update/insert in DB
|
||||||
log.Trace(ctx, "Processing changed folder", "dir", dir, "tracksInDB", len(currentTracks), "tracksInFolder", len(files))
|
log.Trace(ctx, "Processing changed folder", "dir", dir, "tracksInDB", len(currentTracks), "tracksInFolder", len(files))
|
||||||
var filesToUpdate []string
|
var filesToUpdate []string
|
||||||
for filePath, info := range files {
|
for filePath, info := range files {
|
||||||
@ -219,7 +232,6 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
|
|||||||
filesToUpdate = append(filesToUpdate, filePath)
|
filesToUpdate = append(filesToUpdate, filePath)
|
||||||
s.cnt.updated++
|
s.cnt.updated++
|
||||||
}
|
}
|
||||||
delete(currentTracks, filePath)
|
|
||||||
|
|
||||||
// Force a refresh of the album and artist, to cater for cover art files
|
// Force a refresh of the album and artist, to cater for cover art files
|
||||||
err = s.albumMap.update(c.AlbumID)
|
err = s.albumMap.update(c.AlbumID)
|
||||||
@ -230,6 +242,10 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove it from currentTracks (the ones found in DB). After this loop any currentTracks remaining
|
||||||
|
// are considered gone from the music folder and will be deleted from DB
|
||||||
|
delete(currentTracks, filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
numUpdatedTracks := 0
|
numUpdatedTracks := 0
|
||||||
@ -249,7 +265,8 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(ctx, "Finished processing changed folder", "dir", dir, "updated", numUpdatedTracks, "purged", numPurgedTracks, "elapsed", time.Since(start))
|
log.Info(ctx, "Finished processing changed folder", "dir", dir, "updated", numUpdatedTracks,
|
||||||
|
"purged", numPurgedTracks, "elapsed", time.Since(start))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,10 +342,10 @@ func (s *TagScanner2) loadTracks(filePaths []string) (model.MediaFiles, error) {
|
|||||||
return mfs, nil
|
return mfs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TagScanner2) setAdminUser(ctx context.Context) context.Context {
|
func (s *TagScanner2) withAdminUser(ctx context.Context) context.Context {
|
||||||
u, err := s.ds.User(ctx).FindFirstAdmin()
|
u, err := s.ds.User(ctx).FindFirstAdmin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(ctx, "Error retrieving playlist owner", err)
|
log.Warn(ctx, "No admin user found!", err)
|
||||||
u = &model.User{}
|
u = &model.User{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,3 +21,8 @@ func IsImageFile(filePath string) bool {
|
|||||||
extension := filepath.Ext(filePath)
|
extension := filepath.Ext(filePath)
|
||||||
return strings.HasPrefix(mime.TypeByExtension(extension), "image/")
|
return strings.HasPrefix(mime.TypeByExtension(extension), "image/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsPlaylist(filePath string) bool {
|
||||||
|
extension := filepath.Ext(filePath)
|
||||||
|
return strings.ToLower(extension) == ".m3u"
|
||||||
|
}
|
||||||
|
@ -43,4 +43,14 @@ var _ = Describe("Files", func() {
|
|||||||
Expect(IsImageFile("test.mp3")).To(BeFalse())
|
Expect(IsImageFile("test.mp3")).To(BeFalse())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("IsPlaylist", func() {
|
||||||
|
It("returns true for a M3U file", func() {
|
||||||
|
Expect(IsPlaylist(filepath.Join("path", "to", "test.m3u"))).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns false for a non-playlist file", func() {
|
||||||
|
Expect(IsPlaylist("testm3u")).To(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user