2019-12-28 20:24:20 +01:00
|
|
|
package entity
|
|
|
|
|
|
|
|
import (
|
2020-05-26 21:51:34 +02:00
|
|
|
"strings"
|
2019-12-28 20:24:20 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/photoprism/photoprism/internal/maps"
|
2020-05-03 18:00:50 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
2019-12-28 20:24:20 +01:00
|
|
|
)
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// Place used to associate photos to places
|
2019-12-28 20:24:20 +01:00
|
|
|
type Place struct {
|
2020-07-12 08:27:05 +02:00
|
|
|
ID string `gorm:"type:varbinary(42);primary_key;auto_increment:false;" json:"PlaceID" yaml:"PlaceID"`
|
|
|
|
PlaceLabel string `gorm:"type:varbinary(768);unique_index;" json:"Label" yaml:"Label"`
|
|
|
|
PlaceCity string `gorm:"type:varchar(255);" json:"City" yaml:"City,omitempty"`
|
|
|
|
PlaceState string `gorm:"type:varchar(255);" json:"State" yaml:"State,omitempty"`
|
|
|
|
PlaceCountry string `gorm:"type:varbinary(2);" json:"Country" yaml:"Country,omitempty"`
|
|
|
|
PlaceKeywords string `gorm:"type:varchar(255);" json:"Keywords" yaml:"Keywords,omitempty"`
|
|
|
|
PlaceFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
|
|
|
PhotoCount int `gorm:"default:1" json:"PhotoCount" yaml:"-"`
|
|
|
|
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
|
|
|
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-25 19:10:44 +02:00
|
|
|
// UnknownPlace is PhotoPrism's default place.
|
2020-04-25 14:22:47 +02:00
|
|
|
var UnknownPlace = Place{
|
2020-07-12 08:27:05 +02:00
|
|
|
ID: "zz",
|
|
|
|
PlaceLabel: "Unknown",
|
|
|
|
PlaceCity: "Unknown",
|
|
|
|
PlaceState: "Unknown",
|
|
|
|
PlaceCountry: "zz",
|
|
|
|
PlaceKeywords: "",
|
|
|
|
PlaceFavorite: false,
|
|
|
|
PhotoCount: -1,
|
2020-04-25 14:22:47 +02:00
|
|
|
}
|
2019-12-28 20:24:20 +01:00
|
|
|
|
2020-05-25 19:10:44 +02:00
|
|
|
// CreateUnknownPlace creates the default place if not exists.
|
2020-04-30 20:07:03 +02:00
|
|
|
func CreateUnknownPlace() {
|
2020-05-25 19:10:44 +02:00
|
|
|
FirstOrCreatePlace(&UnknownPlace)
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-29 12:56:24 +02:00
|
|
|
// FindPlace finds a matching place or returns nil.
|
|
|
|
func FindPlace(id string, label string) *Place {
|
2019-12-28 20:24:20 +01:00
|
|
|
place := &Place{}
|
|
|
|
|
2020-05-15 13:16:03 +02:00
|
|
|
if label == "" {
|
2020-05-29 12:56:24 +02:00
|
|
|
if err := Db().First(place, "id = ?", id).Error; err != nil {
|
|
|
|
log.Debugf("place: %s for id %s", err.Error(), id)
|
2020-05-15 13:16:03 +02:00
|
|
|
return nil
|
|
|
|
}
|
2020-07-12 08:27:05 +02:00
|
|
|
} else if err := Db().First(place, "id = ? OR place_label = ?", id, label).Error; err != nil {
|
2020-05-29 12:56:24 +02:00
|
|
|
log.Debugf("place: %s for id %s / label %s", err.Error(), id, txt.Quote(label))
|
2020-04-25 16:17:59 +02:00
|
|
|
return nil
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return place
|
|
|
|
}
|
|
|
|
|
2020-07-07 16:40:21 +02:00
|
|
|
// Find fetches entity values from the database the primary key.
|
2020-04-30 20:07:03 +02:00
|
|
|
func (m *Place) Find() error {
|
2020-05-29 12:56:24 +02:00
|
|
|
if err := Db().First(m, "id = ?", m.ID).Error; err != nil {
|
2019-12-28 20:24:20 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:10:44 +02:00
|
|
|
// Create inserts a new row to the database.
|
|
|
|
func (m *Place) Create() error {
|
2020-05-26 11:00:39 +02:00
|
|
|
return Db().Create(m).Error
|
2020-05-25 19:10:44 +02:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:40:21 +02:00
|
|
|
// FirstOrCreatePlace fetches an existing row, inserts a new row or nil in case of errors.
|
2020-05-25 19:10:44 +02:00
|
|
|
func FirstOrCreatePlace(m *Place) *Place {
|
2020-05-29 12:56:24 +02:00
|
|
|
if m.ID == "" {
|
2020-07-07 16:40:21 +02:00
|
|
|
log.Errorf("place: id must not be empty (first or create)")
|
2020-05-25 19:10:44 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-12 08:27:05 +02:00
|
|
|
if m.PlaceLabel == "" {
|
2020-07-07 16:40:21 +02:00
|
|
|
log.Errorf("place: label must not be empty (first or create %s)", m.ID)
|
2020-05-25 19:10:44 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
result := Place{}
|
|
|
|
|
2020-07-12 08:27:05 +02:00
|
|
|
if findErr := Db().Where("id = ? OR place_label = ?", m.ID, m.PlaceLabel).First(&result).Error; findErr == nil {
|
2020-05-25 19:10:44 +02:00
|
|
|
return &result
|
2020-07-07 16:40:21 +02:00
|
|
|
} else if createErr := m.Create(); createErr == nil {
|
|
|
|
return m
|
2020-07-12 08:27:05 +02:00
|
|
|
} else if err := Db().Where("id = ? OR place_label = ?", m.ID, m.PlaceLabel).First(&result).Error; err == nil {
|
2020-07-07 16:40:21 +02:00
|
|
|
return &result
|
|
|
|
} else {
|
|
|
|
log.Errorf("place: %s (first or create %s)", createErr, m.ID)
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:40:21 +02:00
|
|
|
return nil
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-04-25 16:17:59 +02:00
|
|
|
// Unknown returns true if this is an unknown place
|
|
|
|
func (m Place) Unknown() bool {
|
2020-05-29 12:56:24 +02:00
|
|
|
return m.ID == "" || m.ID == UnknownPlace.ID
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// Label returns place label
|
2020-04-16 20:57:00 +02:00
|
|
|
func (m Place) Label() string {
|
2020-07-12 08:27:05 +02:00
|
|
|
return m.PlaceLabel
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// City returns place City
|
2020-04-16 20:57:00 +02:00
|
|
|
func (m Place) City() string {
|
2020-07-12 08:27:05 +02:00
|
|
|
return m.PlaceCity
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-26 21:51:34 +02:00
|
|
|
// LongCity checks if the city name is more than 16 char.
|
|
|
|
func (m Place) LongCity() bool {
|
2020-07-12 08:27:05 +02:00
|
|
|
return len(m.PlaceCity) > 16
|
2020-05-26 21:51:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NoCity checks if the location has no city
|
|
|
|
func (m Place) NoCity() bool {
|
2020-07-12 08:27:05 +02:00
|
|
|
return m.PlaceCity == ""
|
2020-05-26 21:51:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// CityContains checks if the location city contains the text string
|
|
|
|
func (m Place) CityContains(text string) bool {
|
2020-07-12 08:27:05 +02:00
|
|
|
return strings.Contains(text, m.PlaceCity)
|
2020-05-26 21:51:34 +02:00
|
|
|
}
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// State returns place State
|
2020-04-16 20:57:00 +02:00
|
|
|
func (m Place) State() string {
|
2020-07-12 08:27:05 +02:00
|
|
|
return m.PlaceState
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// CountryCode returns place CountryCode
|
2020-04-16 20:57:00 +02:00
|
|
|
func (m Place) CountryCode() string {
|
2020-07-12 08:27:05 +02:00
|
|
|
return m.PlaceCountry
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// CountryName returns place CountryName
|
2020-04-16 20:57:00 +02:00
|
|
|
func (m Place) CountryName() string {
|
2020-07-12 08:27:05 +02:00
|
|
|
return maps.CountryNames[m.PlaceCountry]
|
2019-12-28 20:24:20 +01:00
|
|
|
}
|