diff --git a/internal/api/albums.go b/internal/api/albums.go index a495b9de5..145552c78 100644 --- a/internal/api/albums.go +++ b/internal/api/albums.go @@ -2,7 +2,6 @@ package api import ( "net/http" - "path/filepath" "sync" "github.com/gin-gonic/gin" @@ -20,23 +19,17 @@ import ( var albumMutex = sync.Mutex{} -// SaveAlbumYaml backs up the album metadata in YAML files. +// SaveAlbumYaml saves the album metadata to a YAML backup file. func SaveAlbumYaml(a entity.Album) { c := get.Config() - // Check if album YAML backups are enabled. + // Check if saving YAML backup files is enabled. if !c.BackupAlbums() { return } - // Create or update album backup file. - fileName := a.YamlFileName(c.BackupAlbumsPath()) - - if err := a.SaveAsYaml(fileName); err != nil { - log.Errorf("album: %s (save as yaml)", err) - } else { - log.Debugf("album: updated backup file %s", clean.Log(filepath.Base(fileName))) - } + // Write album metadata to YAML backup file. + _ = a.SaveBackupYaml(c.BackupAlbumsPath()) } // GetAlbum returns album details as JSON. diff --git a/internal/api/photos.go b/internal/api/photos.go index b016c161c..cd1204e28 100644 --- a/internal/api/photos.go +++ b/internal/api/photos.go @@ -2,7 +2,6 @@ package api import ( "net/http" - "path/filepath" "github.com/gin-gonic/gin" @@ -18,7 +17,7 @@ import ( "github.com/photoprism/photoprism/pkg/i18n" ) -// SaveSidecarYaml saves picture metadata as YAML file. +// SaveSidecarYaml saves the photo metadata to a YAML sidecar file. func SaveSidecarYaml(p *entity.Photo) { if p == nil { log.Debugf("api: photo is nil (update yaml)") @@ -27,19 +26,13 @@ func SaveSidecarYaml(p *entity.Photo) { c := get.Config() - // Check if creating and updating YAML sidecar files is enabled. + // Check if saving YAML sidecar files is enabled. if !c.SidecarYaml() { return } - // Create or update metadata export in YAML sidecar file. - fileName := p.YamlFileName(c.OriginalsPath(), c.SidecarPath()) - - if err := p.SaveAsYaml(fileName); err != nil { - log.Errorf("photo: %s (save as yaml)", err) - } else { - log.Debugf("photo: updated sidecar file %s", clean.Log(filepath.Join(p.PhotoPath, p.PhotoName)+fs.ExtYAML)) - } + // Write photo metadata to YAML sidecar file. + _ = p.SaveSidecarYaml(c.OriginalsPath(), c.SidecarPath()) } // GetPhoto returns photo details as JSON. diff --git a/internal/entity/album_yaml.go b/internal/entity/album_yaml.go index 757081c42..ebe715a67 100644 --- a/internal/entity/album_yaml.go +++ b/internal/entity/album_yaml.go @@ -1,6 +1,7 @@ package entity import ( + "fmt" "os" "path/filepath" "sync" @@ -8,6 +9,7 @@ import ( "gopkg.in/yaml.v2" + "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/fs" ) @@ -26,8 +28,16 @@ func (m *Album) Yaml() (out []byte, err error) { return yaml.Marshal(m) } -// SaveAsYaml saves album data as YAML file. +// SaveAsYaml writes the album metadata to a YAML backup file with the specified filename. func (m *Album) SaveAsYaml(fileName string) error { + if m == nil { + return fmt.Errorf("album entity is nil - you may have found a bug") + } else if m.AlbumUID == "" { + return fmt.Errorf("album uid is empty") + } else if fileName == "" { + return fmt.Errorf("yaml filname is empty") + } + data, err := m.Yaml() if err != nil { @@ -50,22 +60,66 @@ func (m *Album) SaveAsYaml(fileName string) error { return nil } -// LoadFromYaml photo data from a YAML file. +// YamlFileName returns the absolute file path for the YAML backup file. +func (m *Album) YamlFileName(backupPath string) (absolute, relative string, err error) { + if m == nil { + return "", "", fmt.Errorf("album entity is nil - you may have found a bug") + } else if m.AlbumUID == "" { + return "", "", fmt.Errorf("album uid is empty") + } + + relative = filepath.Join(m.AlbumType, m.AlbumUID+fs.ExtYAML) + + if backupPath == "" { + return "", relative, fmt.Errorf("backup path is empty") + } + + absolute = filepath.Join(backupPath, relative) + + return absolute, relative, err +} + +// SaveBackupYaml writes the album metadata to a YAML backup file based on the specified storage paths. +func (m *Album) SaveBackupYaml(backupPath string) error { + if m == nil { + return fmt.Errorf("album entity is nil - you may have found a bug") + } else if m.AlbumUID == "" { + return fmt.Errorf("album uid is empty") + } else if backupPath == "" { + return fmt.Errorf("backup path is empty") + } + + // Write metadata to YAML backup file. + if fileName, relName, err := m.YamlFileName(backupPath); err != nil { + log.Warnf("album: %s (save %s)", err, clean.Log(relName)) + return err + } else if err = m.SaveAsYaml(fileName); err != nil { + log.Warnf("album: %s (save %s)", err, clean.Log(relName)) + return err + } else { + log.Debugf("album: saved backup file %s", clean.Log(relName)) + } + + return nil +} + +// LoadFromYaml restores the album metadata from a YAML backup file. func (m *Album) LoadFromYaml(fileName string) error { + if m == nil { + return fmt.Errorf("album entity is nil - you may have found a bug") + } else if fileName == "" { + return fmt.Errorf("yaml filename is empty") + } + data, err := os.ReadFile(fileName) if err != nil { return err } - if err := yaml.Unmarshal(data, m); err != nil { + if err = yaml.Unmarshal(data, m); err != nil { return err } return nil } - -// YamlFileName returns the YAML file name. -func (m *Album) YamlFileName(albumsPath string) string { - return filepath.Join(albumsPath, m.AlbumType, m.AlbumUID+fs.ExtYAML) -} diff --git a/internal/entity/album_yaml_test.go b/internal/entity/album_yaml_test.go index 9c4f975fd..7c4101c05 100644 --- a/internal/entity/album_yaml_test.go +++ b/internal/entity/album_yaml_test.go @@ -2,9 +2,12 @@ package entity import ( "os" + "strings" "testing" "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/pkg/fs" ) func TestAlbum_Yaml(t *testing.T) { @@ -45,7 +48,7 @@ func TestAlbum_Yaml(t *testing.T) { } func TestAlbum_SaveAsYaml(t *testing.T) { - t.Run("berlin-2019", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { m := AlbumFixtures.Get("berlin-2019") if found := m.Find(); found == nil { @@ -54,18 +57,64 @@ func TestAlbum_SaveAsYaml(t *testing.T) { m = *found } - fileName := m.YamlFileName("testdata") + backupPath := fs.Abs("testdata/TestAlbum_SaveAsYaml") - if err := m.SaveAsYaml(fileName); err != nil { + fileName, relName, err := m.YamlFileName(backupPath) + + assert.NoError(t, err) + assert.True(t, strings.HasSuffix(fileName, "internal/entity/testdata/TestAlbum_SaveAsYaml/album/as6sg6bxpogaaba9.yml")) + assert.Equal(t, "album/as6sg6bxpogaaba9.yml", relName) + + if err = m.SaveAsYaml(fileName); err != nil { t.Fatal(err) + return } - if err := m.LoadFromYaml(fileName); err != nil { - t.Fatal(err) + if err = m.LoadFromYaml(fileName); err != nil { + t.Error(err) } - if err := os.Remove(fileName); err != nil { + if err = os.RemoveAll(backupPath); err != nil { + t.Error(err) + } + }) +} + +func TestAlbum_YamlFileName(t *testing.T) { + t.Run("Success", func(t *testing.T) { + m := AlbumFixtures.Get("berlin-2019") + + if found := m.Find(); found == nil { + t.Fatal("should find album") + } else { + m = *found + } + + fileName, relName, err := m.YamlFileName("/foo/bar") + + assert.NoError(t, err) + assert.Equal(t, "/foo/bar/album/as6sg6bxpogaaba9.yml", fileName) + assert.Equal(t, "album/as6sg6bxpogaaba9.yml", relName) + }) +} + +func TestAlbum_SaveBackupYaml(t *testing.T) { + t.Run("Success", func(t *testing.T) { + m := AlbumFixtures.Get("berlin-2019") + + backupPath := fs.Abs("testdata/TestAlbum_SaveBackupYaml") + + if err := fs.MkdirAll(backupPath); err != nil { t.Fatal(err) + return + } + + if err := m.SaveBackupYaml(backupPath); err != nil { + t.Error(err) + } + + if err := os.RemoveAll(backupPath); err != nil { + t.Error(err) } }) } @@ -111,19 +160,3 @@ func TestAlbum_LoadFromYaml(t *testing.T) { } }) } - -func TestAlbum_YamlFileName(t *testing.T) { - t.Run("berlin-2019", func(t *testing.T) { - m := AlbumFixtures.Get("berlin-2019") - - if found := m.Find(); found == nil { - t.Fatal("should find album") - } else { - m = *found - } - - fileName := m.YamlFileName("/foo/bar") - - assert.Equal(t, "/foo/bar/album/as6sg6bxpogaaba9.yml", fileName) - }) -} diff --git a/internal/entity/auth_client.go b/internal/entity/auth_client.go index 944155df9..5cc122ea8 100644 --- a/internal/entity/auth_client.go +++ b/internal/entity/auth_client.go @@ -271,7 +271,7 @@ func (m *Client) Save() error { // Delete marks the entity as deleted. func (m *Client) Delete() (err error) { if m.ClientUID == "" { - return fmt.Errorf("client uid is missing") + return fmt.Errorf("client uid is empty") } if _, err = m.DeleteSessions(); err != nil { @@ -286,7 +286,7 @@ func (m *Client) Delete() (err error) { // DeleteSessions deletes all sessions that belong to this client. func (m *Client) DeleteSessions() (deleted int, err error) { if m.ClientUID == "" { - return 0, fmt.Errorf("client uid is missing") + return 0, fmt.Errorf("client uid is empty") } if deleted = DeleteClientSessions(m, "", 0); deleted > 0 { diff --git a/internal/entity/photo_yaml.go b/internal/entity/photo_yaml.go index 9191a4be3..64dd4d8ef 100644 --- a/internal/entity/photo_yaml.go +++ b/internal/entity/photo_yaml.go @@ -1,6 +1,7 @@ package entity import ( + "fmt" "os" "path/filepath" "sync" @@ -8,6 +9,7 @@ import ( "gopkg.in/yaml.v2" + "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/fs" ) @@ -30,8 +32,16 @@ func (m *Photo) Yaml() ([]byte, error) { return out, err } -// SaveAsYaml saves photo data as YAML file. +// SaveAsYaml writes the photo metadata to a YAML sidecar file with the specified filename. func (m *Photo) SaveAsYaml(fileName string) error { + if m == nil { + return fmt.Errorf("photo entity is nil - you may have found a bug") + } else if fileName == "" { + return fmt.Errorf("yaml filename is empty") + } else if m.PhotoUID == "" { + return fmt.Errorf("photo uid is empty") + } + data, err := m.Yaml() if err != nil { @@ -54,22 +64,55 @@ func (m *Photo) SaveAsYaml(fileName string) error { return nil } -// LoadFromYaml photo data from a YAML file. +// YamlFileName returns both the absolute file path and the relative name for the YAML sidecar file, e.g. for logging. +func (m *Photo) YamlFileName(originalsPath, sidecarPath string) (absolute, relative string, err error) { + absolute, err = fs.FileName(filepath.Join(originalsPath, m.PhotoPath, m.PhotoName), sidecarPath, originalsPath, fs.ExtYAML) + relative = filepath.Join(m.PhotoPath, m.PhotoName) + fs.ExtYAML + + return absolute, relative, err +} + +// SaveSidecarYaml writes the photo metadata to a YAML sidecar file based on the specified storage paths. +func (m *Photo) SaveSidecarYaml(originalsPath, sidecarPath string) error { + if m == nil { + return fmt.Errorf("photo entity is nil - you may have found a bug") + } else if m.PhotoName == "" { + return fmt.Errorf("photo name is empty") + } else if m.PhotoUID == "" { + return fmt.Errorf("photo uid is empty") + } + + // Write metadata to YAML sidecar file. + if fileName, relName, err := m.YamlFileName(originalsPath, sidecarPath); err != nil { + log.Warnf("photo: %s (save %s)", err, clean.Log(relName)) + return err + } else if err = m.SaveAsYaml(fileName); err != nil { + log.Warnf("photo: %s (save %s)", err, clean.Log(relName)) + return err + } else { + log.Infof("photo: saved sidecar file %s", clean.Log(relName)) + } + + return nil +} + +// LoadFromYaml restores the photo metadata from a YAML sidecar file. func (m *Photo) LoadFromYaml(fileName string) error { + if m == nil { + return fmt.Errorf("photo entity is nil - you may have found a bug") + } else if fileName == "" { + return fmt.Errorf("yaml filename is empty") + } + data, err := os.ReadFile(fileName) if err != nil { return err } - if err := yaml.Unmarshal(data, m); err != nil { + if err = yaml.Unmarshal(data, m); err != nil { return err } return nil } - -// YamlFileName returns the YAML file name. -func (m *Photo) YamlFileName(originalsPath, sidecarPath string) string { - return fs.FileName(filepath.Join(originalsPath, m.PhotoPath, m.PhotoName), sidecarPath, originalsPath, fs.ExtYAML) -} diff --git a/internal/entity/photo_yaml_test.go b/internal/entity/photo_yaml_test.go index 38ebabf6c..d2b9cb5c5 100644 --- a/internal/entity/photo_yaml_test.go +++ b/internal/entity/photo_yaml_test.go @@ -6,10 +6,12 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/pkg/fs" ) func TestPhoto_Yaml(t *testing.T) { - t.Run("create from fixture", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { m := PhotoFixtures.Get("Photo01") m.PreloadFiles() result, err := m.Yaml() @@ -23,7 +25,7 @@ func TestPhoto_Yaml(t *testing.T) { } func TestPhoto_SaveAsYaml(t *testing.T) { - t.Run("create from fixture", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { m := PhotoFixtures.Get("Photo01") m.PreloadFiles() @@ -44,13 +46,48 @@ func TestPhoto_SaveAsYaml(t *testing.T) { } func TestPhoto_YamlFileName(t *testing.T) { - t.Run("create from fixture", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { m := PhotoFixtures.Get("Photo01") m.PreloadFiles() - assert.Equal(t, "xxx/2790/02/yyy/Photo01.yml", m.YamlFileName("xxx", "yyy")) + fileName, relative, err := m.YamlFileName("xxx", "yyy") + assert.NoError(t, err) + assert.Equal(t, "xxx/2790/02/yyy/Photo01.yml", fileName) + assert.Equal(t, "2790/02/Photo01.yml", relative) if err := os.RemoveAll("xxx"); err != nil { t.Fatal(err) } }) } + +func TestPhoto_SaveSidecarYaml(t *testing.T) { + t.Run("Success", func(t *testing.T) { + m := PhotoFixtures.Get("Photo01") + m.PreloadFiles() + + basePath := fs.Abs("testdata/yaml") + originalsPath := filepath.Join(basePath, "originals") + sidecarPath := filepath.Join(basePath, "sidecar") + + t.Logf("originalsPath: %s", originalsPath) + t.Logf("sidecarPath: %s", sidecarPath) + + if err := fs.MkdirAll(originalsPath); err != nil { + t.Fatal(err) + return + } + + if err := fs.MkdirAll(sidecarPath); err != nil { + t.Fatal(err) + return + } + + if err := m.SaveSidecarYaml(originalsPath, sidecarPath); err != nil { + t.Error(err) + } + + if err := os.RemoveAll(basePath); err != nil { + t.Error(err) + } + }) +} diff --git a/internal/photoprism/backup_albums.go b/internal/photoprism/backup_albums.go index 68996b473..c74742b8d 100644 --- a/internal/photoprism/backup_albums.go +++ b/internal/photoprism/backup_albums.go @@ -1,7 +1,6 @@ package photoprism import ( - "path/filepath" "sync" "time" @@ -38,22 +37,23 @@ func BackupAlbums(backupPath string, force bool) (count int, err error) { latest = backupAlbumsLatest } + // Save albums to YAML backup files. for _, a := range albums { - if !force && a.UpdatedAt.Before(backupAlbumsLatest) { + // Skip albums that have already been saved to YAML backup files. + if !force && !backupAlbumsLatest.IsZero() && !a.UpdatedAt.IsZero() && + a.UpdatedAt.Before(backupAlbumsLatest) { continue } + // Remember most recent date. if a.UpdatedAt.After(latest) { latest = a.UpdatedAt } - fileName := a.YamlFileName(backupPath) - - if saveErr := a.SaveAsYaml(fileName); saveErr != nil { - log.Errorf("album: %s (save as yaml)", saveErr) + // Write album metadata to YAML backup file. + if saveErr := a.SaveBackupYaml(backupPath); saveErr != nil { err = saveErr } else { - log.Tracef("album: updated backup file %s", clean.Log(filepath.Base(fileName))) count++ } } diff --git a/internal/photoprism/convert_image.go b/internal/photoprism/convert_image.go index cfbbf9709..021d8b039 100644 --- a/internal/photoprism/convert_image.go +++ b/internal/photoprism/convert_image.go @@ -49,8 +49,8 @@ func (c *Convert) ToImage(f *MediaFile, force bool) (*MediaFile, error) { // Replace existing sidecar if "force" is true. if err == nil && mediaFile.IsPreviewImage() { if force && mediaFile.InSidecar() { - if err := mediaFile.Remove(); err != nil { - return mediaFile, fmt.Errorf("convert: failed removing %s (%s)", clean.Log(mediaFile.RootRelName()), err) + if removeErr := mediaFile.Remove(); removeErr != nil { + return mediaFile, fmt.Errorf("convert: failed removing %s (%s)", clean.Log(mediaFile.RootRelName()), removeErr) } else { log.Infof("convert: replacing %s", clean.Log(mediaFile.RootRelName())) } @@ -61,9 +61,9 @@ func (c *Convert) ToImage(f *MediaFile, force bool) (*MediaFile, error) { if !c.conf.VectorEnabled() { return nil, fmt.Errorf("convert: vector graphics support disabled (%s)", clean.Log(f.RootRelName())) } - imageName = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtPNG) + imageName, _ = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtPNG) } else { - imageName = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtJPEG) + imageName, _ = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtJPEG) } if !c.conf.SidecarWritable() { diff --git a/internal/photoprism/convert_video_avc.go b/internal/photoprism/convert_video_avc.go index c575a1226..95ac8af2a 100644 --- a/internal/photoprism/convert_video_avc.go +++ b/internal/photoprism/convert_video_avc.go @@ -61,14 +61,14 @@ func (c *Convert) ToAvc(f *MediaFile, encoder ffmpeg.AvcEncoder, noMutex, force // Use .mp4 file extension for animated images and .avi for videos. if f.IsAnimatedImage() { - avcName = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtMP4) + avcName, _ = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtMP4) } else { - avcName = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtAVC) + avcName, _ = fs.FileName(f.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.ExtAVC) } cmd, useMutex, err := c.AvcConvertCommand(f, avcName, encoder) - // Failed? + // Return if an error occurred. if err != nil { log.Error(err) return nil, err diff --git a/internal/photoprism/delete.go b/internal/photoprism/delete.go index cd6a449b9..d99558038 100644 --- a/internal/photoprism/delete.go +++ b/internal/photoprism/delete.go @@ -16,7 +16,11 @@ func DeletePhoto(p *entity.Photo, mediaFiles bool, originals bool) (numFiles int return 0, errors.New("photo is nil") } - yamlFileName := p.YamlFileName(Config().OriginalsPath(), Config().SidecarPath()) + yamlFileName, yamlRelName, err := p.YamlFileName(Config().OriginalsPath(), Config().SidecarPath()) + + if err != nil { + log.Warnf("photo: %s (delete %s)", err, clean.Log(yamlRelName)) + } // Permanently remove photo from index. files, err := p.DeletePermanently() @@ -33,10 +37,10 @@ func DeletePhoto(p *entity.Photo, mediaFiles bool, originals bool) (numFiles int if !fs.FileExists(yamlFileName) { return numFiles, nil } else if err := os.Remove(yamlFileName); err != nil { - log.Warnf("files: failed deleting sidecar %s", clean.Log(filepath.Base(yamlFileName))) + log.Warnf("photo: failed deleting sidecar file %s", clean.Log(yamlRelName)) } else { numFiles++ - log.Infof("files: deleted sidecar %s", clean.Log(filepath.Base(yamlFileName))) + log.Infof("photo: deleted sidecar file %s", clean.Log(yamlRelName)) } return numFiles, nil diff --git a/internal/photoprism/index.go b/internal/photoprism/index.go index 308c6036f..93c48f76e 100644 --- a/internal/photoprism/index.go +++ b/internal/photoprism/index.go @@ -222,7 +222,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) { // Skip files if the filename extension does not match their mime type, // see https://github.com/photoprism/photoprism/issues/3518 for details. if typeErr := mf.CheckType(); typeErr != nil { - log.Errorf("index: skipped %s due to %s", clean.Log(mf.RootRelName()), typeErr) + log.Warnf("index: skipped %s due to %s", clean.Log(mf.RootRelName()), typeErr) return nil } diff --git a/internal/photoprism/index_mediafile.go b/internal/photoprism/index_mediafile.go index fb9986fc1..00cb6e41e 100644 --- a/internal/photoprism/index_mediafile.go +++ b/internal/photoprism/index_mediafile.go @@ -994,14 +994,8 @@ func (ind *Index) UserMediaFile(m *MediaFile, o IndexOptions, originalName, phot // Create backup of picture metadata in sidecar YAML file. if file.FilePrimary && Config().SidecarYaml() { - // Get YAML file name. - yamlFile := photo.YamlFileName(Config().OriginalsPath(), Config().SidecarPath()) - - // Save backup to file. - if err = photo.SaveAsYaml(yamlFile); err != nil { - log.Errorf("index: %s in %s (save as yaml)", err.Error(), logName) - } else { - log.Debugf("index: updated sidecar file %s", clean.Log(filepath.Join(photo.PhotoPath, photo.PhotoName)+fs.ExtYAML)) + if err = photo.SaveSidecarYaml(Config().OriginalsPath(), Config().SidecarPath()); err != nil { + log.Errorf("index: %s in %s (save as yaml)", err, logName) } } diff --git a/pkg/fs/name.go b/pkg/fs/name.go index 66e8ba114..843f22401 100644 --- a/pkg/fs/name.go +++ b/pkg/fs/name.go @@ -7,8 +7,14 @@ import ( "strings" ) -// FileName returns the relative filename with the same base and a given extension in a directory. -func FileName(fileName, dirName, baseDir, fileExt string) string { +// FileName returns the file path for a sidecar file with the specified extension. +func FileName(fileName, dirName, baseDir, fileExt string) (string, error) { + if fileName == "" { + return "", fmt.Errorf("file name is empty") + } else if fileExt == "" { + return "", fmt.Errorf("file extension is empty") + } + dir := filepath.Dir(fileName) if dirName == "" || dirName == "." { @@ -21,14 +27,15 @@ func FileName(fileName, dirName, baseDir, fileExt string) string { } } + // Create parent directories if they do not exist yet. if err := MkdirAll(dirName); err != nil { - fmt.Println(err.Error()) - return "" + return "", err } + // Compose and return file path. result := filepath.Join(dirName, filepath.Base(fileName)) + fileExt - return result + return result, nil } // RelName returns the file name relative to a directory. @@ -41,11 +48,11 @@ func RelName(fileName, dir string) string { return fileName } - if index := strings.Index(fileName, dir); index == 0 { - if index := strings.LastIndex(dir, string(os.PathSeparator)); index == len(dir)-1 { + if i := strings.Index(fileName, dir); i == 0 { + if i = strings.LastIndex(dir, string(os.PathSeparator)); i == len(dir)-1 { pos := len(dir) return fileName[pos:] - } else if index := strings.LastIndex(dir, string(os.PathSeparator)); index != len(dir) { + } else if i = strings.LastIndex(dir, string(os.PathSeparator)); i != len(dir) { pos := len(dir) + 1 return fileName[pos:] } diff --git a/pkg/fs/name_test.go b/pkg/fs/name_test.go index b3c0781c8..4d254e556 100644 --- a/pkg/fs/name_test.go +++ b/pkg/fs/name_test.go @@ -34,36 +34,29 @@ func TestRelName(t *testing.T) { func TestFileName(t *testing.T) { t.Run("Test copy 3.jpg", func(t *testing.T) { - result := FileName("testdata/Test (4).jpg", ".photoprism", Abs("testdata"), ".xmp") - + result, err := FileName("testdata/Test (4).jpg", ".photoprism", Abs("testdata"), ".xmp") + assert.NoError(t, err) assert.Equal(t, "testdata/.photoprism/Test (4).jpg.xmp", result) }) - t.Run("Test (3).jpg", func(t *testing.T) { - result := FileName("testdata/Test (4).jpg", ".photoprism", Abs("testdata"), ".xmp") - + result, err := FileName("testdata/Test (4).jpg", ".photoprism", Abs("testdata"), ".xmp") + assert.NoError(t, err) assert.Equal(t, "testdata/.photoprism/Test (4).jpg.xmp", result) }) - t.Run("FOO.XMP", func(t *testing.T) { - result := FileName("testdata/FOO.XMP", ".photoprism/sub", Abs("testdata"), ".jpeg") - + result, err := FileName("testdata/FOO.XMP", ".photoprism/sub", Abs("testdata"), ".jpeg") + assert.NoError(t, err) assert.Equal(t, "testdata/.photoprism/sub/FOO.XMP.jpeg", result) }) - t.Run("Test copy 3.jpg", func(t *testing.T) { tempDir := filepath.Join(os.TempDir(), PPHiddenPathname) - - // t.Logf("TEMP DIR, ABS NAME: %s, %s", tempDir, Abs("testdata/Test (4).jpg")) - - result := FileName(Abs("testdata/Test (4).jpg"), tempDir, Abs("testdata"), ".xmp") - + result, err := FileName(Abs("testdata/Test (4).jpg"), tempDir, Abs("testdata"), ".xmp") + assert.NoError(t, err) assert.Equal(t, tempDir+"/Test (4).jpg.xmp", result) }) - t.Run("empty dir", func(t *testing.T) { - result := FileName("testdata/FOO.XMP", "", Abs("testdata"), ".jpeg") - + result, err := FileName("testdata/FOO.XMP", "", Abs("testdata"), ".jpeg") + assert.NoError(t, err) assert.Equal(t, "testdata/FOO.XMP.jpeg", result) }) } @@ -75,7 +68,6 @@ func TestFileNameHidden(t *testing.T) { t.Run("Dot", func(t *testing.T) { assert.True(t, FileNameHidden("/some/.folder")) }) - t.Run("Dots", func(t *testing.T) { assert.False(t, FileNameHidden("/some/image.jpg.")) assert.False(t, FileNameHidden("./.some/foo"))