Slowly getting to the point where only very few people are able to maintain this codebase :) Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
9bd2a867be
commit
a91206a509
60 changed files with 468 additions and 319 deletions
|
@ -39,10 +39,8 @@ services:
|
|||
PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840)
|
||||
PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840)
|
||||
PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage)
|
||||
PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types)
|
||||
PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool
|
||||
PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files
|
||||
PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled)
|
||||
CODECOV_TOKEN:
|
||||
CODECOV_ENV:
|
||||
CODECOV_URL:
|
||||
|
|
|
@ -44,10 +44,8 @@ services:
|
|||
PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840)
|
||||
PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840)
|
||||
PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage)
|
||||
PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types)
|
||||
PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool
|
||||
PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files
|
||||
PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled)
|
||||
|
||||
photoprism-db:
|
||||
image: mariadb:10.5
|
||||
|
|
|
@ -19,14 +19,12 @@ ENV PHOTOPRISM_UPLOAD_NSFW false
|
|||
ENV PHOTOPRISM_DETECT_NSFW false
|
||||
ENV PHOTOPRISM_SIDECAR_JSON true
|
||||
ENV PHOTOPRISM_SIDECAR_YAML false
|
||||
ENV PHOTOPRISM_SIDECAR_HIDDEN true
|
||||
ENV PHOTOPRISM_GEOCODING_API places
|
||||
ENV PHOTOPRISM_THUMB_FILTER lanczos
|
||||
ENV PHOTOPRISM_THUMB_UNCACHED true
|
||||
ENV PHOTOPRISM_THUMB_SIZE 3840
|
||||
ENV PHOTOPRISM_THUMB_LIMIT 3840
|
||||
ENV PHOTOPRISM_JPEG_QUALITY 95
|
||||
ENV PHOTOPRISM_JPEG_HIDDEN false
|
||||
ENV PHOTOPRISM_SITE_CAPTION "Try our demo"
|
||||
|
||||
# Import example photos
|
||||
|
|
|
@ -40,14 +40,12 @@ services:
|
|||
# PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?charset=utf8mb4,utf8&parseTime=true"
|
||||
# PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool
|
||||
# PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files
|
||||
PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled)
|
||||
PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
|
||||
PHOTOPRISM_THUMB_UNCACHED: "false" # On-demand rendering of default thumbnails (high memory and cpu usage)
|
||||
PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840)
|
||||
# PHOTOPRISM_THUMB_SIZE: 3840 # For retina screens (requires more storage)
|
||||
PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840)
|
||||
PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage)
|
||||
PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types)
|
||||
PHOTOPRISM_STORAGE_PATH: "/photoprism/storage" # Storage PATH for generated files like cache and index
|
||||
volumes:
|
||||
- "~/Pictures/Originals:/photoprism/originals" # [local path]:[container path]
|
||||
|
|
|
@ -39,14 +39,12 @@ services:
|
|||
# PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?charset=utf8mb4,utf8&parseTime=true"
|
||||
# PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool
|
||||
# PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files
|
||||
PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled)
|
||||
PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
|
||||
PHOTOPRISM_THUMB_UNCACHED: "false" # On-demand rendering of default thumbnails (high memory and cpu usage)
|
||||
PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840)
|
||||
# PHOTOPRISM_THUMB_SIZE: 3840 # For retina screens (requires more storage)
|
||||
PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840)
|
||||
PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage)
|
||||
PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types)
|
||||
PHOTOPRISM_STORAGE_PATH: "/photoprism/storage" # Storage PATH for generated files like cache and index
|
||||
volumes:
|
||||
- "~/Pictures/Originals:/photoprism/originals" # [local path]:[container path]
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
@ -381,7 +382,8 @@ func DownloadAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
defer func() { _ = zipWriter.Close() }()
|
||||
|
||||
for _, f := range p {
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
fileAlias := f.ShareFileName()
|
||||
|
||||
if fs.FileExists(fileName) {
|
||||
|
@ -477,7 +479,7 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("album-thumbnail: could not find original for %s", fileName)
|
||||
|
|
|
@ -15,12 +15,10 @@ import (
|
|||
|
||||
// NewApiTest returns new API test helper
|
||||
func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config) {
|
||||
conf = config.TestConfig()
|
||||
service.SetConfig(conf)
|
||||
gin.SetMode(gin.TestMode)
|
||||
app = gin.New()
|
||||
router = app.Group("/api/v1")
|
||||
return app, router, conf
|
||||
return app, router, service.Config()
|
||||
}
|
||||
|
||||
// Performs API request with empty request body.
|
||||
|
@ -46,6 +44,7 @@ func TestMain(m *testing.M) {
|
|||
log.SetLevel(logrus.DebugLevel)
|
||||
|
||||
c := config.TestConfig()
|
||||
service.SetConfig(c)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ package api
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
@ -37,7 +37,7 @@ func GetDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("download: file %s is missing", txt.Quote(f.FileName))
|
||||
|
|
|
@ -90,7 +90,7 @@ func GetFolders(router *gin.RouterGroup, conf *config.Config, urlPath, rootName,
|
|||
|
||||
// GET /api/v1/folders/originals
|
||||
func GetFoldersOriginals(router *gin.RouterGroup, conf *config.Config) {
|
||||
GetFolders(router, conf, "originals", entity.RootDefault, conf.OriginalsPath())
|
||||
GetFolders(router, conf, "originals", entity.RootOriginals, conf.OriginalsPath())
|
||||
}
|
||||
|
||||
// GET /api/v1/folders/import
|
||||
|
|
|
@ -46,7 +46,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
assert.Equal(t, "", folder.FolderDescription)
|
||||
assert.Equal(t, entity.TypeDefault, folder.FolderType)
|
||||
assert.Equal(t, entity.SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, entity.RootDefault, folder.Root)
|
||||
assert.Equal(t, entity.RootOriginals, folder.Root)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
@ -85,7 +85,7 @@ func TestGetFoldersOriginals(t *testing.T) {
|
|||
assert.Equal(t, "", folder.FolderDescription)
|
||||
assert.Equal(t, entity.TypeDefault, folder.FolderType)
|
||||
assert.Equal(t, entity.SortOrderName, folder.FolderOrder)
|
||||
assert.Equal(t, entity.RootDefault, folder.Root)
|
||||
assert.Equal(t, entity.RootOriginals, folder.Root)
|
||||
assert.IsType(t, "", folder.FolderUID)
|
||||
assert.Equal(t, false, folder.FolderFavorite)
|
||||
assert.Equal(t, false, folder.FolderIgnore)
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
@ -15,6 +14,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
@ -221,7 +221,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("label-thumbnail: file %s is missing", txt.Quote(f.FileName))
|
||||
|
|
|
@ -3,13 +3,13 @@ package api
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
@ -19,12 +19,12 @@ import (
|
|||
func SavePhotoAsYaml(p entity.Photo, conf *config.Config) {
|
||||
// Write YAML sidecar file (optional).
|
||||
if conf.SidecarYaml() {
|
||||
yamlFile := p.YamlFileName(conf.OriginalsPath(), conf.SidecarHidden())
|
||||
yamlFile := p.YamlFileName(conf.OriginalsPath(), conf.SidecarPath())
|
||||
|
||||
if err := p.SaveAsYaml(yamlFile); err != nil {
|
||||
log.Errorf("photo: %s (update yaml)", err)
|
||||
} else {
|
||||
log.Infof("photo: updated yaml file %s", txt.Quote(fs.RelativeName(yamlFile, conf.OriginalsPath())))
|
||||
log.Infof("photo: updated yaml file %s", txt.Quote(fs.Rel(yamlFile, conf.OriginalsPath())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ func GetPhotoDownload(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("photo: file %s is missing", txt.Quote(f.FileName))
|
||||
|
|
|
@ -4,12 +4,12 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
@ -102,7 +102,7 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("thumbnail: file %s is missing", txt.Quote(f.FileName))
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
@ -63,7 +64,7 @@ func GetPreview(router *gin.RouterGroup, conf *config.Config) {
|
|||
thumbType, _ := thumb.Types["tile_224"]
|
||||
|
||||
for _, f := range p {
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("preview: file %s is missing", txt.Quote(f.FileName))
|
||||
|
|
|
@ -2,10 +2,10 @@ package api
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/video"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
@ -59,7 +59,7 @@ func GetVideo(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("video: file %s is missing", txt.Quote(f.FileName))
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
|
@ -82,7 +83,7 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
|
|||
defer zipWriter.Close()
|
||||
|
||||
for _, f := range files {
|
||||
fileName := path.Join(conf.OriginalsPath(), f.FileName)
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
fileAlias := f.ShareFileName()
|
||||
|
||||
if fs.FileExists(fileName) {
|
||||
|
|
|
@ -92,7 +92,7 @@ func configAction(ctx *cli.Context) error {
|
|||
fmt.Printf("%-25s %s\n", "exiftool-bin", conf.ExifToolBin())
|
||||
fmt.Printf("%-25s %t\n", "sidecar-json", conf.SidecarJson())
|
||||
fmt.Printf("%-25s %t\n", "sidecar-yaml", conf.SidecarYaml())
|
||||
fmt.Printf("%-25s %t\n", "sidecar-hidden", conf.SidecarHidden())
|
||||
fmt.Printf("%-25s %s\n", "sidecar-path", conf.SidecarPath())
|
||||
|
||||
// Places / Geocoding API configuration.
|
||||
fmt.Printf("%-25s %s\n", "geocoding-api", conf.GeoCodingApi())
|
||||
|
@ -106,7 +106,6 @@ func configAction(ctx *cli.Context) error {
|
|||
fmt.Printf("%-25s %d\n", "thumb-limit", conf.ThumbLimit())
|
||||
fmt.Printf("%-25s %s\n", "thumb-path", conf.ThumbPath())
|
||||
fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality())
|
||||
fmt.Printf("%-25s %t\n", "jpeg-hidden", conf.JpegHidden())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func purgeAction(ctx *cli.Context) error {
|
|||
if subPath == "" {
|
||||
log.Infof("removing missing files in %s", txt.Quote(filepath.Base(conf.OriginalsPath())))
|
||||
} else {
|
||||
log.Infof("removing missing files in %s", txt.Quote(fs.RelativeName(filepath.Join(conf.OriginalsPath(), subPath), filepath.Dir(conf.OriginalsPath()))))
|
||||
log.Infof("removing missing files in %s", txt.Quote(fs.Rel(filepath.Join(conf.OriginalsPath(), subPath), filepath.Dir(conf.OriginalsPath()))))
|
||||
}
|
||||
|
||||
if conf.ReadOnly() {
|
||||
|
|
|
@ -59,6 +59,12 @@ func (c *Config) CreateDirectories() error {
|
|||
return createError(c.ImportPath(), err)
|
||||
}
|
||||
|
||||
if filepath.IsAbs(c.SidecarPath()) {
|
||||
if err := os.MkdirAll(c.SidecarPath(), os.ModePerm); err != nil {
|
||||
return createError(c.SidecarPath(), err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(c.CachePath(), os.ModePerm); err != nil {
|
||||
return createError(c.CachePath(), err)
|
||||
}
|
||||
|
@ -188,9 +194,13 @@ func (c *Config) SidecarYaml() bool {
|
|||
return c.params.SidecarYaml
|
||||
}
|
||||
|
||||
// SidecarHidden returns true if new sidecar files should be created in a .photoprism sub directory (hidden).
|
||||
func (c *Config) SidecarHidden() bool {
|
||||
return c.params.SidecarHidden
|
||||
// SidecarPath returns the storage path for automatically created sidecar files.
|
||||
func (c *Config) SidecarPath() string {
|
||||
if c.params.SidecarPath == "" {
|
||||
c.params.SidecarPath = filepath.Join(c.StoragePath(), "sidecar")
|
||||
}
|
||||
|
||||
return c.params.SidecarPath
|
||||
}
|
||||
|
||||
// HeifConvertBin returns the heif-convert executable file name.
|
||||
|
|
|
@ -239,6 +239,12 @@ var GlobalFlags = []cli.Flag{
|
|||
Usage: "create JSON and YAML sidecar files in .photoprism if enabled",
|
||||
EnvVar: "PHOTOPRISM_SIDECAR_HIDDEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "sidecar-path",
|
||||
Usage: "storage `PATH` for automatically created sidecar files (relative or absolute)",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detect-nsfw",
|
||||
Usage: "flag photos as private that may be offensive",
|
||||
|
@ -296,9 +302,4 @@ var GlobalFlags = []cli.Flag{
|
|||
Value: 90,
|
||||
EnvVar: "PHOTOPRISM_JPEG_QUALITY",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "jpeg-hidden",
|
||||
Usage: "create JPEG files in .photoprism when converting other file types",
|
||||
EnvVar: "PHOTOPRISM_JPEG_HIDDEN",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ type Params struct {
|
|||
ExifToolBin string `yaml:"exiftool-bin" flag:"exiftool-bin"`
|
||||
SidecarJson bool `yaml:"sidecar-json" flag:"sidecar-json"`
|
||||
SidecarYaml bool `yaml:"sidecar-yaml" flag:"sidecar-yaml"`
|
||||
SidecarHidden bool `yaml:"sidecar-hidden" flag:"sidecar-hidden"`
|
||||
SidecarPath string `yaml:"sidecar-path" flag:"sidecar-path"`
|
||||
PIDFilename string `yaml:"pid-filename" flag:"pid-filename"`
|
||||
LogFilename string `yaml:"log-filename" flag:"log-filename"`
|
||||
DetachServer bool `yaml:"detach-server" flag:"detach-server"`
|
||||
|
@ -83,7 +83,6 @@ type Params struct {
|
|||
ThumbUncached bool `yaml:"thumb-uncached" flag:"thumb-uncached"`
|
||||
ThumbSize int `yaml:"thumb-size" flag:"thumb-size"`
|
||||
ThumbLimit int `yaml:"thumb-limit" flag:"thumb-limit"`
|
||||
JpegHidden bool `yaml:"jpeg-hidden" flag:"jpeg-hidden"`
|
||||
JpegQuality int `yaml:"jpeg-quality" flag:"jpeg-quality"`
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,6 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
)
|
||||
|
||||
// JpegHidden returns true if JPEG files should be created in a .photoprism sub directory (hidden).
|
||||
func (c *Config) JpegHidden() bool {
|
||||
return c.params.JpegHidden
|
||||
}
|
||||
|
||||
// JpegQuality returns the jpeg quality for resampling, use 95 for high-quality thumbs (25-100).
|
||||
func (c *Config) JpegQuality() int {
|
||||
if c.params.JpegQuality > 100 {
|
||||
|
|
|
@ -60,7 +60,7 @@ func NewTestParams() *Params {
|
|||
ReadOnly: false,
|
||||
DetectNSFW: true,
|
||||
UploadNSFW: false,
|
||||
SidecarHidden: true,
|
||||
SidecarPath: fs.HiddenPath,
|
||||
DarktableBin: "/usr/bin/darktable-cli",
|
||||
ExifToolBin: "/usr/bin/exiftool",
|
||||
AssetsPath: assetsPath,
|
||||
|
|
|
@ -38,9 +38,11 @@ const (
|
|||
TypeRaw = "raw"
|
||||
TypeText = "text"
|
||||
|
||||
RootDefault = ""
|
||||
RootImport = "import"
|
||||
RootPath = "/"
|
||||
RootOriginals = ""
|
||||
RootExamples = "examples"
|
||||
RootSidecar = "sidecar"
|
||||
RootImport = "import"
|
||||
RootPath = "/"
|
||||
|
||||
Updated = "updated"
|
||||
Created = "created"
|
||||
|
|
|
@ -86,7 +86,7 @@ func (m *Folder) SetValuesFromPath() {
|
|||
s = strings.TrimSpace(s)
|
||||
|
||||
if s == "" || s == RootPath {
|
||||
if m.Root == RootDefault {
|
||||
if m.Root == RootOriginals {
|
||||
m.FolderTitle = "Originals"
|
||||
|
||||
return
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
func TestNewFolder(t *testing.T) {
|
||||
t.Run("2020/05", func(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, "2020/05", nil)
|
||||
assert.Equal(t, RootDefault, folder.Root)
|
||||
folder := NewFolder(RootOriginals, "2020/05", nil)
|
||||
assert.Equal(t, RootOriginals, folder.Root)
|
||||
assert.Equal(t, "2020/05", folder.Path)
|
||||
assert.Equal(t, "May 2020", folder.FolderTitle)
|
||||
assert.Equal(t, "", folder.FolderDescription)
|
||||
|
@ -25,7 +25,7 @@ func TestNewFolder(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("/2020/05/01/", func(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, "/2020/05/01/", nil)
|
||||
folder := NewFolder(RootOriginals, "/2020/05/01/", nil)
|
||||
assert.Equal(t, "2020/05/01", folder.Path)
|
||||
assert.Equal(t, "May 2020", folder.FolderTitle)
|
||||
assert.Equal(t, 2020, folder.FolderYear)
|
||||
|
@ -43,7 +43,7 @@ func TestNewFolder(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("/2020/05/23/Iceland 2020", func(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, "/2020/05/23/Iceland 2020", nil)
|
||||
folder := NewFolder(RootOriginals, "/2020/05/23/Iceland 2020", nil)
|
||||
assert.Equal(t, "2020/05/23/Iceland 2020", folder.Path)
|
||||
assert.Equal(t, "Iceland 2020", folder.FolderTitle)
|
||||
assert.Equal(t, 2020, folder.FolderYear)
|
||||
|
@ -52,7 +52,7 @@ func TestNewFolder(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("/London/2020/05/23", func(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, "/London/2020/05/23", nil)
|
||||
folder := NewFolder(RootOriginals, "/London/2020/05/23", nil)
|
||||
assert.Equal(t, "London/2020/05/23", folder.Path)
|
||||
assert.Equal(t, "May 23, 2020", folder.FolderTitle)
|
||||
assert.Equal(t, 2020, folder.FolderYear)
|
||||
|
@ -61,7 +61,7 @@ func TestNewFolder(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, "", nil)
|
||||
folder := NewFolder(RootOriginals, "", nil)
|
||||
assert.Equal(t, "", folder.Path)
|
||||
assert.Equal(t, "Originals", folder.FolderTitle)
|
||||
assert.Equal(t, 0, folder.FolderYear)
|
||||
|
@ -70,7 +70,7 @@ func TestNewFolder(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("root", func(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, RootPath, nil)
|
||||
folder := NewFolder(RootOriginals, RootPath, nil)
|
||||
assert.Equal(t, "", folder.Path)
|
||||
assert.Equal(t, "Originals", folder.FolderTitle)
|
||||
assert.Equal(t, 0, folder.FolderYear)
|
||||
|
@ -80,7 +80,7 @@ func TestNewFolder(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFirstOrCreateFolder(t *testing.T) {
|
||||
folder := NewFolder(RootDefault, RootPath, nil)
|
||||
folder := NewFolder(RootOriginals, RootPath, nil)
|
||||
result := FirstOrCreateFolder(&folder)
|
||||
|
||||
if result == nil {
|
||||
|
@ -95,7 +95,7 @@ func TestFirstOrCreateFolder(t *testing.T) {
|
|||
t.Errorf("FolderCountry should be 'zz'")
|
||||
}
|
||||
|
||||
found := FindFolder(RootDefault, RootPath)
|
||||
found := FindFolder(RootOriginals, RootPath)
|
||||
|
||||
if found == nil {
|
||||
t.Fatal("found should not be nil")
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
|
||||
// Location used to associate photos to location
|
||||
type Location struct {
|
||||
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
|
||||
PlaceID string `gorm:"type:varbinary(16);" json:"-" yaml:"PlaceID"`
|
||||
ID string `gorm:"type:varbinary(24);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
|
||||
PlaceID string `gorm:"type:varbinary(24);" json:"-" yaml:"PlaceID"`
|
||||
Place *Place `gorm:"PRELOAD:true" json:"Place" yaml:"-"`
|
||||
LocName string `gorm:"type:varchar(255);" json:"Name" yaml:"Name,omitempty"`
|
||||
LocCategory string `gorm:"type:varchar(64);" json:"Category" yaml:"Category,omitempty"`
|
||||
|
|
|
@ -50,8 +50,8 @@ type Photo struct {
|
|||
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
||||
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone" yaml:"-"`
|
||||
PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID" yaml:"-"`
|
||||
LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID" yaml:"-"`
|
||||
PlaceID string `gorm:"type:varbinary(24);index;" json:"PlaceID" yaml:"-"`
|
||||
LocationID string `gorm:"type:varbinary(24);index;" json:"LocationID" yaml:"-"`
|
||||
LocSrc string `gorm:"type:varbinary(8);" json:"LocSrc" yaml:"LocSrc,omitempty"`
|
||||
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"Lat" yaml:"Lat,omitempty"`
|
||||
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"Lng" yaml:"Lng,omitempty"`
|
||||
|
|
|
@ -63,10 +63,6 @@ func (m *Photo) LoadFromYaml(fileName string) error {
|
|||
}
|
||||
|
||||
// YamlFileName returns the YAML backup file name.
|
||||
func (m *Photo) YamlFileName(originalsPath string, hidden bool) string {
|
||||
if hidden {
|
||||
return filepath.Join(originalsPath, m.PhotoPath, fs.HiddenPath, m.PhotoName) + fs.YamlExt
|
||||
}
|
||||
|
||||
return filepath.Join(originalsPath, m.PhotoPath, m.PhotoName) + fs.YamlExt
|
||||
func (m *Photo) YamlFileName(originalsPath, sidecarPath string) string {
|
||||
return fs.FileName(filepath.Join(originalsPath, m.PhotoPath, m.PhotoName), sidecarPath, originalsPath, fs.YamlExt, false)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
// Place used to associate photos to places
|
||||
type Place struct {
|
||||
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;" json:"PlaceID" yaml:"PlaceID"`
|
||||
ID string `gorm:"type:varbinary(24);primary_key;auto_increment:false;" json:"PlaceID" yaml:"PlaceID"`
|
||||
LocLabel string `gorm:"type:varbinary(768);unique_index;" json:"Label" yaml:"Label"`
|
||||
LocCity string `gorm:"type:varchar(255);" json:"City" yaml:"City,omitempty"`
|
||||
LocState string `gorm:"type:varchar(255);" json:"State" yaml:"State,omitempty"`
|
||||
|
|
23
internal/photoprism/config.go
Normal file
23
internal/photoprism/config.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package photoprism
|
||||
|
||||
import (
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
var conf *config.Config
|
||||
|
||||
func SetConfig(c *config.Config) {
|
||||
if c == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
conf = c
|
||||
}
|
||||
|
||||
func Config() *config.Config {
|
||||
if conf == nil {
|
||||
panic("config is nil")
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
|
@ -58,7 +58,7 @@ func (c *Convert) Start(path string) error {
|
|||
}
|
||||
|
||||
ignore.Log = func(fileName string) {
|
||||
log.Infof(`convert: ignored "%s"`, fs.RelativeName(fileName, path))
|
||||
log.Infof(`convert: ignored "%s"`, fs.Rel(fileName, path))
|
||||
}
|
||||
|
||||
err := godirwalk.Walk(path, &godirwalk.Options{
|
||||
|
@ -134,8 +134,8 @@ func (c *Convert) ConvertCommand(mf *MediaFile, jpegName string, xmpName string)
|
|||
}
|
||||
|
||||
// ToJson uses exiftool to export metadata to a json file.
|
||||
func (c *Convert) ToJson(mf *MediaFile, hidden bool) (*MediaFile, error) {
|
||||
jsonName := fs.TypeJson.FindSub(mf.FileName(), fs.HiddenPath, c.conf.Settings().Index.Group)
|
||||
func (c *Convert) ToJson(mf *MediaFile) (*MediaFile, error) {
|
||||
jsonName := fs.TypeJson.FindFirst(mf.FileName(), []string{c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.HiddenPath}, c.conf.OriginalsPath(), c.conf.Settings().Index.Group)
|
||||
|
||||
result, err := NewMediaFile(jsonName)
|
||||
|
||||
|
@ -147,15 +147,11 @@ func (c *Convert) ToJson(mf *MediaFile, hidden bool) (*MediaFile, error) {
|
|||
return nil, fmt.Errorf("convert: metadata export to json disabled in read only mode (%s)", mf.RelativeName(c.conf.OriginalsPath()))
|
||||
}
|
||||
|
||||
if hidden {
|
||||
jsonName = mf.HiddenName(".json", c.conf.Settings().Index.Group)
|
||||
} else {
|
||||
jsonName = mf.RelatedName(".json", c.conf.Settings().Index.Group)
|
||||
}
|
||||
jsonName = fs.FileName(mf.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), ".json", c.conf.Settings().Index.Group)
|
||||
|
||||
fileName := mf.RelativeName(c.conf.OriginalsPath())
|
||||
|
||||
log.Infof("convert: %s -> %s", fileName, fs.RelativeName(jsonName, c.conf.OriginalsPath()))
|
||||
log.Infof("convert: %s -> %s", fileName, filepath.Base(jsonName))
|
||||
|
||||
cmd := exec.Command(c.conf.ExifToolBin(), "-j", mf.FileName())
|
||||
|
||||
|
@ -188,7 +184,7 @@ func (c *Convert) ToJson(mf *MediaFile, hidden bool) (*MediaFile, error) {
|
|||
}
|
||||
|
||||
// ToJpeg converts a single image file to JPEG if possible.
|
||||
func (c *Convert) ToJpeg(image *MediaFile, hidden bool) (*MediaFile, error) {
|
||||
func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
|
||||
if c.conf.ReadOnly() {
|
||||
return nil, errors.New("convert: disabled in read-only mode")
|
||||
}
|
||||
|
@ -201,7 +197,7 @@ func (c *Convert) ToJpeg(image *MediaFile, hidden bool) (*MediaFile, error) {
|
|||
return image, nil
|
||||
}
|
||||
|
||||
jpegName := fs.TypeJpeg.FindSub(image.FileName(), fs.HiddenPath, c.conf.Settings().Index.Group)
|
||||
jpegName := fs.TypeJpeg.FindFirst(image.FileName(), []string{c.conf.SidecarPath(), fs.HiddenPath}, c.conf.OriginalsPath(), c.conf.Settings().Index.Group)
|
||||
|
||||
mediaFile, err := NewMediaFile(jpegName)
|
||||
|
||||
|
@ -213,15 +209,10 @@ func (c *Convert) ToJpeg(image *MediaFile, hidden bool) (*MediaFile, error) {
|
|||
return nil, fmt.Errorf("convert: disabled in read only mode (%s)", image.RelativeName(c.conf.OriginalsPath()))
|
||||
}
|
||||
|
||||
if hidden {
|
||||
jpegName = image.HiddenName(fs.JpegExt, c.conf.Settings().Index.Group)
|
||||
} else {
|
||||
jpegName = image.RelatedName(fs.JpegExt, c.conf.Settings().Index.Group)
|
||||
}
|
||||
|
||||
jpegName = fs.FileName(image.FileName(), c.conf.SidecarPath(), c.conf.OriginalsPath(), fs.JpegExt, c.conf.Settings().Index.Group)
|
||||
fileName := image.RelativeName(c.conf.OriginalsPath())
|
||||
|
||||
log.Infof("convert: %s -> %s", fileName, fs.RelativeName(jpegName, c.conf.OriginalsPath()))
|
||||
log.Infof("convert: %s -> %s", fileName, filepath.Base(jpegName))
|
||||
|
||||
xmpName := fs.TypeXMP.Find(image.FileName(), c.conf.Settings().Index.Group)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
|
||||
t.Run("gopher-video.mp4", func(t *testing.T) {
|
||||
fileName := conf.ExamplesPath() + "/gopher-video.mp4"
|
||||
outputName := conf.ExamplesPath() + "/gopher-video.jpg"
|
||||
outputName := conf.ExamplesPath() + "/.photoprism/gopher-video.jpg"
|
||||
|
||||
_ = os.Remove(outputName)
|
||||
|
||||
|
@ -40,7 +40,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
jpegFile, err := convert.ToJpeg(mf, false)
|
||||
jpegFile, err := convert.ToJpeg(mf)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -67,7 +67,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imageJpeg, err := convert.ToJpeg(mf, true)
|
||||
imageJpeg, err := convert.ToJpeg(mf)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -89,7 +89,7 @@ func TestConvert_ToJpeg(t *testing.T) {
|
|||
t.Fatalf("%s for %s", err.Error(), rawFilename)
|
||||
}
|
||||
|
||||
imageRaw, err := convert.ToJpeg(rawMediaFile, true)
|
||||
imageRaw, err := convert.ToJpeg(rawMediaFile)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("%s for %s", err.Error(), rawFilename)
|
||||
|
@ -128,7 +128,7 @@ func TestConvert_ToJson(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
jsonFile, err := convert.ToJson(mf, true)
|
||||
jsonFile, err := convert.ToJson(mf)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -150,7 +150,7 @@ func TestConvert_ToJson(t *testing.T) {
|
|||
|
||||
t.Run("IMG_4120.JPG", func(t *testing.T) {
|
||||
fileName := conf.ExamplesPath() + "/IMG_4120.JPG"
|
||||
outputName := conf.ExamplesPath() + "/IMG_4120.json"
|
||||
outputName := conf.ExamplesPath() + "/.photoprism/IMG_4120.json"
|
||||
|
||||
_ = os.Remove(outputName)
|
||||
|
||||
|
@ -163,7 +163,7 @@ func TestConvert_ToJson(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
jsonFile, err := convert.ToJson(mf, false)
|
||||
jsonFile, err := convert.ToJson(mf)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -196,7 +196,7 @@ func TestConvert_ToJson(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
jsonFile, err := convert.ToJson(mf, true)
|
||||
jsonFile, err := convert.ToJson(mf)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -222,16 +222,20 @@ func TestConvert_Start(t *testing.T) {
|
|||
|
||||
convert := NewConvert(conf)
|
||||
|
||||
convert.Start(conf.ImportPath())
|
||||
err := convert.Start(conf.ImportPath())
|
||||
|
||||
jpegFilename := conf.ImportPath() + "/raw/canon_eos_6d.jpg"
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
jpegFilename := conf.ImportPath() + "/raw/.photoprism/canon_eos_6d.jpg"
|
||||
|
||||
assert.True(t, fs.FileExists(jpegFilename), "Jpeg file was not found - is Darktable installed?")
|
||||
|
||||
image, err := NewMediaFile(jpegFilename)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, jpegFilename, image.fileName, "FileName must be the same")
|
||||
|
@ -240,7 +244,7 @@ func TestConvert_Start(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "Canon EOS 6D", infoRaw.CameraModel, "UpdateCamera model should be Canon EOS M10")
|
||||
|
||||
existingJpegFilename := conf.ImportPath() + "/raw/IMG_2567.jpg"
|
||||
existingJpegFilename := conf.ImportPath() + "/raw/.photoprism/IMG_2567.jpg"
|
||||
|
||||
oldHash := fs.Hash(existingJpegFilename)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ type ConvertJob struct {
|
|||
|
||||
func ConvertWorker(jobs <-chan ConvertJob) {
|
||||
for job := range jobs {
|
||||
if _, err := job.convert.ToJpeg(job.image, job.convert.conf.JpegHidden()); err != nil {
|
||||
if _, err := job.convert.ToJpeg(job.image); err != nil {
|
||||
fileName := job.image.RelativeName(job.convert.conf.OriginalsPath())
|
||||
log.Errorf("convert: could not create jpeg for %s (%s)", fileName, strings.TrimSpace(err.Error()))
|
||||
}
|
||||
|
|
20
internal/photoprism/filename.go
Normal file
20
internal/photoprism/filename.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package photoprism
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
func FileName(fileRoot, fileName string) string {
|
||||
switch fileRoot {
|
||||
case entity.RootSidecar:
|
||||
return path.Join(Config().SidecarPath(), fileName)
|
||||
case entity.RootImport:
|
||||
return path.Join(Config().ImportPath(), fileName)
|
||||
case entity.RootExamples:
|
||||
return path.Join(Config().ExamplesPath(), fileName)
|
||||
default:
|
||||
return path.Join(Config().OriginalsPath(), fileName)
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool {
|
|||
}
|
||||
|
||||
ignore.Log = func(fileName string) {
|
||||
log.Infof(`import: ignored "%s"`, fs.RelativeName(fileName, importPath))
|
||||
log.Infof(`import: ignored "%s"`, fs.Rel(fileName, importPath))
|
||||
}
|
||||
|
||||
err := godirwalk.Walk(importPath, &godirwalk.Options{
|
||||
|
@ -117,7 +117,7 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool {
|
|||
|
||||
if skip, result := fs.SkipWalk(fileName, isDir, isSymlink, done, ignore); skip {
|
||||
if isDir && result != filepath.SkipDir {
|
||||
folder := entity.NewFolder(entity.RootImport, fs.RelativeName(fileName, imp.conf.ImportPath()), nil)
|
||||
folder := entity.NewFolder(entity.RootImport, fs.Rel(fileName, imp.conf.ImportPath()), nil)
|
||||
|
||||
if err := folder.Create(); err == nil {
|
||||
log.Infof("import: added folder /%s", folder.Path)
|
||||
|
@ -182,9 +182,9 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool {
|
|||
for _, directory := range directories {
|
||||
if fs.IsEmpty(directory) {
|
||||
if err := os.Remove(directory); err != nil {
|
||||
log.Errorf("import: could not delete empty folder %s (%s)", txt.Quote(fs.RelativeName(directory, importPath)), err)
|
||||
log.Errorf("import: could not delete empty folder %s (%s)", txt.Quote(fs.Rel(directory, importPath)), err)
|
||||
} else {
|
||||
log.Infof("import: deleted empty folder %s", txt.Quote(fs.RelativeName(directory, importPath)))
|
||||
log.Infof("import: deleted empty folder %s", txt.Quote(fs.Rel(directory, importPath)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool {
|
|||
}
|
||||
|
||||
if err := os.Remove(file); err != nil {
|
||||
log.Errorf("import: could not remove %s (%s)", txt.Quote(fs.RelativeName(file, importPath)), err.Error())
|
||||
log.Errorf("import: could not remove %s (%s)", txt.Quote(fs.Rel(file, importPath)), err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ func (imp *Import) DestinationFilename(mainFile *MediaFile, mediaFile *MediaFile
|
|||
|
||||
if !mediaFile.IsSidecar() {
|
||||
if f, err := entity.FirstFileByHash(mediaFile.Hash()); err == nil {
|
||||
existingFilename := filepath.Join(imp.conf.OriginalsPath(), f.FileName)
|
||||
existingFilename := FileName(f.FileRoot, f.FileName)
|
||||
if fs.FileExists(existingFilename) {
|
||||
return existingFilename, fmt.Errorf("%s is identical to %s (sha1 %s)", txt.Quote(filepath.Base(mediaFile.FileName())), txt.Quote(f.FileName), mediaFile.Hash())
|
||||
} else {
|
||||
|
@ -249,7 +249,7 @@ func (imp *Import) DestinationFilename(mainFile *MediaFile, mediaFile *MediaFile
|
|||
|
||||
for fs.FileExists(result) {
|
||||
if mediaFile.Hash() == fs.Hash(result) {
|
||||
return result, fmt.Errorf("%s already exists", txt.Quote(fs.RelativeName(result, imp.originalsPath())))
|
||||
return result, fmt.Errorf("%s already exists", txt.Quote(fs.Rel(result, imp.originalsPath())))
|
||||
}
|
||||
|
||||
iteration++
|
||||
|
|
|
@ -29,7 +29,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
importPath := job.ImportOpt.Path
|
||||
|
||||
if related.Main == nil {
|
||||
log.Warnf("import: no media file found for %s", txt.Quote(fs.RelativeName(job.FileName, importPath)))
|
||||
log.Warnf("import: no media file found for %s", txt.Quote(fs.Rel(job.FileName, importPath)))
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -50,18 +50,18 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
|
||||
if related.Main.HasSameName(f) {
|
||||
destinationMainFilename = destinationFilename
|
||||
log.Infof("import: moving main %s file %s to %s", f.FileType(), txt.Quote(relativeFilename), txt.Quote(fs.RelativeName(destinationFilename, imp.originalsPath())))
|
||||
log.Infof("import: moving main %s file %s to %s", f.FileType(), txt.Quote(relativeFilename), txt.Quote(fs.Rel(destinationFilename, imp.originalsPath())))
|
||||
} else {
|
||||
log.Infof("import: moving related %s file %s to %s", f.FileType(), txt.Quote(relativeFilename), txt.Quote(fs.RelativeName(destinationFilename, imp.originalsPath())))
|
||||
log.Infof("import: moving related %s file %s to %s", f.FileType(), txt.Quote(relativeFilename), txt.Quote(fs.Rel(destinationFilename, imp.originalsPath())))
|
||||
}
|
||||
|
||||
if opt.Move {
|
||||
if err := f.Move(destinationFilename); err != nil {
|
||||
log.Errorf("import: could not move file to %s (%s)", txt.Quote(fs.RelativeName(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
log.Errorf("import: could not move file to %s (%s)", txt.Quote(fs.Rel(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
}
|
||||
} else {
|
||||
if err := f.Copy(destinationFilename); err != nil {
|
||||
log.Errorf("import: could not copy file to %s (%s)", txt.Quote(fs.RelativeName(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
log.Errorf("import: could not copy file to %s (%s)", txt.Quote(fs.Rel(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -69,7 +69,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
|
||||
if opt.RemoveExistingFiles {
|
||||
if err := f.Remove(); err != nil {
|
||||
log.Errorf("import: could not delete %s (%s)", txt.Quote(fs.RelativeName(f.FileName(), importPath)), err.Error())
|
||||
log.Errorf("import: could not delete %s (%s)", txt.Quote(fs.Rel(f.FileName(), importPath)), err.Error())
|
||||
} else {
|
||||
log.Infof("import: deleted %s (already exists)", txt.Quote(relativeFilename))
|
||||
}
|
||||
|
@ -81,16 +81,16 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
f, err := NewMediaFile(destinationMainFilename)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("import: could not import %s (%s)", txt.Quote(fs.RelativeName(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
log.Errorf("import: could not import %s (%s)", txt.Quote(fs.Rel(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if !f.HasJpeg() {
|
||||
if jpegFile, err := imp.convert.ToJpeg(f, imp.conf.JpegHidden()); err != nil {
|
||||
if jpegFile, err := imp.convert.ToJpeg(f); err != nil {
|
||||
log.Errorf("import: creating jpeg failed (%s)", err.Error())
|
||||
continue
|
||||
} else {
|
||||
log.Infof("import: %s created", fs.RelativeName(jpegFile.FileName(), imp.originalsPath()))
|
||||
log.Infof("import: %s created", fs.Rel(jpegFile.FileName(), imp.originalsPath()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,17 +104,17 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
|
||||
if imp.conf.SidecarJson() && !f.HasJson() {
|
||||
if jsonFile, err := imp.convert.ToJson(f, imp.conf.SidecarHidden()); err != nil {
|
||||
if jsonFile, err := imp.convert.ToJson(f); err != nil {
|
||||
log.Errorf("import: creating json sidecar file failed (%s)", err.Error())
|
||||
} else {
|
||||
log.Infof("import: %s created", fs.RelativeName(jsonFile.FileName(), imp.originalsPath()))
|
||||
log.Infof("import: %s created", fs.Rel(jsonFile.FileName(), imp.originalsPath()))
|
||||
}
|
||||
}
|
||||
|
||||
related, err := f.RelatedFiles(imp.conf.Settings().Index.Group)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("import: could not index %s (%s)", txt.Quote(fs.RelativeName(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
log.Errorf("import: could not index %s (%s)", txt.Quote(fs.Rel(destinationMainFilename, imp.originalsPath())), err.Error())
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
continue
|
||||
}
|
||||
} else {
|
||||
log.Warnf("import: no main file for %s (conversion to jpeg failed?)", fs.RelativeName(destinationMainFilename, imp.originalsPath()))
|
||||
log.Warnf("import: no main file for %s (conversion to jpeg failed?)", fs.Rel(destinationMainFilename, imp.originalsPath()))
|
||||
}
|
||||
|
||||
for _, f := range related.Files {
|
||||
|
|
|
@ -107,7 +107,7 @@ func (ind *Index) Start(opt IndexOptions) map[string]bool {
|
|||
}
|
||||
|
||||
ignore.Log = func(fileName string) {
|
||||
log.Infof(`index: ignored "%s"`, fs.RelativeName(fileName, originalsPath))
|
||||
log.Infof(`index: ignored "%s"`, fs.Rel(fileName, originalsPath))
|
||||
}
|
||||
|
||||
err := godirwalk.Walk(optionsPath, &godirwalk.Options{
|
||||
|
@ -121,7 +121,7 @@ func (ind *Index) Start(opt IndexOptions) map[string]bool {
|
|||
|
||||
if skip, result := fs.SkipWalk(fileName, isDir, isSymlink, done, ignore); skip {
|
||||
if isDir && result != filepath.SkipDir {
|
||||
folder := entity.NewFolder(entity.RootDefault, fs.RelativeName(fileName, originalsPath), nil)
|
||||
folder := entity.NewFolder(entity.RootOriginals, fs.Rel(fileName, originalsPath), nil)
|
||||
|
||||
if err := folder.Create(); err == nil {
|
||||
log.Infof("index: added folder /%s", folder.Path)
|
||||
|
|
|
@ -68,13 +68,12 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
description := entity.Details{}
|
||||
labels := classify.Labels{}
|
||||
|
||||
fileBase := m.Base(ind.conf.Settings().Index.Group)
|
||||
filePath := m.RelativePath(ind.originalsPath())
|
||||
fileRoot := entity.RootDefault
|
||||
fileName := m.RelativeName(ind.originalsPath())
|
||||
quotedName := txt.Quote(m.RelativeName(ind.originalsPath()))
|
||||
fileHash := ""
|
||||
fileRoot, fileBase, filePath, fileName := m.PathNameInfo()
|
||||
|
||||
quotedName := txt.Quote(fileName)
|
||||
fileSize, fileModified := m.Stat()
|
||||
|
||||
fileHash := ""
|
||||
fileChanged := true
|
||||
fileExists := false
|
||||
photoExists := false
|
||||
|
@ -95,7 +94,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
fileQuery = entity.UnscopedDb().First(&file, "file_hash = ?", fileHash)
|
||||
fileExists = fileQuery.Error == nil
|
||||
|
||||
if fileExists && fs.FileExists(filepath.Join(ind.conf.OriginalsPath(), file.FileName)) {
|
||||
if fileExists && fs.FileExists(FileName(file.FileRoot, file.FileName)) {
|
||||
result.Status = IndexDuplicate
|
||||
return result
|
||||
}
|
||||
|
@ -139,11 +138,11 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
} else {
|
||||
photo.PhotoQuality = -1
|
||||
|
||||
if yamlName := fs.TypeYaml.FindSub(m.FileName(), fs.HiddenPath, ind.conf.Settings().Index.Group); yamlName != "" {
|
||||
if yamlName := fs.TypeYaml.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), Config().Settings().Index.Group); yamlName != "" {
|
||||
if err := photo.LoadFromYaml(yamlName); err != nil {
|
||||
log.Errorf("index: %s (restore from yaml) for %s", err.Error(), quotedName)
|
||||
} else {
|
||||
log.Infof("index: restored from %s", txt.Quote(fs.RelativeName(yamlName, ind.originalsPath())))
|
||||
log.Infof("index: restored from %s", txt.Quote(fs.Rel(yamlName, Config().OriginalsPath())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +185,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
switch {
|
||||
case m.IsJpeg():
|
||||
// Color information
|
||||
if p, err := m.Colors(ind.thumbPath()); err != nil {
|
||||
if p, err := m.Colors(Config().ThumbPath()); err != nil {
|
||||
log.Errorf("index: %s for %s", err.Error(), quotedName)
|
||||
} else {
|
||||
file.FileMainColor = p.MainColor.Name()
|
||||
|
@ -275,11 +274,11 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
if file.FilePrimary {
|
||||
primaryFile = file
|
||||
|
||||
if !ind.conf.TensorFlowOff() {
|
||||
if !Config().TensorFlowOff() {
|
||||
// Image classification via TensorFlow.
|
||||
labels = ind.classifyImage(m)
|
||||
|
||||
if !photoExists && ind.conf.Settings().Features.Private && ind.conf.DetectNSFW() {
|
||||
if !photoExists && Config().Settings().Features.Private && Config().DetectNSFW() {
|
||||
photo.PhotoPrivate = ind.NSFW(m)
|
||||
}
|
||||
}
|
||||
|
@ -532,13 +531,13 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
}
|
||||
|
||||
// Write YAML sidecar file (optional).
|
||||
if file.FilePrimary && ind.conf.SidecarYaml() {
|
||||
yamlFile := photo.YamlFileName(ind.originalsPath(), ind.conf.SidecarHidden())
|
||||
if file.FilePrimary && Config().SidecarYaml() {
|
||||
yamlFile := photo.YamlFileName(Config().OriginalsPath(), Config().SidecarPath())
|
||||
|
||||
if err := photo.SaveAsYaml(yamlFile); err != nil {
|
||||
log.Errorf("index: %s (update yaml) for %s", err.Error(), quotedName)
|
||||
} else {
|
||||
log.Infof("index: updated yaml file %s", txt.Quote(fs.RelativeName(yamlFile, ind.originalsPath())))
|
||||
log.Infof("index: updated yaml file %s", txt.Quote(fs.Rel(yamlFile, Config().OriginalsPath())))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,7 +546,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
|
||||
// NSFW returns true if media file might be offensive and detection is enabled.
|
||||
func (ind *Index) NSFW(jpeg *MediaFile) bool {
|
||||
filename, err := jpeg.Thumbnail(ind.thumbPath(), "fit_720")
|
||||
filename, err := jpeg.Thumbnail(Config().ThumbPath(), "fit_720")
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@ -559,7 +558,7 @@ func (ind *Index) NSFW(jpeg *MediaFile) bool {
|
|||
return false
|
||||
} else {
|
||||
if nsfwLabels.NSFW(nsfw.ThresholdHigh) {
|
||||
log.Warnf("index: %s might contain offensive content", txt.Quote(jpeg.RelativeName(ind.originalsPath())))
|
||||
log.Warnf("index: %s might contain offensive content", txt.Quote(jpeg.RelativeName(Config().OriginalsPath())))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -582,7 +581,7 @@ func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
|||
var labels classify.Labels
|
||||
|
||||
for _, thumb := range thumbs {
|
||||
filename, err := jpeg.Thumbnail(ind.thumbPath(), thumb)
|
||||
filename, err := jpeg.Thumbnail(Config().ThumbPath(), thumb)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
|
|
@ -24,7 +24,7 @@ func IndexWorker(jobs <-chan IndexJob) {
|
|||
|
||||
// Skip sidecar files without related media file.
|
||||
if related.Main == nil {
|
||||
log.Warnf("index: no media file found for %s", txt.Quote(fs.RelativeName(job.FileName, ind.originalsPath())))
|
||||
log.Warnf("index: no media file found for %s", txt.Quote(fs.Rel(job.FileName, ind.originalsPath())))
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -37,11 +37,11 @@ func IndexWorker(jobs <-chan IndexJob) {
|
|||
f := related.Main
|
||||
|
||||
if opt.Convert && !f.HasJpeg() {
|
||||
if jpegFile, err := ind.convert.ToJpeg(f, ind.conf.JpegHidden()); err != nil {
|
||||
if jpegFile, err := ind.convert.ToJpeg(f); err != nil {
|
||||
log.Errorf("index: creating jpeg failed (%s)", err.Error())
|
||||
continue
|
||||
} else {
|
||||
log.Infof("index: %s created", fs.RelativeName(jpegFile.FileName(), ind.originalsPath()))
|
||||
log.Infof("index: %s created", fs.Rel(jpegFile.FileName(), ind.originalsPath()))
|
||||
|
||||
if err := jpegFile.ResampleDefault(ind.thumbPath(), false); err != nil {
|
||||
log.Errorf("index: could not create default thumbnails (%s)", err.Error())
|
||||
|
@ -53,10 +53,10 @@ func IndexWorker(jobs <-chan IndexJob) {
|
|||
}
|
||||
|
||||
if ind.conf.SidecarJson() && !f.HasJson() {
|
||||
if jsonFile, err := ind.convert.ToJson(f, ind.conf.SidecarHidden()); err != nil {
|
||||
if jsonFile, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Errorf("index: creating json sidecar file failed (%s)", err.Error())
|
||||
} else {
|
||||
log.Infof("index: %s created", fs.RelativeName(jsonFile.FileName(), ind.originalsPath()))
|
||||
log.Infof("index: %s created", fs.Rel(jsonFile.FileName(), ind.originalsPath()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -253,10 +253,8 @@ func (m *MediaFile) EditedName() string {
|
|||
|
||||
// RelatedFiles returns files which are related to this file.
|
||||
func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err error) {
|
||||
baseFilename := m.AbsBase(stripSequence)
|
||||
// escape any meta characters in the file name
|
||||
baseFilename = regexp.QuoteMeta(baseFilename)
|
||||
matches, err := filepath.Glob(baseFilename + "*")
|
||||
matches, err := filepath.Glob(regexp.QuoteMeta(m.AbsBase(stripSequence)) + "*")
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
|
@ -292,7 +290,7 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
|
|||
|
||||
// Add hidden JPEG if exists.
|
||||
if !result.ContainsJpeg() && result.Main != nil {
|
||||
if jpegName := fs.TypeJpeg.FindSub(result.Main.FileName(), fs.HiddenPath, stripSequence); jpegName != "" {
|
||||
if jpegName := fs.TypeJpeg.FindFirst(result.Main.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); jpegName != "" {
|
||||
if resultFile, err := NewMediaFile(jpegName); err == nil {
|
||||
result.Files = append(result.Files, resultFile)
|
||||
}
|
||||
|
@ -304,6 +302,27 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// PathNameInfo returns file name infos for indexing.
|
||||
func (m *MediaFile) PathNameInfo() (fileRoot, fileBase, relativePath, relativeName string) {
|
||||
fileRoot = m.Root()
|
||||
var rootPath string
|
||||
|
||||
switch fileRoot {
|
||||
case entity.RootSidecar:
|
||||
rootPath = Config().SidecarPath()
|
||||
case entity.RootImport:
|
||||
rootPath = Config().ImportPath()
|
||||
default:
|
||||
rootPath = Config().OriginalsPath()
|
||||
}
|
||||
|
||||
fileBase = m.Base(Config().Settings().Index.Group)
|
||||
relativePath = m.RelativePath(rootPath)
|
||||
relativeName = m.RelativeName(rootPath)
|
||||
|
||||
return fileRoot, fileBase, relativePath, relativeName
|
||||
}
|
||||
|
||||
// FileName returns the filename.
|
||||
func (m *MediaFile) FileName() string {
|
||||
return m.fileName
|
||||
|
@ -319,9 +338,9 @@ func (m *MediaFile) SetFileName(fileName string) {
|
|||
m.fileName = fileName
|
||||
}
|
||||
|
||||
// RelativeName returns the relative filename.
|
||||
// Rel returns the relative filename.
|
||||
func (m *MediaFile) RelativeName(directory string) string {
|
||||
return fs.RelativeName(m.fileName, directory)
|
||||
return fs.Rel(m.fileName, directory)
|
||||
}
|
||||
|
||||
// RelativePath returns the relative path without filename.
|
||||
|
@ -355,7 +374,7 @@ func (m *MediaFile) RelativePath(directory string) string {
|
|||
return pathname
|
||||
}
|
||||
|
||||
// RelativeBase returns the relative filename.
|
||||
// RelBase returns the relative filename.
|
||||
func (m *MediaFile) RelativeBase(directory string, stripSequence bool) string {
|
||||
if relativePath := m.RelativePath(directory); relativePath != "" {
|
||||
return filepath.Join(relativePath, m.Base(stripSequence))
|
||||
|
@ -364,31 +383,53 @@ func (m *MediaFile) RelativeBase(directory string, stripSequence bool) string {
|
|||
return m.Base(stripSequence)
|
||||
}
|
||||
|
||||
// Directory returns the directory
|
||||
// Directory returns the file path.
|
||||
func (m *MediaFile) Directory() string {
|
||||
return filepath.Dir(m.fileName)
|
||||
}
|
||||
|
||||
// SubDirectory returns a sub directory name.
|
||||
func (m *MediaFile) SubDirectory(dir string) string {
|
||||
return filepath.Join(filepath.Dir(m.fileName), dir)
|
||||
}
|
||||
|
||||
// Base returns the filename base without any extensions and path.
|
||||
func (m *MediaFile) Base(stripSequence bool) string {
|
||||
return fs.Base(m.FileName(), stripSequence)
|
||||
}
|
||||
|
||||
// Base returns the filename base without any extensions and path.
|
||||
func (m *MediaFile) Root() string {
|
||||
if strings.HasPrefix(m.FileName(), Config().OriginalsPath()) {
|
||||
return entity.RootOriginals
|
||||
}
|
||||
|
||||
importPath := Config().ImportPath()
|
||||
|
||||
if importPath != "" && strings.HasPrefix(m.FileName(), importPath) {
|
||||
return entity.RootImport
|
||||
}
|
||||
|
||||
sidecarPath := Config().SidecarPath()
|
||||
|
||||
if sidecarPath != "" && strings.HasPrefix(m.FileName(), sidecarPath) {
|
||||
return entity.RootSidecar
|
||||
}
|
||||
|
||||
examplesPath := Config().ExamplesPath()
|
||||
|
||||
if examplesPath != "" && strings.HasPrefix(m.FileName(), examplesPath) {
|
||||
return entity.RootExamples
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// AbsBase returns the directory and base filename without any extensions.
|
||||
func (m *MediaFile) AbsBase(stripSequence bool) string {
|
||||
return fs.AbsBase(m.FileName(), stripSequence)
|
||||
}
|
||||
|
||||
// HiddenName returns the a filename with the same base name and a given extension in a hidden sub directory.
|
||||
func (m *MediaFile) HiddenName(fileExt string, stripSequence bool) string {
|
||||
return fs.SubFileName(m.FileName(), fs.HiddenPath, fileExt, stripSequence)
|
||||
}
|
||||
|
||||
// RelatedName returns the a filename with the same base name and a given extension in the same directory.
|
||||
func (m *MediaFile) RelatedName(fileExt string, stripSequence bool) string {
|
||||
return m.AbsBase(stripSequence) + fileExt
|
||||
}
|
||||
|
||||
// MimeType returns the mime type.
|
||||
func (m *MediaFile) MimeType() string {
|
||||
if m.mimeType != "" {
|
||||
|
@ -597,7 +638,7 @@ func (m *MediaFile) Jpeg() (*MediaFile, error) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
jpegFilename := fs.TypeJpeg.FindSub(m.FileName(), fs.HiddenPath, false)
|
||||
jpegFilename := fs.TypeJpeg.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false)
|
||||
|
||||
if jpegFilename == "" {
|
||||
return nil, fmt.Errorf("no jpeg found for %s", m.FileName())
|
||||
|
@ -612,7 +653,7 @@ func (m *MediaFile) HasJpeg() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
return fs.TypeJpeg.FindSub(m.FileName(), fs.HiddenPath, false) != ""
|
||||
return fs.TypeJpeg.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) != ""
|
||||
}
|
||||
|
||||
// HasJson returns true if this file has or is a json sidecar file.
|
||||
|
@ -621,7 +662,7 @@ func (m *MediaFile) HasJson() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
return fs.TypeJson.FindSub(m.FileName(), fs.HiddenPath, false) != ""
|
||||
return fs.TypeJson.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false) != ""
|
||||
}
|
||||
|
||||
func (m *MediaFile) decodeDimensions() error {
|
||||
|
|
|
@ -20,7 +20,7 @@ func (m *MediaFile) MetaData() (result meta.Data) {
|
|||
err = errors.New("not a photo")
|
||||
}
|
||||
|
||||
if jsonFile := fs.TypeJson.FindSub(m.FileName(), fs.HiddenPath, false); jsonFile == "" {
|
||||
if jsonFile := fs.TypeJson.FindFirst(m.FileName(), []string{Config().OriginalsPath(), Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), false); jsonFile == "" {
|
||||
log.Debugf("mediafile: no json sidecar file found for %s", txt.Quote(filepath.Base(m.FileName())))
|
||||
} else if jsonErr := m.metaData.JSON(jsonFile, m.BaseName()); jsonErr != nil {
|
||||
log.Debug(jsonErr)
|
||||
|
|
|
@ -130,17 +130,23 @@ func TestMediaFile_Exif_HEIF(t *testing.T) {
|
|||
|
||||
img, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
||||
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
info := img.MetaData()
|
||||
|
||||
assert.IsType(t, meta.Data{}, info)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
convert := NewConvert(conf)
|
||||
|
||||
jpeg, err := convert.ToJpeg(img, true)
|
||||
jpeg, err := convert.ToJpeg(img)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("JPEG FILENAME: %s", jpeg.FileName())
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ func TestMain(m *testing.M) {
|
|||
log.SetLevel(logrus.DebugLevel)
|
||||
|
||||
c := config.TestConfig()
|
||||
SetConfig(c)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package photoprism
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
|
@ -30,11 +29,6 @@ func NewPurge(conf *config.Config) *Purge {
|
|||
return instance
|
||||
}
|
||||
|
||||
// originalsPath returns the original media files path as string.
|
||||
func (prg *Purge) originalsPath() string {
|
||||
return prg.conf.OriginalsPath()
|
||||
}
|
||||
|
||||
// Start removes missing files from search results.
|
||||
func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPhotos map[string]bool, err error) {
|
||||
var ignore map[string]bool
|
||||
|
@ -47,7 +41,6 @@ func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPh
|
|||
|
||||
purgedFiles = make(map[string]bool)
|
||||
purgedPhotos = make(map[string]bool)
|
||||
originalsPath := prg.originalsPath()
|
||||
|
||||
if err := mutex.MainWorker.Start(); err != nil {
|
||||
err = fmt.Errorf("purge: %s", err.Error())
|
||||
|
@ -69,7 +62,7 @@ func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPh
|
|||
offset := 0
|
||||
|
||||
for {
|
||||
files, err := query.ExistingFiles(limit, offset, opt.Path)
|
||||
files, err := query.Files(limit, offset, opt.Path, true)
|
||||
|
||||
if err != nil {
|
||||
return purgedFiles, purgedPhotos, err
|
||||
|
@ -84,16 +77,29 @@ func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPh
|
|||
return purgedFiles, purgedPhotos, errors.New("purge canceled")
|
||||
}
|
||||
|
||||
fileName := path.Join(prg.conf.OriginalsPath(), file.FileName)
|
||||
fileName := FileName(file.FileRoot, file.FileName)
|
||||
|
||||
if ignore[fileName] || purgedFiles[fileName] {
|
||||
continue
|
||||
}
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
if file.FileMissing {
|
||||
if fs.FileExists(fileName) {
|
||||
if opt.Dry {
|
||||
log.Infof("purge: found %s", txt.Quote(file.FileName))
|
||||
continue
|
||||
}
|
||||
|
||||
if err := file.Update("FileMissing", false); err != nil {
|
||||
log.Errorf("purge: %s", err)
|
||||
} else {
|
||||
log.Infof("purge: found %s", txt.Quote(file.FileName))
|
||||
}
|
||||
}
|
||||
} else if !fs.FileExists(fileName) {
|
||||
if opt.Dry {
|
||||
purgedFiles[fileName] = true
|
||||
log.Infof("purge: file %s would be removed", txt.Quote(fs.RelativeName(fileName, originalsPath)))
|
||||
log.Infof("purge: file %s would be removed", txt.Quote(file.FileName))
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -101,7 +107,7 @@ func (prg *Purge) Start(opt PurgeOptions) (purgedFiles map[string]bool, purgedPh
|
|||
log.Errorf("purge: %s", err)
|
||||
} else {
|
||||
purgedFiles[fileName] = true
|
||||
log.Infof("purge: removed file %s", txt.Quote(fs.RelativeName(fileName, originalsPath)))
|
||||
log.Infof("purge: removed file %s", txt.Quote(file.FileName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (rs *Resample) Start(force bool) error {
|
|||
}
|
||||
|
||||
ignore.Log = func(fileName string) {
|
||||
log.Infof(`resample: ignored "%s"`, fs.RelativeName(fileName, originalsPath))
|
||||
log.Infof(`resample: ignored "%s"`, fs.Rel(fileName, originalsPath))
|
||||
}
|
||||
|
||||
err := godirwalk.Walk(originalsPath, &godirwalk.Options{
|
||||
|
|
|
@ -23,13 +23,17 @@ func FilesByPath(rootName, pathName string) (files entity.Files, err error) {
|
|||
return files, err
|
||||
}
|
||||
|
||||
// ExistingFiles returns not-missing and not-deleted file entities in the range of limit and offset sorted by id.
|
||||
func ExistingFiles(limit int, offset int, pathName string) (files entity.Files, err error) {
|
||||
// Files returns not-missing and not-deleted file entities in the range of limit and offset sorted by id.
|
||||
func Files(limit int, offset int, pathName string, includeMissing bool) (files entity.Files, err error) {
|
||||
if strings.HasPrefix(pathName, "/") {
|
||||
pathName = pathName[1:]
|
||||
}
|
||||
|
||||
stmt := Db().Unscoped().Where("file_missing = 0 AND deleted_at IS NULL")
|
||||
stmt := Db()
|
||||
|
||||
if !includeMissing {
|
||||
stmt = stmt.Where("file_missing = 0")
|
||||
}
|
||||
|
||||
if pathName != "" {
|
||||
stmt = stmt.Where("files.file_name LIKE ?", pathName+"/%")
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestFilesByPath(t *testing.T) {
|
||||
t.Run("files found", func(t *testing.T) {
|
||||
files, err := FilesByPath(entity.RootDefault, "2016/11")
|
||||
files, err := FilesByPath(entity.RootOriginals, "2016/11")
|
||||
|
||||
t.Logf("files: %+v", files)
|
||||
|
||||
|
@ -23,7 +23,7 @@ func TestFilesByPath(t *testing.T) {
|
|||
|
||||
func TestExistingFiles(t *testing.T) {
|
||||
t.Run("files found", func(t *testing.T) {
|
||||
files, err := ExistingFiles(1000, 0, "/")
|
||||
files, err := Files(1000, 0, "/", true)
|
||||
|
||||
t.Logf("files: %+v", files)
|
||||
|
||||
|
@ -33,7 +33,7 @@ func TestExistingFiles(t *testing.T) {
|
|||
assert.LessOrEqual(t, 5, len(files))
|
||||
})
|
||||
t.Run("search for files path", func(t *testing.T) {
|
||||
files, err := ExistingFiles(1000, 0, "Photos")
|
||||
files, err := Files(1000, 0, "Photos", true)
|
||||
|
||||
t.Logf("files: %+v", files)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func TestFoldersByPath(t *testing.T) {
|
||||
t.Run("root", func(t *testing.T) {
|
||||
folders, err := FoldersByPath(entity.RootDefault, "testdata", "", false)
|
||||
folders, err := FoldersByPath(entity.RootOriginals, "testdata", "", false)
|
||||
|
||||
t.Logf("folders: %+v", folders)
|
||||
|
||||
|
@ -21,7 +21,7 @@ func TestFoldersByPath(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("subdirectory", func(t *testing.T) {
|
||||
folders, err := FoldersByPath(entity.RootDefault, "testdata", "directory", false)
|
||||
folders, err := FoldersByPath(entity.RootOriginals, "testdata", "directory", false)
|
||||
|
||||
t.Logf("folders: %+v", folders)
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
"github.com/jinzhu/inflection"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/jinzhu/inflection"
|
||||
)
|
||||
|
||||
var log = event.Log
|
||||
|
|
|
@ -34,6 +34,8 @@ func SetConfig(c *config.Config) {
|
|||
}
|
||||
|
||||
conf = c
|
||||
|
||||
photoprism.SetConfig(c)
|
||||
}
|
||||
|
||||
func Config() *config.Config {
|
||||
|
|
|
@ -60,7 +60,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 50, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("left_224 options", func(t *testing.T) {
|
||||
left_224 := Types["left_224"]
|
||||
left224 := Types["left_224"]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -77,7 +77,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 750, bounds.Max.X)
|
||||
assert.Equal(t, 500, bounds.Max.Y)
|
||||
|
||||
result := Resample(img, left_224.Width, left_224.Height, left_224.Options...)
|
||||
result := Resample(img, left224.Width, left224.Height, left224.Options...)
|
||||
|
||||
boundsNew := result.Bounds()
|
||||
|
||||
|
@ -85,7 +85,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 224, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("right_224 options", func(t *testing.T) {
|
||||
right_224 := Types["right_224"]
|
||||
right224 := Types["right_224"]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -102,7 +102,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 750, bounds.Max.X)
|
||||
assert.Equal(t, 500, bounds.Max.Y)
|
||||
|
||||
result := Resample(img, right_224.Width, right_224.Height, right_224.Options...)
|
||||
result := Resample(img, right224.Width, right224.Height, right224.Options...)
|
||||
|
||||
boundsNew := result.Bounds()
|
||||
|
||||
|
@ -110,7 +110,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 224, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("fit_1280 options", func(t *testing.T) {
|
||||
fit_1280 := Types["fit_1280"]
|
||||
fit1280 := Types["fit_1280"]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -127,7 +127,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 750, bounds.Max.X)
|
||||
assert.Equal(t, 500, bounds.Max.Y)
|
||||
|
||||
result := Resample(img, fit_1280.Width, fit_1280.Height, fit_1280.Options...)
|
||||
result := Resample(img, fit1280.Width, fit1280.Height, fit1280.Options...)
|
||||
|
||||
boundsNew := result.Bounds()
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package workers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
|
@ -10,6 +9,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/remote"
|
||||
"github.com/photoprism/photoprism/internal/remote/webdav"
|
||||
|
@ -88,7 +88,7 @@ func (worker *Share) Start() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
srcFileName := path.Join(worker.conf.OriginalsPath(), file.File.FileName)
|
||||
srcFileName := photoprism.FileName(file.File.FileRoot, file.File.FileName)
|
||||
|
||||
if a.ShareSize != "" {
|
||||
thumbType, ok := thumb.Types[a.ShareSize]
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/remote/webdav"
|
||||
)
|
||||
|
@ -37,7 +38,7 @@ func (worker *Sync) upload(a entity.Account) (complete bool, err error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
fileName := path.Join(worker.conf.OriginalsPath(), file.FileName)
|
||||
fileName := photoprism.FileName(file.FileRoot, file.FileName)
|
||||
remoteName := path.Join(a.SyncPath, file.FileName)
|
||||
remoteDir := filepath.Dir(remoteName)
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -42,9 +40,9 @@ func Base(fileName string, stripSequence bool) string {
|
|||
return basename
|
||||
}
|
||||
|
||||
// RelativeBase returns the relative filename.
|
||||
func RelativeBase(fileName, dir string, stripSequence bool) string {
|
||||
if name := RelativeName(fileName, dir); name != "" {
|
||||
// RelBase returns the relative filename.
|
||||
func RelBase(fileName, dir string, stripSequence bool) string {
|
||||
if name := Rel(fileName, dir); name != "" {
|
||||
return AbsBase(name, stripSequence)
|
||||
}
|
||||
|
||||
|
@ -55,18 +53,3 @@ func RelativeBase(fileName, dir string, stripSequence bool) string {
|
|||
func AbsBase(fileName string, stripSequence bool) string {
|
||||
return filepath.Join(filepath.Dir(fileName), Base(fileName, stripSequence))
|
||||
}
|
||||
|
||||
// SubFileName returns the a filename with the same base name and a given extension in a sub directory.
|
||||
func SubFileName(fileName, subDir, fileExt string, stripSequence bool) string {
|
||||
baseName := Base(fileName, stripSequence)
|
||||
dirName := filepath.Join(filepath.Dir(fileName), subDir)
|
||||
|
||||
if err := os.MkdirAll(dirName, os.ModePerm); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
result := filepath.Join(dirName, baseName) + fileExt
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -89,30 +89,30 @@ func TestBase(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestRelativeBase(t *testing.T) {
|
||||
func TestRelBase(t *testing.T) {
|
||||
t.Run("/foo/bar.0000.ZIP", func(t *testing.T) {
|
||||
regular := RelativeBase("/foo/bar.0000.ZIP", "/bar", false)
|
||||
regular := RelBase("/foo/bar.0000.ZIP", "/bar", false)
|
||||
assert.Equal(t, "/foo/bar.0000", regular)
|
||||
|
||||
stripped := RelativeBase("/foo/bar.0000.ZIP", "/bar", true)
|
||||
stripped := RelBase("/foo/bar.0000.ZIP", "/bar", true)
|
||||
assert.Equal(t, "/foo/bar.0000", stripped)
|
||||
})
|
||||
|
||||
t.Run("/foo/bar.00001.ZIP", func(t *testing.T) {
|
||||
regular := RelativeBase("/foo/bar.00001.ZIP", "/bar", false)
|
||||
regular := RelBase("/foo/bar.00001.ZIP", "/bar", false)
|
||||
assert.Equal(t, "/foo/bar.00001", regular)
|
||||
|
||||
stripped := RelativeBase("/foo/bar.00001.ZIP", "/bar", true)
|
||||
stripped := RelBase("/foo/bar.00001.ZIP", "/bar", true)
|
||||
assert.Equal(t, "/foo/bar", stripped)
|
||||
})
|
||||
|
||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||
result := RelativeBase("/testdata/foo/Test copy 3.jpg", "/testdata", false)
|
||||
result := RelBase("/testdata/foo/Test copy 3.jpg", "/testdata", false)
|
||||
assert.Equal(t, "foo/Test copy 3", result)
|
||||
})
|
||||
|
||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||
result := RelativeBase("/testdata/foo/Test (3).jpg", "/testdata", false)
|
||||
result := RelBase("/testdata/foo/Test (3).jpg", "/testdata", false)
|
||||
assert.Equal(t, "foo/Test (3)", result)
|
||||
})
|
||||
}
|
||||
|
@ -129,25 +129,4 @@ func TestBaseAbs(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "/testdata/Test (4)", result)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSubFileName(t *testing.T) {
|
||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||
result := SubFileName("/testdata/Test (4).jpg", ".photoprism", ".xmp", true)
|
||||
|
||||
assert.Equal(t, "/testdata/.photoprism/Test.xmp", result)
|
||||
})
|
||||
|
||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||
result := SubFileName("/testdata/Test (4).jpg", ".photoprism", ".xmp", false)
|
||||
|
||||
assert.Equal(t, "/testdata/.photoprism/Test (4).xmp", result)
|
||||
})
|
||||
|
||||
t.Run("FOO.XMP", func(t *testing.T) {
|
||||
result := SubFileName("/testdata/FOO.XMP", ".photoprism", ".jpeg", true)
|
||||
|
||||
assert.Equal(t, "/testdata/.photoprism/FOO.jpeg", result)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -154,48 +154,6 @@ func (t FileType) Find(fileName string, stripSequence bool) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// Find returns the first filename with the same base name and a given type (also searches a sub directory).
|
||||
func (t FileType) FindSub(fileName, subDir string, stripSequence bool) string {
|
||||
base := Base(fileName, stripSequence)
|
||||
dir := filepath.Dir(fileName)
|
||||
|
||||
prefix := filepath.Join(dir, base)
|
||||
prefixLower := filepath.Join(dir, strings.ToLower(base))
|
||||
prefixUpper := filepath.Join(dir, strings.ToUpper(base))
|
||||
|
||||
prefixHidden := filepath.Join(dir, subDir, base)
|
||||
prefixLowerHidden := filepath.Join(dir, subDir, strings.ToLower(base))
|
||||
prefixUpperHidden := filepath.Join(dir, subDir, strings.ToUpper(base))
|
||||
|
||||
for _, ext := range TypeExt[t] {
|
||||
if info, err := os.Stat(prefix + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(prefixLower + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(prefixUpper + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(prefixHidden + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, subDir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(prefixLowerHidden + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, subDir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(prefixUpperHidden + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, subDir, info.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetFileType returns the (expected) type for a given file name.
|
||||
func GetFileType(fileName string) FileType {
|
||||
fileExt := strings.ToLower(filepath.Ext(fileName))
|
||||
|
@ -207,3 +165,47 @@ func GetFileType(fileName string) FileType {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// FindFirst searches a list of directories for the first file with the same base name and a given type.
|
||||
func (t FileType) FindFirst(fileName string, dirs []string, baseDir string, stripSequence bool) string {
|
||||
fileBase := Base(fileName, stripSequence)
|
||||
fileBaseLower := strings.ToLower(fileBase)
|
||||
fileBaseUpper := strings.ToUpper(fileBase)
|
||||
|
||||
fileDir := filepath.Dir(fileName)
|
||||
search := append([]string{fileDir}, dirs...)
|
||||
|
||||
for _, ext := range TypeExt[t] {
|
||||
lastDir := ""
|
||||
|
||||
for _, dir := range search {
|
||||
if dir == "" || dir == lastDir {
|
||||
continue
|
||||
}
|
||||
|
||||
lastDir = dir
|
||||
|
||||
if dir != fileDir {
|
||||
if filepath.IsAbs(dir) {
|
||||
dir = filepath.Join(dir, Rel(fileDir, baseDir))
|
||||
} else {
|
||||
dir = filepath.Join(fileDir, dir)
|
||||
}
|
||||
}
|
||||
|
||||
if info, err := os.Stat(filepath.Join(dir, fileBase) + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(filepath.Join(dir, fileBaseLower) + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, info.Name())
|
||||
}
|
||||
|
||||
if info, err := os.Stat(filepath.Join(dir, fileBaseUpper) + ext); err == nil && info.Mode().IsRegular() {
|
||||
return filepath.Join(dir, info.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -43,64 +43,93 @@ func TestFileType_Find(t *testing.T) {
|
|||
result := TypeJpeg.Find("testdata/test (2).xmp", true)
|
||||
assert.Equal(t, "testdata/test.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("name upper", func(t *testing.T) {
|
||||
result := TypeJpeg.Find("testdata/CATYELLOW.xmp", true)
|
||||
assert.Equal(t, "testdata/CATYELLOW.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("name lower", func(t *testing.T) {
|
||||
result := TypeJpeg.Find("testdata/chameleon_lime.xmp", true)
|
||||
assert.Equal(t, "testdata/chameleon_lime.jpg", result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileType_FindHidden(t *testing.T) {
|
||||
hiddenPath := ".photoprism"
|
||||
func TestFileType_FindFirst(t *testing.T) {
|
||||
dirs := []string{HiddenPath}
|
||||
|
||||
t.Run("find xmp", func(t *testing.T) {
|
||||
result := TypeXMP.FindSub("testdata/test.jpg", hiddenPath, false)
|
||||
result := TypeXMP.FindFirst("testdata/test.jpg", dirs, "", false)
|
||||
assert.Equal(t, "testdata/.photoprism/test.xmp", result)
|
||||
})
|
||||
|
||||
t.Run("find xmp upper ext", func(t *testing.T) {
|
||||
result := TypeXMP.FindSub("testdata/test.PNG", hiddenPath, false)
|
||||
result := TypeXMP.FindFirst("testdata/test.PNG", dirs, "", false)
|
||||
assert.Equal(t, "testdata/.photoprism/test.xmp", result)
|
||||
})
|
||||
|
||||
t.Run("find xmp without sequence", func(t *testing.T) {
|
||||
result := TypeXMP.FindSub("testdata/test (2).jpg", hiddenPath, false)
|
||||
result := TypeXMP.FindFirst("testdata/test (2).jpg", dirs, "", false)
|
||||
assert.Equal(t, "", result)
|
||||
})
|
||||
|
||||
t.Run("find xmp with sequence", func(t *testing.T) {
|
||||
result := TypeXMP.FindSub("testdata/test (2).jpg", hiddenPath, true)
|
||||
result := TypeXMP.FindFirst("testdata/test (2).jpg", dirs, "", true)
|
||||
assert.Equal(t, "testdata/.photoprism/test.xmp", result)
|
||||
})
|
||||
|
||||
t.Run("find jpg", func(t *testing.T) {
|
||||
result := TypeJpeg.FindSub("testdata/test.xmp", hiddenPath, false)
|
||||
result := TypeJpeg.FindFirst("testdata/test.xmp", dirs, "", false)
|
||||
assert.Equal(t, "testdata/test.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("find jpg abs", func(t *testing.T) {
|
||||
result := TypeJpeg.FindFirst(Abs("testdata/test.xmp"), dirs, "", false)
|
||||
assert.Equal(t, Abs("testdata/test.jpg"), result)
|
||||
})
|
||||
|
||||
t.Run("upper ext", func(t *testing.T) {
|
||||
result := TypeJpeg.FindSub("testdata/test.XMP", hiddenPath, false)
|
||||
result := TypeJpeg.FindFirst("testdata/test.XMP", dirs, "", false)
|
||||
assert.Equal(t, "testdata/test.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("with sequence", func(t *testing.T) {
|
||||
result := TypeJpeg.FindSub("testdata/test (2).xmp", hiddenPath, false)
|
||||
result := TypeJpeg.FindFirst("testdata/test (2).xmp", dirs, "", false)
|
||||
assert.Equal(t, "", result)
|
||||
})
|
||||
|
||||
t.Run("strip sequence", func(t *testing.T) {
|
||||
result := TypeJpeg.FindSub("testdata/test (2).xmp", hiddenPath, true)
|
||||
result := TypeJpeg.FindFirst("testdata/test (2).xmp", dirs, "", true)
|
||||
assert.Equal(t, "testdata/test.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("name upper", func(t *testing.T) {
|
||||
result := TypeJpeg.FindSub("testdata/CATYELLOW.xmp", hiddenPath, true)
|
||||
result := TypeJpeg.FindFirst("testdata/CATYELLOW.xmp", dirs, "", true)
|
||||
assert.Equal(t, "testdata/CATYELLOW.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("name lower", func(t *testing.T) {
|
||||
result := TypeJpeg.FindSub("testdata/chameleon_lime.xmp", hiddenPath, true)
|
||||
result := TypeJpeg.FindFirst("testdata/chameleon_lime.xmp", dirs, "", true)
|
||||
assert.Equal(t, "testdata/chameleon_lime.jpg", result)
|
||||
})
|
||||
|
||||
t.Run("example_bmp_notfound", func(t *testing.T) {
|
||||
result := TypeBitmap.FindFirst("testdata/example.00001.jpg", dirs, "", true)
|
||||
assert.Equal(t, "", result)
|
||||
})
|
||||
|
||||
t.Run("example_bmp_found", func(t *testing.T) {
|
||||
result := TypeBitmap.FindFirst("testdata/example.00001.jpg", []string{"directory"}, "", true)
|
||||
assert.Equal(t, "testdata/directory/example.bmp", result)
|
||||
})
|
||||
|
||||
t.Run("example_png_found", func(t *testing.T) {
|
||||
result := TypePng.FindFirst("testdata/example.00001.jpg", []string{"directory", "directory/subdirectory"}, "", true)
|
||||
assert.Equal(t, "testdata/directory/subdirectory/example.png", result)
|
||||
})
|
||||
|
||||
t.Run("example_bmp_found", func(t *testing.T) {
|
||||
result := TypeBitmap.FindFirst(Abs("testdata/example.00001.jpg"), []string{"directory"}, Abs("testdata"), true)
|
||||
assert.Equal(t, Abs("testdata/directory/example.bmp"), result)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,16 +1,47 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RelativeName returns the file name relative to directory.
|
||||
func RelativeName(fileName, dir string) string {
|
||||
// FileName returns the a relative filename with the same base and a given extension in a directory.
|
||||
func FileName(fileName, dirName, baseDir, fileExt string, stripSequence bool) string {
|
||||
fileDir := filepath.Dir(fileName)
|
||||
baseName := Base(fileName, stripSequence)
|
||||
|
||||
if dirName == "" || dirName == "." {
|
||||
dirName = fileDir
|
||||
} else if fileDir != dirName {
|
||||
if filepath.IsAbs(dirName) {
|
||||
dirName = filepath.Join(dirName, Rel(fileDir, baseDir))
|
||||
} else {
|
||||
dirName = filepath.Join(fileDir, dirName)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dirName, os.ModePerm); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return ""
|
||||
}
|
||||
|
||||
result := filepath.Join(dirName, baseName) + fileExt
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Rel returns the file name relative to a directory.
|
||||
func Rel(fileName, dir string) string {
|
||||
if fileName == dir {
|
||||
return ""
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
return fileName
|
||||
}
|
||||
|
||||
if index := strings.Index(fileName, dir); index == 0 {
|
||||
if index := strings.LastIndex(dir, string(os.PathSeparator)); index == len(dir)-1 {
|
||||
pos := len(dir)
|
||||
|
|
|
@ -1,28 +1,60 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRelativeName(t *testing.T) {
|
||||
func TestRel(t *testing.T) {
|
||||
t.Run("same", func(t *testing.T) {
|
||||
assert.Equal(t, "", RelativeName("/some/path", "/some/path"))
|
||||
assert.Equal(t, "", Rel("/some/path", "/some/path"))
|
||||
})
|
||||
t.Run("short", func(t *testing.T) {
|
||||
assert.Equal(t, "/some/", RelativeName("/some/", "/some/path"))
|
||||
assert.Equal(t, "/some/", Rel("/some/", "/some/path"))
|
||||
})
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", RelativeName("", "/some/path"))
|
||||
assert.Equal(t, "", Rel("", "/some/path"))
|
||||
})
|
||||
t.Run("/some/path", func(t *testing.T) {
|
||||
assert.Equal(t, "foo/bar.baz", RelativeName("/some/path/foo/bar.baz", "/some/path"))
|
||||
assert.Equal(t, "foo/bar.baz", Rel("/some/path/foo/bar.baz", "/some/path"))
|
||||
})
|
||||
t.Run("/some/path/", func(t *testing.T) {
|
||||
assert.Equal(t, "foo/bar.baz", RelativeName("/some/path/foo/bar.baz", "/some/path/"))
|
||||
assert.Equal(t, "foo/bar.baz", Rel("/some/path/foo/bar.baz", "/some/path/"))
|
||||
})
|
||||
t.Run("/some/path/bar", func(t *testing.T) {
|
||||
assert.Equal(t, "/some/path/foo/bar.baz", RelativeName("/some/path/foo/bar.baz", "/some/path/bar"))
|
||||
assert.Equal(t, "/some/path/foo/bar.baz", Rel("/some/path/foo/bar.baz", "/some/path/bar"))
|
||||
})
|
||||
}
|
||||
|
||||
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", true)
|
||||
|
||||
assert.Equal(t, "testdata/.photoprism/Test.xmp", result)
|
||||
})
|
||||
|
||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||
result := FileName("testdata/Test (4).jpg", ".photoprism", Abs("testdata"), ".xmp", false)
|
||||
|
||||
assert.Equal(t, "testdata/.photoprism/Test (4).xmp", result)
|
||||
})
|
||||
|
||||
t.Run("FOO.XMP", func(t *testing.T) {
|
||||
result := FileName("testdata/FOO.XMP", ".photoprism/sub", Abs("testdata"), ".jpeg", true)
|
||||
|
||||
assert.Equal(t, "testdata/.photoprism/sub/FOO.jpeg", result)
|
||||
})
|
||||
|
||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||
tempDir := filepath.Join(os.TempDir(), HiddenPath)
|
||||
|
||||
// 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", true)
|
||||
|
||||
assert.Equal(t, tempDir+"/Test.xmp", result)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue