2020-01-05 14:18:40 +01:00
|
|
|
package query
|
2019-12-11 07:37:39 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2019-12-11 16:55:18 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
2019-12-11 07:37:39 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/form"
|
2021-10-06 11:50:48 +02:00
|
|
|
"github.com/photoprism/photoprism/internal/mutex"
|
2021-09-18 15:32:39 +02:00
|
|
|
"github.com/photoprism/photoprism/internal/search"
|
2022-04-15 09:42:07 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/clean"
|
2019-12-11 07:37:39 +01:00
|
|
|
)
|
|
|
|
|
2021-09-18 15:32:39 +02:00
|
|
|
// Albums returns a slice of albums.
|
|
|
|
func Albums(offset, limit int) (results entity.Albums, err error) {
|
|
|
|
err = UnscopedDb().Table("albums").Select("*").Offset(offset).Limit(limit).Find(&results).Error
|
|
|
|
return results, err
|
2019-12-11 07:37:39 +01:00
|
|
|
}
|
|
|
|
|
2020-05-23 20:58:58 +02:00
|
|
|
// AlbumByUID returns a Album based on the UID.
|
|
|
|
func AlbumByUID(albumUID string) (album entity.Album, err error) {
|
2022-08-01 15:57:19 +02:00
|
|
|
return entity.CachedAlbumByUID(albumUID)
|
2019-12-11 07:37:39 +01:00
|
|
|
}
|
|
|
|
|
2021-10-01 16:44:50 +02:00
|
|
|
// AlbumCoverByUID returns an album cover file based on the uid.
|
2022-08-31 17:42:57 +02:00
|
|
|
func AlbumCoverByUID(uid string, public bool) (file entity.File, err error) {
|
2020-05-30 01:41:47 +02:00
|
|
|
a := entity.Album{}
|
|
|
|
|
2022-08-31 17:42:57 +02:00
|
|
|
// Find album.
|
2022-08-01 15:57:19 +02:00
|
|
|
if a, err = AlbumByUID(uid); err != nil {
|
2020-05-30 01:41:47 +02:00
|
|
|
return file, err
|
2020-06-08 18:32:51 +02:00
|
|
|
} else if a.AlbumType != entity.AlbumDefault { // TODO: Optimize
|
2021-11-26 13:59:10 +01:00
|
|
|
f := form.SearchPhotos{Album: a.AlbumUID, Filter: a.AlbumFilter, Order: entity.SortOrderRelevance, Count: 1, Offset: 0, Merged: false}
|
2020-05-30 01:41:47 +02:00
|
|
|
|
2022-08-01 15:57:19 +02:00
|
|
|
if err = f.ParseQueryString(); err != nil {
|
|
|
|
return file, err
|
|
|
|
}
|
|
|
|
|
2022-08-31 17:42:57 +02:00
|
|
|
// Public private only?
|
|
|
|
if !public {
|
|
|
|
f.Public = false
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:32:39 +02:00
|
|
|
if photos, _, err := search.Photos(f); err != nil {
|
2020-05-30 01:41:47 +02:00
|
|
|
return file, err
|
|
|
|
} else if len(photos) > 0 {
|
|
|
|
for _, photo := range photos {
|
2020-05-30 14:52:47 +02:00
|
|
|
if err := Db().Where("photo_uid = ? AND file_primary = 1", photo.PhotoUID).First(&file).Error; err != nil {
|
|
|
|
return file, err
|
|
|
|
} else {
|
|
|
|
return file, nil
|
|
|
|
}
|
2020-05-30 01:41:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-06 11:19:18 +02:00
|
|
|
// Automatically hide empty months.
|
2021-11-18 13:13:48 +01:00
|
|
|
switch a.AlbumType {
|
|
|
|
case entity.AlbumMonth, entity.AlbumState:
|
2021-09-06 11:19:18 +02:00
|
|
|
if err := a.Delete(); err != nil {
|
2021-11-18 13:13:48 +01:00
|
|
|
log.Errorf("%s: %s (hide)", a.AlbumType, err)
|
2021-09-06 11:19:18 +02:00
|
|
|
} else {
|
2022-04-15 09:42:07 +02:00
|
|
|
log.Infof("%s: %s hidden", a.AlbumType, clean.Log(a.AlbumTitle))
|
2021-09-06 11:19:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 13:13:48 +01:00
|
|
|
// Return without album cover.
|
2021-09-06 11:19:18 +02:00
|
|
|
return file, fmt.Errorf("no cover found")
|
2020-05-30 01:41:47 +02:00
|
|
|
}
|
|
|
|
|
2022-08-31 17:42:57 +02:00
|
|
|
// Build query.
|
|
|
|
stmt := Db().Where("files.file_primary = 1 AND files.file_missing = 0 AND files.file_type = 'jpg' AND files.deleted_at IS NULL").
|
2021-02-07 19:04:17 +01:00
|
|
|
Joins("JOIN albums ON albums.album_uid = ?", uid).
|
2020-11-21 18:05:20 +01:00
|
|
|
Joins("JOIN photos_albums pa ON pa.album_uid = albums.album_uid AND pa.photo_uid = files.photo_uid AND pa.hidden = 0").
|
2022-08-31 17:42:57 +02:00
|
|
|
Joins("JOIN photos ON photos.id = files.photo_id AND photos.deleted_at IS NULL")
|
|
|
|
|
|
|
|
// Public pictures only?
|
|
|
|
if public {
|
|
|
|
stmt = stmt.Where("photos.photo_private = 0")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find first picture.
|
|
|
|
if err := stmt.Order("photos.photo_quality DESC, photos.taken_at DESC").
|
2019-12-11 07:37:39 +01:00
|
|
|
First(&file).Error; err != nil {
|
|
|
|
return file, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
|
2022-09-14 22:00:20 +02:00
|
|
|
// UpdateAlbumDates updates the year, month and day of the album based on the indexed photo metadata.
|
2020-12-09 13:10:21 +01:00
|
|
|
func UpdateAlbumDates() error {
|
2021-12-09 02:33:41 +01:00
|
|
|
mutex.Index.Lock()
|
|
|
|
defer mutex.Index.Unlock()
|
2021-10-06 11:50:48 +02:00
|
|
|
|
2020-12-09 21:44:39 +01:00
|
|
|
switch DbDialect() {
|
|
|
|
case MySQL:
|
|
|
|
return UnscopedDb().Exec(`UPDATE albums
|
|
|
|
INNER JOIN
|
|
|
|
(SELECT photo_path, MAX(taken_at_local) AS taken_max
|
|
|
|
FROM photos WHERE taken_src = 'meta' AND photos.photo_quality >= 3 AND photos.deleted_at IS NULL
|
|
|
|
GROUP BY photo_path) AS p ON albums.album_path = p.photo_path
|
|
|
|
SET albums.album_year = YEAR(taken_max), albums.album_month = MONTH(taken_max), albums.album_day = DAY(taken_max)
|
|
|
|
WHERE albums.album_type = 'folder' AND albums.album_path IS NOT NULL AND p.taken_max IS NOT NULL`).Error
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-09 13:10:21 +01:00
|
|
|
}
|
2020-12-17 18:24:55 +01:00
|
|
|
|
2020-12-27 13:11:08 +01:00
|
|
|
// UpdateMissingAlbumEntries sets a flag for missing photo album entries.
|
|
|
|
func UpdateMissingAlbumEntries() error {
|
2021-12-09 02:33:41 +01:00
|
|
|
mutex.Index.Lock()
|
|
|
|
defer mutex.Index.Unlock()
|
2021-10-06 11:50:48 +02:00
|
|
|
|
2020-12-27 13:11:08 +01:00
|
|
|
switch DbDialect() {
|
|
|
|
default:
|
2021-01-16 12:48:43 +01:00
|
|
|
return UnscopedDb().Exec(`UPDATE photos_albums SET missing = 1 WHERE photo_uid IN
|
2020-12-27 13:11:08 +01:00
|
|
|
(SELECT photo_uid FROM photos WHERE deleted_at IS NOT NULL OR photo_quality < 0)`).Error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AlbumEntryFound removes the missing flag from album entries.
|
|
|
|
func AlbumEntryFound(uid string) error {
|
|
|
|
switch DbDialect() {
|
|
|
|
default:
|
|
|
|
return UnscopedDb().Exec(`UPDATE photos_albums SET missing = 0 WHERE photo_uid = ?`, uid).Error
|
|
|
|
}
|
|
|
|
}
|
2022-05-16 23:59:28 +02:00
|
|
|
|
2022-08-01 15:57:19 +02:00
|
|
|
// AlbumsPhotoUIDs returns up to 100000 photo UIDs that belong to the specified albums.
|
2022-05-16 23:59:28 +02:00
|
|
|
func AlbumsPhotoUIDs(albums []string, includeDefault, includePrivate bool) (photos []string, err error) {
|
|
|
|
for _, albumUid := range albums {
|
|
|
|
a, err := AlbumByUID(albumUid)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("query: album %s not found (%s)", albumUid, err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.IsDefault() && !includeDefault {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
frm := form.SearchPhotos{
|
|
|
|
Album: a.AlbumUID,
|
|
|
|
Filter: a.AlbumFilter,
|
2022-08-01 15:57:19 +02:00
|
|
|
Count: 100000,
|
2022-05-16 23:59:28 +02:00
|
|
|
Offset: 0,
|
|
|
|
Public: !includePrivate,
|
|
|
|
Hidden: false,
|
|
|
|
Archived: false,
|
|
|
|
Quality: 1,
|
|
|
|
}
|
|
|
|
|
2022-08-01 15:57:19 +02:00
|
|
|
if err := frm.ParseQueryString(); err != nil {
|
|
|
|
return photos, err
|
|
|
|
}
|
|
|
|
|
2022-05-16 23:59:28 +02:00
|
|
|
res, count, err := search.PhotoIds(frm)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return photos, err
|
|
|
|
} else if count == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ids := make([]string, 0, count)
|
|
|
|
|
|
|
|
for _, r := range res {
|
|
|
|
ids = append(ids, r.PhotoUID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(ids) > 0 {
|
|
|
|
photos = append(photos, ids...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return photos, nil
|
|
|
|
}
|