From add115c7e47880b6bfa47e9dac20052b23bb12b5 Mon Sep 17 00:00:00 2001 From: Rene Hollander Date: Mon, 5 Sep 2022 23:05:37 +0200 Subject: [PATCH] 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. --- internal/photoprism/index.go | 10 ++++- internal/photoprism/mediafile.go | 77 +++++++++++++++++--------------- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/internal/photoprism/index.go b/internal/photoprism/index.go index 1148cdff7..f70a490fd 100644 --- a/internal/photoprism/index.go +++ b/internal/photoprism/index.go @@ -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 { diff --git a/internal/photoprism/mediafile.go b/internal/photoprism/mediafile.go index 6ac3a71e4..55ff807d4 100644 --- a/internal/photoprism/mediafile.go +++ b/internal/photoprism/mediafile.go @@ -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