Skip to content

Commit

Permalink
photoprism#152 Timeline - Search Refactoring, WIP.
Browse files Browse the repository at this point in the history
  • Loading branch information
capraynor committed Jan 17, 2024
1 parent f08ef59 commit e511166
Showing 1 changed file with 91 additions and 52 deletions.
143 changes: 91 additions & 52 deletions internal/search/photos.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,67 +47,29 @@ func PhotoIds(f form.SearchPhotos) (files PhotoResults, count int, err error) {
return searchPhotos(f, nil, "photos.id, photos.photo_uid, files.file_uid")
}

// 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) {
start := time.Now()

// Parse query string and filter.
if err = f.ParseQueryString(); err != nil {
log.Debugf("search: %s", err)
return PhotoResults{}, 0, ErrBadRequest
}

// 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 {
f.Dist = geo.DefaultDist
} else if f.Dist > geo.DistLimit {
f.Dist = geo.DistLimit
}

// Specify table names and joins.
func initiateDB(sess *entity.Session, resultCols string) (db *gorm.DB) {
s := UnscopedDb().Table(entity.File{}.TableName()).Select(resultCols).
Joins("JOIN photos ON files.photo_id = photos.id AND files.media_id IS NOT NULL").
Joins("LEFT JOIN cameras ON photos.camera_id = cameras.id").
Joins("LEFT JOIN lenses ON photos.lens_id = lenses.id").
Joins("LEFT JOIN places ON photos.place_id = places.id")
return s
}

// Accept the album UID as scope for backward compatibility.
if rnd.IsUID(f.Album, entity.AlbumUID) {
if txt.Empty(f.Scope) {
f.Scope = f.Album
}

f.Album = ""
}

// Limit search results to a specific UID scope, e.g. when sharing.
func handleScope(s *gorm.DB, f *form.SearchPhotos) (err error) {
if txt.NotEmpty(f.Scope) {
f.Scope = strings.ToLower(f.Scope)

if idType, idPrefix := rnd.IdType(f.Scope); idType != rnd.TypeUID || idPrefix != entity.AlbumUID {
return PhotoResults{}, 0, ErrInvalidId
return ErrInvalidId
} else if a, err := entity.CachedAlbumByUID(f.Scope); err != nil || a.AlbumUID == "" {
return PhotoResults{}, 0, ErrInvalidId
return 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)
} else if formErr := form.Unserialize(&f, a.AlbumFilter); formErr != nil {
} else if formErr := form.Unserialize(f, a.AlbumFilter); formErr != nil {
log.Debugf("search: %s (%s)", clean.Error(formErr), clean.Log(a.AlbumFilter))
return PhotoResults{}, 0, ErrBadFilter
return 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)
Expand All @@ -122,8 +84,10 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
} else {
f.Scope = ""
}
return nil
}

// Check session permissions and apply as needed.
func handleSessionAndPermissions(s *gorm.DB, sess *entity.Session, f form.SearchPhotos) (err error) {
if sess != nil {
user := sess.User()
aclRole := user.AclRole()
Expand All @@ -149,7 +113,7 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
if f.Scope != "" && !sess.HasShare(f.Scope) && (sess.User().HasSharedAccessOnly(acl.ResourcePhotos) || sess.NotRegistered()) ||
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
return ErrForbidden
}

// Limit results for external users.
Expand All @@ -167,7 +131,10 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
}
}

// Set sort order.
return nil
}

func handleOrder(s *gorm.DB, f form.SearchPhotos) (err error) {
switch f.Order {
case sortby.Edited:
s = s.Where("photos.edited_at IS NOT NULL").Order("photos.edited_at DESC, files.media_id")
Expand Down Expand Up @@ -195,19 +162,23 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
case sortby.Default, sortby.Imported, sortby.Added:
s = s.Order("files.media_id")
default:
return PhotoResults{}, 0, ErrBadSortOrder
return ErrBadSortOrder
}

// Exclude files with errors by default.
return nil
}

func handleExcludeErrorFiles(s *gorm.DB, f form.SearchPhotos) {
if !f.Hidden {
if f.Error {
s = s.Where("files.file_error <> ''")
} else {
s = s.Where("files.file_error = ''")
}
}
}

// Find primary files only?
func handlePrimaryFiles(s *gorm.DB, f form.SearchPhotos) {
if f.Primary {
s = s.Where("files.file_primary = 1")
} else if f.Order == sortby.Similar {
Expand All @@ -218,6 +189,74 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
// Otherwise, find all matching media except sidecar files.
s = s.Where("files.file_sidecar = 0")
}
}

// 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) {
start := time.Now()

// Parse query string and filter.
if err = f.ParseQueryString(); err != nil {
log.Debugf("search: %s", err)
return PhotoResults{}, 0, ErrBadRequest
}

// 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 {
f.Dist = geo.DefaultDist
} else if f.Dist > geo.DistLimit {
f.Dist = geo.DistLimit
}

// Specify table names and joins.
s := initiateDB(sess, resultCols)

// Accept the album UID as scope for backward compatibility.
if rnd.IsUID(f.Album, entity.AlbumUID) {
if txt.Empty(f.Scope) {
f.Scope = f.Album
}

f.Album = ""
}

// Limit search results to a specific UID scope, e.g. when sharing.
e := handleScope(s, &f)
if e != nil {
return PhotoResults{}, 0, e
}

// Check session permissions and apply as needed.
e = handleSessionAndPermissions(s, sess, f)
if e != nil {
return PhotoResults{}, 0, e
}

// Set sort order.
e = handleOrder(s, f)
if e != nil {
return PhotoResults{}, 0, e
}

// Exclude files with errors by default.
handleExcludeErrorFiles(s, f)

// Find primary files only?
handlePrimaryFiles(s, f)

// Find specific UIDs only.
if txt.NotEmpty(f.UID) {
Expand Down

0 comments on commit e511166

Please sign in to comment.