Warning: Following symlinks can make folder lists non-deterministic Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
bd68494f3f
commit
f9ef0775db
7 changed files with 91 additions and 19 deletions
|
@ -13,7 +13,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
t.Run("flat", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
_ = conf.CreateDirectories()
|
||||
expected, err := fs.Dirs(conf.OriginalsPath(), false)
|
||||
expected, err := fs.Dirs(conf.OriginalsPath(), false, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -56,7 +56,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
t.Run("recursive", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
_ = conf.CreateDirectories()
|
||||
expected, err := fs.Dirs(conf.OriginalsPath(), true)
|
||||
expected, err := fs.Dirs(conf.OriginalsPath(), true, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -96,7 +96,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
t.Run("flat", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
_ = conf.CreateDirectories()
|
||||
expected, err := fs.Dirs(conf.ImportPath(), false)
|
||||
expected, err := fs.Dirs(conf.ImportPath(), false, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -140,7 +140,7 @@ func TestGetFoldersImport(t *testing.T) {
|
|||
t.Run("recursive", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
_ = conf.CreateDirectories()
|
||||
expected, err := fs.Dirs(conf.ImportPath(), true)
|
||||
expected, err := fs.Dirs(conf.ImportPath(), true, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
// FoldersByPath returns a slice of folders in a given directory incl sub directories in recursive mode.
|
||||
func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders entity.Folders, err error) {
|
||||
dirs, err := fs.Dirs(filepath.Join(rootPath, path), recursive)
|
||||
dirs, err := fs.Dirs(filepath.Join(rootPath, path), recursive, true)
|
||||
|
||||
if err != nil {
|
||||
return folders, err
|
||||
|
|
|
@ -88,25 +88,51 @@ var ImportPaths = []string{
|
|||
"~/Import",
|
||||
}
|
||||
|
||||
func Dirs(root string, recursive bool) (result []string, err error) {
|
||||
// Dirs returns a slice of directories in a path, optional recursively and with symlinks.
|
||||
//
|
||||
// Warning: Following symlinks can make the result non-deterministic and hard to test!
|
||||
func Dirs(root string, recursive bool, followLinks bool) (result []string, err error) {
|
||||
result = []string{}
|
||||
ignore := NewIgnoreList(".ppignore", true, false)
|
||||
mutex := sync.Mutex{}
|
||||
|
||||
err = fastwalk.Walk(root, func(fileName string, info os.FileMode) error {
|
||||
if info.IsDir() {
|
||||
symlinks := make(map[string]bool)
|
||||
symlinksMutex := sync.Mutex{}
|
||||
|
||||
appendResult := func(fileName string) {
|
||||
fileName = strings.Replace(fileName, root, "", 1)
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
result = append(result, fileName)
|
||||
}
|
||||
|
||||
err = fastwalk.Walk(root, func(fileName string, typ os.FileMode) error {
|
||||
if typ.IsDir() || typ == os.ModeSymlink && followLinks {
|
||||
if ignore.Ignore(fileName) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if fileName != root {
|
||||
mutex.Lock()
|
||||
fileName = strings.Replace(fileName, root, "", 1)
|
||||
result = append(result, fileName)
|
||||
mutex.Unlock()
|
||||
|
||||
if !recursive {
|
||||
appendResult(fileName)
|
||||
|
||||
return filepath.SkipDir
|
||||
} else if typ != os.ModeSymlink {
|
||||
appendResult(fileName)
|
||||
|
||||
return nil
|
||||
} else if resolved, err := filepath.EvalSymlinks(fileName); err == nil {
|
||||
symlinksMutex.Lock()
|
||||
defer symlinksMutex.Unlock()
|
||||
|
||||
if _, ok := symlinks[resolved]; ok {
|
||||
return filepath.SkipDir
|
||||
} else {
|
||||
symlinks[resolved] = true
|
||||
appendResult(fileName)
|
||||
}
|
||||
|
||||
return fastwalk.ErrTraverseLink
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,27 +9,70 @@ import (
|
|||
|
||||
func TestDirs(t *testing.T) {
|
||||
t.Run("recursive", func(t *testing.T) {
|
||||
result, err := Dirs("testdata", true)
|
||||
result, err := Dirs("testdata", true, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := []string{"/directory", "/directory/subdirectory", "/linked"}
|
||||
assert.Contains(t, result, "/directory")
|
||||
assert.Contains(t, result, "/directory/subdirectory")
|
||||
assert.Contains(t, result, "/linked")
|
||||
assert.Contains(t, result, "/linked/photoprism")
|
||||
assert.Contains(t, result, "/linked/photoprism/sub")
|
||||
})
|
||||
|
||||
assert.Equal(t, expected, result)
|
||||
t.Run("recursive no-symlinks", func(t *testing.T) {
|
||||
result, err := Dirs("testdata", true, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Contains(t, result, "/directory")
|
||||
assert.Contains(t, result, "/directory/subdirectory")
|
||||
assert.Contains(t, result, "/linked")
|
||||
})
|
||||
|
||||
t.Run("non-recursive", func(t *testing.T) {
|
||||
result, err := Dirs("testdata", false)
|
||||
result, err := Dirs("testdata", false, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := []string{"/directory", "/linked"}
|
||||
assert.Contains(t, result, "/directory")
|
||||
assert.Contains(t, result, "/linked")
|
||||
})
|
||||
|
||||
assert.Equal(t, expected, result)
|
||||
t.Run("non-recursive no-symlinks", func(t *testing.T) {
|
||||
result, err := Dirs("testdata/directory/subdirectory", false, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Contains(t, result, "/bar")
|
||||
})
|
||||
|
||||
t.Run("non-recursive symlinks", func(t *testing.T) {
|
||||
result, err := Dirs("testdata/linked", false, true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Contains(t, result, "/photoprism")
|
||||
assert.Contains(t, result, "/self")
|
||||
})
|
||||
|
||||
t.Run("no-result", func(t *testing.T) {
|
||||
result, err := Dirs("testdata/linked", false, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ func TestIgnoreList_Ignored(t *testing.T) {
|
|||
expectIgnored := []string{
|
||||
"testdata/directory/bar.txt",
|
||||
"testdata/directory/baz.xml",
|
||||
"testdata/directory/subdirectory/bar",
|
||||
"testdata/directory/subdirectory/example.txt",
|
||||
"testdata/directory/subdirectory/foo.txt",
|
||||
}
|
||||
|
|
1
pkg/fs/testdata/directory/subdirectory/bar/helloworld.md
vendored
Normal file
1
pkg/fs/testdata/directory/subdirectory/bar/helloworld.md
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
# Hello World!
|
1
pkg/fs/testdata/linked/photoprism
vendored
Symbolic link
1
pkg/fs/testdata/linked/photoprism
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
../.photoprism
|
Loading…
Reference in a new issue