Backend: Refactor photo details entity #379 #357

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-07-06 19:15:57 +02:00
parent a19d2a72a6
commit eeef16f07e
11 changed files with 226 additions and 133 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)
})
}

View File

@ -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.

View 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.

View File

@ -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,

View File

@ -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())

View File

@ -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)

View File

@ -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")
}
})
}

View File

@ -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