diff --git a/internal/config/config_ffmpeg.go b/internal/config/config_ffmpeg.go index b80beb0aa..fde61662c 100644 --- a/internal/config/config_ffmpeg.go +++ b/internal/config/config_ffmpeg.go @@ -4,7 +4,7 @@ import "github.com/photoprism/photoprism/internal/ffmpeg" // FFmpegBin returns the ffmpeg executable file name. func (c *Config) FFmpegBin() string { - return findExecutable(c.options.FFmpegBin, "ffmpeg") + return findBin(c.options.FFmpegBin, "ffmpeg") } // FFmpegEnabled checks if FFmpeg is enabled for video transcoding. diff --git a/internal/config/config_filepaths.go b/internal/config/config_filepaths.go index 1c07b8d36..099ecbd2e 100644 --- a/internal/config/config_filepaths.go +++ b/internal/config/config_filepaths.go @@ -8,40 +8,50 @@ import ( "os/user" "path/filepath" "runtime" + "sync" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/fs" ) // binPaths stores known executable paths. -var binPaths = make(map[string]string, 8) -var tempPath = "" +var ( + binPaths = make(map[string]string, 8) + binMu = sync.RWMutex{} + tempPath = "" +) -// findExecutable searches binaries by their name. -func findExecutable(configBin, defaultBin string) (binPath string) { - // Cached? +// findBin resolves the absolute file path of external binaries. +func findBin(configBin, defaultBin string) (binPath string) { cacheKey := defaultBin + configBin - if cached, ok := binPaths[cacheKey]; ok { + binMu.RLock() + cached, found := binPaths[cacheKey] + binMu.RUnlock() + + // Already found? + if found { return cached } - // Default if config value is empty. + // Default binary name? if configBin == "" { binPath = defaultBin } else { binPath = configBin } - // Search. + // Search for binary. if path, err := exec.LookPath(binPath); err == nil { binPath = path } - // Exists? + // Found? if !fs.FileExists(binPath) { binPath = "" } else { + binMu.Lock() binPaths[cacheKey] = binPath + binMu.Unlock() } return binPath @@ -481,17 +491,17 @@ func (c *Config) TestdataPath() string { // MysqlBin returns the mysql executable file name. func (c *Config) MysqlBin() string { - return findExecutable("", "mysql") + return findBin("", "mysql") } // MysqldumpBin returns the mysqldump executable file name. func (c *Config) MysqldumpBin() string { - return findExecutable("", "mysqldump") + return findBin("", "mysqldump") } // SqliteBin returns the sqlite executable file name. func (c *Config) SqliteBin() string { - return findExecutable("", "sqlite3") + return findBin("", "sqlite3") } // AlbumsPath returns the storage path for album YAML files. diff --git a/internal/config/config_filepaths_test.go b/internal/config/config_filepaths_test.go index 6cb7331f3..5e42e82ca 100644 --- a/internal/config/config_filepaths_test.go +++ b/internal/config/config_filepaths_test.go @@ -10,7 +10,7 @@ import ( ) func TestConfig_FindExecutable(t *testing.T) { - assert.Equal(t, "", findExecutable("yyy", "xxx")) + assert.Equal(t, "", findBin("yyy", "xxx")) } func TestConfig_SidecarPath(t *testing.T) { diff --git a/internal/config/config_metadata.go b/internal/config/config_metadata.go index 526518d29..15cb6caf5 100644 --- a/internal/config/config_metadata.go +++ b/internal/config/config_metadata.go @@ -7,7 +7,7 @@ func (c *Config) ExifBruteForce() bool { // ExifToolBin returns the exiftool executable file name. func (c *Config) ExifToolBin() string { - return findExecutable(c.options.ExifToolBin, "exiftool") + return findBin(c.options.ExifToolBin, "exiftool") } // ExifToolJson checks if creating JSON metadata sidecar files with Exiftool is enabled. diff --git a/internal/config/config_raw.go b/internal/config/config_raw.go index 1e94c6974..ad241bb73 100644 --- a/internal/config/config_raw.go +++ b/internal/config/config_raw.go @@ -18,7 +18,7 @@ func (c *Config) RawPresets() bool { // DarktableBin returns the darktable-cli executable file name. func (c *Config) DarktableBin() string { - return findExecutable(c.options.DarktableBin, "darktable-cli") + return findBin(c.options.DarktableBin, "darktable-cli") } // DarktableBlacklist returns the darktable file extension blacklist. @@ -73,7 +73,7 @@ func (c *Config) DarktableEnabled() bool { // RawtherapeeBin returns the rawtherapee-cli executable file name. func (c *Config) RawtherapeeBin() string { - return findExecutable(c.options.RawtherapeeBin, "rawtherapee-cli") + return findBin(c.options.RawtherapeeBin, "rawtherapee-cli") } // RawtherapeeBlacklist returns the RawTherapee file extension blacklist. @@ -93,7 +93,7 @@ func (c *Config) SipsEnabled() bool { // SipsBin returns the SIPS executable file name. func (c *Config) SipsBin() string { - return findExecutable(c.options.SipsBin, "sips") + return findBin(c.options.SipsBin, "sips") } // SipsBlacklist returns the Sips file extension blacklist. @@ -103,7 +103,7 @@ func (c *Config) SipsBlacklist() string { // HeifConvertBin returns the heif-convert executable file name. func (c *Config) HeifConvertBin() string { - return findExecutable(c.options.HeifConvertBin, "heif-convert") + return findBin(c.options.HeifConvertBin, "heif-convert") } // HeifConvertEnabled checks if heif-convert is enabled for HEIF conversion.