Stacks: Only merge photos with trusted time and location #1668

This commit is contained in:
Michael Mayer 2021-11-24 14:32:40 +01:00
parent f7cc802e6c
commit 22b0a44fa7
10 changed files with 711 additions and 581 deletions

View file

@ -610,136 +610,6 @@ func (m *Photo) SetDescription(desc, source string) {
m.DescriptionSrc = source
}
// SetTakenAt changes the photo date if not empty and from the same source.
func (m *Photo) SetTakenAt(taken, local time.Time, zone, source string) {
if taken.IsZero() || taken.Year() < 1000 || taken.Year() > txt.YearMax {
return
}
if SrcPriority[source] < SrcPriority[m.TakenSrc] && !m.TakenAt.IsZero() {
return
}
// Remove time zone if time was extracted from file name.
if source == SrcName {
zone = ""
}
// Round times to avoid jitter.
taken = taken.Round(time.Second).UTC()
// Default local time to taken if zero or invalid.
if local.IsZero() || local.Year() < 1000 {
local = taken
} else {
local = local.Round(time.Second)
}
// Don't update older date.
if SrcPriority[source] <= SrcPriority[SrcAuto] && !m.TakenAt.IsZero() && taken.After(m.TakenAt) {
return
}
// Set UTC time and date source.
m.TakenAt = taken
m.TakenAtLocal = local
m.TakenSrc = source
if zone == time.UTC.String() && m.TimeZone != "" {
// Location exists, set local time from UTC.
m.TakenAtLocal = m.GetTakenAtLocal()
} else if zone != "" {
// Apply new time zone.
m.TimeZone = zone
m.TakenAt = m.GetTakenAt()
} else if m.TimeZoneUTC() {
// Local is UTC.
m.TimeZone = zone
m.TakenAtLocal = taken
} else if m.TimeZone != "" {
// Apply existing time zone.
m.TakenAt = m.GetTakenAt()
}
m.UpdateDateFields()
}
// TimeZoneUTC tests if the current time zone is UTC.
func (m *Photo) TimeZoneUTC() bool {
return strings.EqualFold(m.TimeZone, time.UTC.String())
}
// UpdateTimeZone updates the time zone.
func (m *Photo) UpdateTimeZone(zone string) {
if zone == "" || zone == time.UTC.String() || zone == m.TimeZone {
return
}
if SrcPriority[m.TakenSrc] >= SrcPriority[SrcManual] && m.TimeZone != "" {
return
}
if m.TimeZoneUTC() {
m.TimeZone = zone
m.TakenAtLocal = m.GetTakenAtLocal()
} else {
m.TimeZone = zone
m.TakenAt = m.GetTakenAt()
}
}
// UpdateDateFields updates internal date fields.
func (m *Photo) UpdateDateFields() {
if m.TakenAt.IsZero() || m.TakenAt.Year() < 1000 {
return
}
if m.TakenAtLocal.IsZero() || m.TakenAtLocal.Year() < 1000 {
m.TakenAtLocal = m.TakenAt
}
// Set date to unknown if file system date is about the same as indexing time.
if m.TakenSrc == SrcAuto && m.TakenAt.After(m.CreatedAt.Add(-24*time.Hour)) {
m.PhotoYear = UnknownYear
m.PhotoMonth = UnknownMonth
m.PhotoDay = UnknownDay
} else if m.TakenSrc != SrcManual {
m.PhotoYear = m.TakenAtLocal.Year()
m.PhotoMonth = int(m.TakenAtLocal.Month())
m.PhotoDay = m.TakenAtLocal.Day()
}
}
// SetCoordinates changes the photo lat, lng and altitude if not empty and from an acceptable source.
func (m *Photo) SetCoordinates(lat, lng float32, altitude int, source string) {
m.SetAltitude(altitude, source)
if lat == 0.0 && lng == 0.0 {
return
}
if SrcPriority[source] < SrcPriority[m.PlaceSrc] && m.HasLatLng() {
return
}
m.PhotoLat = lat
m.PhotoLng = lng
m.PlaceSrc = source
}
// SetAltitude sets the photo altitude if not empty and from an acceptable source.
func (m *Photo) SetAltitude(altitude int, source string) {
if altitude == 0 && source != SrcManual {
return
}
if SrcPriority[source] < SrcPriority[m.PlaceSrc] {
return
}
m.PhotoAltitude = altitude
}
// SetCamera updates the camera.
func (m *Photo) SetCamera(camera *Camera, source string) {
if camera == nil {

View file

@ -0,0 +1,121 @@
package entity
import (
"strings"
"time"
"github.com/photoprism/photoprism/pkg/txt"
)
// TrustedTime tests if the photo has a known date and time from a trusted source.
func (m *Photo) TrustedTime() bool {
if SrcPriority[m.TakenSrc] <= SrcPriority[SrcEstimate] {
return false
} else if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() {
return false
} else if m.TimeZone == "" || m.TimeZoneUTC() {
return false
}
return true
}
// SetTakenAt changes the photo date if not empty and from the same source.
func (m *Photo) SetTakenAt(taken, local time.Time, zone, source string) {
if taken.IsZero() || taken.Year() < 1000 || taken.Year() > txt.YearMax {
return
}
if SrcPriority[source] < SrcPriority[m.TakenSrc] && !m.TakenAt.IsZero() {
return
}
// Remove time zone if time was extracted from file name.
if source == SrcName {
zone = ""
}
// Round times to avoid jitter.
taken = taken.Round(time.Second).UTC()
// Default local time to taken if zero or invalid.
if local.IsZero() || local.Year() < 1000 {
local = taken
} else {
local = local.Round(time.Second)
}
// Don't update older date.
if SrcPriority[source] <= SrcPriority[SrcAuto] && !m.TakenAt.IsZero() && taken.After(m.TakenAt) {
return
}
// Set UTC time and date source.
m.TakenAt = taken
m.TakenAtLocal = local
m.TakenSrc = source
if zone == time.UTC.String() && m.TimeZone != "" {
// Location exists, set local time from UTC.
m.TakenAtLocal = m.GetTakenAtLocal()
} else if zone != "" {
// Apply new time zone.
m.TimeZone = zone
m.TakenAt = m.GetTakenAt()
} else if m.TimeZoneUTC() {
// Local is UTC.
m.TimeZone = zone
m.TakenAtLocal = taken
} else if m.TimeZone != "" {
// Apply existing time zone.
m.TakenAt = m.GetTakenAt()
}
m.UpdateDateFields()
}
// TimeZoneUTC tests if the current time zone is UTC.
func (m *Photo) TimeZoneUTC() bool {
return strings.EqualFold(m.TimeZone, time.UTC.String())
}
// UpdateTimeZone updates the time zone.
func (m *Photo) UpdateTimeZone(zone string) {
if zone == "" || zone == time.UTC.String() || zone == m.TimeZone {
return
}
if SrcPriority[m.TakenSrc] >= SrcPriority[SrcManual] && m.TimeZone != "" {
return
}
if m.TimeZoneUTC() {
m.TimeZone = zone
m.TakenAtLocal = m.GetTakenAtLocal()
} else {
m.TimeZone = zone
m.TakenAt = m.GetTakenAt()
}
}
// UpdateDateFields updates internal date fields.
func (m *Photo) UpdateDateFields() {
if m.TakenAt.IsZero() || m.TakenAt.Year() < 1000 {
return
}
if m.TakenAtLocal.IsZero() || m.TakenAtLocal.Year() < 1000 {
m.TakenAtLocal = m.TakenAt
}
// Set date to unknown if file system date is about the same as indexing time.
if m.TakenSrc == SrcAuto && m.TakenAt.After(m.CreatedAt.Add(-24*time.Hour)) {
m.PhotoYear = UnknownYear
m.PhotoMonth = UnknownMonth
m.PhotoDay = UnknownDay
} else if m.TakenSrc != SrcManual {
m.PhotoYear = m.TakenAtLocal.Year()
m.PhotoMonth = int(m.TakenAtLocal.Month())
m.PhotoDay = m.TakenAtLocal.Day()
}
}

View file

@ -0,0 +1,331 @@
package entity
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestPhoto_TrustedTime(t *testing.T) {
t.Run("MissingTakenAt", func(t *testing.T) {
m := Photo{ID: 1, TakenAt: time.Time{}, TakenAtLocal: TimeStamp(), TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.False(t, m.TrustedTime())
})
t.Run("MissingTakenAtLocal", func(t *testing.T) {
m := Photo{ID: 1, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.False(t, m.TrustedTime())
})
t.Run("MissingTimeZone", func(t *testing.T) {
n := TimeStamp()
m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcMeta, TimeZone: ""}
assert.False(t, m.TrustedTime())
})
t.Run("SrcAuto", func(t *testing.T) {
n := TimeStamp()
m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcAuto, TimeZone: "Europe/Berlin"}
assert.False(t, m.TrustedTime())
})
t.Run("SrcEstimate", func(t *testing.T) {
n := TimeStamp()
m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcEstimate, TimeZone: "Europe/Berlin"}
assert.False(t, m.TrustedTime())
})
t.Run("SrcMeta", func(t *testing.T) {
n := TimeStamp()
m := Photo{ID: 1, TakenAt: n, TakenAtLocal: n, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.True(t, m.TrustedTime())
})
}
func TestPhoto_SetTakenAt(t *testing.T) {
t.Run("empty taken", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
m.SetTakenAt(time.Time{}, time.Time{}, "", SrcManual)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
})
t.Run("taken not from the same source", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
m.SetTakenAt(time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), "", SrcAuto)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
})
t.Run("from name", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
m.TimeZone = ""
m.TakenSrc = SrcAuto
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
assert.Equal(t, "", m.TimeZone)
assert.Equal(t, SrcAuto, m.TakenSrc)
m.SetTakenAt(time.Date(2011, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, 11, 11, 10, 7, 18, 0, time.UTC), "America/New_York", SrcName)
assert.Equal(t, "", m.TimeZone)
assert.Equal(t, SrcName, m.TakenSrc)
assert.Equal(t, time.Date(2011, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2019, 11, 11, 10, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("success", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
m.SetTakenAt(time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcMeta)
assert.Equal(t, time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2019, 12, 11, 10, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("fallback", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
t.Logf("SRC, ZONE, UTC, LOCAL: %s / %s / %s /%s", m.TakenSrc, m.TimeZone, m.TakenAt, m.TakenAtLocal)
m.SetTakenAt(time.Date(2019, time.December, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, time.December, 11, 10, 7, 18, 0, time.UTC), "", SrcAuto)
t.Logf("SRC, ZONE, UTC, LOCAL: %s / %s / %s /%s", m.TakenSrc, m.TimeZone, m.TakenAt, m.TakenAtLocal)
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
newTime := time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC)
expected := time.Date(2013, time.November, 11, 8, 7, 18, 0, time.UTC)
m.TimeZone = "Europe/Berlin"
m.SetTakenAt(newTime, newTime, "", SrcName)
assert.Equal(t, expected, m.TakenAt)
assert.Equal(t, m.GetTakenAtLocal(), m.TakenAtLocal)
})
t.Run("time zone", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
zone := "Europe/Berlin"
loc, _ := time.LoadLocation(zone)
newTime := time.Date(2013, 11, 11, 9, 7, 18, 0, loc)
m.SetTakenAt(newTime, newTime, zone, SrcName)
assert.Equal(t, newTime.UTC(), m.TakenAt)
assert.Equal(t, newTime, m.TakenAtLocal)
})
t.Run("time > max year", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
m.SetTakenAt(time.Date(2123, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2123, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcManual)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("success with empty takenAtLocal", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
m.SetTakenAt(time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC),
time.Time{}, "test", SrcXmp)
assert.Equal(t, time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("don't update older date", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC)}
photo.SetTakenAt(time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2014, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcAuto)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
})
t.Run("set local time from utc", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), TimeZone: "Europe/Berlin"}
photo.SetTakenAt(time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2014, 12, 11, 10, 7, 18, 0, time.UTC), time.UTC.String(), SrcManual)
assert.Equal(t, time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
assert.Equal(t, time.Date(2014, 12, 11, 10, 07, 18, 0, time.UTC), photo.TakenAtLocal)
})
t.Run("local is UTC", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), TimeZone: time.UTC.String()}
photo.SetTakenAt(time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2014, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcManual)
assert.Equal(t, time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
assert.Equal(t, time.Date(2014, 12, 11, 9, 07, 18, 0, time.UTC), photo.TakenAtLocal)
})
}
func TestPhoto_UpdateTimeZone(t *testing.T) {
t.Run("PhotoTimeZone", func(t *testing.T) {
m := PhotoFixtures.Get("PhotoTimeZone")
takenLocal := time.Date(2015, time.May, 17, 23, 2, 46, 0, time.UTC)
takenJerusalemUtc := time.Date(2015, time.May, 17, 20, 2, 46, 0, time.UTC)
takenShanghaiUtc := time.Date(2015, time.May, 17, 15, 2, 46, 0, time.UTC)
assert.Equal(t, "", m.TimeZone)
assert.Equal(t, takenLocal, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
zone1 := "Asia/Jerusalem"
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenJerusalemUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenJerusalemUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
zone2 := "Asia/Shanghai"
m.UpdateTimeZone(zone2)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
zone3 := "UTC"
m.UpdateTimeZone(zone3)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
})
t.Run("VideoTimeZone", func(t *testing.T) {
m := PhotoFixtures.Get("VideoTimeZone")
takenUtc := time.Date(2015, 5, 17, 17, 48, 46, 0, time.UTC)
takenJerusalem := time.Date(2015, time.May, 17, 20, 48, 46, 0, time.UTC)
takenShanghaiUtc := time.Date(2015, time.May, 17, 12, 48, 46, 0, time.UTC)
assert.Equal(t, "UTC", m.TimeZone)
assert.Equal(t, takenUtc, m.TakenAt)
assert.Equal(t, takenUtc, m.TakenAtLocal)
zone1 := "Asia/Jerusalem"
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
zone2 := "Asia/Shanghai"
m.UpdateTimeZone(zone2)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
zone3 := "UTC"
m.UpdateTimeZone(zone3)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
})
t.Run("UTC", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
m.TimeZone = "UTC"
zone := "Europe/Berlin"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
m.UpdateTimeZone(zone)
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, m.GetTakenAtLocal(), m.TakenAtLocal)
})
t.Run("Europe/Berlin", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
zone := "Europe/Berlin"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "", m.TimeZone)
m.UpdateTimeZone(zone)
assert.Equal(t, m.GetTakenAt(), m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
})
t.Run("America/New_York", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
m.TimeZone = "Europe/Berlin"
m.TakenAt = m.GetTakenAt()
zone := "America/New_York"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "Europe/Berlin", m.TimeZone)
m.UpdateTimeZone(zone)
assert.Equal(t, m.GetTakenAt(), m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
})
t.Run("manual", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
m.TimeZone = "Europe/Berlin"
m.TakenAt = m.GetTakenAt()
m.TakenSrc = SrcManual
zone := "America/New_York"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "Europe/Berlin", m.TimeZone)
m.UpdateTimeZone(zone)
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "Europe/Berlin", m.TimeZone)
})
t.Run("zone = UTC", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), TimeZone: "Europe/Berlin"}
photo.UpdateTimeZone("")
assert.Equal(t, time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
assert.Equal(t, "Europe/Berlin", photo.TimeZone)
})
}

View file

@ -114,7 +114,7 @@ var PhotoFixtures = PhotoMap{
PhotoPrivate: false,
PhotoScan: false,
PhotoPanorama: false,
TimeZone: "",
TimeZone: "Europe/Berlin",
Place: PlaceFixtures.Pointer("Germany"),
PlaceID: PlaceFixtures.Pointer("Germany").ID,
PlaceSrc: "manual",
@ -951,7 +951,7 @@ var PhotoFixtures = PhotoMap{
PhotoPrivate: false,
PhotoScan: false,
PhotoPanorama: false,
TimeZone: "",
TimeZone: "Europe/Berlin",
Place: PlaceFixtures.Pointer("Germany"),
PlaceID: PlaceFixtures.Pointer("Germany").ID,
PlaceSrc: SrcMeta,
@ -1428,7 +1428,7 @@ var PhotoFixtures = PhotoMap{
PhotoPrivate: false,
PhotoScan: false,
PhotoPanorama: false,
TimeZone: "",
TimeZone: "America/Mexico_City",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
PlaceSrc: SrcMeta,
@ -1489,7 +1489,7 @@ var PhotoFixtures = PhotoMap{
PhotoPrivate: false,
PhotoScan: false,
PhotoPanorama: false,
TimeZone: "",
TimeZone: "America/Mexico_City",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
PlaceSrc: SrcMeta,
@ -1550,7 +1550,7 @@ var PhotoFixtures = PhotoMap{
PhotoPrivate: false,
PhotoScan: false,
PhotoPanorama: true,
TimeZone: "",
TimeZone: "America/Mexico_City",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
PlaceSrc: SrcMeta,
@ -1611,7 +1611,7 @@ var PhotoFixtures = PhotoMap{
PhotoPrivate: false,
PhotoScan: false,
PhotoPanorama: true,
TimeZone: "",
TimeZone: "America/Mexico_City",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
PlaceSrc: SrcMeta,

View file

@ -12,6 +12,36 @@ import (
"gopkg.in/photoprism/go-tz.v2/tz"
)
// SetCoordinates changes the photo lat, lng and altitude if not empty and from an acceptable source.
func (m *Photo) SetCoordinates(lat, lng float32, altitude int, source string) {
m.SetAltitude(altitude, source)
if lat == 0.0 && lng == 0.0 {
return
}
if SrcPriority[source] < SrcPriority[m.PlaceSrc] && m.HasLatLng() {
return
}
m.PhotoLat = lat
m.PhotoLng = lng
m.PlaceSrc = source
}
// SetAltitude sets the photo altitude if not empty and from an acceptable source.
func (m *Photo) SetAltitude(altitude int, source string) {
if altitude == 0 && source != SrcManual {
return
}
if SrcPriority[source] < SrcPriority[m.PlaceSrc] {
return
}
m.PhotoAltitude = altitude
}
// UnknownLocation tests if the photo has an unknown location.
func (m *Photo) UnknownLocation() bool {
return m.CellID == "" || m.CellID == UnknownLocation.ID || m.NoLatLng()
@ -48,6 +78,11 @@ func (m *Photo) HasLocation() bool {
return !m.UnknownLocation()
}
// TrustedLocation tests if the photo has a known location from a trusted source.
func (m *Photo) TrustedLocation() bool {
return m.HasLocation() && SrcPriority[m.PlaceSrc] > SrcPriority[SrcEstimate]
}
// LocationLoaded tests if the photo has a known location that is currently loaded.
func (m *Photo) LocationLoaded() bool {
if m.Cell == nil {

View file

@ -7,6 +7,146 @@ import (
"github.com/stretchr/testify/assert"
)
func TestPhoto_SetAltitude(t *testing.T) {
t.Run("ViaSetCoordinates", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(0, 0, 5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("Update", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetAltitude(5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("SkipUpdate", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetAltitude(5, SrcEstimate)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
})
t.Run("UpdateEmptyAltitude", func(t *testing.T) {
m := Photo{ID: 1, PlaceSrc: SrcMeta, PhotoLat: float32(1.234), PhotoLng: float32(4.321), PhotoAltitude: 0}
m.SetAltitude(-5, SrcAuto)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetAltitude(-5, SrcEstimate)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetAltitude(-5, SrcMeta)
assert.Equal(t, -5, m.PhotoAltitude)
})
t.Run("ZeroAltitudeManual", func(t *testing.T) {
m := Photo{ID: 1, PlaceSrc: SrcManual, PhotoLat: float32(1.234), PhotoLng: float32(4.321), PhotoAltitude: 5}
m.SetAltitude(0, SrcManual)
assert.Equal(t, 0, m.PhotoAltitude)
})
}
func TestPhoto_SetCoordinates(t *testing.T) {
t.Run("empty coordinates", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(0, 0, 5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("same source new values", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcMeta)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source lower priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcName)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
})
t.Run("different source equal priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcKeyword)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source higher priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo21")
assert.Equal(t, SrcEstimate, m.PlaceSrc)
assert.Equal(t, float32(0), m.PhotoLat)
assert.Equal(t, float32(0), m.PhotoLng)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcMeta)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source highest priority (manual)", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcManual)
assert.Equal(t, SrcManual, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
}
func TestPhoto_UnknownLocation(t *testing.T) {
t.Run("no_location", func(t *testing.T) {
m := PhotoFixtures.Get("19800101_000002_D640C559")
@ -30,6 +170,25 @@ func TestPhoto_UnknownLocation(t *testing.T) {
})
}
func TestPhoto_TrustedLocation(t *testing.T) {
t.Run("SrcAuto", func(t *testing.T) {
m := Photo{ID: 1, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcAuto}
assert.False(t, m.TrustedLocation())
})
t.Run("SrcEstimate", func(t *testing.T) {
m := Photo{ID: 1, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcEstimate}
assert.False(t, m.TrustedLocation())
})
t.Run("SrcMetaTrue", func(t *testing.T) {
m := Photo{ID: 1, CellID: "s2:479a03fda18c", PhotoLat: 1, PhotoLng: -1, PlaceSrc: SrcMeta}
assert.True(t, m.TrustedLocation())
})
t.Run("SrcMetaFalse", func(t *testing.T) {
m := Photo{ID: 1, CellID: "s2:479a03fda18c", PhotoLat: 0, PhotoLng: 0, PlaceSrc: SrcMeta}
assert.False(t, m.TrustedLocation())
})
}
func TestPhoto_HasLocation(t *testing.T) {
t.Run("false", func(t *testing.T) {
m := PhotoFixtures.Get("19800101_000002_D640C559")

View file

@ -20,31 +20,43 @@ func (m *Photo) ResolvePrimary() error {
return nil
}
// Stackable tests if the photo may be stacked.
func (m *Photo) Stackable() bool {
if !m.HasID() || m.PhotoStack == IsUnstacked || m.PhotoName == "" {
return false
}
return true
}
// Identical returns identical photos that can be merged.
func (m *Photo) Identical(includeMeta, includeUuid bool) (identical Photos, err error) {
if m.PhotoStack == IsUnstacked || m.PhotoName == "" {
if !m.Stackable() {
return identical, nil
}
includeMeta = includeMeta && m.TrustedLocation() && m.TrustedTime()
includeUuid = includeUuid && rnd.IsUUID(m.UUID)
switch {
case includeMeta && includeUuid && m.HasLocation() && m.TakenSrc == SrcMeta && rnd.IsUUID(m.UUID):
case includeMeta && includeUuid:
if err := Db().
Where("(taken_at = ? AND taken_src = 'meta' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
Where("(taken_at = ? AND taken_src = 'meta' AND place_src <> 'estimate' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
"OR (uuid = ? AND photo_stack > -1)"+
"OR (photo_path = ? AND photo_name = ?)",
m.TakenAt, m.CellID, m.CameraSerial, m.CameraID, m.UUID, m.PhotoPath, m.PhotoName).
Order("photo_quality DESC, id ASC").Find(&identical).Error; err != nil {
return identical, err
}
case includeMeta && m.HasLocation() && m.TakenSrc == SrcMeta:
case includeMeta:
if err := Db().
Where("(taken_at = ? AND taken_src = 'meta' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
Where("(taken_at = ? AND taken_src = 'meta' AND place_src <> 'estimate' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
"OR (photo_path = ? AND photo_name = ?)",
m.TakenAt, m.CellID, m.CameraSerial, m.CameraID, m.PhotoPath, m.PhotoName).
Order("photo_quality DESC, id ASC").Find(&identical).Error; err != nil {
return identical, err
}
case includeUuid && rnd.IsUUID(m.UUID):
case includeUuid:
if err := Db().
Where("(uuid = ? AND photo_stack > -1) OR (photo_path = ? AND photo_name = ?)",
m.UUID, m.PhotoPath, m.PhotoName).

View file

@ -2,10 +2,38 @@ package entity
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestPhoto_Stackable(t *testing.T) {
t.Run("IsStackable", func(t *testing.T) {
m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStackable, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.True(t, m.Stackable())
})
t.Run("IsStacked", func(t *testing.T) {
m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.True(t, m.Stackable())
})
t.Run("NoName", func(t *testing.T) {
m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "", TakenAt: time.Time{}, TakenAtLocal: TimeStamp(), TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.False(t, m.Stackable())
})
t.Run("IsUnstacked", func(t *testing.T) {
m := Photo{ID: 1, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsUnstacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.False(t, m.Stackable())
})
t.Run("NoID", func(t *testing.T) {
m := Photo{ID: 0, PhotoUID: "pr32t8j3feogit2t", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.False(t, m.Stackable())
})
t.Run("NoPhotoUID", func(t *testing.T) {
m := Photo{ID: 1, PhotoUID: "", PhotoName: "foo", PhotoStack: IsStacked, TakenAt: TimeStamp(), TakenAtLocal: time.Time{}, TakenSrc: SrcMeta, TimeZone: "Europe/Berlin"}
assert.False(t, m.Stackable())
})
}
func TestPhoto_IdenticalIdentical(t *testing.T) {
t.Run("success", func(t *testing.T) {
photo := PhotoFixtures.Get("Photo19")

View file

@ -28,7 +28,7 @@ func (m *Photo) QualityScore() (score int) {
score++
}
if SrcPriority[m.PlaceSrc] > SrcPriority[SrcEstimate] {
if m.TrustedLocation() {
score++
}

View file

@ -273,438 +273,6 @@ func TestPhoto_SetDescription(t *testing.T) {
})
}
func TestPhoto_SetTakenAt(t *testing.T) {
t.Run("empty taken", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
m.SetTakenAt(time.Time{}, time.Time{}, "", SrcManual)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
})
t.Run("taken not from the same source", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
m.SetTakenAt(time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), "", SrcAuto)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
})
t.Run("from name", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
m.TimeZone = ""
m.TakenSrc = SrcAuto
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
assert.Equal(t, "", m.TimeZone)
assert.Equal(t, SrcAuto, m.TakenSrc)
m.SetTakenAt(time.Date(2011, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, 11, 11, 10, 7, 18, 0, time.UTC), "America/New_York", SrcName)
assert.Equal(t, "", m.TimeZone)
assert.Equal(t, SrcName, m.TakenSrc)
assert.Equal(t, time.Date(2011, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2019, 11, 11, 10, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("success", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
m.SetTakenAt(time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcMeta)
assert.Equal(t, time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2019, 12, 11, 10, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("fallback", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
t.Logf("SRC, ZONE, UTC, LOCAL: %s / %s / %s /%s", m.TakenSrc, m.TimeZone, m.TakenAt, m.TakenAtLocal)
m.SetTakenAt(time.Date(2019, time.December, 11, 9, 7, 18, 0, time.UTC),
time.Date(2019, time.December, 11, 10, 7, 18, 0, time.UTC), "", SrcAuto)
t.Logf("SRC, ZONE, UTC, LOCAL: %s / %s / %s /%s", m.TakenSrc, m.TimeZone, m.TakenAt, m.TakenAtLocal)
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
newTime := time.Date(2013, time.November, 11, 9, 7, 18, 0, time.UTC)
expected := time.Date(2013, time.November, 11, 8, 7, 18, 0, time.UTC)
m.TimeZone = "Europe/Berlin"
m.SetTakenAt(newTime, newTime, "", SrcName)
assert.Equal(t, expected, m.TakenAt)
assert.Equal(t, m.GetTakenAtLocal(), m.TakenAtLocal)
})
t.Run("time zone", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
zone := "Europe/Berlin"
loc, _ := time.LoadLocation(zone)
newTime := time.Date(2013, 11, 11, 9, 7, 18, 0, loc)
m.SetTakenAt(newTime, newTime, zone, SrcName)
assert.Equal(t, newTime.UTC(), m.TakenAt)
assert.Equal(t, newTime, m.TakenAtLocal)
})
t.Run("time > max year", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
m.SetTakenAt(time.Date(2123, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2123, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcManual)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("success with empty takenAtLocal", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
m.SetTakenAt(time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC),
time.Time{}, "test", SrcXmp)
assert.Equal(t, time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAt)
assert.Equal(t, time.Date(2019, 12, 11, 9, 7, 18, 0, time.UTC), m.TakenAtLocal)
})
t.Run("don't update older date", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC)}
photo.SetTakenAt(time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2014, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcAuto)
assert.Equal(t, time.Date(2013, 11, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
})
t.Run("set local time from utc", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), TimeZone: "Europe/Berlin"}
photo.SetTakenAt(time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2014, 12, 11, 10, 7, 18, 0, time.UTC), time.UTC.String(), SrcManual)
assert.Equal(t, time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
assert.Equal(t, time.Date(2014, 12, 11, 10, 07, 18, 0, time.UTC), photo.TakenAtLocal)
})
t.Run("local is UTC", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), TimeZone: time.UTC.String()}
photo.SetTakenAt(time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC),
time.Date(2014, 12, 11, 10, 7, 18, 0, time.UTC), "", SrcManual)
assert.Equal(t, time.Date(2014, 12, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
assert.Equal(t, time.Date(2014, 12, 11, 9, 07, 18, 0, time.UTC), photo.TakenAtLocal)
})
}
func TestPhoto_UpdateTimeZone(t *testing.T) {
t.Run("PhotoTimeZone", func(t *testing.T) {
m := PhotoFixtures.Get("PhotoTimeZone")
takenLocal := time.Date(2015, time.May, 17, 23, 2, 46, 0, time.UTC)
takenJerusalemUtc := time.Date(2015, time.May, 17, 20, 2, 46, 0, time.UTC)
takenShanghaiUtc := time.Date(2015, time.May, 17, 15, 2, 46, 0, time.UTC)
assert.Equal(t, "", m.TimeZone)
assert.Equal(t, takenLocal, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
zone1 := "Asia/Jerusalem"
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenJerusalemUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenJerusalemUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
zone2 := "Asia/Shanghai"
m.UpdateTimeZone(zone2)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
zone3 := "UTC"
m.UpdateTimeZone(zone3)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenLocal, m.TakenAtLocal)
})
t.Run("VideoTimeZone", func(t *testing.T) {
m := PhotoFixtures.Get("VideoTimeZone")
takenUtc := time.Date(2015, 5, 17, 17, 48, 46, 0, time.UTC)
takenJerusalem := time.Date(2015, time.May, 17, 20, 48, 46, 0, time.UTC)
takenShanghaiUtc := time.Date(2015, time.May, 17, 12, 48, 46, 0, time.UTC)
assert.Equal(t, "UTC", m.TimeZone)
assert.Equal(t, takenUtc, m.TakenAt)
assert.Equal(t, takenUtc, m.TakenAtLocal)
zone1 := "Asia/Jerusalem"
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
m.UpdateTimeZone(zone1)
assert.Equal(t, zone1, m.TimeZone)
assert.Equal(t, takenUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
zone2 := "Asia/Shanghai"
m.UpdateTimeZone(zone2)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
zone3 := "UTC"
m.UpdateTimeZone(zone3)
assert.Equal(t, zone2, m.TimeZone)
assert.Equal(t, takenShanghaiUtc, m.TakenAt)
assert.Equal(t, takenJerusalem, m.TakenAtLocal)
})
t.Run("UTC", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
m.TimeZone = "UTC"
zone := "Europe/Berlin"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
m.UpdateTimeZone(zone)
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, m.GetTakenAtLocal(), m.TakenAtLocal)
})
t.Run("Europe/Berlin", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
zone := "Europe/Berlin"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "", m.TimeZone)
m.UpdateTimeZone(zone)
assert.Equal(t, m.GetTakenAt(), m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
})
t.Run("America/New_York", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
m.TimeZone = "Europe/Berlin"
m.TakenAt = m.GetTakenAt()
zone := "America/New_York"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "Europe/Berlin", m.TimeZone)
m.UpdateTimeZone(zone)
assert.Equal(t, m.GetTakenAt(), m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
})
t.Run("manual", func(t *testing.T) {
m := PhotoFixtures.Get("Photo12")
m.TimeZone = "Europe/Berlin"
m.TakenAt = m.GetTakenAt()
m.TakenSrc = SrcManual
zone := "America/New_York"
takenAt := m.TakenAt
takenAtLocal := m.TakenAtLocal
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "Europe/Berlin", m.TimeZone)
m.UpdateTimeZone(zone)
assert.Equal(t, takenAt, m.TakenAt)
assert.Equal(t, takenAtLocal, m.TakenAtLocal)
assert.Equal(t, "Europe/Berlin", m.TimeZone)
})
t.Run("zone = UTC", func(t *testing.T) {
photo := &Photo{TakenAt: time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), TimeZone: "Europe/Berlin"}
photo.UpdateTimeZone("")
assert.Equal(t, time.Date(2015, 11, 11, 9, 7, 18, 0, time.UTC), photo.TakenAt)
assert.Equal(t, "Europe/Berlin", photo.TimeZone)
})
}
func TestPhoto_SetAltitude(t *testing.T) {
t.Run("ViaSetCoordinates", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(0, 0, 5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("Update", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetAltitude(5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("SkipUpdate", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetAltitude(5, SrcEstimate)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
})
t.Run("UpdateEmptyAltitude", func(t *testing.T) {
m := Photo{ID: 1, PlaceSrc: SrcMeta, PhotoLat: float32(1.234), PhotoLng: float32(4.321), PhotoAltitude: 0}
m.SetAltitude(-5, SrcAuto)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetAltitude(-5, SrcEstimate)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetAltitude(-5, SrcMeta)
assert.Equal(t, -5, m.PhotoAltitude)
})
t.Run("ZeroAltitudeManual", func(t *testing.T) {
m := Photo{ID: 1, PlaceSrc: SrcManual, PhotoLat: float32(1.234), PhotoLng: float32(4.321), PhotoAltitude: 5}
m.SetAltitude(0, SrcManual)
assert.Equal(t, 0, m.PhotoAltitude)
})
}
func TestPhoto_SetCoordinates(t *testing.T) {
t.Run("empty coordinates", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(0, 0, 5, SrcManual)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("same source new values", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcMeta)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source lower priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcName)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
})
t.Run("different source equal priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcKeyword)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source higher priority", func(t *testing.T) {
m := PhotoFixtures.Get("Photo21")
assert.Equal(t, SrcEstimate, m.PlaceSrc)
assert.Equal(t, float32(0), m.PhotoLat)
assert.Equal(t, float32(0), m.PhotoLng)
assert.Equal(t, 0, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcMeta)
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
t.Run("different source highest priority (manual)", func(t *testing.T) {
m := PhotoFixtures.Get("Photo15")
assert.Equal(t, SrcMeta, m.PlaceSrc)
assert.Equal(t, float32(1.234), m.PhotoLat)
assert.Equal(t, float32(4.321), m.PhotoLng)
assert.Equal(t, 3, m.PhotoAltitude)
m.SetCoordinates(5.555, 5.555, 5, SrcManual)
assert.Equal(t, SrcManual, m.PlaceSrc)
assert.Equal(t, float32(5.555), m.PhotoLat)
assert.Equal(t, float32(5.555), m.PhotoLng)
assert.Equal(t, 5, m.PhotoAltitude)
})
}
func TestPhoto_Delete(t *testing.T) {
t.Run("not permanent", func(t *testing.T) {
m := PhotoFixtures.Get("Photo16")
@ -951,22 +519,28 @@ func TestPhoto_Updates(t *testing.T) {
func TestPhoto_SetFavorite(t *testing.T) {
t.Run("set to true", func(t *testing.T) {
photo := Photo{PhotoFavorite: true}
photo.Save()
err := photo.SetFavorite(false)
if err != nil {
if err := photo.Save(); err != nil {
t.Fatal(err)
}
if err := photo.SetFavorite(false); err != nil {
t.Fatal(err)
}
assert.Equal(t, false, photo.PhotoFavorite)
})
t.Run("set to false", func(t *testing.T) {
photo := Photo{PhotoFavorite: false}
photo.Save()
err := photo.SetFavorite(true)
if err != nil {
if err := photo.Save(); err != nil {
t.Fatal(err)
}
if err := photo.SetFavorite(true); err != nil {
t.Fatal(err)
}
assert.Equal(t, true, photo.PhotoFavorite)
})
}