Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
a19d2a72a6
commit
eeef16f07e
11 changed files with 226 additions and 133 deletions
|
@ -364,7 +364,7 @@ func PhotoFileUngroup(router *gin.RouterGroup) {
|
|||
existingPhoto := *file.Photo
|
||||
newPhoto := entity.NewPhoto()
|
||||
|
||||
if err := entity.UnscopedDb().Create(&newPhoto).Error; err != nil {
|
||||
if err := newPhoto.Create(); err != nil {
|
||||
log.Errorf("photo: %s", err.Error())
|
||||
AbortSaveFailed(c)
|
||||
return
|
||||
|
|
|
@ -75,7 +75,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := p.Save(); err != nil {
|
||||
if err := p.SaveLabels(); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
|
|||
|
||||
logError("label", p.RemoveKeyword(label.Label.LabelName))
|
||||
|
||||
if err := p.Save(); err != nil {
|
||||
if err := p.SaveLabels(); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := p.Save(); err != nil {
|
||||
if err := p.SaveLabels(); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
// 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"`
|
||||
Notes string `gorm:"type:text;" json:"Notes" yaml:"Notes,omitempty"`
|
||||
Subject string `gorm:"type:varchar(255);" json:"Subject" yaml:"Subject,omitempty"`
|
||||
Artist string `gorm:"type:varchar(255);" json:"Artist" yaml:"Artist,omitempty"`
|
||||
Copyright string `gorm:"type:varchar(255);" json:"Copyright" yaml:"Copyright,omitempty"`
|
||||
License string `gorm:"type:varchar(255);" json:"License" yaml:"License,omitempty"`
|
||||
PhotoID uint `gorm:"primary_key;auto_increment:false" yaml:"-"`
|
||||
Keywords string `gorm:"type:text;" json:"Keywords" yaml:"Keywords"`
|
||||
Notes string `gorm:"type:text;" json:"Notes" yaml:"Notes,omitempty"`
|
||||
Subject string `gorm:"type:varchar(255);" json:"Subject" yaml:"Subject,omitempty"`
|
||||
Artist string `gorm:"type:varchar(255);" json:"Artist" yaml:"Artist,omitempty"`
|
||||
Copyright string `gorm:"type:varchar(255);" json:"Copyright" yaml:"Copyright,omitempty"`
|
||||
License string `gorm:"type:varchar(255);" json:"License" yaml:"License,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.
|
||||
|
@ -21,6 +30,10 @@ func FirstOrCreateDetails(m *Details) *Details {
|
|||
result := Details{}
|
||||
|
||||
if err := Db().Where("photo_id = ?", m.PhotoID).First(&result).Error; err == nil {
|
||||
if m.CreatedAt.IsZero() {
|
||||
m.CreatedAt = Timestamp()
|
||||
}
|
||||
|
||||
return &result
|
||||
} else if err := m.Create(); err != nil {
|
||||
log.Errorf("details: %s", err)
|
||||
|
|
|
@ -78,3 +78,22 @@ func TestDetails_NoCopyright(t *testing.T) {
|
|||
assert.Equal(t, false, description.NoCopyright())
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDetails(t *testing.T) {
|
||||
t.Run("add to photo", func(t *testing.T) {
|
||||
p := NewPhoto()
|
||||
d := NewDetails(p)
|
||||
p.Details = &d
|
||||
d.Subject = "Foo Bar"
|
||||
d.Keywords = "Baz"
|
||||
|
||||
err := p.Save()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("PHOTO: %#v", p)
|
||||
t.Logf("DETAILS: %#v", d)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -144,19 +144,22 @@ func (m *File) AllFilesMissing() bool {
|
|||
return count == 0
|
||||
}
|
||||
|
||||
// Create inserts a new row to the database.
|
||||
func (m *File) Create() error {
|
||||
if m.PhotoID == 0 {
|
||||
return fmt.Errorf("file: photo id is empty (create)")
|
||||
}
|
||||
|
||||
return UnscopedDb().Create(m).Error
|
||||
}
|
||||
|
||||
// Saves the file in the database.
|
||||
func (m *File) Save() error {
|
||||
if m.PhotoID == 0 {
|
||||
return fmt.Errorf("file: photo id is empty (%s)", m.FileUID)
|
||||
}
|
||||
|
||||
if err := Db().Save(m).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
photo := Photo{}
|
||||
|
||||
return Db().Model(m).Related(&photo).Error
|
||||
return UnscopedDb().Save(m).Error
|
||||
}
|
||||
|
||||
// UpdateVideoInfos updates related video infos based on this file.
|
||||
|
|
|
@ -43,7 +43,6 @@ type Photo struct {
|
|||
TitleSrc string `gorm:"type:varbinary(8);" json:"TitleSrc" yaml:"TitleSrc,omitempty"`
|
||||
PhotoDescription string `gorm:"type:text;" json:"Description" yaml:"Description,omitempty"`
|
||||
DescriptionSrc string `gorm:"type:varbinary(8);" json:"DescriptionSrc" yaml:"DescriptionSrc,omitempty"`
|
||||
Details Details `json:"Details" yaml:"Details"`
|
||||
PhotoPath string `gorm:"type:varbinary(768);index;" json:"Path" yaml:"-"`
|
||||
PhotoName string `gorm:"type:varbinary(255);" json:"Name" yaml:"-"`
|
||||
OriginalName string `gorm:"type:varbinary(768);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
|
@ -72,6 +71,7 @@ type Photo struct {
|
|||
CameraSerial string `gorm:"type:varbinary(255);" json:"CameraSerial" yaml:"CameraSerial,omitempty"`
|
||||
CameraSrc string `gorm:"type:varbinary(8);" json:"CameraSrc" yaml:"-"`
|
||||
LensID uint `gorm:"index:idx_photos_camera_lens;" json:"LensID" yaml:"-"`
|
||||
Details *Details `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Details" yaml:"Details"`
|
||||
Camera *Camera `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Camera" yaml:"-"`
|
||||
Lens *Lens `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Lens" yaml:"-"`
|
||||
Location *Location `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Location" yaml:"-"`
|
||||
|
@ -96,6 +96,10 @@ func NewPhoto() Photo {
|
|||
LensID: UnknownLens.ID,
|
||||
LocationID: UnknownLocation.ID,
|
||||
PlaceID: UnknownPlace.ID,
|
||||
Camera: &UnknownCamera,
|
||||
Lens: &UnknownLens,
|
||||
Location: &UnknownLocation,
|
||||
Place: &UnknownPlace,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,12 +117,14 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
|
|||
|
||||
model.UpdateDateFields()
|
||||
|
||||
details := model.GetDetails()
|
||||
|
||||
if form.Details.PhotoID == model.ID {
|
||||
if err := deepcopier.Copy(&model.Details).From(form.Details); err != nil {
|
||||
if err := deepcopier.Copy(details).From(form.Details); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.Details.Keywords = strings.Join(txt.UniqueWords(txt.Words(model.Details.Keywords)), ", ")
|
||||
details.Keywords = strings.Join(txt.UniqueWords(txt.Words(details.Keywords)), ", ")
|
||||
}
|
||||
|
||||
if locChanged && model.LocationSrc == SrcManual {
|
||||
|
@ -126,10 +132,10 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
|
|||
|
||||
model.AddLabels(labels)
|
||||
|
||||
w := txt.UniqueWords(txt.Words(model.Details.Keywords))
|
||||
w := txt.UniqueWords(txt.Words(details.Keywords))
|
||||
w = append(w, locKeywords...)
|
||||
|
||||
model.Details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
}
|
||||
|
||||
if err := model.SyncKeywordLabels(); err != nil {
|
||||
|
@ -148,7 +154,7 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
|
|||
model.EditedAt = &edited
|
||||
model.PhotoQuality = model.QualityScore()
|
||||
|
||||
if err := UnscopedDb().Save(&model).Error; err != nil {
|
||||
if err := model.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -174,8 +180,38 @@ func (m *Photo) String() string {
|
|||
return "uid " + txt.Quote(m.PhotoUID)
|
||||
}
|
||||
|
||||
// Save the entity in the database.
|
||||
// Create inserts a new row to the database.
|
||||
func (m *Photo) Create() error {
|
||||
if err := UnscopedDb().Create(m).Error; err != nil {
|
||||
log.Errorf("photo: %s (create)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := UnscopedDb().Save(m.GetDetails()).Error; err != nil {
|
||||
log.Errorf("photo: %s (save details after create)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save updates the existing or inserts a new row.
|
||||
func (m *Photo) Save() error {
|
||||
if err := UnscopedDb().Save(m).Error; err != nil {
|
||||
log.Errorf("photo: %s (save)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := UnscopedDb().Save(m.GetDetails()).Error; err != nil {
|
||||
log.Errorf("photo: %s (save details)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the entity in the database.
|
||||
func (m *Photo) SaveLabels() error {
|
||||
if !m.HasID() {
|
||||
return errors.New("photo: can't save to database, id is empty")
|
||||
}
|
||||
|
@ -188,11 +224,11 @@ func (m *Photo) Save() error {
|
|||
log.Info(err)
|
||||
}
|
||||
|
||||
if m.DetailsLoaded() {
|
||||
w := txt.UniqueWords(txt.Words(m.Details.Keywords))
|
||||
w = append(w, labels.Keywords()...)
|
||||
m.Details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
}
|
||||
details := m.GetDetails()
|
||||
|
||||
w := txt.UniqueWords(txt.Words(details.Keywords))
|
||||
w = append(w, labels.Keywords()...)
|
||||
details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
|
||||
if err := m.IndexKeywords(); err != nil {
|
||||
log.Errorf("photo: %s", err.Error())
|
||||
|
@ -200,7 +236,7 @@ func (m *Photo) Save() error {
|
|||
|
||||
m.PhotoQuality = m.QualityScore()
|
||||
|
||||
if err := UnscopedDb().Save(m).Error; err != nil {
|
||||
if err := m.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -267,20 +303,18 @@ func (m *Photo) BeforeSave(scope *gorm.Scope) error {
|
|||
|
||||
// RemoveKeyword removes a word from photo keywords.
|
||||
func (m *Photo) RemoveKeyword(w string) error {
|
||||
if !m.DetailsLoaded() {
|
||||
return fmt.Errorf("can't remove keyword, details not loaded")
|
||||
}
|
||||
details := m.GetDetails()
|
||||
|
||||
words := txt.RemoveFromWords(txt.Words(m.Details.Keywords), w)
|
||||
|
||||
m.Details.Keywords = strings.Join(words, ", ")
|
||||
words := txt.RemoveFromWords(txt.Words(details.Keywords), w)
|
||||
details.Keywords = strings.Join(words, ", ")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncKeywordLabels maintains the label / photo relationship for existing labels and keywords.
|
||||
func (m *Photo) SyncKeywordLabels() error {
|
||||
keywords := txt.UniqueKeywords(m.Details.Keywords)
|
||||
details := m.GetDetails()
|
||||
keywords := txt.UniqueKeywords(details.Keywords)
|
||||
|
||||
var labelIds []uint
|
||||
|
||||
|
@ -300,11 +334,8 @@ func (m *Photo) SyncKeywordLabels() error {
|
|||
|
||||
// IndexKeywords adds given keywords to the photo entry
|
||||
func (m *Photo) IndexKeywords() error {
|
||||
if !m.DetailsLoaded() {
|
||||
return fmt.Errorf("can't index keywords, details not loaded")
|
||||
}
|
||||
|
||||
db := Db()
|
||||
db := UnscopedDb()
|
||||
details := m.GetDetails()
|
||||
|
||||
var keywordIds []uint
|
||||
var keywords []string
|
||||
|
@ -312,9 +343,9 @@ func (m *Photo) IndexKeywords() error {
|
|||
// Add title, description and other keywords
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoTitle)...)
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoDescription)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Details.Keywords)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Details.Subject)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Details.Artist)...)
|
||||
keywords = append(keywords, txt.Keywords(details.Keywords)...)
|
||||
keywords = append(keywords, txt.Keywords(details.Subject)...)
|
||||
keywords = append(keywords, txt.Keywords(details.Artist)...)
|
||||
|
||||
keywords = txt.UniqueWords(keywords)
|
||||
|
||||
|
@ -529,9 +560,19 @@ func (m *Photo) HasDescription() bool {
|
|||
return m.PhotoDescription != ""
|
||||
}
|
||||
|
||||
// DetailsLoaded returns true if photo details exist.
|
||||
func (m *Photo) DetailsLoaded() bool {
|
||||
return m.Details.PhotoID == m.ID
|
||||
// GetDetails returns the photo description details.
|
||||
func (m *Photo) GetDetails() *Details {
|
||||
if m.Details == nil {
|
||||
m.Details = &Details{PhotoID: m.ID}
|
||||
} else {
|
||||
return m.Details
|
||||
}
|
||||
|
||||
if details := FirstOrCreateDetails(m.Details); details != nil {
|
||||
m.Details = details
|
||||
}
|
||||
|
||||
return m.Details
|
||||
}
|
||||
|
||||
// FileTitle returns a photo title based on the file name and/or path.
|
||||
|
|
|
@ -58,7 +58,7 @@ var PhotoFixtures = PhotoMap{
|
|||
TimeZone: "",
|
||||
PhotoYear: 2790,
|
||||
PhotoMonth: 2,
|
||||
Details: DetailsFixtures.Get("lake", 1000000),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000000),
|
||||
DescriptionSrc: "",
|
||||
LocationID: UnknownLocation.ID,
|
||||
Location: &UnknownLocation,
|
||||
|
@ -118,7 +118,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownPlace.CountryCode(),
|
||||
PhotoYear: 2790,
|
||||
PhotoMonth: 2,
|
||||
Details: DetailsFixtures.Get("lake", 1000001),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000001),
|
||||
DescriptionSrc: "",
|
||||
Keywords: []Keyword{},
|
||||
Albums: []Album{},
|
||||
|
@ -159,7 +159,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownPlace.CountryCode(),
|
||||
PhotoYear: 1990,
|
||||
PhotoMonth: 3,
|
||||
Details: DetailsFixtures.Get("lake", 1000002),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000002),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -211,7 +211,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: LocationFixtures.Pointer("caravan park").Place.CountryCode(),
|
||||
PhotoYear: 1990,
|
||||
PhotoMonth: 4,
|
||||
Details: DetailsFixtures.Get("bridge", 1000003),
|
||||
Details: DetailsFixtures.Pointer("bridge", 1000003),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -263,7 +263,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 1000004),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000004),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -315,7 +315,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 1000005),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000005),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -363,7 +363,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownPlace.CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 1000006),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000006),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -411,7 +411,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownPlace.CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 1000007),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000007),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -454,7 +454,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 1000008),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000008),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -502,7 +502,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 1000009),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000009),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -550,7 +550,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("holidaypark").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 10000010),
|
||||
Details: DetailsFixtures.Pointer("lake", 10000010),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -598,7 +598,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("emptyNameLongCity").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: DetailsFixtures.Get("lake", 10000011),
|
||||
Details: DetailsFixtures.Pointer("lake", 10000011),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -655,7 +655,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("emptyNameShortCity").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: Details{},
|
||||
Details: &Details{},
|
||||
DescriptionSrc: "",
|
||||
Keywords: []Keyword{},
|
||||
Albums: []Album{},
|
||||
|
@ -699,7 +699,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("veryLongLocName").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: Details{},
|
||||
Details: &Details{},
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -742,7 +742,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mediumLongLocName").CountryCode(),
|
||||
PhotoYear: 2014,
|
||||
PhotoMonth: 7,
|
||||
Details: Details{},
|
||||
Details: &Details{},
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -796,7 +796,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownCountry.ID,
|
||||
PhotoYear: 0,
|
||||
PhotoMonth: 0,
|
||||
Details: DetailsFixtures.Get("blacklist", 1000015),
|
||||
Details: DetailsFixtures.Pointer("blacklist", 1000015),
|
||||
DescriptionSrc: "location",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
@ -848,7 +848,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownCountry.ID,
|
||||
PhotoYear: 0,
|
||||
PhotoMonth: 0,
|
||||
Details: DetailsFixtures.Get("lake", 1000015),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000015),
|
||||
DescriptionSrc: "location",
|
||||
Keywords: []Keyword{},
|
||||
Albums: []Album{},
|
||||
|
@ -896,7 +896,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
|
||||
PhotoYear: 0,
|
||||
PhotoMonth: 0,
|
||||
Details: DetailsFixtures.Get("lake", 1000015),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000015),
|
||||
DescriptionSrc: "location",
|
||||
Keywords: []Keyword{},
|
||||
Albums: []Album{},
|
||||
|
@ -946,7 +946,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
|
||||
PhotoYear: 0,
|
||||
PhotoMonth: 0,
|
||||
Details: DetailsFixtures.Get("lake", 1000015),
|
||||
Details: DetailsFixtures.Pointer("lake", 1000015),
|
||||
DescriptionSrc: "location",
|
||||
Keywords: []Keyword{},
|
||||
Albums: []Album{},
|
||||
|
@ -992,7 +992,7 @@ var PhotoFixtures = PhotoMap{
|
|||
PhotoCountry: UnknownPlace.CountryCode(),
|
||||
PhotoYear: 1990,
|
||||
PhotoMonth: 4,
|
||||
Details: DetailsFixtures.Get("bridge", 1000019),
|
||||
Details: DetailsFixtures.Pointer("bridge", 1000019),
|
||||
DescriptionSrc: "",
|
||||
Camera: CameraFixtures.Pointer("canon-eos-6d"),
|
||||
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
|
||||
|
|
|
@ -111,11 +111,10 @@ func (m *Photo) Optimize() (updated bool, err error) {
|
|||
log.Info(err)
|
||||
}
|
||||
|
||||
if m.DetailsLoaded() {
|
||||
w := txt.UniqueWords(txt.Words(m.Details.Keywords))
|
||||
w = append(w, labels.Keywords()...)
|
||||
m.Details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
}
|
||||
details := m.GetDetails()
|
||||
w := txt.UniqueWords(txt.Words(details.Keywords))
|
||||
w = append(w, labels.Keywords()...)
|
||||
details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
|
||||
if err := m.IndexKeywords(); err != nil {
|
||||
log.Errorf("photo: %s", err.Error())
|
||||
|
|
|
@ -42,8 +42,10 @@ func (m *Photo) QualityScore() (score int) {
|
|||
|
||||
blacklisted := false
|
||||
|
||||
if m.Details.Keywords != "" {
|
||||
keywords := txt.Words(m.Details.Keywords)
|
||||
details := m.GetDetails()
|
||||
|
||||
if details.Keywords != "" {
|
||||
keywords := txt.Words(details.Keywords)
|
||||
|
||||
for _, w := range keywords {
|
||||
w = strings.ToLower(w)
|
||||
|
|
|
@ -65,7 +65,8 @@ func TestSavePhotoForm(t *testing.T) {
|
|||
assert.Equal(t, "image", m.PhotoType)
|
||||
assert.Equal(t, float32(7.9999), m.PhotoLat)
|
||||
assert.NotNil(t, m.EditedAt)
|
||||
t.Log(m.Details.Keywords)
|
||||
|
||||
t.Log(m.GetDetails().Keywords)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -97,7 +98,7 @@ func TestPhoto_Save(t *testing.T) {
|
|||
PlaceID: "765",
|
||||
PhotoCountry: "de",
|
||||
Keywords: []Keyword{},
|
||||
Details: Details{
|
||||
Details: &Details{
|
||||
PhotoID: 11111,
|
||||
Keywords: "test cat dog",
|
||||
Subject: "animals",
|
||||
|
@ -108,14 +109,14 @@ func TestPhoto_Save(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
err := photo.Save()
|
||||
err := photo.SaveLabels()
|
||||
|
||||
assert.EqualError(t, err, "photo: can't save to database, id is empty")
|
||||
})
|
||||
|
||||
t.Run("existing photo", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("19800101_000002_D640C559")
|
||||
err := m.Save()
|
||||
err := m.SaveLabels()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -280,14 +281,30 @@ func TestPhoto_NoCameraSerial(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestPhoto_DetailsLoaded(t *testing.T) {
|
||||
func TestPhoto_GetDetails(t *testing.T) {
|
||||
t.Run("true", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("19800101_000002_D640C559")
|
||||
assert.True(t, m.DetailsLoaded())
|
||||
result := m.GetDetails()
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("result should never be nil")
|
||||
}
|
||||
|
||||
if result.PhotoID == 0 {
|
||||
t.Fatal("PhotoID should not be 0")
|
||||
}
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("Photo12")
|
||||
assert.False(t, m.DetailsLoaded())
|
||||
result := m.GetDetails()
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("result should never be nil")
|
||||
}
|
||||
|
||||
if result.PhotoID != 0 {
|
||||
t.Fatal("PhotoID should be 0")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,6 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
|
||||
photo := entity.NewPhoto()
|
||||
metaData := meta.Data{}
|
||||
description := entity.Details{}
|
||||
labels := classify.Labels{}
|
||||
|
||||
fileRoot, fileBase, filePath, fileName := m.PathNameInfo()
|
||||
|
@ -134,9 +133,9 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
return result
|
||||
}
|
||||
|
||||
if photoExists {
|
||||
entity.UnscopedDb().Model(&photo).Related(&description)
|
||||
} else {
|
||||
details := photo.GetDetails()
|
||||
|
||||
if !photoExists {
|
||||
photo.PhotoQuality = -1
|
||||
|
||||
if yamlName := fs.TypeYaml.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" {
|
||||
|
@ -214,16 +213,16 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
photo.SetTitle(data.Title, entity.SrcXmp)
|
||||
photo.SetDescription(data.Description, entity.SrcXmp)
|
||||
|
||||
if photo.Details.NoNotes() && data.Comment != "" {
|
||||
photo.Details.Notes = data.Comment
|
||||
if details.NoNotes() && data.Comment != "" {
|
||||
details.Notes = data.Comment
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && data.Artist != "" {
|
||||
photo.Details.Artist = data.Artist
|
||||
if details.NoArtist() && data.Artist != "" {
|
||||
details.Artist = data.Artist
|
||||
}
|
||||
|
||||
if photo.Details.NoCopyright() && data.Copyright != "" {
|
||||
photo.Details.Copyright = data.Copyright
|
||||
if details.NoCopyright() && data.Copyright != "" {
|
||||
details.Copyright = data.Copyright
|
||||
}
|
||||
}
|
||||
case m.IsRaw(), m.IsHEIF(), m.IsImageOther():
|
||||
|
@ -233,24 +232,24 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
photo.SetTakenAt(metaData.TakenAt, metaData.TakenAtLocal, metaData.TimeZone, entity.SrcMeta)
|
||||
photo.SetCoordinates(metaData.Lat, metaData.Lng, metaData.Altitude, entity.SrcMeta)
|
||||
|
||||
if photo.Details.NoNotes() {
|
||||
photo.Details.Notes = metaData.Comment
|
||||
if details.NoNotes() {
|
||||
details.Notes = metaData.Comment
|
||||
}
|
||||
|
||||
if photo.Details.NoSubject() {
|
||||
photo.Details.Subject = metaData.Subject
|
||||
if details.NoSubject() {
|
||||
details.Subject = metaData.Subject
|
||||
}
|
||||
|
||||
if photo.Details.NoKeywords() {
|
||||
photo.Details.Keywords = metaData.Keywords
|
||||
if details.NoKeywords() {
|
||||
details.Keywords = metaData.Keywords
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && metaData.Artist != "" {
|
||||
photo.Details.Artist = metaData.Artist
|
||||
if details.NoArtist() && metaData.Artist != "" {
|
||||
details.Artist = metaData.Artist
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && metaData.CameraOwner != "" {
|
||||
photo.Details.Artist = metaData.CameraOwner
|
||||
if details.NoArtist() && metaData.CameraOwner != "" {
|
||||
details.Artist = metaData.CameraOwner
|
||||
}
|
||||
|
||||
if photo.NoCameraSerial() {
|
||||
|
@ -290,24 +289,24 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
photo.SetTakenAt(metaData.TakenAt, metaData.TakenAtLocal, metaData.TimeZone, entity.SrcMeta)
|
||||
photo.SetCoordinates(metaData.Lat, metaData.Lng, metaData.Altitude, entity.SrcMeta)
|
||||
|
||||
if photo.Details.NoNotes() {
|
||||
photo.Details.Notes = metaData.Comment
|
||||
if details.NoNotes() {
|
||||
details.Notes = metaData.Comment
|
||||
}
|
||||
|
||||
if photo.Details.NoSubject() {
|
||||
photo.Details.Subject = metaData.Subject
|
||||
if details.NoSubject() {
|
||||
details.Subject = metaData.Subject
|
||||
}
|
||||
|
||||
if photo.Details.NoKeywords() {
|
||||
photo.Details.Keywords = metaData.Keywords
|
||||
if details.NoKeywords() {
|
||||
details.Keywords = metaData.Keywords
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && metaData.Artist != "" {
|
||||
photo.Details.Artist = metaData.Artist
|
||||
if details.NoArtist() && metaData.Artist != "" {
|
||||
details.Artist = metaData.Artist
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && metaData.CameraOwner != "" {
|
||||
photo.Details.Artist = metaData.CameraOwner
|
||||
if details.NoArtist() && metaData.CameraOwner != "" {
|
||||
details.Artist = metaData.CameraOwner
|
||||
}
|
||||
|
||||
if photo.NoCameraSerial() {
|
||||
|
@ -385,24 +384,24 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
photo.SetTakenAt(metaData.TakenAt, metaData.TakenAtLocal, metaData.TimeZone, entity.SrcMeta)
|
||||
photo.SetCoordinates(metaData.Lat, metaData.Lng, metaData.Altitude, entity.SrcMeta)
|
||||
|
||||
if photo.Details.NoNotes() {
|
||||
photo.Details.Notes = metaData.Comment
|
||||
if details.NoNotes() {
|
||||
details.Notes = metaData.Comment
|
||||
}
|
||||
|
||||
if photo.Details.NoSubject() {
|
||||
photo.Details.Subject = metaData.Subject
|
||||
if details.NoSubject() {
|
||||
details.Subject = metaData.Subject
|
||||
}
|
||||
|
||||
if photo.Details.NoKeywords() {
|
||||
photo.Details.Keywords = metaData.Keywords
|
||||
if details.NoKeywords() {
|
||||
details.Keywords = metaData.Keywords
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && metaData.Artist != "" {
|
||||
photo.Details.Artist = metaData.Artist
|
||||
if details.NoArtist() && metaData.Artist != "" {
|
||||
details.Artist = metaData.Artist
|
||||
}
|
||||
|
||||
if photo.Details.NoArtist() && metaData.CameraOwner != "" {
|
||||
photo.Details.Artist = metaData.CameraOwner
|
||||
if details.NoArtist() && metaData.CameraOwner != "" {
|
||||
details.Artist = metaData.CameraOwner
|
||||
}
|
||||
|
||||
if photo.NoCameraSerial() {
|
||||
|
@ -480,14 +479,14 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
file.FileOrientation = m.Orientation()
|
||||
|
||||
if photoExists {
|
||||
if err := entity.UnscopedDb().Save(&photo).Error; err != nil {
|
||||
if err := photo.Save(); err != nil {
|
||||
log.Errorf("index: %s for %s", err.Error(), logName)
|
||||
result.Status = IndexFailed
|
||||
result.Error = err
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
if err := entity.UnscopedDb().Create(&photo).Error; err != nil {
|
||||
if err := photo.Create(); err != nil {
|
||||
log.Errorf("index: %s", err)
|
||||
result.Status = IndexFailed
|
||||
result.Error = err
|
||||
|
@ -529,7 +528,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
log.Debugf("%s (%s)", err.Error(), logName)
|
||||
}
|
||||
|
||||
w := txt.Keywords(photo.Details.Keywords)
|
||||
w := txt.Keywords(details.Keywords)
|
||||
|
||||
if !fs.IsID(fileBase) {
|
||||
w = append(w, txt.FilenameKeywords(filePath)...)
|
||||
|
@ -541,17 +540,17 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
w = append(w, file.FileMainColor)
|
||||
w = append(w, labels.Keywords()...)
|
||||
|
||||
photo.Details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
details.Keywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
|
||||
if photo.Details.Keywords != "" {
|
||||
log.Tracef("index: set keywords %s for %s", photo.Details.Keywords, logName)
|
||||
if details.Keywords != "" {
|
||||
log.Tracef("index: set keywords %s for %s", details.Keywords, logName)
|
||||
} else {
|
||||
log.Tracef("index: no keywords for %s", logName)
|
||||
}
|
||||
|
||||
photo.PhotoQuality = photo.QualityScore()
|
||||
|
||||
if err := entity.UnscopedDb().Save(&photo).Error; err != nil {
|
||||
if err := photo.Save(); err != nil {
|
||||
log.Errorf("index: %s for %s", err, logName)
|
||||
result.Status = IndexFailed
|
||||
result.Error = err
|
||||
|
@ -570,7 +569,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
photo.PhotoQuality = photo.QualityScore()
|
||||
}
|
||||
|
||||
if err := entity.UnscopedDb().Unscoped().Save(&photo).Error; err != nil {
|
||||
if err := photo.Save(); err != nil {
|
||||
log.Errorf("index: %s for %s", err, logName)
|
||||
result.Status = IndexFailed
|
||||
result.Error = err
|
||||
|
@ -583,7 +582,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
if fileQuery.Error == nil {
|
||||
file.UpdatedIn = int64(time.Since(start))
|
||||
|
||||
if err := entity.UnscopedDb().Save(&file).Error; err != nil {
|
||||
if err := file.Save(); err != nil {
|
||||
log.Errorf("index: %s for %s", err, logName)
|
||||
result.Status = IndexFailed
|
||||
result.Error = err
|
||||
|
@ -592,7 +591,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
} else {
|
||||
file.CreatedIn = int64(time.Since(start))
|
||||
|
||||
if err := entity.UnscopedDb().Create(&file).Error; err != nil {
|
||||
if err := file.Create(); err != nil {
|
||||
log.Errorf("index: %s for %s", err, logName)
|
||||
result.Status = IndexFailed
|
||||
result.Error = err
|
||||
|
|
Loading…
Reference in a new issue