2021-09-18 15:32:39 +02:00
package search
2020-05-08 15:41:01 +02:00
import (
"fmt"
2021-10-01 17:26:29 +02:00
"path"
2020-05-08 15:41:01 +02:00
"strings"
"time"
2021-10-01 00:05:49 +02:00
"github.com/dustin/go-humanize/english"
2020-05-11 14:49:00 +02:00
"github.com/jinzhu/gorm"
2021-09-29 22:57:26 +02:00
2022-09-30 00:42:19 +02:00
"github.com/photoprism/photoprism/internal/acl"
2020-05-08 15:41:01 +02:00
"github.com/photoprism/photoprism/internal/entity"
2022-09-30 00:42:19 +02:00
"github.com/photoprism/photoprism/internal/event"
2020-05-08 15:41:01 +02:00
"github.com/photoprism/photoprism/internal/form"
2023-09-19 22:03:40 +02:00
"github.com/photoprism/photoprism/pkg/clean"
2021-10-01 17:26:29 +02:00
"github.com/photoprism/photoprism/pkg/fs"
2023-09-20 16:56:38 +02:00
"github.com/photoprism/photoprism/pkg/geo"
2023-10-06 02:57:07 +02:00
"github.com/photoprism/photoprism/pkg/media"
2023-09-19 22:03:40 +02:00
"github.com/photoprism/photoprism/pkg/pluscode"
2021-09-29 22:57:26 +02:00
"github.com/photoprism/photoprism/pkg/rnd"
2023-09-19 22:03:40 +02:00
"github.com/photoprism/photoprism/pkg/s2"
2023-01-30 12:27:34 +01:00
"github.com/photoprism/photoprism/pkg/sortby"
2020-05-08 15:41:01 +02:00
"github.com/photoprism/photoprism/pkg/txt"
)
2022-04-15 09:42:07 +02:00
// PhotosColsAll contains all supported result column names.
2022-03-30 20:36:25 +02:00
var PhotosColsAll = SelectString ( Photo { } , [ ] string { "*" } )
2022-04-15 09:42:07 +02:00
// PhotosColsView contains the result column names necessary for the photo viewer.
2022-03-30 20:36:25 +02:00
var PhotosColsView = SelectString ( Photo { } , SelectCols ( GeoResult { } , [ ] string { "*" } ) )
2022-09-30 19:15:10 +02:00
// Photos finds PhotoResults based on the search form without checking rights or permissions.
2021-11-26 13:59:10 +01:00
func Photos ( f form . SearchPhotos ) ( results PhotoResults , count int , err error ) {
2022-09-30 00:42:19 +02:00
return searchPhotos ( f , nil , PhotosColsAll )
}
2022-09-30 19:15:10 +02:00
// UserPhotos finds PhotoResults based on the search form and user session.
2022-09-30 00:42:19 +02:00
func UserPhotos ( f form . SearchPhotos , sess * entity . Session ) ( results PhotoResults , count int , err error ) {
return searchPhotos ( f , sess , PhotosColsAll )
2022-03-30 20:36:25 +02:00
}
2022-05-16 23:59:28 +02:00
// PhotoIds finds photo and file ids based on the search form provided and returns them as PhotoResults.
func PhotoIds ( f form . SearchPhotos ) ( files PhotoResults , count int , err error ) {
f . Merged = false
f . Primary = true
2022-09-30 00:42:19 +02:00
return searchPhotos ( f , nil , "photos.id, photos.photo_uid, files.file_uid" )
2022-05-16 23:59:28 +02:00
}
2022-09-30 00:42:19 +02:00
// searchPhotos finds photos based on the search form and user session then returns them as PhotoResults.
func searchPhotos ( f form . SearchPhotos , sess * entity . Session , resultCols string ) ( results PhotoResults , count int , err error ) {
2020-05-23 20:58:58 +02:00
start := time . Now ( )
2022-09-30 00:42:19 +02:00
// Parse query string and filter.
if err = f . ParseQueryString ( ) ; err != nil {
log . Debugf ( "search: %s" , err )
return PhotoResults { } , 0 , ErrBadRequest
}
2020-05-08 15:41:01 +02:00
2023-09-20 12:10:49 +02:00
// Find photos near another?
if txt . NotEmpty ( f . Near ) {
photo := Photo { }
// Find a nearby picture using the UID or return an empty result otherwise.
if err = Db ( ) . First ( & photo , "photo_uid = ?" , f . Near ) . Error ; err != nil {
log . Debugf ( "search: %s (find nearby)" , err )
return PhotoResults { } , 0 , ErrNotFound
}
// Set the S2 Cell ID to search for.
f . S2 = photo . CellID
}
// Set default search distance.
if f . Dist <= 0 {
2023-09-20 16:56:38 +02:00
f . Dist = geo . DefaultDist
} else if f . Dist > geo . DistLimit {
f . Dist = geo . DistLimit
2023-09-20 12:10:49 +02:00
}
2023-09-19 22:03:40 +02:00
2022-09-30 00:42:19 +02:00
// Specify table names and joins.
s := UnscopedDb ( ) . Table ( entity . File { } . TableName ( ) ) . Select ( resultCols ) .
2022-03-30 20:36:25 +02:00
Joins ( "JOIN photos ON files.photo_id = photos.id AND files.media_id IS NOT NULL" ) .
2021-01-10 12:38:51 +01:00
Joins ( "LEFT JOIN cameras ON photos.camera_id = cameras.id" ) .
Joins ( "LEFT JOIN lenses ON photos.lens_id = lenses.id" ) .
2022-03-30 20:36:25 +02:00
Joins ( "LEFT JOIN places ON photos.place_id = places.id" )
2020-06-04 17:06:42 +02:00
2022-09-30 19:15:10 +02:00
// Accept the album UID as scope for backward compatibility.
2022-10-02 11:38:30 +02:00
if rnd . IsUID ( f . Album , entity . AlbumUID ) {
2022-09-30 19:15:10 +02:00
if txt . Empty ( f . Scope ) {
f . Scope = f . Album
2021-01-20 12:08:48 +01:00
}
2022-09-30 19:15:10 +02:00
f . Album = ""
2022-09-30 00:42:19 +02:00
}
// Limit search results to a specific UID scope, e.g. when sharing.
2022-09-30 19:15:10 +02:00
if txt . NotEmpty ( f . Scope ) {
f . Scope = strings . ToLower ( f . Scope )
if idType , idPrefix := rnd . IdType ( f . Scope ) ; idType != rnd . TypeUID || idPrefix != entity . AlbumUID {
2022-09-30 00:42:19 +02:00
return PhotoResults { } , 0 , ErrInvalidId
2022-09-30 19:15:10 +02:00
} else if a , err := entity . CachedAlbumByUID ( f . Scope ) ; err != nil || a . AlbumUID == "" {
2022-09-30 00:42:19 +02:00
return PhotoResults { } , 0 , ErrInvalidId
} else if a . AlbumFilter == "" {
s = s . Joins ( "JOIN photos_albums ON photos_albums.photo_uid = files.photo_uid" ) .
Where ( "photos_albums.hidden = 0 AND photos_albums.album_uid = ?" , a . AlbumUID )
2023-09-20 22:07:24 +02:00
} else if formErr := form . Unserialize ( & f , a . AlbumFilter ) ; formErr != nil {
log . Debugf ( "search: %s (%s)" , clean . Error ( formErr ) , clean . Log ( a . AlbumFilter ) )
2022-09-30 00:42:19 +02:00
return PhotoResults { } , 0 , ErrBadFilter
} else {
f . Filter = a . AlbumFilter
s = s . Where ( "files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)" , a . AlbumUID )
}
2023-09-20 12:10:49 +02:00
2023-09-20 16:56:38 +02:00
// Enforce search distance range (km).
if f . Dist <= 0 {
f . Dist = geo . DefaultDist
} else if f . Dist > geo . ScopeDistLimit {
f . Dist = geo . ScopeDistLimit
2023-09-20 12:10:49 +02:00
}
2022-09-30 00:42:19 +02:00
} else {
2022-09-30 19:15:10 +02:00
f . Scope = ""
2022-09-30 00:42:19 +02:00
}
// Check session permissions and apply as needed.
if sess != nil {
2022-09-30 19:15:10 +02:00
user := sess . User ( )
aclRole := user . AclRole ( )
2022-09-30 00:42:19 +02:00
2023-02-21 10:47:15 +01:00
// Exclude private content.
2022-09-30 00:42:19 +02:00
if acl . Resources . Deny ( acl . ResourcePhotos , aclRole , acl . AccessPrivate ) {
f . Public = true
f . Private = false
}
2023-02-21 10:47:15 +01:00
// Exclude archived content.
2022-09-30 00:42:19 +02:00
if acl . Resources . Deny ( acl . ResourcePhotos , aclRole , acl . ActionDelete ) {
f . Archived = false
f . Review = false
}
2023-02-21 10:47:15 +01:00
// Exclude hidden files.
2022-09-30 00:42:19 +02:00
if acl . Resources . Deny ( acl . ResourceFiles , aclRole , acl . AccessAll ) {
f . Hidden = false
}
2022-10-02 22:09:02 +02:00
// Visitors and other restricted users can only access shared content.
2023-10-07 17:33:04 +02:00
if f . Scope != "" && ! sess . HasShare ( f . Scope ) && ( sess . User ( ) . HasSharedAccessOnly ( acl . ResourcePhotos ) || sess . NotRegistered ( ) ) ||
2022-10-02 22:09:02 +02:00
f . Scope == "" && acl . Resources . Deny ( acl . ResourcePhotos , aclRole , acl . ActionSearch ) {
event . AuditErr ( [ ] string { sess . IP ( ) , "session %s" , "%s %s as %s" , "denied" } , sess . RefID , acl . ActionSearch . String ( ) , string ( acl . ResourcePhotos ) , aclRole )
return PhotoResults { } , 0 , ErrForbidden
}
// Limit results for external users.
2022-09-30 19:15:10 +02:00
if f . Scope == "" && acl . Resources . DenyAll ( acl . ResourcePhotos , aclRole , acl . Permissions { acl . AccessAll , acl . AccessLibrary } ) {
2022-10-07 21:35:01 +02:00
sharedAlbums := "photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND missing = 0 AND album_uid IN (?)) OR "
2022-10-02 22:09:02 +02:00
if sess . IsVisitor ( ) || sess . NotRegistered ( ) {
2022-10-07 21:35:01 +02:00
s = s . Where ( sharedAlbums + "photos.published_at > ?" , sess . SharedUIDs ( ) , entity . TimeStamp ( ) )
2023-02-20 15:54:33 +01:00
} else if basePath := user . GetBasePath ( ) ; basePath == "" {
2022-10-07 21:35:01 +02:00
s = s . Where ( sharedAlbums + "photos.created_by = ? OR photos.published_at > ?" , sess . SharedUIDs ( ) , user . UserUID , entity . TimeStamp ( ) )
2022-09-30 19:15:10 +02:00
} else {
2022-10-07 21:35:01 +02:00
s = s . Where ( sharedAlbums + "photos.created_by = ? OR photos.published_at > ? OR photos.photo_path = ? OR photos.photo_path LIKE ?" ,
2023-02-20 15:54:33 +01:00
sess . SharedUIDs ( ) , user . UserUID , entity . TimeStamp ( ) , basePath , basePath + "/%" )
2022-09-30 19:15:10 +02:00
}
2022-09-30 00:42:19 +02:00
}
2021-01-20 12:08:48 +01:00
}
2022-09-30 19:15:10 +02:00
// Set sort order.
switch f . Order {
2023-01-30 12:27:34 +01:00
case sortby . Edited :
2022-09-30 19:15:10 +02:00
s = s . Where ( "photos.edited_at IS NOT NULL" ) . Order ( "photos.edited_at DESC, files.media_id" )
2023-01-30 12:27:34 +01:00
case sortby . Relevance :
2022-09-30 19:15:10 +02:00
if f . Label != "" {
s = s . Order ( "photos.photo_quality DESC, photos_labels.uncertainty ASC, files.time_index" )
} else {
s = s . Order ( "photos.photo_quality DESC, files.time_index" )
}
2023-01-30 12:27:34 +01:00
case sortby . Duration :
2022-10-02 22:09:02 +02:00
s = s . Order ( "photos.photo_duration DESC, files.time_index" )
2023-01-30 12:27:34 +01:00
case sortby . Size :
2022-10-24 12:56:17 +02:00
s = s . Order ( "files.file_size DESC, files.time_index" )
2023-01-30 12:27:34 +01:00
case sortby . Newest :
2022-09-30 19:15:10 +02:00
s = s . Order ( "files.time_index" )
2023-01-30 12:27:34 +01:00
case sortby . Oldest :
2022-09-30 19:15:10 +02:00
s = s . Order ( "files.photo_taken_at, files.media_id" )
2023-01-30 12:27:34 +01:00
case sortby . Similar :
2022-09-30 19:15:10 +02:00
s = s . Where ( "files.file_diff > 0" )
2023-09-04 12:28:41 +02:00
s = s . Order ( "photos.photo_color, photos.cell_id, files.file_diff, files.photo_id, files.time_index" )
2023-01-30 12:27:34 +01:00
case sortby . Name :
2022-09-30 19:15:10 +02:00
s = s . Order ( "photos.photo_path, photos.photo_name, files.time_index" )
2023-01-30 12:27:34 +01:00
case sortby . Random :
s = s . Order ( sortby . RandomExpr ( s . Dialect ( ) ) )
case sortby . Default , sortby . Imported , sortby . Added :
2022-09-30 19:15:10 +02:00
s = s . Order ( "files.media_id" )
default :
return PhotoResults { } , 0 , ErrBadSortOrder
}
2023-10-06 02:22:48 +02:00
// Exclude files with errors by default.
2020-06-04 17:06:42 +02:00
if ! f . Hidden {
if f . Error {
s = s . Where ( "files.file_error <> ''" )
} else {
s = s . Where ( "files.file_error = ''" )
}
}
2020-05-08 15:41:01 +02:00
2023-10-06 02:22:48 +02:00
// Find primary files only?
2020-06-28 15:23:15 +02:00
if f . Primary {
s = s . Where ( "files.file_primary = 1" )
2023-10-06 02:57:07 +02:00
} else if f . Order == sortby . Similar {
s = s . Where ( "files.file_primary = 1 OR files.media_type = ?" , media . Video )
} else if f . Order == sortby . Random {
s = s . Where ( "files.file_primary = 1 AND photos.photo_type NOT IN ('live','video') OR photos.photo_type IN ('live','video') AND files.media_type IN ('live','video')" )
2023-10-06 02:22:48 +02:00
} else {
// Otherwise, find all matching media except sidecar files.
s = s . Where ( "files.file_sidecar = 0" )
2020-06-28 15:23:15 +02:00
}
2023-02-21 10:47:15 +01:00
// Find specific UIDs only.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . UID ) {
2022-09-30 00:42:19 +02:00
ids := SplitOr ( strings . ToLower ( f . UID ) )
2022-10-02 11:38:30 +02:00
idType , prefix := rnd . ContainsType ( ids )
if idType == rnd . TypeUnknown {
return PhotoResults { } , 0 , fmt . Errorf ( "%s ids specified" , idType )
} else if idType . SHA ( ) {
s = s . Where ( "files.file_hash IN (?)" , ids )
} else if idType == rnd . TypeUID {
2022-09-30 00:42:19 +02:00
switch prefix {
case entity . PhotoUID :
s = s . Where ( "photos.photo_uid IN (?)" , ids )
case entity . FileUID :
s = s . Where ( "files.file_uid IN (?)" , ids )
default :
2022-10-02 11:38:30 +02:00
return PhotoResults { } , 0 , fmt . Errorf ( "invalid ids specified" )
2022-09-30 00:42:19 +02:00
}
}
2020-05-08 15:41:01 +02:00
2023-02-21 10:47:15 +01:00
// Find UIDs only to improve performance.
2022-09-30 00:42:19 +02:00
if sess == nil && f . FindUidOnly ( ) {
2021-12-16 11:42:57 +01:00
if result := s . Scan ( & results ) ; result . Error != nil {
return results , 0 , result . Error
}
2020-05-23 20:58:58 +02:00
2021-12-16 15:26:54 +01:00
log . Debugf ( "photos: found %s for %s [%s]" , english . Plural ( len ( results ) , "result" , "results" ) , f . SerializeAll ( ) , time . Since ( start ) )
2020-05-08 15:41:01 +02:00
2021-12-16 11:42:57 +01:00
if f . Merged {
2022-03-29 00:21:50 +02:00
return results . Merge ( )
2021-12-16 11:42:57 +01:00
}
return results , len ( results ) , nil
}
2020-05-08 15:41:01 +02:00
}
2023-02-21 10:47:15 +01:00
// Find Unique Image ID (Exif), Document ID, or Instance ID (XMP).
2022-12-28 17:50:08 +01:00
if txt . NotEmpty ( f . ID ) {
for _ , id := range SplitAnd ( strings . ToLower ( f . ID ) ) {
if ids := SplitOr ( id ) ; len ( ids ) > 0 {
s = s . Where ( "files.instance_id IN (?) OR photos.uuid IN (?)" , ids , ids )
}
}
}
2020-05-14 19:03:12 +02:00
// Filter by label, label category and keywords.
2020-05-08 15:41:01 +02:00
var categories [ ] entity . Category
2020-05-11 14:49:00 +02:00
var labels [ ] entity . Label
2020-05-08 15:41:01 +02:00
var labelIds [ ] uint
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Label ) {
2023-09-20 22:07:24 +02:00
if labelErr := Db ( ) . Where ( AnySlug ( "label_slug" , f . Label , txt . Or ) ) . Or ( AnySlug ( "custom_slug" , f . Label , txt . Or ) ) . Find ( & labels ) . Error ; len ( labels ) == 0 || labelErr != nil {
2021-12-14 17:07:38 +01:00
log . Debugf ( "search: label %s not found" , txt . LogParamLower ( f . Label ) )
2021-09-23 23:46:17 +02:00
return PhotoResults { } , 0 , nil
2020-05-08 15:41:01 +02:00
} else {
2020-05-29 18:04:30 +02:00
for _ , l := range labels {
labelIds = append ( labelIds , l . ID )
2022-03-30 20:36:25 +02:00
Log ( "find categories" , Db ( ) . Where ( "category_id = ?" , l . ID ) . Find ( & categories ) . Error )
log . Debugf ( "search: label %s includes %d categories" , txt . LogParamLower ( l . LabelName ) , len ( categories ) )
2020-05-08 15:41:01 +02:00
2020-05-29 18:04:30 +02:00
for _ , category := range categories {
labelIds = append ( labelIds , category . LabelID )
}
2020-05-08 15:41:01 +02:00
}
2022-03-29 00:21:50 +02:00
s = s . Joins ( "JOIN photos_labels ON photos_labels.photo_id = files.photo_id AND photos_labels.uncertainty < 100 AND photos_labels.label_id IN (?)" , labelIds ) .
2020-05-29 18:04:30 +02:00
Group ( "photos.id, files.id" )
2020-05-08 15:41:01 +02:00
}
}
2021-09-03 20:14:11 +02:00
// Set search filters based on search terms.
2021-09-17 15:52:25 +02:00
if terms := txt . SearchTerms ( f . Query ) ; f . Query != "" && len ( terms ) == 0 {
2021-09-29 22:57:26 +02:00
if f . Title == "" {
f . Title = fmt . Sprintf ( "%s*" , strings . Trim ( f . Query , "%*" ) )
2021-09-29 20:09:34 +02:00
f . Query = ""
}
2021-09-17 15:52:25 +02:00
} else if len ( terms ) > 0 {
2021-09-03 20:14:11 +02:00
switch {
case terms [ "faces" ] :
f . Query = strings . ReplaceAll ( f . Query , "faces" , "" )
f . Faces = "true"
2021-09-06 15:42:30 +02:00
case terms [ "people" ] :
f . Query = strings . ReplaceAll ( f . Query , "people" , "" )
f . Faces = "true"
2021-09-03 20:14:11 +02:00
case terms [ "videos" ] :
f . Query = strings . ReplaceAll ( f . Query , "videos" , "" )
f . Video = true
2021-10-12 14:31:27 +02:00
case terms [ "video" ] :
f . Query = strings . ReplaceAll ( f . Query , "video" , "" )
f . Video = true
2023-02-11 20:18:04 +01:00
case terms [ "vectors" ] :
f . Query = strings . ReplaceAll ( f . Query , "vectors" , "" )
f . Vector = true
case terms [ "vector" ] :
f . Query = strings . ReplaceAll ( f . Query , "vector" , "" )
2022-04-14 08:39:52 +02:00
f . Vector = true
2022-04-15 09:42:07 +02:00
case terms [ "animated" ] :
f . Query = strings . ReplaceAll ( f . Query , "animated" , "" )
f . Animated = true
2022-04-14 08:39:52 +02:00
case terms [ "gifs" ] :
f . Query = strings . ReplaceAll ( f . Query , "gifs" , "" )
f . Animated = true
case terms [ "gif" ] :
f . Query = strings . ReplaceAll ( f . Query , "gif" , "" )
f . Animated = true
2021-10-13 16:12:56 +02:00
case terms [ "live" ] :
f . Query = strings . ReplaceAll ( f . Query , "live" , "" )
f . Live = true
case terms [ "raws" ] :
f . Query = strings . ReplaceAll ( f . Query , "raws" , "" )
f . Raw = true
2022-04-14 14:13:54 +02:00
case terms [ "raw" ] :
f . Query = strings . ReplaceAll ( f . Query , "raw" , "" )
f . Raw = true
2021-09-03 20:14:11 +02:00
case terms [ "favorites" ] :
f . Query = strings . ReplaceAll ( f . Query , "favorites" , "" )
2023-09-01 20:30:54 +02:00
f . Favorite = "true"
2021-09-03 20:14:11 +02:00
case terms [ "stacks" ] :
f . Query = strings . ReplaceAll ( f . Query , "stacks" , "" )
f . Stack = true
case terms [ "panoramas" ] :
f . Query = strings . ReplaceAll ( f . Query , "panoramas" , "" )
f . Panorama = true
case terms [ "scans" ] :
f . Query = strings . ReplaceAll ( f . Query , "scans" , "" )
2023-08-16 10:34:55 +02:00
f . Scan = "true"
2021-09-03 20:14:11 +02:00
case terms [ "monochrome" ] :
f . Query = strings . ReplaceAll ( f . Query , "monochrome" , "" )
f . Mono = true
2021-10-12 14:31:27 +02:00
case terms [ "mono" ] :
f . Query = strings . ReplaceAll ( f . Query , "mono" , "" )
f . Mono = true
2021-09-03 20:14:11 +02:00
}
}
2023-06-29 18:35:02 +02:00
// Filter by location info.
if txt . No ( f . Geo ) {
s = s . Where ( "photos.cell_id = 'zz'" )
} else if txt . NotEmpty ( f . Geo ) {
2020-07-12 08:27:05 +02:00
s = s . Where ( "photos.cell_id <> 'zz'" )
2023-06-29 18:35:02 +02:00
}
2020-05-08 15:41:01 +02:00
2023-06-29 18:35:02 +02:00
// Filter by query string.
if f . Query != "" {
2020-05-29 18:04:30 +02:00
if err := Db ( ) . Where ( AnySlug ( "custom_slug" , f . Query , " " ) ) . Find ( & labels ) . Error ; len ( labels ) == 0 || err != nil {
2023-06-29 18:35:02 +02:00
log . Tracef ( "search: label %s not found, using fuzzy search" , txt . LogParamLower ( f . Query ) )
2020-05-08 15:41:01 +02:00
2021-08-29 19:19:54 +02:00
for _ , where := range LikeAnyKeyword ( "k.keyword" , f . Query ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( "files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))" , gorm . Expr ( where ) )
2020-05-11 14:49:00 +02:00
}
2020-05-08 15:41:01 +02:00
} else {
2020-05-11 14:49:00 +02:00
for _ , l := range labels {
labelIds = append ( labelIds , l . ID )
2020-05-08 15:41:01 +02:00
2020-05-11 14:49:00 +02:00
Db ( ) . Where ( "category_id = ?" , l . ID ) . Find ( & categories )
2020-05-08 15:41:01 +02:00
2023-06-29 18:35:02 +02:00
log . Tracef ( "search: label %s includes %d categories" , txt . LogParamLower ( l . LabelName ) , len ( categories ) )
2020-05-08 15:41:01 +02:00
2020-05-11 14:49:00 +02:00
for _ , category := range categories {
labelIds = append ( labelIds , category . LabelID )
}
}
2020-05-08 15:41:01 +02:00
2021-08-29 19:19:54 +02:00
if wheres := LikeAnyKeyword ( "k.keyword" , f . Query ) ; len ( wheres ) > 0 {
2021-08-29 16:16:49 +02:00
for _ , where := range wheres {
2022-03-29 00:21:50 +02:00
s = s . Where ( "files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?)) OR " +
"files.photo_id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))" , gorm . Expr ( where ) , labelIds )
2021-08-29 16:16:49 +02:00
}
2020-05-11 14:49:00 +02:00
} else {
2022-03-29 00:21:50 +02:00
s = s . Where ( "files.photo_id IN (SELECT pl.photo_id FROM photos_labels pl WHERE pl.uncertainty < 100 AND pl.label_id IN (?))" , labelIds )
2020-05-11 14:49:00 +02:00
}
2020-05-08 15:41:01 +02:00
}
}
2023-02-21 10:47:15 +01:00
// Search for one or more keywords.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Keywords ) {
2022-01-05 18:51:18 +01:00
for _ , where := range LikeAnyWord ( "k.keyword" , f . Keywords ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( "files.photo_id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))" , gorm . Expr ( where ) )
2021-08-29 16:16:49 +02:00
}
}
2023-02-21 10:47:15 +01:00
// Filter by number of faces.
if f . Faces == "" {
// Do nothing.
} else if txt . IsUInt ( f . Faces ) {
2021-10-12 14:31:27 +02:00
s = s . Where ( "photos.photo_faces >= ?" , txt . Int ( f . Faces ) )
2021-10-13 16:12:56 +02:00
} else if txt . New ( f . Faces ) && f . Face == "" {
f . Face = f . Faces
f . Faces = ""
2021-10-12 14:31:27 +02:00
} else if txt . Yes ( f . Faces ) {
s = s . Where ( "photos.photo_faces > 0" )
} else if txt . No ( f . Faces ) {
s = s . Where ( "photos.photo_faces = 0" )
}
2021-09-23 14:23:00 +02:00
// Filter for specific face clusters? Example: PLJ7A3G4MBGZJRMVDIUCBLC46IAP4N7O
2023-02-21 10:47:15 +01:00
if f . Face == "" {
// Do nothing.
} else if len ( f . Face ) >= 32 {
2022-04-02 22:23:38 +02:00
for _ , f := range SplitAnd ( strings . ToUpper ( f . Face ) ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE face_id IN (?))" ,
2022-04-02 22:23:38 +02:00
entity . Marker { } . TableName ( ) ) , SplitOr ( f ) )
2021-09-18 15:32:39 +02:00
}
2021-09-29 20:09:34 +02:00
} else if txt . New ( f . Face ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE subj_uid IS NULL OR subj_uid = '')" ,
2021-09-29 20:09:34 +02:00
entity . Marker { } . TableName ( ) ) , entity . MarkerFace )
2021-09-23 14:23:00 +02:00
} else if txt . No ( f . Face ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE face_id IS NULL OR face_id = '')" ,
2021-09-23 14:23:00 +02:00
entity . Marker { } . TableName ( ) ) , entity . MarkerFace )
} else if txt . Yes ( f . Face ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? WHERE face_id IS NOT NULL AND face_id <> '')" ,
2021-09-23 14:23:00 +02:00
entity . Marker { } . TableName ( ) ) , entity . MarkerFace )
2023-02-21 10:47:15 +01:00
} else if txt . IsUInt ( f . Face ) {
s = s . Where ( "files.photo_id IN (SELECT photo_id FROM files f JOIN markers m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 AND m.marker_type = ? JOIN faces ON faces.id = m.face_id WHERE m.face_id IS NOT NULL AND m.face_id <> '' AND faces.face_kind = ?)" ,
entity . MarkerFace , txt . Int ( f . Face ) )
2021-09-18 15:32:39 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter for one or more subjects.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Subject ) {
2022-04-02 22:23:38 +02:00
for _ , subj := range SplitAnd ( strings . ToLower ( f . Subject ) ) {
2022-09-28 09:01:17 +02:00
if subjects := SplitOr ( subj ) ; rnd . ContainsUID ( subjects , 'j' ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 WHERE subj_uid IN (?))" ,
2021-09-20 12:36:59 +02:00
entity . Marker { } . TableName ( ) ) , subjects )
} else {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))" ,
2021-09-20 12:36:59 +02:00
entity . Marker { } . TableName ( ) , entity . Subject { } . TableName ( ) ) , gorm . Expr ( AnySlug ( "s.subj_slug" , subj , txt . Or ) ) )
}
2021-08-30 11:56:34 +02:00
}
2022-03-24 18:30:59 +01:00
} else if txt . NotEmpty ( f . Subjects ) {
2021-09-20 09:57:48 +02:00
for _ , where := range LikeAllNames ( Cols { "subj_name" , "subj_alias" } , f . Subjects ) {
2022-03-29 00:21:50 +02:00
s = s . Where ( fmt . Sprintf ( "files.photo_id IN (SELECT photo_id FROM files f JOIN %s m ON f.file_uid = m.file_uid AND m.marker_invalid = 0 JOIN %s s ON s.subj_uid = m.subj_uid WHERE (?))" ,
2021-08-29 16:16:49 +02:00
entity . Marker { } . TableName ( ) , entity . Subject { } . TableName ( ) ) , gorm . Expr ( where ) )
}
}
2023-02-21 10:47:15 +01:00
// Filter by status.
2020-06-04 14:56:27 +02:00
if f . Hidden {
s = s . Where ( "photos.photo_quality = -1" )
s = s . Where ( "photos.deleted_at IS NULL" )
} else if f . Archived {
2020-12-15 20:14:06 +01:00
s = s . Where ( "photos.photo_quality > -1" )
2020-05-08 15:41:01 +02:00
s = s . Where ( "photos.deleted_at IS NOT NULL" )
} else {
s = s . Where ( "photos.deleted_at IS NULL" )
if f . Private {
s = s . Where ( "photos.photo_private = 1" )
} else if f . Public {
s = s . Where ( "photos.photo_private = 0" )
}
if f . Review {
s = s . Where ( "photos.photo_quality < 3" )
} else if f . Quality != 0 && f . Private == false {
s = s . Where ( "photos.photo_quality >= ?" , f . Quality )
}
}
2023-02-21 10:47:15 +01:00
// Filter by camera id or name.
2022-03-24 18:30:59 +01:00
if txt . IsPosInt ( f . Camera ) {
s = s . Where ( "photos.camera_id = ?" , txt . UInt ( f . Camera ) )
} else if txt . NotEmpty ( f . Camera ) {
v := strings . Trim ( f . Camera , "*%" ) + "%"
2022-03-25 18:01:34 +01:00
s = s . Where ( "cameras.camera_name LIKE ? OR cameras.camera_model LIKE ? OR cameras.camera_slug LIKE ?" , v , v , v )
2020-05-08 15:41:01 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by lens id or name.
2022-03-24 18:30:59 +01:00
if txt . IsPosInt ( f . Lens ) {
s = s . Where ( "photos.lens_id = ?" , txt . UInt ( f . Lens ) )
} else if txt . NotEmpty ( f . Lens ) {
v := strings . Trim ( f . Lens , "*%" ) + "%"
2022-03-25 18:01:34 +01:00
s = s . Where ( "lenses.lens_name LIKE ? OR lenses.lens_model LIKE ? OR lenses.lens_slug LIKE ?" , v , v , v )
2020-05-08 15:41:01 +02:00
}
2023-10-20 12:54:55 +02:00
// Filter by ISO Number (light sensitivity) range.
2023-10-12 14:53:40 +02:00
if rangeStart , rangeEnd , rangeErr := txt . IntRange ( f . Iso , 0 , 10000000 ) ; rangeErr == nil {
s = s . Where ( "photos.photo_iso >= ? AND photos.photo_iso <= ?" , rangeStart , rangeEnd )
}
2023-10-20 12:54:55 +02:00
// Filter by Focal Length (35mm equivalent) range.
2023-10-12 14:53:40 +02:00
if rangeStart , rangeEnd , rangeErr := txt . IntRange ( f . Mm , 0 , 10000000 ) ; rangeErr == nil {
s = s . Where ( "photos.photo_focal_length >= ? AND photos.photo_focal_length <= ?" , rangeStart , rangeEnd )
}
2023-10-20 12:54:55 +02:00
// Filter by Aperture (f-number) range.
if rangeStart , rangeEnd , rangeErr := txt . FloatRange ( f . F , 0 , 10000000 ) ; rangeErr == nil {
s = s . Where ( "photos.photo_f_number >= ? AND photos.photo_f_number <= ?" , rangeStart - 0.01 , rangeEnd + 0.01 )
}
2023-02-21 10:47:15 +01:00
// Filter by year.
2021-09-20 23:32:35 +02:00
if f . Year != "" {
s = s . Where ( AnyInt ( "photos.photo_year" , f . Year , txt . Or , entity . UnknownYear , txt . YearMax ) )
2020-05-08 15:41:01 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by month.
2021-09-20 23:32:35 +02:00
if f . Month != "" {
s = s . Where ( AnyInt ( "photos.photo_month" , f . Month , txt . Or , entity . UnknownMonth , txt . MonthMax ) )
2020-05-08 15:41:01 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by day.
2021-09-20 23:32:35 +02:00
if f . Day != "" {
s = s . Where ( AnyInt ( "photos.photo_day" , f . Day , txt . Or , entity . UnknownDay , txt . DayMax ) )
2020-07-06 07:41:33 +02:00
}
2023-11-20 11:47:23 +01:00
// Filter by Resolution in Megapixels (MP).
if rangeStart , rangeEnd , rangeErr := txt . IntRange ( f . Mp , 0 , 32000 ) ; rangeErr == nil {
s = s . Where ( "photos.photo_resolution >= ? AND photos.photo_resolution <= ?" , rangeStart , rangeEnd )
}
// Find panoramic pictures only.
if f . Panorama {
s = s . Where ( "photos.photo_panorama = 1" )
}
// Find portrait/landscape/square pictures only.
if f . Portrait {
s = s . Where ( "files.file_portrait = 1" )
} else if f . Landscape {
s = s . Where ( "files.file_aspect_ratio > 1.25" )
} else if f . Square {
s = s . Where ( "files.file_aspect_ratio = 1" )
}
2023-02-21 10:47:15 +01:00
// Filter by main color.
2020-05-08 15:41:01 +02:00
if f . Color != "" {
2022-04-02 22:23:38 +02:00
s = s . Where ( "files.file_main_color IN (?)" , SplitOr ( strings . ToLower ( f . Color ) ) )
2020-05-08 15:41:01 +02:00
}
2023-10-21 16:33:00 +02:00
// Filter by chroma.
if f . Mono {
s = s . Where ( "files.file_chroma = 0" )
} else if f . Chroma > 9 {
s = s . Where ( "files.file_chroma > ?" , f . Chroma )
} else if f . Chroma > 0 {
s = s . Where ( "files.file_chroma > 0 AND files.file_chroma <= ?" , f . Chroma )
}
if f . Diff != 0 {
s = s . Where ( "files.file_diff = ?" , f . Diff )
}
2023-09-01 20:30:54 +02:00
// Filter by favorite flag.
if txt . No ( f . Favorite ) {
s = s . Where ( "photos.photo_favorite = 0" )
} else if txt . NotEmpty ( f . Favorite ) {
2020-05-08 15:41:01 +02:00
s = s . Where ( "photos.photo_favorite = 1" )
}
2023-08-16 10:34:55 +02:00
// Filter by scan flag.
if txt . No ( f . Scan ) {
s = s . Where ( "photos.photo_scan = 0" )
} else if txt . NotEmpty ( f . Scan ) {
2020-07-06 14:35:25 +02:00
s = s . Where ( "photos.photo_scan = 1" )
2020-07-05 14:48:49 +02:00
}
2023-11-20 11:47:23 +01:00
// Filter by stack flag.
2020-12-19 19:15:32 +01:00
if f . Stackable {
s = s . Where ( "photos.photo_stack > -1" )
} else if f . Unstacked {
s = s . Where ( "photos.photo_stack = -1" )
2020-12-11 17:21:13 +01:00
}
2023-02-21 10:47:15 +01:00
// Filter by location country.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Country ) {
2022-04-02 22:23:38 +02:00
s = s . Where ( "photos.photo_country IN (?)" , SplitOr ( strings . ToLower ( f . Country ) ) )
2020-05-08 15:41:01 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by location state.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . State ) {
2022-04-02 22:23:38 +02:00
s = s . Where ( "places.place_state IN (?)" , SplitOr ( f . State ) )
2020-05-29 18:04:30 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by location city.
2022-08-31 23:04:00 +02:00
if txt . NotEmpty ( f . City ) {
s = s . Where ( "places.place_city IN (?)" , SplitOr ( f . City ) )
}
2023-02-21 10:47:15 +01:00
// Filter by location category.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Category ) {
2020-07-12 08:27:05 +02:00
s = s . Joins ( "JOIN cells ON photos.cell_id = cells.id" ) .
2022-04-02 22:23:38 +02:00
Where ( "cells.cell_category IN (?)" , SplitOr ( strings . ToLower ( f . Category ) ) )
2020-05-29 18:04:30 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by media type.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Type ) {
2022-04-02 22:23:38 +02:00
s = s . Where ( "photos.photo_type IN (?)" , SplitOr ( strings . ToLower ( f . Type ) ) )
2021-10-13 16:12:56 +02:00
} else if f . Video {
2022-04-14 08:39:52 +02:00
s = s . Where ( "photos.photo_type = ?" , entity . MediaVideo )
} else if f . Vector {
s = s . Where ( "photos.photo_type = ?" , entity . MediaVector )
} else if f . Animated {
s = s . Where ( "photos.photo_type = ?" , entity . MediaAnimated )
2021-10-13 16:12:56 +02:00
} else if f . Raw {
2022-04-14 08:39:52 +02:00
s = s . Where ( "photos.photo_type = ?" , entity . MediaRaw )
2021-10-13 16:12:56 +02:00
} else if f . Live {
2022-04-14 08:39:52 +02:00
s = s . Where ( "photos.photo_type = ?" , entity . MediaLive )
} else if f . Photo {
2023-02-11 20:18:04 +01:00
s = s . Where ( "photos.photo_type IN ('image','live','animated','vector','raw')" )
2020-05-21 10:03:56 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by storage path.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Path ) {
2020-05-21 10:03:56 +02:00
p := f . Path
if strings . HasPrefix ( p , "/" ) {
p = p [ 1 : ]
}
if strings . HasSuffix ( p , "/" ) {
s = s . Where ( "photos.photo_path = ?" , p [ : len ( p ) - 1 ] )
} else {
2021-09-29 20:09:34 +02:00
where , values := OrLike ( "photos.photo_path" , p )
s = s . Where ( where , values ... )
2020-05-21 10:03:56 +02:00
}
}
2021-10-01 17:26:29 +02:00
// Filter by primary file name without path and extension.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Name ) {
2021-10-01 17:26:29 +02:00
where , names := OrLike ( "photos.photo_name" , f . Name )
// Omit file path and known extensions.
for i := range names {
names [ i ] = fs . StripKnownExt ( path . Base ( names [ i ] . ( string ) ) )
}
s = s . Where ( where , names ... )
2020-05-21 10:03:56 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by complete file names.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Filename ) {
2021-09-29 20:09:34 +02:00
where , values := OrLike ( "files.file_name" , f . Filename )
s = s . Where ( where , values ... )
2020-12-03 21:07:38 +01:00
}
2023-02-21 10:47:15 +01:00
// Filter by original file name.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Original ) {
2021-09-29 20:09:34 +02:00
where , values := OrLike ( "photos.original_name" , f . Original )
s = s . Where ( where , values ... )
2020-06-03 10:33:09 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by title.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Title ) {
2021-09-29 20:09:34 +02:00
where , values := OrLike ( "photos.photo_title" , f . Title )
s = s . Where ( where , values ... )
2020-05-08 15:41:01 +02:00
}
2023-02-21 10:47:15 +01:00
// Filter by hash.
2022-03-24 18:30:59 +01:00
if txt . NotEmpty ( f . Hash ) {
2022-04-02 22:23:38 +02:00
s = s . Where ( "files.file_hash IN (?)" , SplitOr ( strings . ToLower ( f . Hash ) ) )
2020-05-08 15:41:01 +02:00
}
2023-09-20 03:18:30 +02:00
// Filter by location code.
2023-09-20 12:10:49 +02:00
if txt . NotEmpty ( f . S2 ) {
2023-09-19 22:03:40 +02:00
// S2 Cell ID.
2023-09-20 12:10:49 +02:00
s2Min , s2Max := s2 . PrefixedRange ( f . S2 , s2 . Level ( f . Dist ) )
2023-09-19 22:03:40 +02:00
s = s . Where ( "photos.cell_id BETWEEN ? AND ?" , s2Min , s2Max )
2023-09-20 12:10:49 +02:00
} else if txt . NotEmpty ( f . Olc ) {
2023-09-19 22:03:40 +02:00
// Open Location Code (OLC).
2023-09-20 12:10:49 +02:00
s2Min , s2Max := s2 . PrefixedRange ( pluscode . S2 ( f . Olc ) , s2 . Level ( f . Dist ) )
2023-09-19 22:03:40 +02:00
s = s . Where ( "photos.cell_id BETWEEN ? AND ?" , s2Min , s2Max )
2023-09-20 03:18:30 +02:00
}
// Filter by GPS Bounds (Lat N, Lng E, Lat S, Lng W).
2023-09-20 16:56:38 +02:00
if latN , lngE , latS , lngW , boundsErr := clean . GPSBounds ( f . Latlng ) ; boundsErr == nil {
s = s . Where ( "photos.photo_lat BETWEEN ? AND ?" , latS , latN )
s = s . Where ( "photos.photo_lng BETWEEN ? AND ?" , lngW , lngE )
2023-09-19 22:03:40 +02:00
}
2023-10-20 12:54:55 +02:00
// Filter by GPS Latitude range (from +90 to -90 degrees).
2023-09-20 16:56:38 +02:00
if latN , latS , latErr := clean . GPSLatRange ( f . Lat , f . Dist ) ; latErr == nil {
s = s . Where ( "photos.photo_lat BETWEEN ? AND ?" , latS , latN )
2020-05-08 15:41:01 +02:00
}
2023-10-20 12:54:55 +02:00
// Filter by GPS Longitude range (from -180 to +180 degrees)
2023-09-20 16:56:38 +02:00
if lngE , lngW , lngErr := clean . GPSLngRange ( f . Lng , f . Dist ) ; lngErr == nil {
s = s . Where ( "photos.photo_lng BETWEEN ? AND ?" , lngW , lngE )
2023-07-24 10:12:22 +02:00
}
2023-10-20 12:54:55 +02:00
// Filter by GPS Altitude (m) range.
if rangeStart , rangeEnd , rangeErr := txt . IntRange ( f . Alt , - 6378000 , 1000000000 ) ; rangeErr == nil {
s = s . Where ( "photos.photo_altitude BETWEEN ? AND ?" , rangeStart , rangeEnd )
2023-10-20 12:23:20 +02:00
}
2023-09-19 22:03:40 +02:00
// Find photos taken before date.
2020-05-08 15:41:01 +02:00
if ! f . Before . IsZero ( ) {
s = s . Where ( "photos.taken_at <= ?" , f . Before . Format ( "2006-01-02" ) )
}
2023-09-19 22:03:40 +02:00
// Find photos taken after date.
2020-05-08 15:41:01 +02:00
if ! f . After . IsZero ( ) {
s = s . Where ( "photos.taken_at >= ?" , f . After . Format ( "2006-01-02" ) )
}
2023-02-21 10:47:15 +01:00
// Find stacks only.
2020-07-13 15:59:54 +02:00
if f . Stack {
2022-03-30 20:36:25 +02:00
s = s . Where ( "photos.id IN (SELECT a.photo_id FROM files a JOIN files b ON a.id != b.id AND a.photo_id = b.photo_id AND a.file_type = b.file_type WHERE a.file_type='jpg')" )
2020-07-05 17:22:26 +02:00
}
2022-09-30 19:15:10 +02:00
// Find photos in albums or not in an album, unless search results are limited to a scope.
if f . Scope == "" {
if f . Unsorted {
2023-01-02 18:43:18 +01:00
s = s . Where ( "photos.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid WHERE pa.hidden = 0 AND a.deleted_at IS NULL)" )
2022-09-30 19:15:10 +02:00
} else if txt . NotEmpty ( f . Album ) {
v := strings . Trim ( f . Album , "*%" ) + "%"
s = s . Where ( "photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (a.album_title LIKE ? OR a.album_slug LIKE ?))" , v , v )
} else if txt . NotEmpty ( f . Albums ) {
for _ , where := range LikeAnyWord ( "a.album_title" , f . Albums ) {
s = s . Where ( "photos.photo_uid IN (SELECT pa.photo_uid FROM photos_albums pa JOIN albums a ON a.album_uid = pa.album_uid AND pa.hidden = 0 WHERE (?))" , gorm . Expr ( where ) )
}
2021-08-29 16:16:49 +02:00
}
2020-05-30 01:41:47 +02:00
}
2022-09-30 19:15:10 +02:00
// Limit offset and count.
if f . Count > 0 && f . Count <= MaxResults {
s = s . Limit ( f . Count ) . Offset ( f . Offset )
} else {
s = s . Limit ( MaxResults ) . Offset ( f . Offset )
}
2022-09-30 00:42:19 +02:00
// Query database.
if err = s . Scan ( & results ) . Error ; err != nil {
2020-05-30 14:52:47 +02:00
return results , 0 , err
2020-05-08 15:41:01 +02:00
}
2022-09-30 00:42:19 +02:00
// Log number of results.
2021-12-16 15:26:54 +01:00
log . Debugf ( "photos: found %s for %s [%s]" , english . Plural ( len ( results ) , "result" , "results" ) , f . SerializeAll ( ) , time . Since ( start ) )
2020-05-23 20:58:58 +02:00
2023-02-21 10:47:15 +01:00
// Merge files that belong to the same photo.
2020-05-08 15:41:01 +02:00
if f . Merged {
2022-09-30 00:42:19 +02:00
// Return merged files.
2022-03-29 00:21:50 +02:00
return results . Merge ( )
2020-05-08 15:41:01 +02:00
}
2022-09-30 00:42:19 +02:00
// Return unmerged files.
2020-05-08 15:41:01 +02:00
return results , len ( results ) , nil
}