Backups: Improved saving of photo and album YAML files #4243

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-05-14 08:45:18 +02:00
parent a5e1cd14d5
commit 9527082a03
15 changed files with 268 additions and 118 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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)
})
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)
}
})
}

View File

@@ -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++
}
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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:]
}

View File

@@ -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"))