photoprism/internal/entity/place.go

177 lines
4.6 KiB
Go

package entity
import (
"strings"
"sync"
"time"
"github.com/photoprism/photoprism/internal/maps"
"github.com/photoprism/photoprism/pkg/txt"
)
var placeMutex = sync.Mutex{}
// Place used to associate photos to places
type Place struct {
ID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"PlaceID" yaml:"PlaceID"`
PlaceLabel string `gorm:"type:VARCHAR(400);unique_index;" json:"Label" yaml:"Label"`
PlaceDistrict string `gorm:"type:VARCHAR(100);index;" json:"District" yaml:"District,omitempty"`
PlaceCity string `gorm:"type:VARCHAR(100);index;" json:"City" yaml:"City,omitempty"`
PlaceState string `gorm:"type:VARCHAR(100);index;" json:"State" yaml:"State,omitempty"`
PlaceCountry string `gorm:"type:VARBINARY(2);" json:"Country" yaml:"Country,omitempty"`
PlaceKeywords string `gorm:"type:VARCHAR(300);" 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:"-"`
}
// TableName returns the entity database table name.
func (Place) TableName() string {
return "places"
}
// UnknownPlace is PhotoPrism's default place.
var UnknownPlace = Place{
ID: UnknownID,
PlaceLabel: "Unknown",
PlaceDistrict: "Unknown",
PlaceCity: "Unknown",
PlaceState: "Unknown",
PlaceCountry: UnknownID,
PlaceKeywords: "",
PlaceFavorite: false,
PhotoCount: -1,
}
// CreateUnknownPlace creates the default place if not exists.
func CreateUnknownPlace() {
FirstOrCreatePlace(&UnknownPlace)
}
// FindPlace finds a matching place or returns nil.
func FindPlace(id string, label string) *Place {
place := Place{}
if label == "" {
if err := Db().Where("id = ?", id).First(&place).Error; err != nil {
log.Debugf("place: %s no found", txt.Quote(id))
return nil
} else {
return &place
}
}
if err := Db().Where("id = ? OR place_label = ?", id, label).First(&place).Error; err != nil {
return nil
} else {
return &place
}
}
// Find fetches entity values from the database the primary key.
func (m *Place) Find() error {
if err := Db().First(m, "id = ?", m.ID).Error; err != nil {
return err
}
return nil
}
// Create inserts a new row to the database.
func (m *Place) Create() error {
placeMutex.Lock()
defer placeMutex.Unlock()
return Db().Create(m).Error
}
// Save updates the existing or inserts a new row.
func (m *Place) Save() error {
placeMutex.Lock()
defer placeMutex.Unlock()
return Db().Save(m).Error
}
// Delete removes the entity from the index.
func (m *Place) Delete() (err error) {
return UnscopedDb().Delete(m).Error
}
// FirstOrCreatePlace fetches an existing row, inserts a new row or nil in case of errors.
func FirstOrCreatePlace(m *Place) *Place {
if m.ID == "" {
log.Errorf("place: id must not be empty (find or create)")
return nil
}
if m.PlaceLabel == "" {
log.Errorf("place: label must not be empty (find or create place %s)", m.ID)
return nil
}
result := Place{}
if findErr := Db().Where("id = ? OR place_label = ?", m.ID, m.PlaceLabel).First(&result).Error; findErr == nil {
return &result
} else if createErr := m.Create(); createErr == nil {
return m
} else if err := Db().Where("id = ? OR place_label = ?", m.ID, m.PlaceLabel).First(&result).Error; err == nil {
return &result
} else {
log.Errorf("place: %s (create %s)", createErr, m.ID)
}
return nil
}
// Unknown returns true if this is an unknown place
func (m Place) Unknown() bool {
return m.ID == "" || m.ID == UnknownPlace.ID
}
// Label returns place label
func (m Place) Label() string {
return m.PlaceLabel
}
// District returns the place district name if any.
func (m Place) District() string {
return m.PlaceDistrict
}
// City returns place city if any.
func (m Place) City() string {
return m.PlaceCity
}
// LongCity checks if the city name is more than 16 char.
func (m Place) LongCity() bool {
return len(m.PlaceCity) > 16
}
// NoCity checks if the location has no city
func (m Place) NoCity() bool {
return m.PlaceCity == ""
}
// CityContains checks if the location city contains the text string
func (m Place) CityContains(text string) bool {
return strings.Contains(text, m.PlaceCity)
}
// State returns place State
func (m Place) State() string {
return m.PlaceState
}
// CountryCode returns place CountryCode
func (m Place) CountryCode() string {
return m.PlaceCountry
}
// CountryName returns place CountryName
func (m Place) CountryName() string {
return maps.CountryNames[m.PlaceCountry]
}