Search: Improve "year", "month", and "day" filters

This commit is contained in:
Michael Mayer 2021-09-20 23:32:35 +02:00
parent a66de2e822
commit 070efcbc66
12 changed files with 102 additions and 35 deletions

View file

@ -3,6 +3,7 @@ package entity
import (
"fmt"
"os"
"strconv"
"strings"
"time"
@ -199,8 +200,8 @@ func NewMonthAlbum(albumTitle, albumSlug string, year, month int) *Album {
}
f := form.PhotoSearch{
Year: year,
Month: month,
Year: strconv.Itoa(year),
Month: strconv.Itoa(month),
Public: true,
}

View file

@ -47,9 +47,9 @@ type PhotoSearch struct {
Category string `form:"category"` // Moments
Country string `form:"country"` // Moments
State string `form:"state"` // Moments
Year int `form:"year"` // Moments
Month int `form:"month"` // Moments
Day int `form:"day"` // Moments
Year string `form:"year"` // Moments
Month string `form:"month"` // Moments
Day string `form:"day"` // Moments
Face string `form:"face"` // UIDs
Subject string `form:"subject"` // UIDs
Person string `form:"person"` // Alias for Subject

View file

@ -34,9 +34,9 @@ type PhotoSearchGeo struct {
Album string `form:"album"`
Albums string `form:"albums"`
Country string `form:"country"`
Year int `form:"year"` // Moments
Month int `form:"month"` // Moments
Day int `form:"day"` // Moments
Year string `form:"year"` // Moments
Month string `form:"month"` // Moments
Day string `form:"day"` // Moments
Color string `form:"color"`
Camera int `form:"camera"`
Lens int `form:"lens"`

View file

@ -244,7 +244,7 @@ func TestPhotoSearch_Serialize(t *testing.T) {
Photo: false,
Lat: 1.5,
Lng: -10.33333,
Year: 2002,
Year: "2002",
Chroma: 1,
Diff: 424242,
Before: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC),
@ -264,7 +264,7 @@ func TestPhotoSearch_SerializeAll(t *testing.T) {
Photo: false,
Lat: 1.5,
Lng: -10.33333,
Year: 2002,
Year: "2002|2003",
Chroma: 1,
Diff: 424242,
Before: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC),

View file

@ -4,6 +4,7 @@ import (
"fmt"
"math"
"runtime/debug"
"strconv"
"strings"
"github.com/photoprism/photoprism/internal/config"
@ -126,7 +127,7 @@ func (w *Moments) Start() (err error) {
for _, mom := range results {
f := form.PhotoSearch{
Country: mom.Country,
Year: mom.Year,
Year: strconv.Itoa(mom.Year),
Public: true,
}

View file

@ -201,3 +201,37 @@ func AnySlug(col, search, sep string) (where string) {
return strings.Join(wheres, " OR ")
}
// AnyInt returns a where condition that matches any integer within a range.
func AnyInt(col, numbers, sep string, min, max int) (where string) {
if numbers == "" {
return ""
}
if sep == "" {
sep = txt.Or
}
var matches []int
var wheres []string
for _, n := range strings.Split(numbers, sep) {
i := txt.Int(n)
if i == 0 || i < min || i > max {
continue
}
matches = append(matches, i)
}
if len(matches) == 0 {
return ""
}
for _, n := range matches {
wheres = append(wheres, fmt.Sprintf("%s = %d", col, n))
}
return strings.Join(wheres, " OR ")
}

View file

@ -3,6 +3,8 @@ package search
import (
"testing"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/stretchr/testify/assert"
@ -267,3 +269,30 @@ func TestAnySlug(t *testing.T) {
assert.Equal(t, "custom_slug = '' OR custom_slug = ''", where)
})
}
func TestAnyInt(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
where := AnyInt("photos.photo_month", "", txt.Or, entity.UnknownMonth, txt.MonthMax)
assert.Equal(t, "", where)
})
t.Run("Range", func(t *testing.T) {
where := AnyInt("photos.photo_month", "-3|0|10|9|11|12|13", txt.Or, entity.UnknownMonth, txt.MonthMax)
assert.Equal(t, "photos.photo_month = 10 OR photos.photo_month = 9 OR photos.photo_month = 11 OR photos.photo_month = 12", where)
})
t.Run("Chars", func(t *testing.T) {
where := AnyInt("photos.photo_month", "a|b|c", txt.Or, entity.UnknownMonth, txt.MonthMax)
assert.Equal(t, "", where)
})
t.Run("CommaSeparated", func(t *testing.T) {
where := AnyInt("photos.photo_month", "-3,10,9,11,12,13", ",", entity.UnknownMonth, txt.MonthMax)
assert.Equal(t, "photos.photo_month = 10 OR photos.photo_month = 9 OR photos.photo_month = 11 OR photos.photo_month = 12", where)
})
t.Run("Invalid", func(t *testing.T) {
where := AnyInt("photos.photo_month", " , | ", ",", entity.UnknownMonth, txt.MonthMax)
assert.Equal(t, "", where)
})
}

View file

@ -284,18 +284,18 @@ func Photos(f form.PhotoSearch) (results PhotoResults, count int, err error) {
}
// Filter by year?
if (f.Year > 0 && f.Year <= txt.YearMax) || f.Year == entity.UnknownYear {
s = s.Where("photos.photo_year = ?", f.Year)
if f.Year != "" {
s = s.Where(AnyInt("photos.photo_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax))
}
// Filter by month?
if (f.Month >= txt.MonthMin && f.Month <= txt.MonthMax) || f.Month == entity.UnknownMonth {
s = s.Where("photos.photo_month = ?", f.Month)
if f.Month != "" {
s = s.Where(AnyInt("photos.photo_month", f.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
}
// Filter by day?
if (f.Day >= txt.DayMin && f.Month <= txt.DayMax) || f.Day == entity.UnknownDay {
s = s.Where("photos.photo_day = ?", f.Day)
if f.Day != "" {
s = s.Where(AnyInt("photos.photo_day", f.Day, txt.Or, entity.UnknownDay, txt.DayMax))
}
// Find or exclude people if detected.

View file

@ -165,18 +165,18 @@ func PhotosGeo(f form.PhotoSearchGeo) (results GeoResults, err error) {
}
// Filter by year?
if (f.Year > 0 && f.Year <= txt.YearMax) || f.Year == entity.UnknownYear {
s = s.Where("photos.photo_year = ?", f.Year)
if f.Year != "" {
s = s.Where(AnyInt("photos.photo_year", f.Year, txt.Or, entity.UnknownYear, txt.YearMax))
}
// Filter by month?
if (f.Month >= txt.MonthMin && f.Month <= txt.MonthMax) || f.Month == entity.UnknownMonth {
s = s.Where("photos.photo_month = ?", f.Month)
if f.Month != "" {
s = s.Where(AnyInt("photos.photo_month", f.Month, txt.Or, entity.UnknownMonth, txt.MonthMax))
}
// Filter by day?
if (f.Day >= txt.DayMin && f.Month <= txt.DayMax) || f.Day == entity.UnknownDay {
s = s.Where("photos.photo_day = ?", f.Day)
if f.Day != "" {
s = s.Where(AnyInt("photos.photo_day", f.Day, txt.Or, entity.UnknownDay, txt.DayMax))
}
// Find or exclude people if detected.

View file

@ -179,8 +179,8 @@ func TestGeo(t *testing.T) {
Album: "test",
Camera: 123,
Lens: 123,
Year: 2010,
Month: 12,
Year: "2010",
Month: "12",
Color: "red",
Country: entity.UnknownID,
Type: "jpg",
@ -282,8 +282,8 @@ func TestGeo(t *testing.T) {
})
t.Run("day", func(t *testing.T) {
var f form.PhotoSearchGeo
f.Day = 18
f.Month = 4
f.Day = "18"
f.Month = "4"
photos, err := PhotosGeo(f)

View file

@ -1,6 +1,7 @@
package search
import (
"strconv"
"testing"
"github.com/photoprism/photoprism/internal/entity"
@ -597,8 +598,8 @@ func TestPhotos(t *testing.T) {
f.Count = 5000
f.Offset = 0
f.Lens = 1000000
f.Month = 7
f.Year = 2790
f.Month = strconv.Itoa(7)
f.Year = strconv.Itoa(2790)
f.Album = "at9lxuqxpogaaba8"
photos, _, err := Photos(f)
@ -688,8 +689,8 @@ func TestPhotos(t *testing.T) {
var f form.PhotoSearch
f.Hidden = true
f.Scan = true
f.Year = 2010
f.Day = 1
f.Year = "2010"
f.Day = "1"
f.Photo = true
f.Name = "xxx"
f.Original = "xxyy"
@ -708,8 +709,8 @@ func TestPhotos(t *testing.T) {
var f form.PhotoSearch
f.Hidden = true
f.Scan = true
f.Year = 2010
f.Day = 1
f.Year = strconv.Itoa(2010)
f.Day = strconv.Itoa(1)
f.Video = true
f.Name = "xxx"
f.Original = "xxyy"

View file

@ -2,6 +2,7 @@ package txt
import (
"strconv"
"strings"
)
// Int converts a string to a signed integer or 0 if invalid.
@ -10,7 +11,7 @@ func Int(s string) int {
return 0
}
result, err := strconv.ParseInt(s, 10, 32)
result, err := strconv.ParseInt(strings.TrimSpace(s), 10, 32)
if err != nil {
return 0
@ -25,7 +26,7 @@ func UInt(s string) uint {
return 0
}
result, err := strconv.ParseInt(s, 10, 32)
result, err := strconv.ParseInt(strings.TrimSpace(s), 10, 32)
if err != nil || result < 0 {
return 0