photoprism/internal/entity/details.go
2021-09-23 23:46:17 +02:00

242 lines
5.9 KiB
Go

package entity
import (
"fmt"
"sync"
"time"
"github.com/photoprism/photoprism/pkg/txt"
)
var photoDetailsMutex = sync.Mutex{}
// ClipDetail is the size of a Details database column in runes.
const ClipDetail = 250
// Details stores additional metadata fields for each photo to improve search performance.
type Details struct {
PhotoID uint `gorm:"primary_key;auto_increment:false" yaml:"-"`
Keywords string `gorm:"type:TEXT;" json:"Keywords" yaml:"Keywords"`
KeywordsSrc string `gorm:"type:VARBINARY(8);" json:"KeywordsSrc" yaml:"KeywordsSrc,omitempty"`
Notes string `gorm:"type:TEXT;" json:"Notes" yaml:"Notes,omitempty"`
NotesSrc string `gorm:"type:VARBINARY(8);" json:"NotesSrc" yaml:"NotesSrc,omitempty"`
Subject string `gorm:"type:VARCHAR(250);" json:"Subject" yaml:"Subject,omitempty"`
SubjectSrc string `gorm:"type:VARBINARY(8);" json:"SubjectSrc" yaml:"SubjectSrc,omitempty"`
Artist string `gorm:"type:VARCHAR(250);" json:"Artist" yaml:"Artist,omitempty"`
ArtistSrc string `gorm:"type:VARBINARY(8);" json:"ArtistSrc" yaml:"ArtistSrc,omitempty"`
Copyright string `gorm:"type:VARCHAR(250);" json:"Copyright" yaml:"Copyright,omitempty"`
CopyrightSrc string `gorm:"type:VARBINARY(8);" json:"CopyrightSrc" yaml:"CopyrightSrc,omitempty"`
License string `gorm:"type:VARCHAR(250);" json:"License" yaml:"License,omitempty"`
LicenseSrc string `gorm:"type:VARBINARY(8);" json:"LicenseSrc" yaml:"LicenseSrc,omitempty"`
CreatedAt time.Time `yaml:"-"`
UpdatedAt time.Time `yaml:"-"`
}
// NewDetails creates new photo details.
func NewDetails(photo Photo) Details {
return Details{PhotoID: photo.ID}
}
// Create inserts a new row to the database.
func (m *Details) Create() error {
photoDetailsMutex.Lock()
defer photoDetailsMutex.Unlock()
if m.PhotoID == 0 {
return fmt.Errorf("details: photo id must not be empty (create)")
}
return UnscopedDb().Create(m).Error
}
// Save updates existing photo details or inserts a new row.
func (m *Details) Save() error {
if m.PhotoID == 0 {
return fmt.Errorf("details: photo id must not be empty (save)")
}
return UnscopedDb().Save(m).Error
}
// FirstOrCreateDetails returns the existing row, inserts a new row or nil in case of errors.
func FirstOrCreateDetails(m *Details) *Details {
result := Details{}
if err := m.Create(); err == nil {
return m
} else if err := Db().Where("photo_id = ?", m.PhotoID).First(&result).Error; err == nil {
if m.CreatedAt.IsZero() {
m.CreatedAt = TimeStamp()
}
return &result
} else {
log.Errorf("details: %s (find or create %d)", err, m.PhotoID)
}
return nil
}
// NoKeywords tests if the photo has no Keywords.
func (m *Details) NoKeywords() bool {
return m.Keywords == ""
}
// NoSubject tests if the photo has no Subject.
func (m *Details) NoSubject() bool {
return m.Subject == ""
}
// NoNotes tests if the photo has no Notes.
func (m *Details) NoNotes() bool {
return m.Notes == ""
}
// NoArtist tests if the photo has no Artist.
func (m *Details) NoArtist() bool {
return m.Artist == ""
}
// NoCopyright tests if the photo has no Copyright.
func (m *Details) NoCopyright() bool {
return m.Copyright == ""
}
// NoLicense tests if the photo has no License.
func (m *Details) NoLicense() bool {
return m.License == ""
}
// HasKeywords tests if the photo has a Keywords.
func (m *Details) HasKeywords() bool {
return !m.NoKeywords()
}
// HasSubject tests if the photo has a Subject.
func (m *Details) HasSubject() bool {
return !m.NoSubject()
}
// HasNotes tests if the photo has a Notes.
func (m *Details) HasNotes() bool {
return !m.NoNotes()
}
// HasArtist tests if the photo has an Artist.
func (m *Details) HasArtist() bool {
return !m.NoArtist()
}
// HasCopyright tests if the photo has a Copyright
func (m *Details) HasCopyright() bool {
return !m.NoCopyright()
}
// HasLicense tests if the photo has a License.
func (m *Details) HasLicense() bool {
return !m.NoLicense()
}
// SetKeywords updates the photo details field.
func (m *Details) SetKeywords(data, src string) {
val := txt.Clip(data, txt.ClipDescription)
if val == "" {
return
}
if (SrcPriority[src] < SrcPriority[m.KeywordsSrc]) && m.HasKeywords() {
// Ignore if priority is lower and keywords already exist.
return
}
if SrcPriority[src] > SrcPriority[m.KeywordsSrc] {
// Overwrite existing keywords if priority is higher.
m.Keywords = val
} else {
// Merge keywords if priority is the same.
m.Keywords = txt.MergeWords(m.Keywords, val)
}
m.KeywordsSrc = src
}
// SetSubject updates the photo details field.
func (m *Details) SetSubject(data, src string) {
val := txt.Clip(data, ClipDetail)
if val == "" {
return
}
if (SrcPriority[src] < SrcPriority[m.SubjectSrc]) && m.HasSubject() {
return
}
m.Subject = val
m.SubjectSrc = src
}
// SetNotes updates the photo details field.
func (m *Details) SetNotes(data, src string) {
val := txt.Clip(data, txt.ClipDescription)
if val == "" {
return
}
if (SrcPriority[src] < SrcPriority[m.NotesSrc]) && m.HasNotes() {
return
}
m.Notes = val
m.NotesSrc = src
}
// SetArtist updates the photo details field.
func (m *Details) SetArtist(data, src string) {
val := txt.Clip(data, ClipDetail)
if val == "" {
return
}
if (SrcPriority[src] < SrcPriority[m.ArtistSrc]) && m.HasArtist() {
return
}
m.Artist = val
m.ArtistSrc = src
}
// SetCopyright updates the photo details field.
func (m *Details) SetCopyright(data, src string) {
val := txt.Clip(data, ClipDetail)
if val == "" {
return
}
if (SrcPriority[src] < SrcPriority[m.CopyrightSrc]) && m.HasCopyright() {
return
}
m.Copyright = val
m.CopyrightSrc = src
}
// SetLicense updates the photo details field.
func (m *Details) SetLicense(data, src string) {
val := txt.Clip(data, ClipDetail)
if val == "" {
return
}
if (SrcPriority[src] < SrcPriority[m.LicenseSrc]) && m.HasLicense() {
return
}
m.License = val
m.LicenseSrc = src
}