photoprism/internal/query/photo.go
Michael Mayer 604849e92c Search: Include RAW files in results by default #2040
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>
2023-10-06 02:22:48 +02:00

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
}