Skip to content

Commit

Permalink
Give warning when playlists are not imported due to not having an adm…
Browse files Browse the repository at this point in the history
…in user
  • Loading branch information
deluan committed Jul 19, 2020
1 parent 41138bd commit feca030
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 30 deletions.
23 changes: 15 additions & 8 deletions scanner/load_tree.go
Expand Up @@ -12,10 +12,16 @@ import (
"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) {
newMap := make(map[string]time.Time)
newMap := make(dirMap)
err := loadMap(ctx, rootFolder, rootFolder, newMap)
if err != nil {
log.Error(ctx, "Error loading directory tree", err)
Expand All @@ -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 {
children, lastUpdated, err := loadDir(ctx, currentFolder)
children, dirMapValue, err := loadDir(ctx, currentFolder)
if err != nil {
return err
}
Expand All @@ -36,18 +42,18 @@ func loadMap(ctx context.Context, rootPath string, currentFolder string, dirMap
}

dir := filepath.Clean(currentFolder)
dirMap[dir] = lastUpdated
dirMap[dir] = dirMapValue

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)
if err != nil {
log.Error(ctx, "Error stating dir", "path", dirPath, err)
return
}
lastUpdated = dirInfo.ModTime()
info.modTime = dirInfo.ModTime()

files, err := ioutil.ReadDir(dirPath)
if err != nil {
Expand All @@ -63,9 +69,10 @@ func loadDir(ctx context.Context, dirPath string) (children []string, lastUpdate
if isDir && !isDirIgnored(dirPath, f) && isDirReadable(dirPath, f) {
children = append(children, filepath.Join(dirPath, f.Name()))
} else {
if f.ModTime().After(lastUpdated) {
lastUpdated = f.ModTime()
if f.ModTime().After(info.modTime) {
info.modTime = f.ModTime()
}
info.hasPlaylist = info.hasPlaylist || utils.IsPlaylist(f.Name())
}
}
return
Expand Down
12 changes: 7 additions & 5 deletions scanner/playlist_sync.go
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/model/request"
"github.com/deluan/navidrome/utils"
)

type playlistSync struct {
Expand All @@ -22,15 +23,15 @@ func newPlaylistSync(ds model.DataStore) *playlistSync {
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)
if err != nil {
log.Error(ctx, "Error reading files", "dir", dir, err)
return err
return count
}
for _, f := range files {
match, _ := filepath.Match("*.m3u", strings.ToLower(f.Name()))
if !match {
if !utils.IsPlaylist(f.Name()) {
continue
}
pls, err := s.parsePlaylist(ctx, f.Name(), dir)
Expand All @@ -43,8 +44,9 @@ func (s *playlistSync) processPlaylists(ctx context.Context, dir string) error {
if err != nil {
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) {
Expand Down
51 changes: 34 additions & 17 deletions scanner/tag_scanner_2.go
Expand Up @@ -33,19 +33,22 @@ func NewTagScanner2(rootFolder string, ds model.DataStore) *TagScanner2 {
}

// Scan algorithm overview:
// Load all directories under the music folder, with their ModTime (self or any non-dir children)
// Find changed folders (based on lastModifiedSince) and deletes folders (comparing to the DB)
// For each deleted folder: delete all files from DB whose path starts with the delete folder path
// For each changed folder: Get all files from DB whose path starts with the changed folder, scan each file:
// 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 deleted folders (comparing to the DB)
// 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 (non-recursively), check each file:
// if file in folder is newer, update the one in DB
// if file in folder does not exists in DB, add
// for each file in the DB that is not found in the folder, delete from DB
// 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 it from DB
// Create new albums/artists, update counters:
// collect all albumIDs and artistIDs from previous steps
// 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 {
ctx = s.setAdminUser(ctx)
ctx = s.withAdminUser(ctx)

start := time.Now()
allDirs, err := s.getDirTree(ctx)
Expand Down Expand Up @@ -88,13 +91,23 @@ func (s *TagScanner2) Scan(ctx context.Context, lastModifiedSince time.Time) err
_ = s.artistMap.flush()

// Now that all mediafiles are imported/updated, search for and import playlists
u, _ := request.UserFrom(ctx)
plsCount := 0
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))
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
}
Expand All @@ -114,8 +127,8 @@ func (s *TagScanner2) getChangedDirs(ctx context.Context, dirs dirMap, lastModif
start := time.Now()
log.Trace(ctx, "Checking for changed folders")
var changed []string
for d, t := range dirs {
if t.After(lastModified) {
for d, info := range dirs {
if info.modTime.After(lastModified) {
changed = append(changed, d)
}
}
Expand Down Expand Up @@ -206,7 +219,7 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
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))
var filesToUpdate []string
for filePath, info := range files {
Expand All @@ -219,7 +232,6 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
filesToUpdate = append(filesToUpdate, filePath)
s.cnt.updated++
}
delete(currentTracks, filePath)

// Force a refresh of the album and artist, to cater for cover art files
err = s.albumMap.update(c.AlbumID)
Expand All @@ -230,6 +242,10 @@ func (s *TagScanner2) processChangedDir(ctx context.Context, dir string) error {
if err != nil {
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
Expand All @@ -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
}

Expand Down Expand Up @@ -325,10 +342,10 @@ func (s *TagScanner2) loadTracks(filePaths []string) (model.MediaFiles, error) {
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()
if err != nil {
log.Error(ctx, "Error retrieving playlist owner", err)
log.Warn(ctx, "No admin user found!", err)
u = &model.User{}
}

Expand Down
5 changes: 5 additions & 0 deletions utils/files.go
Expand Up @@ -21,3 +21,8 @@ func IsImageFile(filePath string) bool {
extension := filepath.Ext(filePath)
return strings.HasPrefix(mime.TypeByExtension(extension), "image/")
}

func IsPlaylist(filePath string) bool {
extension := filepath.Ext(filePath)
return strings.ToLower(extension) == ".m3u"
}
10 changes: 10 additions & 0 deletions utils/files_test.go
Expand Up @@ -43,4 +43,14 @@ var _ = Describe("Files", func() {
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())
})
})
})

0 comments on commit feca030

Please sign in to comment.