diff --git a/internal/entity/camera.go b/internal/entity/camera.go index 1111744e5..e768f5edf 100644 --- a/internal/entity/camera.go +++ b/internal/entity/camera.go @@ -2,6 +2,7 @@ package entity import ( "strings" + "sync" "time" "github.com/gosimple/slug" @@ -9,6 +10,8 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) +var cameraMutex = sync.Mutex{} + // Camera model and make (as extracted from UpdateExif metadata) type Camera struct { ID uint `gorm:"primary_key" json:"ID" yaml:"ID"` @@ -169,6 +172,9 @@ func NewCamera(modelName string, makeName string) *Camera { // Create inserts a new row to the database. func (m *Camera) Create() error { + cameraMutex.Lock() + defer cameraMutex.Unlock() + return Db().Create(m).Error } @@ -176,9 +182,9 @@ func (m *Camera) Create() error { func FirstOrCreateCamera(m *Camera) *Camera { result := Camera{} - if err := Db().Where("camera_model = ? AND camera_make = ?", m.CameraModel, m.CameraMake).First(&result).Error; err == nil { + if res := Db().Where("camera_model = ? AND camera_make = ?", m.CameraModel, m.CameraMake).First(&result); res.Error == nil { return &result - } else if createErr := m.Create(); createErr == nil { + } else if err := m.Create(); err == nil { if !m.Unknown() { event.EntitiesCreated("cameras", []*Camera{m}) @@ -188,10 +194,10 @@ func FirstOrCreateCamera(m *Camera) *Camera { } return m - } else if err := Db().Where("camera_model = ? AND camera_make = ?", m.CameraModel, m.CameraMake).First(&result).Error; err == nil { + } else if res := Db().Where("camera_model = ? AND camera_make = ?", m.CameraModel, m.CameraMake).First(&result); res.Error == nil { return &result } else { - log.Errorf("camera: %s (first or create %s)", createErr, m.String()) + log.Errorf("camera: %s (create %s)", err.Error(), txt.Quote(m.String())) } return nil diff --git a/internal/entity/cell.go b/internal/entity/cell.go index b541e3636..0f77a6af4 100644 --- a/internal/entity/cell.go +++ b/internal/entity/cell.go @@ -2,6 +2,7 @@ package entity import ( "strings" + "sync" "time" "github.com/photoprism/photoprism/internal/event" @@ -10,6 +11,8 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) +var cellMutex = sync.Mutex{} + // Cell represents a S2 cell with location data. type Cell struct { ID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"ID" yaml:"ID"` @@ -95,6 +98,9 @@ func (m *Cell) Find(api string) error { m.CellName = l.Name() m.CellCategory = l.Category() + cellMutex.Lock() + defer cellMutex.Unlock() + if createErr := db.Create(m).Error; createErr == nil { log.Debugf("location: added cell %s [%s]", m.ID, time.Since(start)) return nil diff --git a/internal/entity/keyword.go b/internal/entity/keyword.go index eb97d1496..7dd83acd7 100644 --- a/internal/entity/keyword.go +++ b/internal/entity/keyword.go @@ -2,10 +2,13 @@ package entity import ( "strings" + "sync" "github.com/photoprism/photoprism/pkg/txt" ) +var keywordMutex = sync.Mutex{} + // Keyword used for full text search type Keyword struct { ID uint `gorm:"primary_key"` @@ -41,6 +44,9 @@ func (m *Keyword) Save() error { // Create inserts a new row to the database. func (m *Keyword) Create() error { + keywordMutex.Lock() + defer keywordMutex.Unlock() + return Db().Create(m).Error } diff --git a/internal/entity/label.go b/internal/entity/label.go index 2e361dcbd..97732cc22 100644 --- a/internal/entity/label.go +++ b/internal/entity/label.go @@ -1,6 +1,7 @@ package entity import ( + "sync" "time" "github.com/gosimple/slug" @@ -11,6 +12,8 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) +var labelMutex = sync.Mutex{} + type Labels []Label // Label is used for photo, album and location categorization @@ -65,11 +68,17 @@ func NewLabel(name string, priority int) *Label { // Save updates the existing or inserts a new label. func (m *Label) Save() error { + labelMutex.Lock() + defer labelMutex.Unlock() + return Db().Save(m).Error } // Create inserts the label to the database. func (m *Label) Create() error { + labelMutex.Lock() + defer labelMutex.Unlock() + return Db().Create(m).Error } diff --git a/internal/entity/lens.go b/internal/entity/lens.go index 974978acd..f141e1cd0 100644 --- a/internal/entity/lens.go +++ b/internal/entity/lens.go @@ -2,6 +2,7 @@ package entity import ( "strings" + "sync" "time" "github.com/gosimple/slug" @@ -9,6 +10,8 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) +var lensMutex = sync.Mutex{} + // Lens represents camera lens (as extracted from UpdateExif metadata) type Lens struct { ID uint `gorm:"primary_key" json:"ID" yaml:"ID"` @@ -81,6 +84,9 @@ func NewLens(modelName string, makeName string) *Lens { // Create inserts a new row to the database. func (m *Lens) Create() error { + lensMutex.Lock() + defer lensMutex.Unlock() + return Db().Create(m).Error } @@ -88,9 +94,9 @@ func (m *Lens) Create() error { func FirstOrCreateLens(m *Lens) *Lens { result := Lens{} - if err := Db().Where("lens_slug = ?", m.LensSlug).First(&result).Error; err == nil { + if res := Db().Where("lens_slug = ?", m.LensSlug).First(&result); res.Error == nil { return &result - } else if createErr := m.Create(); createErr == nil { + } else if err := m.Create(); err == nil { if !m.Unknown() { event.EntitiesCreated("lenses", []*Lens{m}) @@ -100,10 +106,10 @@ func FirstOrCreateLens(m *Lens) *Lens { } return m - } else if err := Db().Where("lens_slug = ?", m.LensSlug).First(&result).Error; err == nil { + } else if res := Db().Where("lens_slug = ?", m.LensSlug).First(&result); res.Error == nil { return &result } else { - log.Errorf("lens: %s (first or create %s)", createErr, m.String()) + log.Errorf("lens: %s (create %s)", err.Error(), txt.Quote(m.String())) } return nil diff --git a/internal/entity/photo_yaml.go b/internal/entity/photo_yaml.go index 97f364f4b..eba7dbded 100644 --- a/internal/entity/photo_yaml.go +++ b/internal/entity/photo_yaml.go @@ -4,11 +4,14 @@ import ( "io/ioutil" "os" "path/filepath" + "sync" "github.com/photoprism/photoprism/pkg/fs" "gopkg.in/yaml.v2" ) +var photoYamlMutex = sync.Mutex{} + // Yaml returns photo data as YAML string. func (m *Photo) Yaml() ([]byte, error) { out, err := yaml.Marshal(m) @@ -33,6 +36,9 @@ func (m *Photo) SaveAsYaml(fileName string) error { return err } + photoYamlMutex.Lock() + defer photoYamlMutex.Unlock() + // Write YAML data to file. if err := ioutil.WriteFile(fileName, data, os.ModePerm); err != nil { return err diff --git a/internal/entity/place.go b/internal/entity/place.go index 2ab9f16a5..93f97c1ba 100644 --- a/internal/entity/place.go +++ b/internal/entity/place.go @@ -2,11 +2,14 @@ package entity import ( "strings" + "sync" "time" "github.com/photoprism/photoprism/internal/maps" ) +var placeMutex = sync.Mutex{} + // Place used to associate photos to places type Place struct { ID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"PlaceID" yaml:"PlaceID"` @@ -69,6 +72,9 @@ func (m *Place) Find() error { // Create inserts a new row to the database. func (m *Place) Create() error { + placeMutex.Lock() + defer placeMutex.Unlock() + return Db().Create(m).Error }