diff --git a/api/scanner/scanner_album.go b/api/scanner/scanner_album.go index 56b8fa81..e183ae8d 100644 --- a/api/scanner/scanner_album.go +++ b/api/scanner/scanner_album.go @@ -181,7 +181,13 @@ func findMediaForAlbum(album *models.Album, cache *scanner_cache.AlbumScannerCac for _, item := range dirContent { photoPath := path.Join(album.Path, item.Name()) - if !item.IsDir() && cache.IsPathMedia(photoPath) { + isDirSymlink, err := utils.IsDirSymlink(photoPath) + if err != nil { + log.Printf("Cannot detect whether %s is symlink to a directory. Pretending it is not", photoPath) + isDirSymlink = false + } + + if !item.IsDir() && !isDirSymlink && cache.IsPathMedia(photoPath) { // Match file against ignore data if albumIgnore.MatchesPath(item.Name()) { log.Printf("File %s ignored\n", item.Name()) diff --git a/api/scanner/scanner_user.go b/api/scanner/scanner_user.go index 2d605364..c0248b9d 100644 --- a/api/scanner/scanner_user.go +++ b/api/scanner/scanner_user.go @@ -11,6 +11,7 @@ import ( "github.com/photoview/photoview/api/graphql/models" "github.com/photoview/photoview/api/scanner/scanner_cache" "github.com/photoview/photoview/api/scanner/scanner_utils" + "github.com/photoview/photoview/api/utils" "github.com/pkg/errors" ignore "github.com/sabhiram/go-gitignore" "gorm.io/gorm" @@ -198,7 +199,13 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach continue } - if item.IsDir() && directoryContainsPhotos(subalbumPath, album_cache, albumIgnore) { + isDirSymlink, err := utils.IsDirSymlink(subalbumPath) + if err != nil { + scanErrors = append(scanErrors, errors.Wrapf(err, "could not check for symlink target of %s", subalbumPath)) + continue + } + + if (item.IsDir() || isDirSymlink) && directoryContainsPhotos(subalbumPath, album_cache, albumIgnore) { scanQueue.PushBack(scanInfo{ path: subalbumPath, parent: album, diff --git a/api/utils/utils.go b/api/utils/utils.go index 5ee1bfd9..a69fc0c0 100644 --- a/api/utils/utils.go +++ b/api/utils/utils.go @@ -5,7 +5,11 @@ import ( "fmt" "log" "math/big" + "os" "path" + "path/filepath" + + "github.com/pkg/errors" ) func GenerateToken() string { @@ -80,3 +84,32 @@ func FaceRecognitionModelsPath() string { return EnvFaceRecognitionModelsPath.GetValue() } + +// IsDirSymlink checks that the given path is a symlink and resolves to a +// directory. +func IsDirSymlink(path string) (bool, error) { + isDirSymlink := false + + fileInfo, err := os.Lstat(path) + if err != nil { + return false, errors.Wrapf(err, "could not stat %s", path) + } + + //Resolve symlinks + if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink { + resolvedPath, err := filepath.EvalSymlinks(path) + if err != nil { + return false, errors.Wrapf(err, "Cannot resolve linktarget of %s, ignoring it", path) + } + + resolvedFile, err := os.Stat(resolvedPath) + if err != nil { + return false, errors.Wrapf(err, "Cannot get fileinfo of linktarget %s of symlink %s, ignoring it", resolvedPath, path) + } + isDirSymlink = resolvedFile.IsDir() + + return isDirSymlink, nil + } + + return false, nil +} diff --git a/api/utils/utils_test.go b/api/utils/utils_test.go new file mode 100644 index 00000000..ed4ebc9e --- /dev/null +++ b/api/utils/utils_test.go @@ -0,0 +1,77 @@ +package utils_test + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/photoview/photoview/api/test_utils" + "github.com/photoview/photoview/api/utils" +) + +func TestMain(m *testing.M) { + os.Exit(test_utils.IntegrationTestRun(m)) +} + +func TestIsDirSymlink(t *testing.T) { + test_utils.FilesystemTest(t) + + // Prepare a temporary directory for testing purposes + dir, err := ioutil.TempDir("", "testing") + if err != nil { + t.Fatalf("unable to create temp directory for testing") + } + defer os.RemoveAll(dir) + + // Create regular file + _, err = os.Create(path.Join(dir, "regular_file")) + if err != nil { + t.Fatalf("unable to create regular file for testing") + } + + // Create directory + err = os.Mkdir(path.Join(dir, "directory"), 0755) + if err != nil { + t.Fatalf("unable to create directory for testing") + } + + // Create symlink to regular file + err = os.Symlink(path.Join(dir, "regular_file"), path.Join(dir, "file_link")) + if err != nil { + t.Fatalf("unable to create file link for testing") + } + + // Create symlink to directory + err = os.Symlink(path.Join(dir, "directory"), path.Join(dir, "dir_link")) + if err != nil { + t.Fatalf("unable to create dir link for testing") + } + + // Execute the actual tests + + isDirLink, _ := utils.IsDirSymlink(path.Join(dir, "regular_file")) + if isDirLink { + t.Error("Failed detection of regular file") + } + + isDirLink, _ = utils.IsDirSymlink(path.Join(dir, "directory")) + if isDirLink { + t.Error("Failed detection of directory") + } + + isDirLink, _ = utils.IsDirSymlink(path.Join(dir, "file_link")) + if isDirLink { + t.Error("Failed detection of link to regular file") + } + + isDirLink, _ = utils.IsDirSymlink(path.Join(dir, "dir_link")) + if !isDirLink { + t.Error("Failed detection of link to directory") + } + + isDirLink, err = utils.IsDirSymlink(path.Join(dir, "non_existant")) + if err == nil { + t.Error("Missing error for non-existant file") + } +}