Skip resolving paths for symlinks during index if the path isn't a symlink.

godirwalk can inform us if the file currently processed is a symlink or not (which is gathered without extra stat syscalls).Using this information to skip resolving the symlink to the absolute path (which is necessary to get the stat info of the image file instead of the symlink to it) saves on a lot of syscalls. Resolve causes a Stat syscall for each level in the path, which is very expensive and slows down scanning.
This commit is contained in:
Rene Hollander 2022-09-05 23:05:37 +02:00 committed by Michael Mayer
parent c2e1989dad
commit add115c7e4
2 changed files with 51 additions and 36 deletions

View file

@ -187,7 +187,15 @@ func (ind *Index) Start(o IndexOptions) fs.Done {
return nil
}
mf, err := NewMediaFile(fileName)
var mf *MediaFile
var err error
if isSymlink {
mf, err = NewMediaFile(fileName)
} else {
// If the file found while scanning is not a symlink we can
// skip resolving the fileName, which is resource intensive.
mf, err = NewMediaFileSkipResolve(fileName, fileName)
}
// Check if file exists and is not empty.
if err != nil {

View file

@ -36,39 +36,53 @@ import (
// MediaFile represents a single photo, video or sidecar file.
type MediaFile struct {
fileName string
fileRoot string
statErr error
modTime time.Time
fileSize int64
fileType fs.Type
mimeType string
takenAt time.Time
takenAtSrc string
hash string
checksum string
hasJpeg bool
noColorProfile bool
colorProfile string
width int
height int
metaData meta.Data
metaOnce sync.Once
fileMutex sync.Mutex
location *entity.Cell
imageConfig *image.Config
fileName string
fileNameResolved string
fileRoot string
statErr error
modTime time.Time
fileSize int64
fileType fs.Type
mimeType string
takenAt time.Time
takenAtSrc string
hash string
checksum string
hasJpeg bool
noColorProfile bool
colorProfile string
width int
height int
metaData meta.Data
metaOnce sync.Once
fileMutex sync.Mutex
location *entity.Cell
imageConfig *image.Config
}
// NewMediaFile returns a new media file.
func NewMediaFile(fileName string) (m *MediaFile, err error) {
fileNameResolved, err := fs.Resolve(fileName)
if err != nil {
return nil, err
}
return NewMediaFileSkipResolve(fileName, fileNameResolved)
}
// NewMediaFileSkipResolve returns a new media file without resolving symlinks.
// This is useful because if it's known fileName is fully resolved it's a lot
// faster.
func NewMediaFileSkipResolve(fileName string, fileNameResolved string) (m *MediaFile, err error) {
// Create struct.
m = &MediaFile{
fileName: fileName,
fileRoot: entity.RootUnknown,
fileType: fs.UnknownType,
metaData: meta.New(),
width: -1,
height: -1,
fileName: fileName,
fileNameResolved: fileNameResolved,
fileRoot: entity.RootUnknown,
fileType: fs.UnknownType,
metaData: meta.New(),
width: -1,
height: -1,
}
// Check if file exists and is not empty.
@ -97,14 +111,7 @@ func (m *MediaFile) Stat() (size int64, mod time.Time, err error) {
return m.fileSize, m.modTime, m.statErr
}
fileName := m.FileName()
// Resolve symlinks.
if fileName, err = fs.Resolve(fileName); err != nil {
m.statErr = err
m.modTime = time.Time{}
m.fileSize = -1
} else if s, err := os.Stat(fileName); err != nil {
if s, err := os.Stat(m.fileNameResolved); err != nil {
m.statErr = err
m.modTime = time.Time{}
m.fileSize = -1