photoprism/internal/workers/meta.go

155 lines
3.7 KiB
Go
Raw Permalink Normal View History

package workers
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"time"
"github.com/dustin/go-humanize/english"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query"
)
// Meta represents a background index and metadata optimization worker.
type Meta struct {
conf *config.Config
lastRun time.Time
}
// NewMeta returns a new Meta worker.
func NewMeta(conf *config.Config) *Meta {
return &Meta{conf: conf}
}
// originalsPath returns the original media files path as string.
func (w *Meta) originalsPath() string {
return w.conf.OriginalsPath()
}
// Start metadata optimization routine.
func (w *Meta) Start(delay, interval time.Duration, force bool) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("index: %s (worker panic)\nstack: %s", r, debug.Stack())
log.Error(err)
}
}()
if err = mutex.MetaWorker.Start(); err != nil {
return err
}
defer mutex.MetaWorker.Stop()
// Check time when worker was last executed.
updateIndex := force || w.lastRun.Before(time.Now().Add(-1*entity.IndexUpdateInterval))
// Run faces worker if needed.
if updateIndex || entity.UpdateFaces.Load() {
log.Debugf("index: running face recognition")
if faces := photoprism.NewFaces(w.conf); faces.Disabled() {
log.Debugf("index: skipping face recognition")
} else if err := faces.Start(photoprism.FacesOptions{}); err != nil {
log.Warn(err)
}
}
// Refresh index metadata.
log.Debugf("index: updating metadata")
start := time.Now()
settings := w.conf.Settings()
done := make(map[string]bool)
limit := 1000
offset := 0
optimized := 0
for {
photos, err := query.PhotosMetadataUpdate(limit, offset, delay, interval)
if err != nil {
return err
}
if len(photos) == 0 {
break
}
for _, photo := range photos {
if mutex.MetaWorker.Canceled() {
return errors.New("index: metadata optimization canceled")
}
if done[photo.PhotoUID] {
continue
}
done[photo.PhotoUID] = true
updated, merged, err := photo.Optimize(settings.StackMeta(), settings.StackUUID(), settings.Features.Estimates, force)
if err != nil {
log.Errorf("index: %s in optimization worker", err)
} else if updated {
optimized++
log.Debugf("index: updated photo %s", photo.String())
}
for _, p := range merged {
log.Infof("index: merged %s", p.PhotoUID)
done[p.PhotoUID] = true
}
}
if mutex.MetaWorker.Canceled() {
return errors.New("index: optimization canceled")
}
offset += limit
}
if optimized > 0 {
log.Infof("index: updated %s [%s]", english.Plural(optimized, "photo", "photos"), time.Since(start))
updateIndex = true
}
// Only update index if necessary.
if updateIndex {
// Set photo quality scores to -1 if files are missing.
if err = query.FlagHiddenPhotos(); err != nil {
log.Warnf("index: %s in optimization worker", err)
}
// Run moments worker.
if moments := photoprism.NewMoments(w.conf); moments == nil {
log.Errorf("index: failed updating moments")
} else if err = moments.Start(); err != nil {
log.Warnf("moments: %s in optimization worker", err)
}
// Update precalculated photo and file counts.
if err = entity.UpdateCounts(); err != nil {
log.Warnf("index: %s in optimization worker", err)
}
// Update album, subject, and label cover thumbs.
if err = query.UpdateCovers(); err != nil {
log.Warnf("index: %s in optimization worker", err)
}
}
// Update time when worker was last executed.
w.lastRun = entity.TimeStamp()
// Run garbage collection.
runtime.GC()
return nil
}