photoprism/internal/entity/entity.go
2021-10-06 12:16:52 +02:00

194 lines
4.7 KiB
Go

/*
Package entity contains models for data storage based on GORM.
See http://gorm.io/docs/ for more information about GORM.
Additional information concerning data storage can be found in our Developer Guide:
https://github.com/photoprism/photoprism/wiki/Storage
*/
package entity
import (
"fmt"
"time"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/event"
)
var log = event.Log
var GeoApi = "places"
// logError logs the message if the argument is an error.
func logError(result *gorm.DB) {
if result.Error != nil {
log.Error(result.Error.Error())
}
}
// TypeString returns an entity type string for logging.
func TypeString(entityType string) string {
if entityType == "" {
return "unknown"
}
return entityType
}
type Types map[string]interface{}
// Entities contains database entities and their table names.
var Entities = Types{
"errors": &Error{},
"addresses": &Address{},
"users": &User{},
"accounts": &Account{},
"folders": &Folder{},
"duplicates": &Duplicate{},
"files": &File{},
"files_share": &FileShare{},
"files_sync": &FileSync{},
"photos": &Photo{},
"details": &Details{},
"places": &Place{},
"cells": &Cell{},
"cameras": &Camera{},
"lenses": &Lens{},
"countries": &Country{},
"albums": &Album{},
"photos_albums": &PhotoAlbum{},
"labels": &Label{},
"categories": &Category{},
"photos_labels": &PhotoLabel{},
"keywords": &Keyword{},
"photos_keywords": &PhotoKeyword{},
"passwords": &Password{},
"links": &Link{},
Subject{}.TableName(): &Subject{},
Face{}.TableName(): &Face{},
Marker{}.TableName(): &Marker{},
}
type RowCount struct {
Count int
}
// WaitForMigration waits for the database migration to be successful.
func (list Types) WaitForMigration() {
attempts := 100
for name := range list {
for i := 0; i <= attempts; i++ {
count := RowCount{}
if err := Db().Raw(fmt.Sprintf("SELECT COUNT(*) AS count FROM %s", name)).Scan(&count).Error; err == nil {
log.Tracef("entity: %s migrated", txt.Quote(name))
break
} else {
log.Debugf("entity: waiting for %s migration (%s)", txt.Quote(name), err.Error())
}
if i == attempts {
panic("migration failed")
}
time.Sleep(50 * time.Millisecond)
}
}
}
// Truncate removes all data from tables without dropping them.
func (list Types) Truncate() {
for name := range list {
if err := Db().Exec(fmt.Sprintf("DELETE FROM %s WHERE 1", name)).Error; err == nil {
// log.Debugf("entity: removed all data from %s", name)
break
} else if err.Error() != "record not found" {
log.Debugf("entity: %s in %s", err, txt.Quote(name))
}
}
}
// Migrate migrates all database tables of registered entities.
func (list Types) Migrate() {
for name, entity := range list {
if err := UnscopedDb().AutoMigrate(entity).Error; err != nil {
log.Debugf("entity: %s (waiting 1s)", err.Error())
time.Sleep(time.Second)
if err := UnscopedDb().AutoMigrate(entity).Error; err != nil {
log.Errorf("entity: failed migrating %s", txt.Quote(name))
panic(err)
}
}
}
}
// Drop drops all database tables of registered entities.
func (list Types) Drop() {
for _, entity := range list {
if err := UnscopedDb().DropTableIfExists(entity).Error; err != nil {
panic(err)
}
}
}
// CreateDefaultFixtures inserts default fixtures for test and production.
func CreateDefaultFixtures() {
CreateUnknownAddress()
CreateDefaultUsers()
CreateUnknownPlace()
CreateUnknownLocation()
CreateUnknownCountry()
CreateUnknownCamera()
CreateUnknownLens()
}
// MigrateDb creates database tables and inserts default fixtures as needed.
func MigrateDb(dropDeprecated bool) {
if dropDeprecated {
DeprecatedTables.Drop()
}
Entities.Migrate()
Entities.WaitForMigration()
CreateDefaultFixtures()
}
// ResetTestFixtures re-creates registered database tables and inserts test fixtures.
func ResetTestFixtures() {
Entities.Migrate()
Entities.WaitForMigration()
Entities.Truncate()
CreateDefaultFixtures()
CreateTestFixtures()
}
// InitTestDb connects to and completely initializes the test database incl fixtures.
func InitTestDb(driver, dsn string) *Gorm {
if HasDbProvider() {
return nil
}
if driver == "test" || driver == "sqlite" || driver == "" || dsn == "" {
driver = "sqlite3"
dsn = ".test.db"
}
log.Infof("initializing %s test db in %s", driver, dsn)
db := &Gorm{
Driver: driver,
Dsn: dsn,
}
SetDbProvider(db)
ResetTestFixtures()
return db
}