1
Fork 0

Treat symlinks to directories like directories

This allows symlinking to create additional subalbums.

Closes: #431
This commit is contained in:
Marco Herrn 2021-07-15 16:16:05 +02:00
parent 2e4f83abbf
commit 0129cb7703
4 changed files with 125 additions and 2 deletions

View File

@ -181,7 +181,13 @@ func findMediaForAlbum(album *models.Album, cache *scanner_cache.AlbumScannerCac
for _, item := range dirContent { for _, item := range dirContent {
photoPath := path.Join(album.Path, item.Name()) 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 // Match file against ignore data
if albumIgnore.MatchesPath(item.Name()) { if albumIgnore.MatchesPath(item.Name()) {
log.Printf("File %s ignored\n", item.Name()) log.Printf("File %s ignored\n", item.Name())

View File

@ -11,6 +11,7 @@ import (
"github.com/photoview/photoview/api/graphql/models" "github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/scanner/scanner_cache" "github.com/photoview/photoview/api/scanner/scanner_cache"
"github.com/photoview/photoview/api/scanner/scanner_utils" "github.com/photoview/photoview/api/scanner/scanner_utils"
"github.com/photoview/photoview/api/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
ignore "github.com/sabhiram/go-gitignore" ignore "github.com/sabhiram/go-gitignore"
"gorm.io/gorm" "gorm.io/gorm"
@ -198,7 +199,13 @@ func findAlbumsForUser(db *gorm.DB, user *models.User, album_cache *scanner_cach
continue 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{ scanQueue.PushBack(scanInfo{
path: subalbumPath, path: subalbumPath,
parent: album, parent: album,

View File

@ -5,7 +5,11 @@ import (
"fmt" "fmt"
"log" "log"
"math/big" "math/big"
"os"
"path" "path"
"path/filepath"
"github.com/pkg/errors"
) )
func GenerateToken() string { func GenerateToken() string {
@ -80,3 +84,32 @@ func FaceRecognitionModelsPath() string {
return EnvFaceRecognitionModelsPath.GetValue() 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
}

77
api/utils/utils_test.go Normal file
View File

@ -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")
}
}