604849e92c
With these changes the size and type of the RAW file as well as other details can be displayed in the Cards View. This also improves the indexing of camera and lens metadata. Signed-off-by: Michael Mayer <michael@photoprism.app>
229 lines
6.2 KiB
Go
229 lines
6.2 KiB
Go
package query
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/dustin/go-humanize/english"
|
|
"github.com/jinzhu/gorm"
|
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
"github.com/photoprism/photoprism/internal/mutex"
|
|
)
|
|
|
|
// PhotoByID returns a Photo based on the ID.
|
|
func PhotoByID(photoID uint64) (photo entity.Photo, err error) {
|
|
if err := UnscopedDb().Where("id = ?", photoID).
|
|
Preload("Labels", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("photos_labels.uncertainty ASC, photos_labels.label_id DESC")
|
|
}).
|
|
Preload("Labels.Label").
|
|
Preload("Camera").
|
|
Preload("Lens").
|
|
Preload("Details").
|
|
Preload("Place").
|
|
Preload("Cell").
|
|
Preload("Cell.Place").
|
|
First(&photo).Error; err != nil {
|
|
return photo, err
|
|
}
|
|
|
|
return photo, nil
|
|
}
|
|
|
|
// PhotoByUID returns a Photo based on the UID.
|
|
func PhotoByUID(photoUID string) (photo entity.Photo, err error) {
|
|
if err := UnscopedDb().Where("photo_uid = ?", photoUID).
|
|
Preload("Labels", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("photos_labels.uncertainty ASC, photos_labels.label_id DESC")
|
|
}).
|
|
Preload("Labels.Label").
|
|
Preload("Camera").
|
|
Preload("Lens").
|
|
Preload("Details").
|
|
Preload("Place").
|
|
Preload("Cell").
|
|
Preload("Cell.Place").
|
|
First(&photo).Error; err != nil {
|
|
return photo, err
|
|
}
|
|
|
|
return photo, nil
|
|
}
|
|
|
|
// PhotoPreloadByUID returns a Photo based on the UID with all dependencies preloaded.
|
|
func PhotoPreloadByUID(photoUID string) (photo entity.Photo, err error) {
|
|
if err := UnscopedDb().Where("photo_uid = ?", photoUID).
|
|
Preload("Labels", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("photos_labels.uncertainty ASC, photos_labels.label_id DESC")
|
|
}).
|
|
Preload("Labels.Label").
|
|
Preload("Camera").
|
|
Preload("Lens").
|
|
Preload("Details").
|
|
Preload("Place").
|
|
Preload("Cell").
|
|
Preload("Cell.Place").
|
|
First(&photo).Error; err != nil {
|
|
return photo, err
|
|
}
|
|
|
|
photo.PreloadMany()
|
|
|
|
return photo, nil
|
|
}
|
|
|
|
// MissingPhotos returns photo entities without existing files.
|
|
func MissingPhotos(limit int, offset int) (entities entity.Photos, err error) {
|
|
err = Db().
|
|
Select("photos.*").
|
|
Where("id NOT IN (SELECT photo_id FROM files WHERE file_missing = 0 AND file_root = '/' AND deleted_at IS NULL)").
|
|
Order("photos.id").
|
|
Limit(limit).Offset(offset).Find(&entities).Error
|
|
|
|
return entities, err
|
|
}
|
|
|
|
// ArchivedPhotos finds and returns archived photos.
|
|
func ArchivedPhotos(limit int, offset int) (entities entity.Photos, err error) {
|
|
err = UnscopedDb().
|
|
Select("photos.*").
|
|
Where("photos.photo_quality > -1").
|
|
Where("photos.deleted_at IS NOT NULL").
|
|
Order("photos.id").
|
|
Limit(limit).Offset(offset).Find(&entities).Error
|
|
|
|
return entities, err
|
|
}
|
|
|
|
// PhotosMetadataUpdate returns photos selected for metadata maintenance.
|
|
func PhotosMetadataUpdate(limit, offset int, delay, interval time.Duration) (entities entity.Photos, err error) {
|
|
err = Db().
|
|
Preload("Labels", func(db *gorm.DB) *gorm.DB {
|
|
return db.Order("photos_labels.uncertainty ASC, photos_labels.label_id DESC")
|
|
}).
|
|
Preload("Labels.Label").
|
|
Preload("Camera").
|
|
Preload("Lens").
|
|
Preload("Details").
|
|
Preload("Place").
|
|
Preload("Cell").
|
|
Preload("Cell.Place").
|
|
Where("checked_at IS NULL OR checked_at < ?", time.Now().Add(-1*interval)).
|
|
Where("updated_at < ? OR (cell_id = 'zz' AND photo_lat <> 0)", time.Now().Add(-1*delay)).
|
|
Order("photos.ID ASC").Limit(limit).Offset(offset).Find(&entities).Error
|
|
|
|
return entities, err
|
|
}
|
|
|
|
// OrphanPhotos finds orphan index entries that may be removed.
|
|
func OrphanPhotos() (photos entity.Photos, err error) {
|
|
err = UnscopedDb().
|
|
Raw(`SELECT * FROM photos WHERE
|
|
deleted_at IS NOT NULL
|
|
AND photo_quality = -1
|
|
AND id NOT IN (SELECT photo_id FROM files WHERE files.deleted_at IS NULL)`).
|
|
Find(&photos).Error
|
|
|
|
return photos, err
|
|
}
|
|
|
|
// FixPrimaries tries to set a primary file for photos that have none.
|
|
func FixPrimaries() error {
|
|
mutex.Index.Lock()
|
|
defer mutex.Index.Unlock()
|
|
|
|
start := time.Now()
|
|
|
|
var photos entity.Photos
|
|
|
|
// Remove primary file flag from broken or missing files.
|
|
if err := UnscopedDb().Table(entity.File{}.TableName()).
|
|
Where("(file_error <> '' OR file_missing = 1) AND file_primary <> 0").
|
|
UpdateColumn("file_primary", 0).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find photos without primary file.
|
|
if err := UnscopedDb().
|
|
Raw(`SELECT * FROM photos
|
|
WHERE deleted_at IS NULL
|
|
AND id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1)`).
|
|
Find(&photos).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(photos) == 0 {
|
|
log.Debugf("index: found no photos without primary file [%s]", time.Since(start))
|
|
return nil
|
|
}
|
|
|
|
// Try to find matching primary files.
|
|
for _, p := range photos {
|
|
log.Debugf("index: searching primary file for %s", p.PhotoUID)
|
|
|
|
if err := p.SetPrimary(""); err != nil {
|
|
log.Infof("index: %s", err)
|
|
}
|
|
}
|
|
|
|
log.Debugf("index: updated primary files [%s]", time.Since(start))
|
|
|
|
return nil
|
|
}
|
|
|
|
// FlagHiddenPhotos sets the quality score of photos without valid primary file to -1.
|
|
func FlagHiddenPhotos() (err error) {
|
|
mutex.Index.Lock()
|
|
defer mutex.Index.Unlock()
|
|
|
|
// Start time for logs.
|
|
start := time.Now()
|
|
|
|
// IDs of hidden photos.
|
|
var hidden []uint
|
|
|
|
// Number of updated photos.
|
|
n := 0
|
|
|
|
// Find and flag hidden photos.
|
|
if err = Db().Table(entity.Photo{}.TableName()).
|
|
Where("id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1 AND file_missing = 0 AND file_error = '' AND deleted_at IS NULL) AND photo_quality > -1").
|
|
Pluck("id", &hidden).Error; err != nil {
|
|
// Find query failed.
|
|
return err
|
|
} else if found := len(hidden); found == 0 {
|
|
// Nothing to update.
|
|
return nil
|
|
} else {
|
|
// Update photos in batches to be compatible with SQLite.
|
|
batch := 500
|
|
|
|
for i := 0; i < len(hidden); i += batch {
|
|
j := i + batch
|
|
|
|
if j > len(hidden) {
|
|
j = len(hidden)
|
|
}
|
|
|
|
// Next batch.
|
|
ids := hidden[i:j]
|
|
|
|
// Set photos.photo_quality = -1.
|
|
if err = Db().Table(entity.Photo{}.TableName()).Where("id IN (?)", ids).UpdateColumn("photo_quality", -1).Error; err != nil {
|
|
// Failed.
|
|
log.Warnf("index: failed to flag %d pictures as hidden", len(ids))
|
|
return err
|
|
} else {
|
|
// Success.
|
|
n += len(ids)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Log number of updated photos, if any.
|
|
if n > 0 {
|
|
log.Infof("index: flagged %s as hidden [%s]", english.Plural(int(n), "photo", "photos"), time.Since(start))
|
|
}
|
|
|
|
return nil
|
|
}
|