mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Index: Improve handling of archived photos, skip when possible #2257
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/acl"
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
@@ -30,8 +31,9 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
|
settings := conf.Settings()
|
||||||
|
|
||||||
if !conf.Settings().Features.Library {
|
if !settings.Features.Library {
|
||||||
AbortFeatureDisabled(c)
|
AbortFeatureDisabled(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -45,12 +47,12 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure index options.
|
||||||
path := conf.OriginalsPath()
|
path := conf.OriginalsPath()
|
||||||
|
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||||
|
skipArchived := settings.Index.SkipArchived
|
||||||
|
|
||||||
ind := service.Index()
|
indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false, skipArchived)
|
||||||
|
|
||||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
|
||||||
indOpt := photoprism.NewIndexOptions(filepath.Clean(f.Path), f.Rescan, convert, true, false)
|
|
||||||
|
|
||||||
if len(indOpt.Path) > 1 {
|
if len(indOpt.Path) > 1 {
|
||||||
event.InfoMsg(i18n.MsgIndexingFiles, clean.Log(indOpt.Path))
|
event.InfoMsg(i18n.MsgIndexingFiles, clean.Log(indOpt.Path))
|
||||||
@@ -58,17 +60,21 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||||||
event.InfoMsg(i18n.MsgIndexingOriginals)
|
event.InfoMsg(i18n.MsgIndexingOriginals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start indexing.
|
||||||
|
ind := service.Index()
|
||||||
indexed := ind.Start(indOpt)
|
indexed := ind.Start(indOpt)
|
||||||
|
|
||||||
RemoveFromFolderCache(entity.RootOriginals)
|
RemoveFromFolderCache(entity.RootOriginals)
|
||||||
|
|
||||||
prg := service.Purge()
|
// Configure purge options.
|
||||||
|
|
||||||
prgOpt := photoprism.PurgeOptions{
|
prgOpt := photoprism.PurgeOptions{
|
||||||
Path: filepath.Clean(f.Path),
|
Path: filepath.Clean(f.Path),
|
||||||
Ignore: indexed,
|
Ignore: indexed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start purging.
|
||||||
|
prg := service.Purge()
|
||||||
|
|
||||||
if files, photos, err := prg.Start(prgOpt); err != nil {
|
if files, photos, err := prg.Start(prgOpt); err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UpperFirst(err.Error())})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ func Index() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
|
settings := conf.Settings()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
@@ -59,8 +60,8 @@ func Index() error {
|
|||||||
|
|
||||||
ind := service.Index()
|
ind := service.Index()
|
||||||
|
|
||||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||||
indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false)
|
indOpt := photoprism.NewIndexOptions(entity.RootPath, false, convert, true, false, true)
|
||||||
|
|
||||||
indexed := ind.Start(indOpt)
|
indexed := ind.Start(indOpt)
|
||||||
|
|
||||||
|
|||||||
@@ -250,9 +250,11 @@ func facesIndexAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
var indexed fs.Done
|
var indexed fs.Done
|
||||||
|
|
||||||
|
settings := conf.Settings()
|
||||||
|
|
||||||
if w := service.Index(); w != nil {
|
if w := service.Index(); w != nil {
|
||||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
convert := settings.Index.Convert && conf.SidecarWritable()
|
||||||
opt := photoprism.NewIndexOptions(subPath, true, convert, true, true)
|
opt := photoprism.NewIndexOptions(subPath, true, convert, true, true, true)
|
||||||
|
|
||||||
indexed = w.Start(opt)
|
indexed = w.Start(opt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ var IndexCommand = cli.Command{
|
|||||||
var indexFlags = []cli.Flag{
|
var indexFlags = []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "force, f",
|
Name: "force, f",
|
||||||
Usage: "re-index all originals, including unchanged files",
|
Usage: "rescan all originals, including unchanged files",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "archived, a",
|
||||||
|
Usage: "update archived photos, do not skip them",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "cleanup, c",
|
Name: "cleanup, c",
|
||||||
@@ -70,7 +74,7 @@ func indexAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
if w := service.Index(); w != nil {
|
if w := service.Index(); w != nil {
|
||||||
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
convert := conf.Settings().Index.Convert && conf.SidecarWritable()
|
||||||
opt := photoprism.NewIndexOptions(subPath, ctx.Bool("force"), convert, true, false)
|
opt := photoprism.NewIndexOptions(subPath, ctx.Bool("force"), convert, true, false, !ctx.Bool("archived"))
|
||||||
|
|
||||||
indexed = w.Start(opt)
|
indexed = w.Start(opt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var ShowConfigCommand = cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "md, m",
|
Name: "md, m",
|
||||||
Usage: "renders valid Markdown",
|
Usage: "render valid Markdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: showConfigAction,
|
Action: showConfigAction,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var ShowFiltersCommand = cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "md, m",
|
Name: "md, m",
|
||||||
Usage: "renders valid Markdown",
|
Usage: "render valid Markdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: showFiltersAction,
|
Action: showFiltersAction,
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ var ShowFormatsCommand = cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "short, s",
|
Name: "short, s",
|
||||||
Usage: "hides format descriptions",
|
Usage: "hide format descriptions",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "md, m",
|
Name: "md, m",
|
||||||
Usage: "renders valid Markdown",
|
Usage: "render valid Markdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: showFormatsAction,
|
Action: showFormatsAction,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var ShowTagsCommand = cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "md, m",
|
Name: "md, m",
|
||||||
Usage: "renders valid Markdown",
|
Usage: "render valid Markdown",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: showTagsAction,
|
Action: showTagsAction,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package config
|
|||||||
|
|
||||||
// IndexSettings represents indexing settings.
|
// IndexSettings represents indexing settings.
|
||||||
type IndexSettings struct {
|
type IndexSettings struct {
|
||||||
Path string `json:"path" yaml:"Path"`
|
Path string `json:"path" yaml:"Path"`
|
||||||
Convert bool `json:"convert" yaml:"Convert"`
|
Convert bool `json:"convert" yaml:"Convert"`
|
||||||
Rescan bool `json:"rescan" yaml:"Rescan"`
|
Rescan bool `json:"rescan" yaml:"Rescan"`
|
||||||
|
SkipArchived bool `json:"skipArchived" yaml:"SkipArchived"`
|
||||||
}
|
}
|
||||||
|
|||||||
1
internal/config/testdata/settings.yml
vendored
1
internal/config/testdata/settings.yml
vendored
@@ -36,6 +36,7 @@ Index:
|
|||||||
Path: /
|
Path: /
|
||||||
Convert: true
|
Convert: true
|
||||||
Rescan: false
|
Rescan: false
|
||||||
|
SkipArchived: false
|
||||||
Stack:
|
Stack:
|
||||||
UUID: true
|
UUID: true
|
||||||
Meta: true
|
Meta: true
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ func Count(m interface{}, keys []string, values []interface{}) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int
|
db, count := UnscopedDb(), 0
|
||||||
|
|
||||||
stmt := Db().Model(m)
|
stmt := db.Model(m)
|
||||||
|
|
||||||
|
// Compose where condition.
|
||||||
for k := range keys {
|
for k := range keys {
|
||||||
stmt.Where("? = ?", gorm.Expr(keys[k]), values[k])
|
stmt.Where("? = ?", gorm.Expr(keys[k]), values[k])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch count from database.
|
||||||
if err := stmt.Count(&count).Error; err != nil {
|
if err := stmt.Count(&count).Error; err != nil {
|
||||||
log.Debugf("entity: %s (count records)", err)
|
log.Debugf("entity: %s (count records)", err)
|
||||||
return -1
|
return -1
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import (
|
|||||||
|
|
||||||
// Update updates an existing record in the database.
|
// Update updates an existing record in the database.
|
||||||
func Update(m interface{}, keyNames ...string) (err error) {
|
func Update(m interface{}, keyNames ...string) (err error) {
|
||||||
|
// Unscoped so soft-deleted records can still be updated.
|
||||||
|
db := UnscopedDb()
|
||||||
|
|
||||||
// New entity?
|
// New entity?
|
||||||
if Db().NewRecord(m) {
|
if db.NewRecord(m) {
|
||||||
return fmt.Errorf("new record")
|
return fmt.Errorf("new record")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract interface slice with all values including zero.
|
||||||
values, keys, err := ModelValues(m, keyNames...)
|
values, keys, err := ModelValues(m, keyNames...)
|
||||||
|
|
||||||
// Has keys and values?
|
// Has keys and values?
|
||||||
@@ -20,16 +24,16 @@ func Update(m interface{}, keyNames ...string) (err error) {
|
|||||||
return fmt.Errorf("record keys missing")
|
return fmt.Errorf("record keys missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform update.
|
// Update values.
|
||||||
res := Db().Model(m).Updates(values)
|
result := db.Model(m).Updates(values)
|
||||||
|
|
||||||
// Successful?
|
// Successful?
|
||||||
if res.Error != nil {
|
if result.Error != nil {
|
||||||
return err
|
return err
|
||||||
} else if res.RowsAffected > 1 {
|
} else if result.RowsAffected > 1 {
|
||||||
log.Debugf("entity: updated statement affected more than one record - possible bug")
|
log.Debugf("entity: updated statement affected more than one record - possible bug")
|
||||||
return nil
|
return nil
|
||||||
} else if res.RowsAffected == 1 {
|
} else if result.RowsAffected == 1 {
|
||||||
return nil
|
return nil
|
||||||
} else if Count(m, keyNames, keys) != 1 {
|
} else if Count(m, keyNames, keys) != 1 {
|
||||||
return fmt.Errorf("record not found")
|
return fmt.Errorf("record not found")
|
||||||
|
|||||||
@@ -47,22 +47,31 @@ func ModelValues(m interface{}, keyNames ...string) (result Values, keys []inter
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
name := field.Name
|
fieldName := field.Name
|
||||||
|
|
||||||
// Skip timestamps.
|
// Skip timestamps.
|
||||||
if name == "" || name == "UpdatedAt" || name == "CreatedAt" {
|
if fieldName == "" || fieldName == "UpdatedAt" || fieldName == "CreatedAt" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
v := values.Field(i)
|
v := values.Field(i)
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer:
|
||||||
|
continue
|
||||||
|
case reflect.Struct:
|
||||||
|
if v.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Skip read-only fields.
|
// Skip read-only fields.
|
||||||
if !v.CanSet() {
|
if !v.CanSet() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip keys.
|
// Skip keys.
|
||||||
if isKey(name) {
|
if isKey(fieldName) {
|
||||||
if !v.IsZero() {
|
if !v.IsZero() {
|
||||||
keys = append(keys, v.Interface())
|
keys = append(keys, v.Interface())
|
||||||
}
|
}
|
||||||
@@ -70,7 +79,7 @@ func ModelValues(m interface{}, keyNames ...string) (result Values, keys []inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add value to result.
|
// Add value to result.
|
||||||
result[name] = v.Interface()
|
result[fieldName] = v.Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
|
|||||||
@@ -102,8 +102,9 @@ func (imp *Import) Start(opt ImportOptions) fs.Done {
|
|||||||
|
|
||||||
filesImported := 0
|
filesImported := 0
|
||||||
|
|
||||||
convert := imp.conf.Settings().Index.Convert && imp.conf.SidecarWritable()
|
settings := imp.conf.Settings()
|
||||||
indexOpt := NewIndexOptions("/", true, convert, true, false)
|
convert := settings.Index.Convert && imp.conf.SidecarWritable()
|
||||||
|
indexOpt := NewIndexOptions("/", true, convert, true, false, false)
|
||||||
skipRaw := imp.conf.DisableRaw()
|
skipRaw := imp.conf.DisableRaw()
|
||||||
ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false)
|
ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false)
|
||||||
|
|
||||||
|
|||||||
@@ -307,8 +307,12 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName, photoUID
|
|||||||
}
|
}
|
||||||
|
|
||||||
if photo.PhotoQuality == -1 && (file.FilePrimary || fileChanged) {
|
if photo.PhotoQuality == -1 && (file.FilePrimary || fileChanged) {
|
||||||
// Restore photos that have been purged automatically.
|
// Restore pictures that have been purged automatically.
|
||||||
photo.DeletedAt = nil
|
photo.DeletedAt = nil
|
||||||
|
} else if o.SkipArchived && photo.DeletedAt != nil {
|
||||||
|
// Skip archived pictures for faster indexing.
|
||||||
|
result.Status = IndexArchived
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra labels to ba added when new files have a photo id.
|
// Extra labels to ba added when new files have a photo id.
|
||||||
|
|||||||
@@ -7,18 +7,20 @@ type IndexOptions struct {
|
|||||||
Convert bool
|
Convert bool
|
||||||
Stack bool
|
Stack bool
|
||||||
FacesOnly bool
|
FacesOnly bool
|
||||||
|
SkipArchived bool
|
||||||
OriginalsLimit int
|
OriginalsLimit int
|
||||||
ResolutionLimit int
|
ResolutionLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIndexOptions returns new index options instance.
|
// NewIndexOptions returns new index options instance.
|
||||||
func NewIndexOptions(path string, rescan, convert, stack, facesOnly bool) IndexOptions {
|
func NewIndexOptions(path string, rescan, convert, stack, facesOnly, skipArchived bool) IndexOptions {
|
||||||
result := IndexOptions{
|
result := IndexOptions{
|
||||||
Path: path,
|
Path: path,
|
||||||
Rescan: rescan,
|
Rescan: rescan,
|
||||||
Convert: convert,
|
Convert: convert,
|
||||||
Stack: stack,
|
Stack: stack,
|
||||||
FacesOnly: facesOnly,
|
FacesOnly: facesOnly,
|
||||||
|
SkipArchived: skipArchived,
|
||||||
OriginalsLimit: Config().OriginalsLimit(),
|
OriginalsLimit: Config().OriginalsLimit(),
|
||||||
ResolutionLimit: Config().ResolutionLimit(),
|
ResolutionLimit: Config().ResolutionLimit(),
|
||||||
}
|
}
|
||||||
@@ -33,20 +35,20 @@ func (o *IndexOptions) SkipUnchanged() bool {
|
|||||||
|
|
||||||
// IndexOptionsAll returns new index options with all options set to true.
|
// IndexOptionsAll returns new index options with all options set to true.
|
||||||
func IndexOptionsAll() IndexOptions {
|
func IndexOptionsAll() IndexOptions {
|
||||||
return NewIndexOptions("/", true, true, true, false)
|
return NewIndexOptions("/", true, true, true, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexOptionsFacesOnly returns new index options for updating faces only.
|
// IndexOptionsFacesOnly returns new index options for updating faces only.
|
||||||
func IndexOptionsFacesOnly() IndexOptions {
|
func IndexOptionsFacesOnly() IndexOptions {
|
||||||
return NewIndexOptions("/", true, true, true, true)
|
return NewIndexOptions("/", true, true, true, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexOptionsSingle returns new index options for unstacked, single files.
|
// IndexOptionsSingle returns new index options for unstacked, single files.
|
||||||
func IndexOptionsSingle() IndexOptions {
|
func IndexOptionsSingle() IndexOptions {
|
||||||
return NewIndexOptions("/", true, true, false, false)
|
return NewIndexOptions("/", true, true, false, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexOptionsNone returns new index options with all options set to false.
|
// IndexOptionsNone returns new index options with all options set to false.
|
||||||
func IndexOptionsNone() IndexOptions {
|
func IndexOptionsNone() IndexOptions {
|
||||||
return NewIndexOptions("", false, false, false, false)
|
return NewIndexOptions("", false, false, false, false, false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user