Config: Show error if originals and storage path seem identical #1642

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2024-01-21 14:22:16 +01:00
parent 7917482580
commit 86dc89c4b9
37 changed files with 464 additions and 235 deletions

View file

@ -68,7 +68,7 @@ func SaveConfigOptions(router *gin.RouterGroup) {
return
}
if err := yaml.Unmarshal(yamlData, v); err != nil {
if err = yaml.Unmarshal(yamlData, v); err != nil {
log.Warnf("config: failed parsing values in %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return
@ -90,21 +90,21 @@ func SaveConfigOptions(router *gin.RouterGroup) {
}
// Make sure directory exists.
if err := os.MkdirAll(filepath.Dir(fileName), fs.ModeDir); err != nil {
if err = fs.MkdirAll(filepath.Dir(fileName)); err != nil {
log.Errorf("config: failed creating config path %s (%s)", filepath.Dir(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return
}
// Write YAML data to file.
if err := os.WriteFile(fileName, yamlData, fs.ModeFile); err != nil {
if err = fs.WriteFile(fileName, yamlData); err != nil {
log.Errorf("config: failed writing values to %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return
}
// Reload options.
if err := conf.Options().Load(fileName); err != nil {
if err = conf.Options().Load(fileName); err != nil {
log.Warnf("config: failed loading values from %s (%s)", clean.Log(fileName), err)
c.AbortWithStatusJSON(http.StatusInternalServerError, err)
return
@ -113,7 +113,7 @@ func SaveConfigOptions(router *gin.RouterGroup) {
// Set restart flag.
mutex.Restart.Store(true)
// Propagate changes.
// Update package defaults.
conf.Propagate()
// Flush session cache and update client config.

View file

@ -43,7 +43,7 @@ func SharePreview(router *gin.RouterGroup) {
thumbPath := path.Join(conf.ThumbCachePath(), "share")
if err := os.MkdirAll(thumbPath, fs.ModeDir); err != nil {
if err := fs.MkdirAll(thumbPath); err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return

View file

@ -98,7 +98,7 @@ func GetVideo(router *gin.RouterGroup) {
AddVideoCacheHeader(c, conf.CdnVideo())
c.DataFromReader(http.StatusOK, info.VideoSize(), info.VideoContentType(), reader, nil)
return
} else if cacheName, cacheErr := fs.CacheFile(filepath.Join(conf.MediaFileCachePath(f.FileHash), f.FileHash+info.VideoFileExt()), reader); cacheErr != nil {
} else if cacheName, cacheErr := fs.CacheFileFromReader(filepath.Join(conf.MediaFileCachePath(f.FileHash), f.FileHash+info.VideoFileExt()), reader); cacheErr != nil {
log.Errorf("video: failed to cache %s embedded in %s (%s)", strings.ToUpper(videoFileType), clean.Log(f.FileName), cacheErr)
AddContentTypeHeader(c, video.ContentTypeAVC)
c.File(get.Config().StaticFile("video/404.mp4"))

View file

@ -110,7 +110,7 @@ func backupAction(ctx *cli.Context) error {
// Create backup directory if not exists.
if dir := filepath.Dir(indexFileName); dir != "." {
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
if err = fs.MkdirAll(dir); err != nil {
return err
}
}

View file

@ -105,8 +105,10 @@ func startAction(ctx *cli.Context) error {
}
if child != nil {
if !fs.Overwrite(conf.PIDFilename(), []byte(strconv.Itoa(child.Pid))) {
if writeErr := fs.WriteString(conf.PIDFilename(), strconv.Itoa(child.Pid)); writeErr != nil {
log.Error(writeErr)
log.Fatalf("failed writing process id to %s", clean.Log(conf.PIDFilename()))
return nil
}
log.Infof("daemon started with process id %v\n", child.Pid)

View file

@ -138,7 +138,7 @@ func NewConfig(ctx *cli.Context) *Config {
start: start,
}
// Overwrite values with options.yml from config path.
// WriteFile values with options.yml from config path.
if optionsYaml := c.OptionsYaml(); fs.FileExists(optionsYaml) {
if err := c.options.Load(optionsYaml); err != nil {
log.Warnf("config: failed loading values from %s (%s)", clean.Log(optionsYaml), err)
@ -243,17 +243,22 @@ func (c *Config) Propagate() {
func (c *Config) Init() error {
start := time.Now()
// Create configured directory paths.
// Fail if the originals and storage path are identical.
if c.OriginalsPath() == c.StoragePath() {
return fmt.Errorf("config: originals and storage folder must be different directories")
}
// Make sure that the configured storage directories exist and are properly configured.
if err := c.CreateDirectories(); err != nil {
return fmt.Errorf("config: %s", err)
}
// Init storage directories with a random serial.
// Initialize the storage path with a random serial.
if err := c.InitSerial(); err != nil {
return fmt.Errorf("config: %s", err)
}
// Detect case-insensitive file system.
// Detect whether files are stored on a case-insensitive file system.
if insensitive, err := c.CaseInsensitive(); err != nil {
return err
} else if insensitive {
@ -261,12 +266,12 @@ func (c *Config) Init() error {
fs.IgnoreCase()
}
// Detect CPU.
// Detect the CPU type and available memory.
if cpuName := cpuid.CPU.BrandName; cpuName != "" {
log.Debugf("config: running on %s, %s memory detected", clean.Log(cpuid.CPU.BrandName), humanize.Bytes(TotalMem))
}
// Exit if less than 128 MB RAM was detected.
// Fail if less than 128 MB of memory were detected.
if TotalMem < 128*Megabyte {
return fmt.Errorf("config: %s of memory detected, %d GB required", humanize.Bytes(TotalMem), MinMem/Gigabyte)
}
@ -277,18 +282,17 @@ func (c *Config) Init() error {
log.Warnf("config: tensorflow as well as indexing and conversion of RAW images have been disabled automatically")
}
// Show swap info.
// Show swap space disclaimer.
if TotalMem < RecommendedMem {
log.Infof("config: make sure your server has enough swap configured to prevent restarts when there are memory usage spikes")
}
// Show wakeup interval warning if face recognition is enabled
// and the worker runs less than once per hour.
// Show wake-up interval warning if face recognition is activated and the worker runs less than once an hour.
if !c.DisableFaces() && !c.Unsafe() && c.WakeupInterval() > time.Hour {
log.Warnf("config: the wakeup interval is %s, but must be 1h or less for face recognition to work", c.WakeupInterval().String())
}
// Set HTTPS proxy for outgoing connections.
// Configure HTTPS proxy for outgoing connections.
if httpsProxy := c.HttpsProxy(); httpsProxy != "" {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: c.HttpsProxyInsecure(),
@ -297,13 +301,13 @@ func (c *Config) Init() error {
_ = os.Setenv("HTTPS_PROXY", httpsProxy)
}
// Set HTTP user agent.
// Configure HTTP user agent.
places.UserAgent = c.UserAgent()
c.initSettings()
c.initHub()
// Propagate configuration.
// Update package defaults.
c.Propagate()
// Connect to database.

View file

@ -60,122 +60,153 @@ func findBin(configBin, defaultBin string) (binPath string) {
// CreateDirectories creates directories for storing photos, metadata and cache files.
func (c *Config) CreateDirectories() error {
// Make sure the configured assets path exits.
if c.AssetsPath() == "" {
return notFoundError("assets")
} else if err := os.MkdirAll(c.AssetsPath(), fs.ModeDir); err != nil {
return createError(c.AssetsPath(), err)
// Error if the originals and storage path are identical.
if c.OriginalsPath() == c.StoragePath() {
return fmt.Errorf("originals and storage folder must be different directories")
}
// Make sure the configured storage folder exists and create a ".ppstorage" file in it.
// Make sure that the configured storage path exists and initialize it with
// ".ppstorage" and ".ppignore files" so that it is not accidentally indexed.
if dir := c.StoragePath(); dir == "" {
return notFoundError("storage")
} else if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
} else if err = fs.Touch(filepath.Join(dir, fs.PPStorageFilename)); err != nil {
} else if _, err = fs.WriteUnixTime(filepath.Join(dir, fs.PPStorageFilename)); err != nil {
return fmt.Errorf("%s file in %s could not be created", fs.PPStorageFilename, clean.Log(dir))
} else if err = fs.WriteString(filepath.Join(dir, fs.PPIgnoreFilename), fs.PPIgnoreAll); err != nil {
return fmt.Errorf("%s file in %s could not be created", fs.PPIgnoreFilename, clean.Log(dir))
}
if c.UsersPath() == "" {
return notFoundError("users")
} else if err := os.MkdirAll(c.UsersStoragePath(), fs.ModeDir); err != nil {
return createError(c.UsersStoragePath(), err)
}
if c.CmdCachePath() == "" {
return notFoundError("cmd cache")
} else if err := os.MkdirAll(c.CmdCachePath(), fs.ModeDir); err != nil {
return createError(c.CmdCachePath(), err)
}
if c.BackupPath() == "" {
return notFoundError("backup")
} else if err := os.MkdirAll(c.BackupPath(), fs.ModeDir); err != nil {
return createError(c.BackupPath(), err)
}
if c.OriginalsPath() == "" {
// Create originals path if it does not exist yet and return an error if it could be a storage folder.
if dir := c.OriginalsPath(); dir == "" {
return notFoundError("originals")
} else if err := os.MkdirAll(c.OriginalsPath(), fs.ModeDir); err != nil {
return createError(c.OriginalsPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
} else if fs.FileExists(filepath.Join(dir, fs.PPStorageFilename)) {
return fmt.Errorf("originals path %s contains a %s file", clean.Log(dir), fs.PPStorageFilename)
}
if c.ImportPath() == "" {
// Create import path if it does not exist yet and return an error if it could be a storage folder.
if dir := c.ImportPath(); dir == "" {
return notFoundError("import")
} else if err := os.MkdirAll(c.ImportPath(), fs.ModeDir); err != nil {
return createError(c.ImportPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
} else if fs.FileExists(filepath.Join(dir, fs.PPStorageFilename)) {
return fmt.Errorf("import path %s contains a %s file", clean.Log(dir), fs.PPStorageFilename)
}
if filepath.IsAbs(c.SidecarPath()) {
if err := os.MkdirAll(c.SidecarPath(), fs.ModeDir); err != nil {
return createError(c.SidecarPath(), err)
// Create storage path if it doesn't exist yet.
if dir := c.UsersStoragePath(); dir == "" {
return notFoundError("users storage")
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
// Create assets path if it doesn't exist yet.
if dir := c.AssetsPath(); dir == "" {
return notFoundError("assets")
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
// Create command cache storage path if it doesn't exist yet.
if dir := c.CmdCachePath(); dir == "" {
return notFoundError("cmd cache")
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
// Create backup storage path if it doesn't exist yet.
if dir := c.BackupPath(); dir == "" {
return notFoundError("backup")
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
// Create sidecar storage path if it doesn't exist yet.
if dir := c.SidecarPath(); filepath.IsAbs(dir) {
if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
}
if c.CachePath() == "" {
// Create and initialize cache storage directory.
if dir := c.CachePath(); dir == "" {
return notFoundError("cache")
} else if err := os.MkdirAll(c.CachePath(), fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(dir); err != nil {
return createError(c.CachePath(), err)
} else if err = fs.WriteString(filepath.Join(dir, fs.PPIgnoreFilename), fs.PPIgnoreAll); err != nil {
return fmt.Errorf("%s file in %s could not be created", fs.PPIgnoreFilename, clean.Log(dir))
}
if c.MediaCachePath() == "" {
// Create media cache storage path if it doesn't exist yet.
if dir := c.MediaCachePath(); dir == "" {
return notFoundError("media")
} else if err := os.MkdirAll(c.MediaCachePath(), fs.ModeDir); err != nil {
return createError(c.MediaCachePath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.ThumbCachePath() == "" {
// Create thumbnail cache storage path if it doesn't exist yet.
if dir := c.ThumbCachePath(); dir == "" {
return notFoundError("thumbs")
} else if err := os.MkdirAll(c.ThumbCachePath(), fs.ModeDir); err != nil {
return createError(c.ThumbCachePath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.ConfigPath() == "" {
// Create and initialize config directory.
if dir := c.ConfigPath(); dir == "" {
return notFoundError("config")
} else if err := os.MkdirAll(c.ConfigPath(), fs.ModeDir); err != nil {
return createError(c.ConfigPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
} else if err = fs.WriteString(filepath.Join(dir, fs.PPIgnoreFilename), fs.PPIgnoreAll); err != nil {
return fmt.Errorf("%s file in %s could not be created", fs.PPIgnoreFilename, clean.Log(dir))
}
if c.CertificatesPath() == "" {
// Create certificates config path if it doesn't exist yet.
if dir := c.CertificatesPath(); dir == "" {
return notFoundError("certificates")
} else if err := os.MkdirAll(c.CertificatesPath(), fs.ModeDir); err != nil {
return createError(c.CertificatesPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.TempPath() == "" {
// Create temporary file path if it doesn't exist yet.
if dir := c.TempPath(); dir == "" {
return notFoundError("temp")
} else if err := os.MkdirAll(c.TempPath(), fs.ModeDir); err != nil {
return createError(c.TempPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.AlbumsPath() == "" {
// Create albums backup path if it doesn't exist yet.
if dir := c.AlbumsPath(); dir == "" {
return notFoundError("albums")
} else if err := os.MkdirAll(c.AlbumsPath(), fs.ModeDir); err != nil {
return createError(c.AlbumsPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.TensorFlowModelPath() == "" {
// Create TensorFlow model path if it doesn't exist yet.
if dir := c.TensorFlowModelPath(); dir == "" {
return notFoundError("tensorflow model")
} else if err := os.MkdirAll(c.TensorFlowModelPath(), fs.ModeDir); err != nil {
return createError(c.TensorFlowModelPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.BuildPath() == "" {
// Create frontend build path if it doesn't exist yet.
if dir := c.BuildPath(); dir == "" {
return notFoundError("build")
} else if err := os.MkdirAll(c.BuildPath(), fs.ModeDir); err != nil {
return createError(c.BuildPath(), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if filepath.Dir(c.PIDFilename()) == "" {
if dir := filepath.Dir(c.PIDFilename()); dir == "" {
return notFoundError("pid file")
} else if err := os.MkdirAll(filepath.Dir(c.PIDFilename()), fs.ModeDir); err != nil {
return createError(filepath.Dir(c.PIDFilename()), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if filepath.Dir(c.LogFilename()) == "" {
if dir := filepath.Dir(c.LogFilename()); dir == "" {
return notFoundError("log file")
} else if err := os.MkdirAll(filepath.Dir(c.LogFilename()), fs.ModeDir); err != nil {
return createError(filepath.Dir(c.LogFilename()), err)
} else if err := fs.MkdirAll(dir); err != nil {
return createError(dir, err)
}
if c.DarktableEnabled() {
@ -341,7 +372,7 @@ func (c *Config) UserStoragePath(userUid string) string {
dir := filepath.Join(c.UsersStoragePath(), userUid)
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
if err := fs.MkdirAll(dir); err != nil {
return ""
}
@ -356,7 +387,7 @@ func (c *Config) UserUploadPath(userUid, token string) (string, error) {
dir := filepath.Join(c.UserStoragePath(userUid), "upload", clean.Token(token))
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
if err := fs.MkdirAll(dir); err != nil {
return "", err
}
@ -395,7 +426,7 @@ func (c *Config) tempPath() string {
if c.options.TempPath != "" {
if dir := fs.Abs(c.options.TempPath); dir == "" {
// Ignore.
} else if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(dir); err != nil {
// Ignore.
} else if fs.PathWritable(dir) {
return dir
@ -405,7 +436,7 @@ func (c *Config) tempPath() string {
// Find alternative temp path based on storage serial checksum.
if dir := filepath.Join(osTempDir, "photoprism_"+c.SerialChecksum()); dir == "" {
// Ignore.
} else if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(dir); err != nil {
// Ignore.
} else if fs.PathWritable(dir) {
return dir
@ -414,7 +445,7 @@ func (c *Config) tempPath() string {
// Find alternative temp path based on built-in TempDir() function.
if dir, err := ioutil.TempDir(osTempDir, "photoprism_"); err != nil || dir == "" {
// Ignore.
} else if err = os.MkdirAll(dir, fs.ModeDir); err != nil {
} else if err = fs.MkdirAll(dir); err != nil {
// Ignore.
} else if fs.PathWritable(dir) {
return dir
@ -467,7 +498,7 @@ func (c *Config) MediaFileCachePath(hash string) string {
}
// Ensure the subdirectory exists, or log an error otherwise.
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
if err := fs.MkdirAll(dir); err != nil {
log.Errorf("cache: failed to create subdirectory for media file")
}

View file

@ -185,7 +185,7 @@ func TestConfig_OriginalsAlbumsPath(t *testing.T) {
}
func TestConfig_CreateDirectories(t *testing.T) {
t.Run("no error", func(t *testing.T) {
t.Run("Success", func(t *testing.T) {
testConfigMutex.Lock()
defer testConfigMutex.Unlock()
@ -194,9 +194,21 @@ func TestConfig_CreateDirectories(t *testing.T) {
token: rnd.Base36(8),
}
if err := c.CreateDirectories(); err != nil {
t.Fatal(err)
assert.NoError(t, c.CreateDirectories())
})
t.Run("IdenticalPaths", func(t *testing.T) {
testConfigMutex.Lock()
defer testConfigMutex.Unlock()
c := &Config{
options: NewTestOptions("config"),
token: rnd.Base36(8),
}
c.options.StoragePath = "./testdata"
c.options.OriginalsPath = "./testdata"
assert.Error(t, c.CreateDirectories())
})
}

View file

@ -1,8 +1,6 @@
package config
import (
"os"
"github.com/photoprism/photoprism/pkg/fs"
)
@ -42,7 +40,7 @@ func (c *Config) CreateDarktableCachePath() (string, error) {
if cachePath == "" {
return "", nil
} else if err := os.MkdirAll(cachePath, fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(cachePath); err != nil {
return cachePath, err
} else {
c.options.DarktableCachePath = cachePath
@ -57,7 +55,7 @@ func (c *Config) CreateDarktableConfigPath() (string, error) {
if configPath == "" {
return "", nil
} else if err := os.MkdirAll(configPath, fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(configPath); err != nil {
return configPath, err
} else {
c.options.DarktableConfigPath = configPath

View file

@ -47,7 +47,7 @@ func (ext Extensions) Init(c *Config) {
start := time.Now()
if err := e.init(c); err != nil {
log.Warnf("config: %s while initializing the %s extension [%s]", err, clean.Log(e.name), time.Since(start))
log.Warnf("config: %s when loading %s extension", err, clean.Log(e.name))
} else {
log.Tracef("config: %s extension loaded [%s]", clean.Log(e.name), time.Since(start))
}

View file

@ -1,8 +1,6 @@
package config
import (
"os"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/customize"
"github.com/photoprism/photoprism/internal/entity"
@ -25,7 +23,7 @@ func (c *Config) initSettings() {
defaultsFile := c.SettingsYamlDefaults(settingsFile)
// Make sure that the config path exists.
if err := os.MkdirAll(configPath, fs.ModeDir); err != nil {
if err := fs.MkdirAll(configPath); err != nil {
log.Errorf("settings: %s", createError(configPath, err))
}

View file

@ -165,7 +165,7 @@ func NewTestConfig(pkg string) *Config {
s := customize.NewSettings(c.DefaultTheme(), c.DefaultLocale())
if err := os.MkdirAll(c.ConfigPath(), fs.ModeDir); err != nil {
if err := fs.MkdirAll(c.ConfigPath()); err != nil {
log.Fatalf("config: %s", err.Error())
}

View file

@ -35,7 +35,7 @@ func (m *Album) SaveAsYaml(fileName string) error {
}
// Make sure directory exists.
if err := os.MkdirAll(filepath.Dir(fileName), fs.ModeDir); err != nil {
if err = fs.MkdirAll(filepath.Dir(fileName)); err != nil {
return err
}
@ -43,7 +43,7 @@ func (m *Album) SaveAsYaml(fileName string) error {
defer albumYamlMutex.Unlock()
// Write YAML data to file.
if err := os.WriteFile(fileName, data, fs.ModeFile); err != nil {
if err = fs.WriteFile(fileName, data); err != nil {
return err
}

View file

@ -39,7 +39,7 @@ func (m *Photo) SaveAsYaml(fileName string) error {
}
// Make sure directory exists.
if err := os.MkdirAll(filepath.Dir(fileName), fs.ModeDir); err != nil {
if err = fs.MkdirAll(filepath.Dir(fileName)); err != nil {
return err
}
@ -47,7 +47,7 @@ func (m *Photo) SaveAsYaml(fileName string) error {
defer photoYamlMutex.Unlock()
// Write YAML data to file.
if err := os.WriteFile(fileName, data, fs.ModeFile); err != nil {
if err = fs.WriteFile(fileName, data); err != nil {
return err
}

View file

@ -206,7 +206,7 @@ func (c *Config) ReSync(token string) (err error) {
c.session = nil
// Make sure storage folder exists.
if err = os.MkdirAll(filepath.Dir(c.FileName), fs.ModeDir); err != nil {
if err = fs.MkdirAll(filepath.Dir(c.FileName)); err != nil {
return err
}
@ -330,11 +330,11 @@ func (c *Config) Save() error {
c.Propagate()
if err = os.MkdirAll(filepath.Dir(c.FileName), fs.ModeDir); err != nil {
if err = fs.MkdirAll(filepath.Dir(c.FileName)); err != nil {
return err
}
if err = os.WriteFile(c.FileName, data, fs.ModeFile); err != nil {
if err = fs.WriteFile(c.FileName, data); err != nil {
return err
}

View file

@ -1,7 +1,6 @@
package photoprism
import (
"os"
"path/filepath"
"github.com/photoprism/photoprism/internal/entity"
@ -63,7 +62,7 @@ func ImportWorker(jobs <-chan ImportJob) {
if fs.PathExists(destDir) {
// Do nothing.
} else if err := os.MkdirAll(destDir, fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(destDir); err != nil {
log.Errorf("import: failed creating folder for %s (%s)", clean.Log(f.BaseName()), err.Error())
} else {
destDirRel := fs.RelName(destDir, imp.originalsPath())

View file

@ -549,7 +549,7 @@ func (m *MediaFile) HasSameName(f *MediaFile) bool {
// Move file to a new destination with the filename provided in parameter.
func (m *MediaFile) Move(dest string) error {
if err := os.MkdirAll(filepath.Dir(dest), fs.ModeDir); err != nil {
if err := fs.MkdirAll(filepath.Dir(dest)); err != nil {
return err
}
@ -576,7 +576,7 @@ func (m *MediaFile) Move(dest string) error {
// Copy a MediaFile to another file by destinationFilename.
func (m *MediaFile) Copy(dest string) error {
if err := os.MkdirAll(filepath.Dir(dest), fs.ModeDir); err != nil {
if err := fs.MkdirAll(filepath.Dir(dest)); err != nil {
return err
}

View file

@ -798,7 +798,7 @@ func TestMediaFile_Move(t *testing.T) {
origName := tmpPath + "/original.jpg"
destName := tmpPath + "/destination.jpg"
if err := os.MkdirAll(tmpPath, fs.ModeDir); err != nil {
if err := fs.MkdirAll(tmpPath); err != nil {
t.Fatal(err)
}
@ -834,7 +834,7 @@ func TestMediaFile_Copy(t *testing.T) {
tmpPath := conf.CachePath() + "/_tmp/TestMediaFile_Copy"
if err := os.MkdirAll(tmpPath, fs.ModeDir); err != nil {
if err := fs.MkdirAll(tmpPath); err != nil {
t.Fatal(err)
}
@ -2303,14 +2303,14 @@ func TestMediaFile_RenameSidecarFiles(t *testing.T) {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(conf.SidecarPath(), "foo"), fs.ModeDir); err != nil {
if err = fs.MkdirAll(filepath.Join(conf.SidecarPath(), "foo")); err != nil {
t.Fatal(err)
}
srcName := filepath.Join(conf.SidecarPath(), "foo/bar.jpg.json")
dstName := filepath.Join(conf.SidecarPath(), "2020/12/foobar.jpg.json")
if err := os.WriteFile(srcName, []byte("{}"), 0666); err != nil {
if err = os.WriteFile(srcName, []byte("{}"), 0666); err != nil {
t.Fatal(err)
}

View file

@ -267,7 +267,7 @@ func (c *Client) Download(src, dest string, force bool) (err error) {
if err != nil {
// Create local storage path.
if err := os.MkdirAll(dir, fs.ModeDir); err != nil {
if err = fs.MkdirAll(dir); err != nil {
return fmt.Errorf("webdav: cannot create folder %s (%s)", clean.Log(dir), err)
}
} else if !dirInfo.IsDir() {

View file

@ -49,7 +49,7 @@ func (ext Extensions) Init(router *gin.Engine, conf *config.Config) {
start := time.Now()
if err := e.init(router, conf); err != nil {
log.Warnf("server: %s while initializing the %s extension [%s]", err, clean.Log(e.name), time.Since(start))
log.Warnf("server: %s when loading %s extension", err, clean.Log(e.name))
} else {
log.Tracef("server: %s extension loaded [%s]", clean.Log(e.name), time.Since(start))
}

View file

@ -165,13 +165,13 @@ func WebDAVSetFavoriteFlag(fileName string) {
}
// Make sure directory exists.
if err := os.MkdirAll(filepath.Dir(yamlName), fs.ModeDir); err != nil {
if err := fs.MkdirAll(filepath.Dir(yamlName)); err != nil {
log.Errorf("webdav: %s", err.Error())
return
}
// Write YAML data to file.
if err := os.WriteFile(yamlName, []byte("Favorite: true\n"), fs.ModeFile); err != nil {
if err := fs.WriteFile(yamlName, []byte("Favorite: true\n")); err != nil {
log.Errorf("webdav: %s", err.Error())
return
}

View file

@ -2,7 +2,6 @@ package server
import (
"net/http"
"os"
"path/filepath"
"strings"
"sync"
@ -122,7 +121,7 @@ func WebDAVAuth(conf *config.Config) gin.HandlerFunc {
event.AuditWarn([]string{clientIp, "client %s", "session %s", "access webdav as %s", message}, clean.Log(sess.ClientInfo()), sess.RefID, clean.LogQuote(user.Username()))
WebDAVAbortUnauthorized(c)
return
} else if err := os.MkdirAll(filepath.Join(conf.OriginalsPath(), user.GetUploadPath()), fs.ModeDir); err != nil {
} else if err := fs.MkdirAll(filepath.Join(conf.OriginalsPath(), user.GetUploadPath())); err != nil {
// Log warning if upload path could not be created.
message := "failed to create user upload path"
event.AuditWarn([]string{clientIp, "client %s", "session %s", "access webdav as %s", message}, clean.Log(sess.ClientInfo()), sess.RefID, clean.LogQuote(user.Username()))
@ -177,7 +176,7 @@ func WebDAVAuth(conf *config.Config) gin.HandlerFunc {
message := "webdav access is disabled"
event.AuditWarn([]string{clientIp, "webdav login as %s", message}, clean.LogQuote(username))
event.LoginError(clientIp, "webdav", username, api.UserAgent(c), message)
} else if err = os.MkdirAll(filepath.Join(conf.OriginalsPath(), user.GetUploadPath()), fs.ModeDir); err != nil {
} else if err = fs.MkdirAll(filepath.Join(conf.OriginalsPath(), user.GetUploadPath())); err != nil {
// Abort if upload path could not be created.
message := "failed to create user upload path"
event.AuditWarn([]string{clientIp, "webdav login as %s", message}, clean.LogQuote(username))

View file

@ -5,7 +5,6 @@ import (
"fmt"
"image"
"image/png"
"os"
"path"
"path/filepath"
@ -45,7 +44,7 @@ func FileName(hash, thumbPath string, width, height int, opts ...ResampleOption)
suffix := Suffix(width, height, opts...)
p := path.Join(thumbPath, hash[0:1], hash[1:2], hash[2:3])
if err := os.MkdirAll(p, fs.ModeDir); err != nil {
if err = fs.MkdirAll(p); err != nil {
return "", err
}

View file

@ -39,21 +39,25 @@ func testFastWalk(t *testing.T, files map[string]string, callback func(path stri
t.Fatal(err)
}
defer os.RemoveAll(tempdir)
for path, contents := range files {
file := filepath.Join(tempdir, "/src", path)
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
if err = os.MkdirAll(filepath.Dir(file), 0755); err != nil {
t.Fatal(err)
}
var err error
if strings.HasPrefix(contents, "LINK:") {
err = os.Symlink(strings.TrimPrefix(contents, "LINK:"), file)
} else {
err = os.WriteFile(file, []byte(contents), 0644)
}
if err != nil {
t.Fatal(err)
}
}
got := map[string]os.FileMode{}
var mu sync.Mutex
if err := fastwalk.Walk(tempdir, func(path string, typ os.FileMode) error {

View file

@ -2,7 +2,6 @@ package fs
import (
"fmt"
"os"
"path"
)
@ -19,7 +18,7 @@ func CachePath(basePath, fileHash, namespace string, create bool) (cachePath str
cachePath = path.Join(basePath, namespace, fileHash[0:1], fileHash[1:2], fileHash[2:3])
if create {
if err := os.MkdirAll(cachePath, ModeDir); err != nil {
if err = MkdirAll(cachePath); err != nil {
return "", err
}
}

View file

@ -2,6 +2,7 @@ package fs
const (
PPIgnoreFilename = ".ppignore"
PPIgnoreAll = "*"
PPStorageFilename = ".ppstorage"
PPHiddenPathname = ".photoprism"
)

View file

@ -15,7 +15,7 @@ func Copy(src, dest string) (err error) {
}
}()
if err := os.MkdirAll(filepath.Dir(dest), ModeDir); err != nil {
if err = MkdirAll(filepath.Dir(dest)); err != nil {
return err
}

View file

@ -189,6 +189,7 @@ func Dirs(root string, recursive bool, followLinks bool) (result []string, err e
return result, err
}
// FindDir checks if any of the specified directories exist and returns the absolute path of the first directory found.
func FindDir(dirs []string) string {
for _, dir := range dirs {
absDir := Abs(dir)
@ -199,3 +200,9 @@ func FindDir(dirs []string) string {
return ""
}
// MkdirAll creates a directory including all parent directories that might not yet exist.
// No error is returned if the directory already exists.
func MkdirAll(dir string) error {
return os.MkdirAll(dir, ModeDir)
}

View file

@ -81,14 +81,19 @@ func TestDirs(t *testing.T) {
})
}
func TestFindDirs(t *testing.T) {
t.Run("/directory", func(t *testing.T) {
result := FindDir([]string{"/directory", "/directory/subdirectory", "/linked"})
assert.Equal(t, "", result)
})
t.Run("./testdata", func(t *testing.T) {
func TestFindDir(t *testing.T) {
t.Run("Found", func(t *testing.T) {
result := FindDir([]string{"./testdata"})
assert.True(t, strings.HasSuffix(result, "/pkg/fs/testdata"))
})
t.Run("NotFound", func(t *testing.T) {
result := FindDir([]string{"/directory", "/directory/subdirectory", "/linked"})
assert.Equal(t, "", result)
})
}
func TestMkdirAll(t *testing.T) {
t.Run("Exists", func(t *testing.T) {
assert.NoError(t, MkdirAll("testdata"))
})
}

View file

@ -100,17 +100,6 @@ func PathWritable(path string) bool {
return Writable(path)
}
// Overwrite overwrites the file with data. Creates file if not present.
func Overwrite(fileName string, data []byte) bool {
f, err := os.Create(fileName)
if err != nil {
return false
}
_, err = f.Write(data)
return err == nil
}
// Abs returns the full path of a file or directory, "~" is replaced with home.
func Abs(name string) string {
if name == "" {
@ -147,7 +136,7 @@ func copyToFile(f *zip.File, dest string) (fileName string, err error) {
if f.FileInfo().IsDir() {
// Make Folder
return fileName, os.MkdirAll(fileName, ModeDir)
return fileName, MkdirAll(fileName)
}
// Make File
@ -157,8 +146,7 @@ func copyToFile(f *zip.File, dest string) (fileName string, err error) {
fdir = fileName[:lastIndex]
}
err = os.MkdirAll(fdir, ModeDir)
if err != nil {
if err = MkdirAll(fdir); err != nil {
return fileName, err
}
@ -181,7 +169,7 @@ func copyToFile(f *zip.File, dest string) (fileName string, err error) {
func Download(fileName string, url string) error {
if dir := filepath.Dir(fileName); dir == "" || dir == "/" || dir == "." || dir == ".." {
return fmt.Errorf("invalid path")
} else if err := os.MkdirAll(dir, ModeDir); err != nil {
} else if err := MkdirAll(dir); err != nil {
return err
}

View file

@ -57,19 +57,6 @@ func TestWritable(t *testing.T) {
assert.False(t, Writable(""))
}
func TestOverwrite(t *testing.T) {
data := make([]byte, 3)
data[1] = 3
data[2] = 8
tmpPath := "./testdata/_tmp"
os.Mkdir(tmpPath, 0777)
defer os.RemoveAll(tmpPath)
result := Overwrite("./testdata/_tmp/notyetexisting.jpg", data)
assert.FileExists(t, "./testdata/_tmp/notyetexisting.jpg")
assert.True(t, result)
}
func TestExpandedFilename(t *testing.T) {
t.Run("test.jpg", func(t *testing.T) {
filename := Abs("./testdata/test.jpg")

View file

@ -6,7 +6,7 @@ import (
"path/filepath"
)
// Moves a file to a new destination.
// Move moves an existing file to a new destination and returns an error if it fails.
func Move(src, dest string) (err error) {
defer func() {
if r := recover(); r != nil {
@ -14,19 +14,19 @@ func Move(src, dest string) (err error) {
}
}()
if err := os.MkdirAll(filepath.Dir(dest), ModeDir); err != nil {
if err = MkdirAll(filepath.Dir(dest)); err != nil {
return err
}
if err := os.Rename(src, dest); err == nil {
if err = os.Rename(src, dest); err == nil {
return nil
}
if err := Copy(src, dest); err != nil {
if err = Copy(src, dest); err != nil {
return err
}
if err := os.Remove(src); err != nil {
if err = os.Remove(src); err != nil {
return err
}

View file

@ -9,19 +9,19 @@ import (
// FileName returns the relative filename with the same base and a given extension in a directory.
func FileName(fileName, dirName, baseDir, fileExt string) string {
fileDir := filepath.Dir(fileName)
dir := filepath.Dir(fileName)
if dirName == "" || dirName == "." {
dirName = fileDir
} else if fileDir != dirName {
dirName = dir
} else if dir != dirName {
if filepath.IsAbs(dirName) {
dirName = filepath.Join(dirName, RelName(fileDir, baseDir))
dirName = filepath.Join(dirName, RelName(dir, baseDir))
} else {
dirName = filepath.Join(fileDir, dirName)
dirName = filepath.Join(dir, dirName)
}
}
if err := os.MkdirAll(dirName, ModeDir); err != nil {
if err := MkdirAll(dirName); err != nil {
fmt.Println(err.Error())
return ""
}

View file

@ -1,13 +0,0 @@
package fs
import (
"os"
"strconv"
"time"
)
// Touch creates a file with the specified name that contains the current Unix timestamp.
// If the file path does not exist or it cannot be written, an error is returned.
func Touch(fileName string) error {
return os.WriteFile(fileName, []byte(strconv.FormatInt(time.Now().Unix(), 10)), ModeFile)
}

View file

@ -1,29 +0,0 @@
package fs
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTouch(t *testing.T) {
t.Run("Testdata", func(t *testing.T) {
fileName := filepath.Join("testdata", PPStorageFilename)
assert.False(t, FileExists(fileName))
assert.False(t, FileExistsNotEmpty(fileName))
// Create "testdata/.ppstorage" file.
assert.NoError(t, Touch(fileName))
assert.True(t, FileExists(fileName))
assert.True(t, FileExistsNotEmpty(fileName))
// Delete "testdata/.ppstorage" file.
assert.NoError(t, os.Remove(fileName))
assert.False(t, FileExists(fileName))
assert.False(t, FileExistsNotEmpty(fileName))
})
}

View file

@ -4,39 +4,74 @@ import (
"errors"
"io"
"os"
"strconv"
"time"
)
// WriteFile writes data from a reader to a new file.
func WriteFile(fileName string, reader io.Reader) (err error) {
// WriteFile overwrites a file with the specified bytes as content.
// If the path does not exist or the file cannot be written, an error is returned.
func WriteFile(fileName string, data []byte) error {
file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, ModeFile)
if err != nil {
return err
}
_, err = file.Write(data)
if closeErr := file.Close(); closeErr != nil && err == nil {
err = closeErr
}
return err
}
// WriteString overwrites a file with the specified string as content.
// If the path does not exist or the file cannot be written, an error is returned.
func WriteString(fileName string, s string) error {
return WriteFile(fileName, []byte(s))
}
// WriteUnixTime overwrites a file with the current Unix timestamp as content.
// If the path does not exist or the file cannot be written, an error is returned.
func WriteUnixTime(fileName string) (unixTime int64, err error) {
unixTime = time.Now().Unix()
return unixTime, WriteString(fileName, strconv.FormatInt(unixTime, 10))
}
// WriteFileFromReader writes data from an io.Reader to a newly created file with the specified name.
// If the path does not exist or the file cannot be written, an error is returned.
func WriteFileFromReader(fileName string, reader io.Reader) (err error) {
if fileName == "" {
return errors.New("filename missing")
} else if reader == nil {
return errors.New("reader missing")
}
var f *os.File
var file *os.File
if f, err = os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, ModeFile); err != nil {
if file, err = os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, ModeFile); err != nil {
return err
}
defer f.Close()
_, err = io.Copy(file, reader)
if _, err = io.Copy(f, reader); err != nil {
return err
if closeErr := file.Close(); closeErr != nil && err == nil {
err = closeErr
}
return nil
return err
}
// CacheFile writes data from a reader to a new file if it does not already exist,
// and returns the name of the file or an error otherwise.
func CacheFile(fileName string, reader io.Reader) (string, error) {
// CacheFileFromReader writes data from an io.Reader to a file with the specified name if it does not exist.
// If the path does not exist or the file cannot be written, an error is returned.
// No error is returned if the file already exists.
func CacheFileFromReader(fileName string, reader io.Reader) (string, error) {
if FileExistsNotEmpty(fileName) {
return fileName, nil
}
if err := WriteFile(fileName, reader); err != nil {
if err := WriteFileFromReader(fileName, reader); err != nil {
return "", err
}

203
pkg/fs/write_test.go Normal file
View file

@ -0,0 +1,203 @@
package fs
import (
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWriteFile(t *testing.T) {
t.Run("Success", func(t *testing.T) {
pathName := "./testdata/_WriteFile_Success"
fileName := filepath.Join(pathName, "notyetexisting.jpg")
fileData := []byte("foobar")
if err := MkdirAll(pathName); err != nil {
t.Fatal(err)
}
defer func() {
_ = os.Remove(fileName)
if err := os.RemoveAll(pathName); err != nil {
t.Fatal(err)
}
}()
assert.True(t, PathExists(pathName))
fileErr := WriteFile(fileName, fileData)
assert.NoError(t, fileErr)
assert.FileExists(t, fileName)
})
}
func TestWriteString(t *testing.T) {
t.Run("Success", func(t *testing.T) {
pathName := "./testdata/_WriteString_Success"
fileName := filepath.Join(pathName, PPIgnoreFilename)
fileData := "*"
if err := MkdirAll(pathName); err != nil {
t.Fatal(err)
}
defer func() {
_ = os.Remove(fileName)
if err := os.RemoveAll(pathName); err != nil {
t.Fatal(err)
}
}()
assert.True(t, PathExists(pathName))
fileErr := WriteString(fileName, fileData)
assert.NoError(t, fileErr)
assert.FileExists(t, fileName)
readLines, readErr := ReadLines(fileName)
assert.NoError(t, readErr)
assert.Len(t, readLines, 1)
assert.Equal(t, fileData, readLines[0])
})
}
func TestWriteUnixTime(t *testing.T) {
t.Run("Success", func(t *testing.T) {
pathName := "./testdata/_WriteUnixTime_Success"
fileName := filepath.Join(pathName, PPStorageFilename)
if err := MkdirAll(pathName); err != nil {
t.Fatal(err)
}
defer func() {
_ = os.Remove(fileName)
if err := os.RemoveAll(pathName); err != nil {
t.Fatal(err)
}
}()
assert.True(t, PathExists(pathName))
unixTime, fileErr := WriteUnixTime(fileName)
assert.NoError(t, fileErr)
assert.FileExists(t, fileName)
readLines, readErr := ReadLines(fileName)
assert.NoError(t, readErr)
assert.Len(t, readLines, 1)
assert.Equal(t, strconv.FormatInt(unixTime, 10), readLines[0])
})
}
func TestWriteFileFromReader(t *testing.T) {
t.Run("Success", func(t *testing.T) {
pathName := "./testdata/_WriteFileFromReader_Success"
fileName1 := filepath.Join(pathName, "1.txt")
fileName2 := filepath.Join(pathName, "2.txt")
if err := MkdirAll(pathName); err != nil {
t.Fatal(err)
}
defer func() {
_ = os.Remove(fileName1)
_ = os.Remove(fileName2)
if err := os.RemoveAll(pathName); err != nil {
t.Fatal(err)
}
}()
assert.True(t, PathExists(pathName))
unixTime, writeErr := WriteUnixTime(fileName1)
assert.NoError(t, writeErr)
assert.True(t, unixTime >= time.Now().Unix())
fileReader, readerErr := os.Open(fileName1)
assert.NoError(t, readerErr)
fileErr := WriteFileFromReader(fileName2, fileReader)
assert.NoError(t, fileErr)
readLines, readErr := ReadLines(fileName2)
assert.NoError(t, readErr)
assert.Len(t, readLines, 1)
assert.Equal(t, strconv.FormatInt(unixTime, 10), readLines[0])
})
}
func TestCacheFileFromReader(t *testing.T) {
t.Run("Success", func(t *testing.T) {
pathName := "./testdata/_CacheFileFromReader_Success"
fileName1 := filepath.Join(pathName, "1.txt")
fileName2 := filepath.Join(pathName, "2.txt")
fileName3 := filepath.Join(pathName, "3.txt")
if err := MkdirAll(pathName); err != nil {
t.Fatal(err)
}
defer func() {
_ = os.Remove(fileName1)
_ = os.Remove(fileName2)
_ = os.Remove(fileName3)
if err := os.RemoveAll(pathName); err != nil {
t.Fatal(err)
}
}()
assert.True(t, PathExists(pathName))
unixTime, writeErr := WriteUnixTime(fileName1)
assert.NoError(t, writeErr)
assert.True(t, unixTime >= time.Now().Unix())
fileReader, readerErr := os.Open(fileName1)
assert.NoError(t, readerErr)
cacheFile, cacheErr := CacheFileFromReader(fileName2, fileReader)
assert.NoError(t, cacheErr)
assert.Equal(t, fileName2, cacheFile)
readLines, readErr := ReadLines(cacheFile)
assert.NoError(t, readErr)
assert.Len(t, readLines, 1)
assert.Equal(t, strconv.FormatInt(unixTime, 10), readLines[0])
if err := WriteString(fileName3, "0"); err != nil {
t.Fatal(err)
}
cacheFile, cacheErr = CacheFileFromReader(fileName3, fileReader)
assert.NoError(t, cacheErr)
assert.Equal(t, fileName3, cacheFile)
readLines, readErr = ReadLines(cacheFile)
assert.NoError(t, readErr)
assert.Len(t, readLines, 1)
assert.Equal(t, "0", readLines[0])
})
}