fix(scanner): use forward slashes in folder watcher path handling on Windows (#5381)

Signed-off-by: junkerderprovinz <jdp@braethoria.com>
This commit is contained in:
junkerderprovinz 2026-08-09 16:55:56 +02:00
commit 602c25723e
3 changed files with 13 additions and 16 deletions

View file

@ -170,7 +170,6 @@ var _ = Describe("Scanner", Ordered, func() {
})
It("should update the album", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
Expect(runScanner(ctx, true)).To(Succeed())
albums, err := ds.Album(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album.name": "Help!"}})
@ -308,7 +307,6 @@ var _ = Describe("Scanner", Ordered, func() {
var beatlesMBID = uuid.NewString()
BeforeEach(func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
By("Having two MP3 albums")
beatles := _t{
"artist": "The Beatles",

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io/fs"
"path"
"path/filepath"
"strings"
"sync"
@ -250,6 +251,10 @@ func (w *watcher) processLibraryEvents(ctx context.Context, lib *model.Library,
log.Error(ctx, "Error getting relative path", "libraryID", lib.ID, "absolutePath", absLibPath, "path", path, err)
continue
}
// fs.FS paths always use "/", but filepath.Rel returns OS separators,
// so normalize before feeding the value to fs lookups and downstream
// notifications (otherwise back-slashes break matching on Windows).
path = filepath.ToSlash(path)
if isIgnoredPath(ctx, fsys, path) {
log.Trace(ctx, "Ignoring change", "libraryID", lib.ID, "path", path)
@ -282,13 +287,13 @@ func (w *watcher) processLibraryEvents(ctx context.Context, lib *model.Library,
// resolveFolderPath takes a path (which may be a file or directory) and returns
// the folder path to scan. If the path is a file, it walks up to find the parent
// directory. Returns empty string if the path should scan the library root.
func resolveFolderPath(fsys fs.FS, path string) string {
func resolveFolderPath(fsys fs.FS, p string) string {
// Handle root paths immediately
if path == "." || path == "" {
if p == "." || p == "" {
return ""
}
folderPath := path
folderPath := p
for {
info, err := fs.Stat(fsys, folderPath)
if err == nil && info.IsDir() {
@ -299,13 +304,13 @@ func resolveFolderPath(fsys fs.FS, path string) string {
// Reached root, scan entire library
return ""
}
// Walk up the tree
dir, _ := filepath.Split(folderPath)
if dir == "" || dir == "." {
// Walk up the tree. fs.FS paths always use "/", so use path.Dir
// (filepath would emit "\" on Windows and never match the slash keys).
parent := path.Dir(folderPath)
if parent == "." || parent == folderPath {
return ""
}
// Remove trailing slash
folderPath = filepath.Clean(dir)
folderPath = parent
}
}

View file

@ -381,7 +381,6 @@ var _ = Describe("Watcher", func() {
})
It("should NOT send notification when nested ignored folder is deleted", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
startEventProcessing()
// Simulate deletion of music/rock/artist/temp (matches **/temp)
@ -395,7 +394,6 @@ var _ = Describe("Watcher", func() {
})
It("should send notification for non-ignored nested folder", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
startEventProcessing()
// Simulate change in music/rock/artist (doesn't match any pattern)
@ -420,7 +418,6 @@ var _ = Describe("Watcher", func() {
})
It("should NOT send notification for file changes in ignored folders", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
startEventProcessing()
// Simulate file change in rock/_TEMP/file.mp3
@ -503,13 +500,11 @@ var _ = Describe("resolveFolderPath", func() {
})
It("walks up to parent directory when given a file path", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
result := resolveFolderPath(mockFS, "artist1/album1/track1.mp3")
Expect(result).To(Equal("artist1/album1"))
})
It("walks up multiple levels if needed", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
result := resolveFolderPath(mockFS, "artist1/album1/nonexistent/file.mp3")
Expect(result).To(Equal("artist1/album1"))
})
@ -530,7 +525,6 @@ var _ = Describe("resolveFolderPath", func() {
})
It("handles nested file paths correctly", func() {
tests.SkipOnWindows("path separator bug (#TBD-path-sep-scanner)")
result := resolveFolderPath(mockFS, "artist1/album2/song.flac")
Expect(result).To(Equal("artist1/album2"))
})