Backend: Move photo description to separate table
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
ccd92a4715
commit
eedaae8f91
12 changed files with 246 additions and 212 deletions
|
@ -228,11 +228,11 @@
|
|||
placeholder=""
|
||||
:rows="1"
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoDescription"
|
||||
v-model="model.Description.PhotoDescription"
|
||||
></v-textarea>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 class="pa-2">
|
||||
<v-flex xs12 md6 class="pa-2">
|
||||
<v-textarea
|
||||
hide-details
|
||||
auto-grow
|
||||
|
@ -240,30 +240,10 @@
|
|||
placeholder=""
|
||||
:rows="1"
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoKeywords"
|
||||
v-model="model.Description.PhotoKeywords"
|
||||
></v-textarea>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 md3 class="pa-2">
|
||||
<v-text-field
|
||||
hide-details
|
||||
label="Copyright"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoCopyright"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 md3 class="pa-2">
|
||||
<v-text-field
|
||||
hide-details
|
||||
label="Artist"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoArtist"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 md6 class="pa-2">
|
||||
<v-textarea
|
||||
hide-details
|
||||
|
@ -272,7 +252,51 @@
|
|||
placeholder=""
|
||||
:rows="1"
|
||||
color="secondary-dark"
|
||||
v-model="model.PhotoNotes"
|
||||
v-model="model.Description.PhotoNotes"
|
||||
></v-textarea>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 md3 class="pa-2">
|
||||
<v-textarea
|
||||
hide-details
|
||||
auto-grow
|
||||
label="Subject"
|
||||
placeholder=""
|
||||
:rows="1"
|
||||
color="secondary-dark"
|
||||
v-model="model.Description.PhotoSubject"
|
||||
></v-textarea>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 md3 class="pa-2">
|
||||
<v-text-field
|
||||
hide-details
|
||||
label="Artist"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.Description.PhotoArtist"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 md3 class="pa-2">
|
||||
<v-text-field
|
||||
hide-details
|
||||
label="Copyright"
|
||||
placeholder=""
|
||||
color="secondary-dark"
|
||||
v-model="model.Description.PhotoCopyright"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 sm6 md3 class="pa-2">
|
||||
<v-textarea
|
||||
hide-details
|
||||
auto-grow
|
||||
label="License"
|
||||
placeholder=""
|
||||
:rows="1"
|
||||
color="secondary-dark"
|
||||
v-model="model.Description.PhotoLicense"
|
||||
></v-textarea>
|
||||
</v-flex>
|
||||
|
||||
|
@ -309,9 +333,7 @@
|
|||
return {
|
||||
config: this.$config.values,
|
||||
all: {
|
||||
countries: [{code: "", name: this.$gettext("Unknown")}],
|
||||
cameras: [{ID: 0, CameraModel: this.$gettext("Unknown")}],
|
||||
lenses: [{ID: 0, LensModel: "Unknown"}],
|
||||
countries: [{code: "", name: ""}],
|
||||
colors: [{label: "Unknown", name: ""}],
|
||||
},
|
||||
readonly: this.$config.getValue("readonly"),
|
||||
|
@ -380,10 +402,10 @@
|
|||
return this.all.countries.concat(this.config.countries);
|
||||
},
|
||||
cameraOptions() {
|
||||
return this.all.cameras.concat(this.config.cameras);
|
||||
return this.config.cameras;
|
||||
},
|
||||
lensOptions() {
|
||||
return this.all.lenses.concat(this.config.lenses);
|
||||
return this.config.lenses;
|
||||
},
|
||||
colorOptions() {
|
||||
return this.all.colors.concat(this.config.colors);
|
||||
|
|
|
@ -18,7 +18,12 @@ class Abstract {
|
|||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key) && key !== "__originalValues") {
|
||||
this[key] = values[key];
|
||||
this.__originalValues[key] = values[key];
|
||||
if(typeof values[key] === "object") {
|
||||
this.__originalValues[key] = JSON.parse(JSON.stringify(values[key]));
|
||||
} else {
|
||||
this.__originalValues[key] = values[key];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +53,7 @@ class Abstract {
|
|||
val = this[key];
|
||||
}
|
||||
|
||||
if(!changed || val !== this.__originalValues[key]) {
|
||||
if(!changed || JSON.stringify(val) !== JSON.stringify(this.__originalValues[key])) {
|
||||
result[key] = val;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,11 +11,6 @@ class Photo extends Abstract {
|
|||
PhotoPath: "",
|
||||
PhotoName: "",
|
||||
PhotoTitle: "",
|
||||
PhotoDescription: "",
|
||||
PhotoNotes: "",
|
||||
PhotoKeywords: "",
|
||||
PhotoArtist: "",
|
||||
PhotoCopyright: "",
|
||||
PhotoFavorite: false,
|
||||
PhotoPrivate: false,
|
||||
PhotoNSFW: false,
|
||||
|
@ -42,11 +37,21 @@ class Photo extends Abstract {
|
|||
PhotoYear: 0,
|
||||
PhotoMonth: 0,
|
||||
TakenAtLocal: "",
|
||||
ModifiedDate: false,
|
||||
ModifiedTitle: false,
|
||||
ModifiedDetails: false,
|
||||
ModifiedDescription: false,
|
||||
ModifiedDate: false,
|
||||
ModifiedLocation: false,
|
||||
ModifiedCamera: false,
|
||||
TimeZone: "",
|
||||
Description: {
|
||||
PhotoDescription: "",
|
||||
PhotoKeywords: "",
|
||||
PhotoNotes: "",
|
||||
PhotoSubject: "",
|
||||
PhotoArtist: "",
|
||||
PhotoCopyright: "",
|
||||
PhotoLicense: "",
|
||||
},
|
||||
Files: [],
|
||||
Labels: [],
|
||||
Keywords: [],
|
||||
|
@ -228,11 +233,11 @@ class Photo extends Abstract {
|
|||
values.ModifiedTitle = true
|
||||
}
|
||||
|
||||
if(values.PhotoKeywords) {
|
||||
values.ModifiedKeywords = true
|
||||
if(values.Description) {
|
||||
values.ModifiedDescription = true
|
||||
}
|
||||
|
||||
if(values.PhotoLat || values.PhotoLng || values.PhotoAltitude) {
|
||||
if(values.PhotoLat || values.PhotoLng || values.PhotoAltitude || values.PhotoCountry) {
|
||||
values.ModifiedLocation = true
|
||||
}
|
||||
|
||||
|
@ -240,8 +245,8 @@ class Photo extends Abstract {
|
|||
values.ModifiedDate = true
|
||||
}
|
||||
|
||||
if(values.CameraID || values.LensID || values.PhotoCountry) {
|
||||
values.ModifiedDetails = true
|
||||
if(values.CameraID || values.LensID) {
|
||||
values.ModifiedCamera = true
|
||||
}
|
||||
|
||||
return Api.put(this.getEntityResource(), values).then((response) => Promise.resolve(this.setValues(response.data)));
|
||||
|
|
|
@ -63,6 +63,7 @@ func (c *Config) MigrateDb() {
|
|||
&entity.Account{},
|
||||
&entity.File{},
|
||||
&entity.Photo{},
|
||||
&entity.Description{},
|
||||
&entity.Event{},
|
||||
&entity.Place{},
|
||||
&entity.Location{},
|
||||
|
|
57
internal/entity/description.go
Normal file
57
internal/entity/description.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// Description stores additional metadata fields for each photo to improve search performance.
|
||||
type Description struct {
|
||||
PhotoID uint `gorm:"primary_key;auto_increment:false"`
|
||||
PhotoDescription string `gorm:"type:text;" json:"PhotoDescription"`
|
||||
PhotoKeywords string `gorm:"type:text;" json:"PhotoKeywords"`
|
||||
PhotoNotes string `gorm:"type:text;" json:"PhotoNotes"`
|
||||
PhotoSubject string `json:"PhotoSubject"`
|
||||
PhotoArtist string `json:"PhotoArtist"`
|
||||
PhotoCopyright string `json:"PhotoCopyright"`
|
||||
PhotoLicense string `json:"PhotoLicense"`
|
||||
}
|
||||
|
||||
// FirstOrCreate returns the matching entity or creates a new one.
|
||||
func (m *Description) FirstOrCreate(db *gorm.DB) error {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
||||
return db.FirstOrCreate(m, "photo_id = ?", m.PhotoID).Error
|
||||
}
|
||||
|
||||
// NoDescription checks if the photo has no Description
|
||||
func (m *Description) NoDescription() bool {
|
||||
return m.PhotoDescription == ""
|
||||
}
|
||||
|
||||
// NoKeywords checks if the photo has no Keywords
|
||||
func (m *Description) NoKeywords() bool {
|
||||
return m.PhotoKeywords == ""
|
||||
}
|
||||
|
||||
// NoSubject checks if the photo has no Subject
|
||||
func (m *Description) NoSubject() bool {
|
||||
return m.PhotoSubject == ""
|
||||
}
|
||||
|
||||
// NoNotes checks if the photo has no Notes
|
||||
func (m *Description) NoNotes() bool {
|
||||
return m.PhotoNotes == ""
|
||||
}
|
||||
|
||||
// NoArtist checks if the photo has no Artist
|
||||
func (m *Description) NoArtist() bool {
|
||||
return m.PhotoArtist == ""
|
||||
}
|
||||
|
||||
// NoCopyright checks if the photo has no Copyright
|
||||
func (m *Description) NoCopyright() bool {
|
||||
return m.PhotoCopyright == ""
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
@ -18,13 +19,6 @@ type Photo struct {
|
|||
PhotoPath string `gorm:"type:varbinary(512);index;"`
|
||||
PhotoName string `gorm:"type:varbinary(256);"`
|
||||
PhotoTitle string `json:"PhotoTitle"`
|
||||
PhotoSubject string `json:"PhotoSubject"`
|
||||
PhotoKeywords string `json:"PhotoKeywords"`
|
||||
PhotoDescription string `gorm:"type:text;" json:"PhotoDescription"`
|
||||
PhotoNotes string `gorm:"type:text;" json:"PhotoNotes"`
|
||||
PhotoArtist string `json:"PhotoArtist"`
|
||||
PhotoCopyright string `json:"PhotoCopyright"`
|
||||
PhotoLicense string `json:"PhotoLicense"`
|
||||
PhotoFavorite bool `json:"PhotoFavorite"`
|
||||
PhotoPrivate bool `json:"PhotoPrivate"`
|
||||
PhotoNSFW bool `json:"PhotoNSFW"`
|
||||
|
@ -39,30 +33,31 @@ type Photo struct {
|
|||
CameraID uint `gorm:"index:idx_photos_camera_lens;" json:"CameraID"`
|
||||
CameraSerial string `gorm:"type:varbinary(128);" json:"CameraSerial"`
|
||||
LensID uint `gorm:"index:idx_photos_camera_lens;" json:"LensID"`
|
||||
AccountID uint `json:"AccountID"`
|
||||
PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID"`
|
||||
LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID"`
|
||||
LocationEstimated bool `json:"LocationEstimated"`
|
||||
PhotoCountry string `gorm:"index:idx_photos_country_year_month;" json:"PhotoCountry"`
|
||||
PhotoYear int `gorm:"index:idx_photos_country_year_month;"`
|
||||
PhotoMonth int `gorm:"index:idx_photos_country_year_month;"`
|
||||
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone"`
|
||||
TakenAtLocal time.Time `gorm:"type:datetime;"`
|
||||
ModifiedTitle bool `json:"ModifiedTitle"`
|
||||
ModifiedDetails bool `json:"ModifiedDetails"`
|
||||
ModifiedLocation bool `json:"ModifiedLocation"`
|
||||
ModifiedKeywords bool `json:"ModifiedKeywords"`
|
||||
ModifiedDate bool `json:"ModifiedDate"`
|
||||
Camera *Camera `json:"Camera"`
|
||||
Lens *Lens `json:"Lens"`
|
||||
Location *Location `json:"-"`
|
||||
Place *Place `json:"-"`
|
||||
Account *Account `json:"-"`
|
||||
Files []File
|
||||
Labels []PhotoLabel
|
||||
Keywords []Keyword `json:"-"`
|
||||
Albums []Album `json:"-"`
|
||||
CreatedAt time.Time
|
||||
AccountID uint `json:"AccountID"`
|
||||
PlaceID string `gorm:"type:varbinary(16);index;" json:"PlaceID"`
|
||||
LocationID string `gorm:"type:varbinary(16);index;" json:"LocationID"`
|
||||
LocationEstimated bool `json:"LocationEstimated"`
|
||||
PhotoCountry string `gorm:"index:idx_photos_country_year_month;" json:"PhotoCountry"`
|
||||
PhotoYear int `gorm:"index:idx_photos_country_year_month;"`
|
||||
PhotoMonth int `gorm:"index:idx_photos_country_year_month;"`
|
||||
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone"`
|
||||
TakenAtLocal time.Time `gorm:"type:datetime;"`
|
||||
ModifiedTitle bool `json:"ModifiedTitle"`
|
||||
ModifiedDescription bool `json:"ModifiedDescription"`
|
||||
ModifiedDate bool `json:"ModifiedDate"`
|
||||
ModifiedLocation bool `json:"ModifiedLocation"`
|
||||
ModifiedCamera bool `json:"ModifiedCamera"`
|
||||
Description Description `json:"Description"`
|
||||
Camera *Camera `json:"Camera"`
|
||||
Lens *Lens `json:"Lens"`
|
||||
Location *Location `json:"-"`
|
||||
Place *Place `json:"-"`
|
||||
Account *Account `json:"-"`
|
||||
Files []File
|
||||
Labels []PhotoLabel
|
||||
Keywords []Keyword `json:"-"`
|
||||
Albums []Album `json:"-"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
@ -73,6 +68,16 @@ func SavePhoto(model Photo, form form.Photo, db *gorm.DB) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if form.Description.PhotoID == model.ID {
|
||||
if err := deepcopier.Copy(&model.Description).From(form.Description); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model.Description.PhotoKeywords = strings.Join(txt.UniqueKeywords(model.Description.PhotoKeywords), ", ")
|
||||
}
|
||||
|
||||
log.Debugf("model: %+v", model)
|
||||
|
||||
model.IndexKeywords(db)
|
||||
|
||||
return db.Save(&model).Error
|
||||
|
@ -123,10 +128,10 @@ func (m *Photo) IndexKeywords(db *gorm.DB) {
|
|||
|
||||
// Add title, description and other keywords
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoTitle)...)
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoKeywords)...)
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoSubject)...)
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoArtist)...)
|
||||
keywords = append(keywords, txt.Keywords(m.PhotoDescription)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Description.PhotoDescription)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Description.PhotoKeywords)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Description.PhotoSubject)...)
|
||||
keywords = append(keywords, txt.Keywords(m.Description.PhotoArtist)...)
|
||||
|
||||
keywords = txt.UniqueWords(keywords)
|
||||
|
||||
|
@ -223,36 +228,6 @@ func (m *Photo) NoTitle() bool {
|
|||
return m.PhotoTitle == ""
|
||||
}
|
||||
|
||||
// NoDescription checks if the photo has no Description
|
||||
func (m *Photo) NoDescription() bool {
|
||||
return m.PhotoDescription == ""
|
||||
}
|
||||
|
||||
// NoNotes checks if the photo has no Notes
|
||||
func (m *Photo) NoNotes() bool {
|
||||
return m.PhotoNotes == ""
|
||||
}
|
||||
|
||||
// NoArtist checks if the photo has no Artist
|
||||
func (m *Photo) NoArtist() bool {
|
||||
return m.PhotoArtist == ""
|
||||
}
|
||||
|
||||
// NoCopyright checks if the photo has no Copyright
|
||||
func (m *Photo) NoCopyright() bool {
|
||||
return m.PhotoCopyright == ""
|
||||
}
|
||||
|
||||
// NoSubject checks if the photo has no Subject
|
||||
func (m *Photo) NoSubject() bool {
|
||||
return m.PhotoSubject == ""
|
||||
}
|
||||
|
||||
// NoKeywords checks if the photo has no Keywords
|
||||
func (m *Photo) NoKeywords() bool {
|
||||
return m.PhotoKeywords == ""
|
||||
}
|
||||
|
||||
// NoCameraSerial checks if the photo has no CameraSerial
|
||||
func (m *Photo) NoCameraSerial() bool {
|
||||
return m.CameraSerial == ""
|
||||
|
|
|
@ -8,13 +8,18 @@ import (
|
|||
|
||||
// Photo represents a photo edit form.
|
||||
type Photo struct {
|
||||
TakenAt time.Time `json:"TakenAt"`
|
||||
PhotoTitle string `json:"PhotoTitle"`
|
||||
PhotoDescription string `json:"PhotoDescription"`
|
||||
PhotoNotes string `json:"PhotoNotes"`
|
||||
PhotoArtist string `json:"PhotoArtist"`
|
||||
PhotoKeywords string `json:"PhotoKeywords"`
|
||||
PhotoCopyright string `json:"PhotoCopyright"`
|
||||
TakenAt time.Time `json:"TakenAt"`
|
||||
PhotoTitle string `json:"PhotoTitle"`
|
||||
Description struct {
|
||||
PhotoID uint `json:"PhotoID" deepcopier:"skip"`
|
||||
PhotoDescription string `json:"PhotoDescription"`
|
||||
PhotoKeywords string `json:"PhotoKeywords"`
|
||||
PhotoNotes string `json:"PhotoNotes"`
|
||||
PhotoSubject string `json:"PhotoSubject"`
|
||||
PhotoArtist string `json:"PhotoArtist"`
|
||||
PhotoCopyright string `json:"PhotoCopyright"`
|
||||
PhotoLicense string `json:"PhotoLicense"`
|
||||
} `json:"Description"`
|
||||
PhotoFavorite bool `json:"PhotoFavorite"`
|
||||
PhotoPrivate bool `json:"PhotoPrivate"`
|
||||
PhotoNSFW bool `json:"PhotoNSFW"`
|
||||
|
@ -24,20 +29,20 @@ type Photo struct {
|
|||
PhotoAltitude int `json:"PhotoAltitude"`
|
||||
PhotoFocalLength int `json:"PhotoFocalLength"`
|
||||
PhotoIso int `json:"PhotoIso"`
|
||||
PhotoFNumber float64 `json:"PhotoFNumber"`
|
||||
PhotoExposure string `json:"PhotoExposure"`
|
||||
CameraID uint `json:"CameraID"`
|
||||
LensID uint `json:"LensID"`
|
||||
LocationID string `json:"LocationID"`
|
||||
PlaceID string `json:"PlaceID"`
|
||||
PhotoCountry string `json:"PhotoCountry"`
|
||||
TimeZone string `json:"TimeZone"`
|
||||
TakenAtLocal time.Time `json:"TakenAtLocal"`
|
||||
ModifiedTitle bool `json:"ModifiedTitle"`
|
||||
ModifiedKeywords bool `json:"ModifiedKeywords"`
|
||||
ModifiedDetails bool `json:"ModifiedDetails"`
|
||||
ModifiedLocation bool `json:"ModifiedLocation"`
|
||||
ModifiedDate bool `json:"ModifiedDate"`
|
||||
PhotoFNumber float64 `json:"PhotoFNumber"`
|
||||
PhotoExposure string `json:"PhotoExposure"`
|
||||
CameraID uint `json:"CameraID"`
|
||||
LensID uint `json:"LensID"`
|
||||
LocationID string `json:"LocationID"`
|
||||
PlaceID string `json:"PlaceID"`
|
||||
PhotoCountry string `json:"PhotoCountry"`
|
||||
TimeZone string `json:"TimeZone"`
|
||||
TakenAtLocal time.Time `json:"TakenAtLocal"`
|
||||
ModifiedTitle bool `json:"ModifiedTitle"`
|
||||
ModifiedDescription bool `json:"ModifiedDescription"`
|
||||
ModifiedDate bool `json:"ModifiedDate"`
|
||||
ModifiedLocation bool `json:"ModifiedLocation"`
|
||||
ModifiedCamera bool `json:"ModifiedCamera"`
|
||||
}
|
||||
|
||||
func NewPhoto(m interface{}) (f Photo, err error) {
|
||||
|
|
|
@ -9,9 +9,6 @@ type PhotoSearch struct {
|
|||
Query string `form:"q"`
|
||||
ID string `form:"id"`
|
||||
Title string `form:"title"`
|
||||
Description string `form:"description"`
|
||||
Notes string `form:"notes"`
|
||||
Artist string `form:"artist"`
|
||||
Hash string `form:"hash"`
|
||||
Duplicate bool `form:"duplicate"`
|
||||
Archived bool `form:"archived"`
|
||||
|
|
|
@ -37,7 +37,7 @@ func TestParseQueryString(t *testing.T) {
|
|||
assert.Equal(t, 33.45343166666667, form.Lat)
|
||||
})
|
||||
t.Run("valid query 2", func(t *testing.T) {
|
||||
form := &PhotoSearch{Query: "chroma:600 description:\"test\" after:2018-01-15 duplicate:false favorites:true lng:33.45343166666667"}
|
||||
form := &PhotoSearch{Query: "chroma:600 title:\"test\" after:2018-01-15 duplicate:false favorites:true lng:33.45343166666667"}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
|
@ -48,13 +48,13 @@ func TestParseQueryString(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, uint(0x258), form.Chroma)
|
||||
assert.Equal(t, "test", form.Description)
|
||||
assert.Equal(t, "test", form.Title)
|
||||
assert.Equal(t, time.Date(2018, 01, 15, 0, 0, 0, 0, time.UTC), form.After)
|
||||
assert.Equal(t, false, form.Duplicate)
|
||||
assert.Equal(t, 33.45343166666667, form.Lng)
|
||||
})
|
||||
t.Run("valid query with umlauts", func(t *testing.T) {
|
||||
form := &PhotoSearch{Query: "description:\"tübingen\""}
|
||||
form := &PhotoSearch{Query: "title:\"tübingen\""}
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
|
@ -64,7 +64,7 @@ func TestParseQueryString(t *testing.T) {
|
|||
t.Fatal("err should be nil")
|
||||
}
|
||||
|
||||
assert.Equal(t, "tübingen", form.Description)
|
||||
assert.Equal(t, "tübingen", form.Title)
|
||||
})
|
||||
t.Run("query for invalid filter", func(t *testing.T) {
|
||||
form := &PhotoSearch{Query: "xxx:false"}
|
||||
|
|
|
@ -33,6 +33,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
|
|||
start := time.Now()
|
||||
|
||||
var photo entity.Photo
|
||||
var description entity.Description
|
||||
var file, primaryFile entity.File
|
||||
var metaData meta.Data
|
||||
var photoQuery, fileQuery *gorm.DB
|
||||
|
@ -87,6 +88,10 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
|
|||
return indexResultSkipped
|
||||
}
|
||||
|
||||
if photoExists {
|
||||
ind.db.Model(&photo).Related(&description)
|
||||
}
|
||||
|
||||
if fileHash == "" {
|
||||
fileHash = m.Hash()
|
||||
}
|
||||
|
@ -132,28 +137,28 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
|
|||
photo.PhotoTitle = metaData.Title
|
||||
}
|
||||
|
||||
if photo.NoDescription() {
|
||||
photo.PhotoDescription = metaData.Description
|
||||
if photo.Description.NoDescription() {
|
||||
photo.Description.PhotoDescription = metaData.Description
|
||||
}
|
||||
|
||||
if photo.NoNotes() {
|
||||
photo.PhotoNotes = metaData.Comment
|
||||
if photo.Description.NoNotes() {
|
||||
photo.Description.PhotoNotes = metaData.Comment
|
||||
}
|
||||
|
||||
if photo.NoSubject() {
|
||||
photo.PhotoSubject = metaData.Subject
|
||||
if photo.Description.NoSubject() {
|
||||
photo.Description.PhotoSubject = metaData.Subject
|
||||
}
|
||||
|
||||
if photo.NoKeywords() {
|
||||
photo.PhotoKeywords = metaData.Keywords
|
||||
if photo.Description.NoKeywords() {
|
||||
photo.Description.PhotoKeywords = metaData.Keywords
|
||||
}
|
||||
|
||||
if photo.NoArtist() && metaData.Artist != "" {
|
||||
photo.PhotoArtist = metaData.Artist
|
||||
if photo.Description.NoArtist() && metaData.Artist != "" {
|
||||
photo.Description.PhotoArtist = metaData.Artist
|
||||
}
|
||||
|
||||
if photo.NoArtist() && metaData.CameraOwner != "" {
|
||||
photo.PhotoArtist = metaData.CameraOwner
|
||||
if photo.Description.NoArtist() && metaData.CameraOwner != "" {
|
||||
photo.Description.PhotoArtist = metaData.CameraOwner
|
||||
}
|
||||
|
||||
if photo.NoCameraSerial() {
|
||||
|
@ -168,7 +173,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
|
|||
}
|
||||
}
|
||||
|
||||
if !photo.ModifiedDetails && (fileChanged || o.UpdateCamera) {
|
||||
if !photo.ModifiedCamera && (fileChanged || o.UpdateCamera) {
|
||||
// Set UpdateCamera, Lens, Focal Length and F Number
|
||||
photo.Camera = entity.NewCamera(m.CameraModel(), m.CameraMake()).FirstOrCreate(ind.db)
|
||||
photo.Lens = entity.NewLens(m.LensModel(), m.LensMake()).FirstOrCreate(ind.db)
|
||||
|
@ -219,16 +224,20 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
|
|||
photo.PhotoTitle = data.Title
|
||||
}
|
||||
|
||||
if photo.NoCopyright() && data.Copyright != "" {
|
||||
photo.PhotoCopyright = data.Copyright
|
||||
if photo.Description.NoCopyright() && data.Copyright != "" {
|
||||
photo.Description.PhotoCopyright = data.Copyright
|
||||
}
|
||||
|
||||
if photo.NoArtist() && data.Artist != "" {
|
||||
photo.PhotoArtist = data.Artist
|
||||
if photo.Description.NoArtist() && data.Artist != "" {
|
||||
photo.Description.PhotoArtist = data.Artist
|
||||
}
|
||||
|
||||
if photo.NoDescription() && data.Description != "" {
|
||||
photo.PhotoDescription = data.Description
|
||||
if photo.Description.NoDescription() && data.Description != "" {
|
||||
photo.Description.PhotoDescription = data.Description
|
||||
}
|
||||
|
||||
if photo.Description.NoNotes() && data.Comment != "" {
|
||||
photo.Description.PhotoNotes = data.Comment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -271,24 +280,22 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) I
|
|||
}
|
||||
|
||||
if file.FilePrimary && (fileChanged || o.UpdateKeywords) {
|
||||
w := txt.Keywords(photo.PhotoKeywords)
|
||||
w := txt.Keywords(photo.Description.PhotoKeywords)
|
||||
|
||||
if !photo.ModifiedKeywords {
|
||||
if NonCanonical(fileBase) {
|
||||
w = append(w, txt.Keywords(filePath)...)
|
||||
w = append(w, txt.Keywords(fileBase)...)
|
||||
}
|
||||
|
||||
w = append(w, locKeywords...)
|
||||
w = append(w, txt.Keywords(file.OriginalName)...)
|
||||
w = append(w, file.FileMainColor)
|
||||
w = append(w, labels.Keywords()...)
|
||||
if NonCanonical(fileBase) {
|
||||
w = append(w, txt.Keywords(filePath)...)
|
||||
w = append(w, txt.Keywords(fileBase)...)
|
||||
}
|
||||
|
||||
photo.PhotoKeywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
w = append(w, locKeywords...)
|
||||
w = append(w, txt.Keywords(file.OriginalName)...)
|
||||
w = append(w, file.FileMainColor)
|
||||
w = append(w, labels.Keywords()...)
|
||||
|
||||
if photo.PhotoKeywords != "" {
|
||||
log.Debugf("index: updated photo keywords (%s)", photo.PhotoKeywords)
|
||||
photo.Description.PhotoKeywords = strings.Join(txt.UniqueWords(w), ", ")
|
||||
|
||||
if photo.Description.PhotoKeywords != "" {
|
||||
log.Debugf("index: updated photo keywords (%s)", photo.Description.PhotoKeywords)
|
||||
} else {
|
||||
log.Debug("index: no photo keywords")
|
||||
}
|
||||
|
|
|
@ -26,14 +26,9 @@ type PhotoResult struct {
|
|||
PhotoPath string
|
||||
PhotoName string
|
||||
PhotoTitle string
|
||||
PhotoDescription string
|
||||
PhotoYear int
|
||||
PhotoMonth int
|
||||
PhotoCountry string
|
||||
PhotoArtist string
|
||||
PhotoKeywords string
|
||||
PhotoColors string
|
||||
PhotoColor string
|
||||
PhotoFavorite bool
|
||||
PhotoPrivate bool
|
||||
PhotoSensitive bool
|
||||
|
@ -259,14 +254,6 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
|
|||
q = q.Where("LOWER(photos.photo_title) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Title)))
|
||||
}
|
||||
|
||||
if f.Description != "" {
|
||||
q = q.Where("LOWER(photos.photo_description) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Description)))
|
||||
}
|
||||
|
||||
if f.Notes != "" {
|
||||
q = q.Where("LOWER(photos.photo_notes) LIKE ?", fmt.Sprintf("%%%s%%", strings.ToLower(f.Notes)))
|
||||
}
|
||||
|
||||
if f.Hash != "" {
|
||||
q = q.Where("files.file_hash = ?", f.Hash)
|
||||
}
|
||||
|
@ -350,7 +337,7 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
|
|||
|
||||
// FindPhotoByID returns a Photo based on the ID.
|
||||
func (s *Repo) FindPhotoByID(photoID uint64) (photo entity.Photo, err error) {
|
||||
if err := s.db.Where("id = ?", photoID).First(&photo).Error; err != nil {
|
||||
if err := s.db.Where("id = ?", photoID).Preload("Description").First(&photo).Error; err != nil {
|
||||
return photo, err
|
||||
}
|
||||
|
||||
|
@ -359,7 +346,7 @@ func (s *Repo) FindPhotoByID(photoID uint64) (photo entity.Photo, err error) {
|
|||
|
||||
// FindPhotoByUUID returns a Photo based on the UUID.
|
||||
func (s *Repo) FindPhotoByUUID(photoUUID string) (photo entity.Photo, err error) {
|
||||
if err := s.db.Where("photo_uuid = ?", photoUUID).First(&photo).Error; err != nil {
|
||||
if err := s.db.Where("photo_uuid = ?", photoUUID).Preload("Description").First(&photo).Error; err != nil {
|
||||
return photo, err
|
||||
}
|
||||
|
||||
|
@ -375,6 +362,7 @@ func (s *Repo) PreloadPhotoByUUID(photoUUID string) (photo entity.Photo, err err
|
|||
Preload("Labels.Label").
|
||||
Preload("Camera").
|
||||
Preload("Lens").
|
||||
Preload("Description").
|
||||
First(&photo).Error; err != nil {
|
||||
return photo, err
|
||||
}
|
||||
|
|
|
@ -216,34 +216,6 @@ func TestSearch_Photos_Query(t *testing.T) {
|
|||
|
||||
t.Logf("results: %+v", photos)
|
||||
})
|
||||
t.Run("form.description", func(t *testing.T) {
|
||||
var f form.PhotoSearch
|
||||
f.Query = "description:xxx"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("results: %+v", photos)
|
||||
})
|
||||
t.Run("form.notes", func(t *testing.T) {
|
||||
var f form.PhotoSearch
|
||||
f.Query = "notes:xxx"
|
||||
f.Count = 3
|
||||
f.Offset = 0
|
||||
|
||||
photos, err := search.Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("results: %+v", photos)
|
||||
})
|
||||
t.Run("form.hash", func(t *testing.T) {
|
||||
var f form.PhotoSearch
|
||||
f.Query = "hash:xxx"
|
||||
|
|
Loading…
Reference in a new issue