mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Backups: Improved saving of photo and album YAML files #4243
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:]
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user