mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
845 lines
23 KiB
Go
845 lines
23 KiB
Go
//go:build ignore
|
|
// +build ignore
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"math"
|
|
"math/rand/v2"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/photoprism/photoprism/internal/ai/classify"
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
"github.com/photoprism/photoprism/internal/entity/migrate"
|
|
"github.com/photoprism/photoprism/internal/event"
|
|
"github.com/photoprism/photoprism/pkg/fs"
|
|
"github.com/photoprism/photoprism/pkg/media"
|
|
"github.com/photoprism/photoprism/pkg/rnd"
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
|
)
|
|
|
|
var drivers = map[string]func(string) gorm.Dialector{
|
|
MySQL: mysql.Open,
|
|
SQLite3: sqlite.Open,
|
|
}
|
|
|
|
var log = event.Log
|
|
|
|
// Log logs the error if any and keeps quiet otherwise.
|
|
func Log(model, action string, err error) {
|
|
if err != nil {
|
|
log.Errorf("%s: %s (%s)", model, err, action)
|
|
}
|
|
}
|
|
|
|
// UTC returns the current Coordinated Universal Time (UTC).
|
|
func UTC() time.Time {
|
|
return time.Now().UTC()
|
|
}
|
|
|
|
// Now returns the current time in UTC, truncated to seconds.
|
|
func Now() time.Time {
|
|
return UTC().Truncate(time.Second)
|
|
}
|
|
|
|
// Db returns the default *gorm.DB connection.
|
|
func Db() *gorm.DB {
|
|
if dbConn == nil {
|
|
return nil
|
|
}
|
|
|
|
return dbConn.Db()
|
|
}
|
|
|
|
// UnscopedDb returns an unscoped *gorm.DB connection
|
|
// that returns all records including deleted records.
|
|
func UnscopedDb() *gorm.DB {
|
|
return Db().Unscoped()
|
|
}
|
|
|
|
// Supported test databases.
|
|
const (
|
|
MySQL = "mysql"
|
|
SQLite3 = "sqlite"
|
|
SQLiteTestDB = ".test.db"
|
|
SQLiteMemoryDSN = ":memory:?cache=shared"
|
|
)
|
|
|
|
// dbConn is the global gorm.DB connection provider.
|
|
var dbConn Gorm
|
|
|
|
// Gorm is a gorm.DB connection provider interface.
|
|
type Gorm interface {
|
|
Db() *gorm.DB
|
|
}
|
|
|
|
// DbConn is a gorm.DB connection provider.
|
|
type DbConn struct {
|
|
Driver string
|
|
Dsn string
|
|
|
|
once sync.Once
|
|
db *gorm.DB
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// Db returns the gorm db connection.
|
|
func (g *DbConn) Db() *gorm.DB {
|
|
g.once.Do(g.Open)
|
|
|
|
if g.db == nil {
|
|
log.Fatal("migrate: database not connected")
|
|
}
|
|
|
|
return g.db
|
|
}
|
|
|
|
// Open creates a new gorm db connection.
|
|
func (g *DbConn) Open() {
|
|
log.Infof("Opening DB connection with driver %s", g.Driver)
|
|
var db *gorm.DB
|
|
var err error
|
|
if g.Driver == entity.Postgres {
|
|
postgresDB, pgxPool := entity.OpenPostgreSQL(g.Dsn)
|
|
g.pool = pgxPool
|
|
db, err = gorm.Open(postgres.New(postgres.Config{Conn: postgresDB}), gormConfig())
|
|
} else {
|
|
db, err = gorm.Open(drivers[g.Driver](g.Dsn), gormConfig())
|
|
}
|
|
|
|
if err != nil || db == nil {
|
|
for i := 1; i <= 12; i++ {
|
|
fmt.Printf("gorm.Open(%s, %s) %d\n", g.Driver, g.Dsn, i)
|
|
if g.Driver == entity.Postgres {
|
|
postgresDB, pgxPool := entity.OpenPostgreSQL(g.Dsn)
|
|
g.pool = pgxPool
|
|
db, err = gorm.Open(postgres.New(postgres.Config{Conn: postgresDB}), gormConfig())
|
|
} else {
|
|
db, err = gorm.Open(drivers[g.Driver](g.Dsn), gormConfig())
|
|
}
|
|
|
|
if db != nil && err == nil {
|
|
break
|
|
} else {
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
}
|
|
|
|
if err != nil || db == nil {
|
|
fmt.Println(err)
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
log.Info("DB connection established successfully")
|
|
|
|
if g.Driver != entity.Postgres {
|
|
sqlDB, _ := db.DB()
|
|
|
|
sqlDB.SetMaxIdleConns(4) // in config_db it uses c.DatabaseConnsIdle(), but we don't have the c here.
|
|
sqlDB.SetMaxOpenConns(256) // in config_db it uses c.DatabaseConns(), but we don't have the c here.
|
|
}
|
|
|
|
g.db = db
|
|
}
|
|
|
|
// Close closes the gorm db connection.
|
|
func (g *DbConn) Close() {
|
|
if g.db != nil {
|
|
sqlDB, _ := g.db.DB()
|
|
if err := sqlDB.Close(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
g.db = nil
|
|
}
|
|
}
|
|
|
|
func gormConfig() *gorm.Config {
|
|
return &gorm.Config{
|
|
Logger: logger.New(
|
|
log,
|
|
logger.Config{
|
|
SlowThreshold: time.Second, // Slow SQL threshold
|
|
LogLevel: logger.Error, // Log level
|
|
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
|
ParameterizedQueries: true, // Don't include params in the SQL log
|
|
Colorful: false, // Disable color
|
|
},
|
|
),
|
|
// Set UTC as the default for created and updated timestamps.
|
|
NowFunc: func() time.Time {
|
|
return UTC()
|
|
},
|
|
}
|
|
}
|
|
|
|
// IsDialect returns true if the given sql dialect is used.
|
|
func IsDialect(name string) bool {
|
|
return name == Db().Dialector.Name()
|
|
}
|
|
|
|
// DbDialect returns the sql dialect name.
|
|
func DbDialect() string {
|
|
return Db().Dialector.Name()
|
|
}
|
|
|
|
// SetDbProvider sets the Gorm database connection provider.
|
|
func SetDbProvider(conn Gorm) {
|
|
dbConn = conn
|
|
}
|
|
|
|
// HasDbProvider returns true if a db provider exists.
|
|
func HasDbProvider() bool {
|
|
return dbConn != nil
|
|
}
|
|
|
|
var characterRunes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
|
|
func randomSHA1() string {
|
|
result := make([]rune, 32)
|
|
for i := range result {
|
|
result[i] = characterRunes[rand.IntN(len(characterRunes))]
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
numberOfPhotos int
|
|
driver string
|
|
dsn string
|
|
dropdb bool
|
|
sqlitescript bool
|
|
)
|
|
|
|
log = logrus.StandardLogger()
|
|
log.SetLevel(logrus.TraceLevel)
|
|
event.AuditLog = log
|
|
|
|
flag.IntVar(&numberOfPhotos, "numberOfPhotos", 0, "Number of photos to generate")
|
|
flag.StringVar(&driver, "driver", "sqlite", "GORM driver to use. Choose from sqlite, mysql and postgres")
|
|
flag.StringVar(&dsn, "dsn", "testdb.db", "DSN to access the database")
|
|
flag.BoolVar(&dropdb, "dropdb", false, "Drop/Delete the database")
|
|
flag.BoolVar(&sqlitescript, "sqlitescript", true, "Create an SQLite database from script")
|
|
flag.Parse()
|
|
|
|
if numberOfPhotos < 1 {
|
|
flag.PrintDefaults()
|
|
log.Errorf("Number of photos is not enough %d", numberOfPhotos)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if _, ok := drivers[driver]; ok == false {
|
|
flag.PrintDefaults()
|
|
log.Errorf("driver %v is not valid", driver)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(dsn) < 3 {
|
|
flag.PrintDefaults()
|
|
log.Errorf("dsn %v is to short", dsn)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Set default test database driver.
|
|
if driver == "test" || driver == "sqlite" || driver == "" || dsn == "" {
|
|
driver = SQLite3
|
|
}
|
|
|
|
// Set default database DSN.
|
|
if driver == SQLite3 {
|
|
if dsn == "" {
|
|
dsn = SQLiteMemoryDSN
|
|
}
|
|
}
|
|
|
|
allowDelete := dropdb
|
|
if driver == MySQL && allowDelete {
|
|
basedsn := dsn[0 : strings.Index(dsn, "/")+1]
|
|
basedbname := dsn[strings.Index(dsn, "/")+1 : strings.Index(dsn, "?")]
|
|
log.Infof("Connecting to %v", basedsn)
|
|
database, err := gorm.Open(mysql.Open(basedsn), &gorm.Config{})
|
|
if err != nil {
|
|
log.Errorf("Unable to connect to MariaDB %v", err)
|
|
}
|
|
log.Infof("Dropping database %v if it exists", basedbname)
|
|
if res := database.Exec("DROP DATABASE IF EXISTS " + basedbname + ";"); res.Error != nil {
|
|
log.Errorf("Unable to drop database %v", res.Error)
|
|
os.Exit(1)
|
|
}
|
|
|
|
log.Infof("Creating database %v if it doesnt exist", basedbname)
|
|
if res := database.Exec("CREATE DATABASE IF NOT EXISTS " + basedbname + ";"); res.Error != nil {
|
|
log.Errorf("Unable to create database %v", res.Error)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
if driver == SQLite3 && dsn != SQLiteMemoryDSN && allowDelete {
|
|
filename := dsn
|
|
if strings.Index(dsn, "?") > 0 {
|
|
if strings.Index(dsn, ":") > 0 {
|
|
filename = dsn[strings.Index(dsn, ":")+1 : strings.Index(dsn, "?")]
|
|
} else {
|
|
filename = dsn[0:strings.Index(dsn, "?")]
|
|
}
|
|
}
|
|
log.Infof("Removing file %v", filename)
|
|
os.Remove(filename)
|
|
}
|
|
|
|
log.Infof("Connecting to driver %v with dsn %v", driver, dsn)
|
|
// Create gorm.DB connection provider.
|
|
db := &DbConn{
|
|
Driver: driver,
|
|
Dsn: dsn,
|
|
}
|
|
defer db.Close()
|
|
|
|
SetDbProvider(db)
|
|
|
|
// Disable journal to speed up.
|
|
if driver == SQLite3 {
|
|
Db().Exec("PRAGMA journal_mode=OFF")
|
|
}
|
|
|
|
start := time.Now()
|
|
|
|
log.Info("Create PhotoPrism tables if they don't exist")
|
|
// Run migration if the photos table doesn't exist.
|
|
// Otherwise assume that we have a valid structured database.
|
|
photoCounter := int64(0)
|
|
if err := Db().Model(&entity.Photo{}).Count(&photoCounter).Error; err != nil {
|
|
// Handle SQLite differently as it does table recreates on initial migrate, so we need to be able to simulate that.
|
|
if driver == SQLite3 && sqlitescript {
|
|
filename := dsn
|
|
if strings.Index(dsn, "?") > 0 {
|
|
if strings.Index(dsn, ":") > 0 {
|
|
filename = dsn[strings.Index(dsn, ":")+1 : strings.Index(dsn, "?")]
|
|
} else {
|
|
filename = dsn[0:strings.Index(dsn, "?")]
|
|
}
|
|
}
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
bashCmd := fmt.Sprintf("cat ./sqlite3.sql | sqlite3 %s", filename)
|
|
|
|
cmd = exec.Command("bash", "-c", bashCmd)
|
|
|
|
// Write to stdout or file.
|
|
var f *os.File
|
|
log.Infof("restore: creating database tables from script")
|
|
f = os.Stdout
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
cmd.Stdout = f
|
|
|
|
// Log exact command for debugging in trace mode.
|
|
log.Debug(cmd.String())
|
|
|
|
// Run restore command.
|
|
if cmdErr := cmd.Run(); cmdErr != nil {
|
|
if errStr := strings.TrimSpace(stderr.String()); errStr != "" {
|
|
log.Error(errStr)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
} else {
|
|
entity.Entities.Migrate(Db(), migrate.Opt(true, false, nil))
|
|
if err := entity.Entities.WaitForMigration(Db()); err != nil {
|
|
log.Errorf("migrate: %s [%s]", err, time.Since(start))
|
|
}
|
|
}
|
|
} else {
|
|
log.Errorf("The photos table already exists in driver %v dsn %v.\nAborting...", driver, dsn)
|
|
os.Exit(1)
|
|
}
|
|
|
|
entity.SetDbProvider(dbConn)
|
|
|
|
log.Info("Create default fixtures")
|
|
|
|
entity.CreateDefaultFixtures()
|
|
|
|
// Load the database with data.
|
|
|
|
// Create all the labels and keywords that have specific handling in internal/ai/classify/rules.go
|
|
log.Info("Create labels and keywords")
|
|
keywords := make(map[string]uint)
|
|
labels := make(map[string]uint)
|
|
keywordRandoms := make(map[int]uint)
|
|
labelRandoms := make(map[int]uint)
|
|
keywordPos, labelPos := 0, 0
|
|
for label, rule := range classify.Rules {
|
|
keyword := entity.Keyword{
|
|
Keyword: label,
|
|
Skip: false,
|
|
}
|
|
Db().Create(&keyword)
|
|
keywords[label] = keyword.ID
|
|
keywordRandoms[keywordPos] = keyword.ID
|
|
keywordPos++
|
|
if rule.Label != "" {
|
|
if _, found := keywords[rule.Label]; found == false {
|
|
keyword = entity.Keyword{
|
|
Keyword: rule.Label,
|
|
Skip: false,
|
|
}
|
|
Db().Create(&keyword)
|
|
keywords[rule.Label] = keyword.ID
|
|
keywordRandoms[keywordPos] = keyword.ID
|
|
keywordPos++
|
|
}
|
|
for _, category := range rule.Categories {
|
|
if _, found := labels[category]; found == false {
|
|
labelDb := entity.Label{
|
|
LabelSlug: strings.ToLower(category),
|
|
CustomSlug: strings.ToLower(category),
|
|
LabelName: strings.ToLower(category),
|
|
LabelPriority: 0,
|
|
LabelFavorite: false,
|
|
LabelDescription: "",
|
|
LabelNotes: "",
|
|
PhotoCount: 0,
|
|
LabelCategories: []*entity.Label{},
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
DeletedAt: gorm.DeletedAt{},
|
|
New: false,
|
|
}
|
|
Db().Create(&labelDb)
|
|
labels[category] = labelDb.ID
|
|
labelRandoms[labelPos] = labelDb.ID
|
|
labelPos++
|
|
}
|
|
}
|
|
if _, found := labels[rule.Label]; found == false {
|
|
labelDb := entity.Label{
|
|
LabelSlug: strings.ToLower(rule.Label),
|
|
CustomSlug: strings.ToLower(rule.Label),
|
|
LabelName: strings.ToLower(rule.Label),
|
|
LabelPriority: 0,
|
|
LabelFavorite: false,
|
|
LabelDescription: "",
|
|
LabelNotes: "",
|
|
PhotoCount: 0,
|
|
LabelCategories: []*entity.Label{},
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
DeletedAt: gorm.DeletedAt{},
|
|
New: false,
|
|
}
|
|
Db().Create(&labelDb)
|
|
labels[rule.Label] = labelDb.ID
|
|
labelRandoms[labelPos] = labelDb.ID
|
|
labelPos++
|
|
for _, category := range rule.Categories {
|
|
categoryDb := entity.Category{
|
|
LabelID: labelDb.ID,
|
|
CategoryID: labels[category],
|
|
}
|
|
Db().Create(&categoryDb)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create every possible camera and some lenses. Yeah the data is garbage but it's test data anyway.
|
|
log.Info("Create cameras and lenses")
|
|
lensList := [6]string{"Wide Angle", "Fisheye", "Ultra Wide Angle", "Macro", "Super Zoom", "F80"}
|
|
cameras := make(map[string]uint)
|
|
lenses := make(map[string]uint)
|
|
cameraRandoms := make(map[int]uint)
|
|
lensRandoms := make(map[int]uint)
|
|
cameraPos, lensPos := 0, 0
|
|
|
|
for _, make := range entity.CameraMakes {
|
|
for _, model := range entity.CameraModels {
|
|
camera := entity.NewCamera(make, model)
|
|
if _, found := cameras[camera.CameraSlug]; found == false {
|
|
Db().Create(camera)
|
|
cameras[camera.CameraSlug] = camera.ID
|
|
cameraRandoms[cameraPos] = camera.ID
|
|
cameraPos++
|
|
}
|
|
}
|
|
for _, model := range lensList {
|
|
lens := entity.NewLens(make, model)
|
|
if _, found := lenses[lens.LensSlug]; found == false {
|
|
Db().Create(lens)
|
|
lenses[lens.LensSlug] = lens.ID
|
|
lensRandoms[lensPos] = lens.ID
|
|
lensPos++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load up Countries and Places.
|
|
log.Info("Create countries and places")
|
|
countries := make(map[int]string)
|
|
countryPos := 0
|
|
places := make(map[int]string)
|
|
placePos := 0
|
|
|
|
PlaceUID := byte('P')
|
|
|
|
file, _ := os.Open("../../pkg/txt/resources/countries.txt")
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
for scanner.Scan() {
|
|
parts := strings.Split(scanner.Text(), ":")
|
|
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
|
|
country := entity.NewCountry(strings.ToLower(parts[0]), strings.ToLower(parts[1]))
|
|
counter := int64(0)
|
|
Db().Model(&entity.Country{}).Where("id = ?", country.ID).Count(&counter)
|
|
if counter == 0 {
|
|
Db().Create(country)
|
|
countries[countryPos] = strings.ToLower(parts[0])
|
|
countryPos++
|
|
}
|
|
}
|
|
|
|
for word := range txt.StopWords {
|
|
placeUID := rnd.GenerateUID(PlaceUID)
|
|
country := countries[rand.IntN(len(countries))]
|
|
place := entity.Place{
|
|
ID: placeUID,
|
|
PlaceLabel: word,
|
|
PlaceDistrict: word,
|
|
PlaceCity: word,
|
|
PlaceState: word,
|
|
PlaceCountry: country,
|
|
PlaceKeywords: "",
|
|
PlaceFavorite: false,
|
|
PhotoCount: 0,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
Db().Create(&place)
|
|
places[placePos] = placeUID
|
|
placePos++
|
|
}
|
|
|
|
// Create some Subjects
|
|
log.Info("Create subjects")
|
|
subjects := make(map[int]entity.Subject)
|
|
subjectPos := 0
|
|
|
|
for i := 1; i <= 100; i++ {
|
|
subject := entity.Subject{
|
|
SubjUID: rnd.GenerateUID('j'),
|
|
SubjType: entity.SubjPerson,
|
|
SubjSrc: entity.SrcImage,
|
|
SubjSlug: fmt.Sprintf("person-%03d", i),
|
|
SubjName: fmt.Sprintf("Person %03d", i),
|
|
SubjFavorite: false,
|
|
SubjPrivate: false,
|
|
SubjExcluded: false,
|
|
FileCount: 0,
|
|
PhotoCount: 0,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
DeletedAt: gorm.DeletedAt{},
|
|
}
|
|
Db().Create(&subject)
|
|
subjects[subjectPos] = subject
|
|
subjectPos++
|
|
}
|
|
|
|
log.Info("Start creating photos")
|
|
for i := 1; i <= numberOfPhotos; i++ {
|
|
if _, frac := math.Modf(float64(i) / 100.0); frac == 0 {
|
|
log.Infof("Generating photo number %v", i)
|
|
}
|
|
month := rand.IntN(11) + 1
|
|
day := rand.IntN(28) + 1
|
|
year := rand.IntN(45) + 1980
|
|
takenAt := time.Date(year, time.Month(month), day, rand.IntN(24), rand.IntN(60), rand.IntN(60), rand.IntN(1000), time.UTC)
|
|
labelCount := rand.IntN(5)
|
|
|
|
// Create the cell for the Photo's location
|
|
placeId := places[rand.IntN(len(places))]
|
|
lat := (rand.Float64() * 180.0) - 90.0
|
|
lng := (rand.Float64() * 360.0) - 180.0
|
|
cell := entity.NewCell(lat, lng)
|
|
cell.PlaceID = placeId
|
|
Db().FirstOrCreate(cell)
|
|
|
|
folder := entity.Folder{}
|
|
if res := Db().Model(&entity.Folder{}).Where("path = ?", fmt.Sprintf("%04d", year)).First(&folder); res.RowsAffected == 0 {
|
|
folder = entity.NewFolder("/", fmt.Sprintf("%04d", year), time.Now().UTC())
|
|
folder.Create()
|
|
}
|
|
folder = entity.Folder{}
|
|
if res := Db().Model(&entity.Folder{}).Where("path = ?", fmt.Sprintf("%04d/%02d", year, month)).First(&folder); res.RowsAffected == 0 {
|
|
folder = entity.NewFolder("/", fmt.Sprintf("%04d/%02d", year, month), time.Now().UTC())
|
|
folder.Create()
|
|
}
|
|
|
|
photo := entity.Photo{
|
|
// ID
|
|
//
|
|
// UUID
|
|
TakenAt: takenAt,
|
|
TakenAtLocal: takenAt,
|
|
TakenSrc: entity.SrcMeta,
|
|
PhotoUID: rnd.GenerateUID(entity.PhotoUID),
|
|
PhotoType: "image",
|
|
TypeSrc: entity.SrcAuto,
|
|
PhotoTitle: "Performance Test Load",
|
|
TitleSrc: entity.SrcImage,
|
|
PhotoDescription: "",
|
|
DescriptionSrc: entity.SrcAuto,
|
|
PhotoPath: fmt.Sprintf("%04d/%02d", year, month),
|
|
PhotoName: fmt.Sprintf("PIC%08d", i),
|
|
OriginalName: fmt.Sprintf("PIC%08d", i),
|
|
PhotoStack: 0,
|
|
PhotoFavorite: false,
|
|
PhotoPrivate: false,
|
|
PhotoScan: false,
|
|
PhotoPanorama: false,
|
|
TimeZone: "America/Mexico_City",
|
|
PlaceID: placeId,
|
|
PlaceSrc: entity.SrcMeta,
|
|
CellID: cell.ID,
|
|
CellAccuracy: 0,
|
|
PhotoAltitude: 5,
|
|
PhotoLat: lat,
|
|
PhotoLng: lng,
|
|
PhotoCountry: countries[rand.IntN(len(countries))],
|
|
PhotoYear: year,
|
|
PhotoMonth: month,
|
|
PhotoDay: day,
|
|
PhotoIso: 400,
|
|
PhotoExposure: "1/60",
|
|
PhotoFNumber: 8,
|
|
PhotoFocalLength: 2,
|
|
PhotoQuality: 3,
|
|
PhotoFaces: 0,
|
|
PhotoResolution: 0,
|
|
// PhotoDuration : 0,
|
|
PhotoColor: 12,
|
|
CameraID: cameraRandoms[rand.IntN(len(cameraRandoms))],
|
|
CameraSerial: "",
|
|
CameraSrc: "",
|
|
LensID: lensRandoms[rand.IntN(len(lensRandoms))],
|
|
// Details :,
|
|
// Camera
|
|
// Lens
|
|
// Cell
|
|
// Place
|
|
Keywords: []entity.Keyword{},
|
|
Albums: []entity.Album{},
|
|
Files: []entity.File{},
|
|
Labels: []entity.PhotoLabel{},
|
|
// CreatedBy
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
EditedAt: nil,
|
|
PublishedAt: nil,
|
|
CheckedAt: nil,
|
|
EstimatedAt: nil,
|
|
DeletedAt: gorm.DeletedAt{},
|
|
}
|
|
Db().Create(&photo)
|
|
// Allocate the labels for this photo
|
|
for i := 0; i < labelCount; i++ {
|
|
photoLabel := entity.NewPhotoLabel(photo.ID, labelRandoms[rand.IntN(len(labelRandoms))], 0, entity.SrcMeta)
|
|
Db().FirstOrCreate(photoLabel)
|
|
}
|
|
// Allocate the keywords for this photo
|
|
keywordCount := rand.IntN(5)
|
|
keywordStr := ""
|
|
for i := 0; i < keywordCount; i++ {
|
|
photoKeyword := entity.PhotoKeyword{PhotoID: photo.ID, KeywordID: keywordRandoms[rand.IntN(len(keywordRandoms))]}
|
|
keyword := entity.Keyword{}
|
|
Db().Model(&entity.Keyword{}).Where("id = ?", photoKeyword.KeywordID).First(&keyword)
|
|
Db().FirstOrCreate(&photoKeyword)
|
|
if len(keywordStr) > 0 {
|
|
keywordStr = fmt.Sprintf("%s,%s", keywordStr, keyword.Keyword)
|
|
} else {
|
|
keywordStr = keyword.Keyword
|
|
}
|
|
}
|
|
|
|
// Create File
|
|
file := entity.File{
|
|
// ID
|
|
// Photo
|
|
PhotoID: photo.ID,
|
|
PhotoUID: photo.PhotoUID,
|
|
PhotoTakenAt: photo.TakenAt,
|
|
// TimeIndex
|
|
// MediaID
|
|
// MediaUTC
|
|
InstanceID: "",
|
|
FileUID: rnd.GenerateUID(entity.FileUID),
|
|
FileName: fmt.Sprintf("%04d/%02d/PIC%08d.jpg", year, month, i),
|
|
FileRoot: entity.RootSidecar,
|
|
OriginalName: "",
|
|
FileHash: rnd.GenerateUID(entity.FileUID),
|
|
FileSize: rand.Int64N(1000000),
|
|
FileCodec: "",
|
|
FileType: string(fs.ImageJpeg),
|
|
MediaType: string(media.Image),
|
|
FileMime: "image/jpg",
|
|
FilePrimary: true,
|
|
FileSidecar: false,
|
|
FileMissing: false,
|
|
FilePortrait: true,
|
|
FileVideo: false,
|
|
FileDuration: 0,
|
|
// FileFPS
|
|
// FileFrames
|
|
FileWidth: 1200,
|
|
FileHeight: 1600,
|
|
FileOrientation: 6,
|
|
FileOrientationSrc: entity.SrcMeta,
|
|
FileProjection: "",
|
|
FileAspectRatio: 0.75,
|
|
// FileHDR : false,
|
|
// FileWatermark
|
|
// FileColorProfile
|
|
FileMainColor: "magenta",
|
|
FileColors: "226611CC1",
|
|
FileLuminance: "ABCDEF123",
|
|
FileDiff: 456,
|
|
FileChroma: 15,
|
|
// FileSoftware
|
|
// FileError
|
|
ModTime: time.Now().Unix(),
|
|
CreatedAt: time.Now().UTC(),
|
|
CreatedIn: 935962,
|
|
UpdatedAt: time.Now().UTC(),
|
|
UpdatedIn: 935962,
|
|
// PublishedAt
|
|
DeletedAt: gorm.DeletedAt{},
|
|
Share: []entity.FileShare{},
|
|
Sync: []entity.FileSync{},
|
|
//markers
|
|
}
|
|
Db().Create(&file)
|
|
|
|
// Add Markers
|
|
markersToCreate := rand.IntN(5)
|
|
for i := 0; i < markersToCreate; i++ {
|
|
subject := subjects[rand.IntN(len(subjects))]
|
|
marker := entity.Marker{
|
|
MarkerUID: rnd.GenerateUID('m'),
|
|
FileUID: file.FileUID,
|
|
MarkerType: entity.MarkerFace,
|
|
MarkerName: subject.SubjName,
|
|
MarkerReview: false,
|
|
MarkerInvalid: false,
|
|
SubjUID: subject.SubjUID,
|
|
SubjSrc: subject.SubjSrc,
|
|
X: rand.Float32() * 1024.0,
|
|
Y: rand.Float32() * 2048.0,
|
|
W: rand.Float32() * 10.0,
|
|
H: rand.Float32() * 20.0,
|
|
Q: 10,
|
|
Size: 100,
|
|
Score: 10,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
Db().Create(&marker)
|
|
face := entity.Face{
|
|
ID: randomSHA1(),
|
|
FaceSrc: entity.SrcImage,
|
|
FaceKind: 1,
|
|
FaceHidden: false,
|
|
SubjUID: subject.SubjUID,
|
|
Samples: 5,
|
|
SampleRadius: 0.35,
|
|
Collisions: 5,
|
|
CollisionRadius: 0.5,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
Db().Create(&face)
|
|
}
|
|
|
|
// Add to Album
|
|
albumSlug := fmt.Sprintf("my-photos-from-%04d", year)
|
|
album := entity.Album{}
|
|
if res := Db().Model(&entity.Album{}).Where("album_slug = ?", albumSlug).First(&album); res.RowsAffected == 0 {
|
|
album = entity.Album{
|
|
AlbumUID: rnd.GenerateUID(entity.AlbumUID),
|
|
AlbumSlug: albumSlug,
|
|
AlbumPath: "",
|
|
AlbumType: entity.AlbumManual,
|
|
AlbumTitle: fmt.Sprintf("My Photos From %04d", year),
|
|
AlbumLocation: "",
|
|
AlbumCategory: "",
|
|
AlbumCaption: "",
|
|
AlbumDescription: "A wonderful year",
|
|
AlbumNotes: "",
|
|
AlbumFilter: "",
|
|
AlbumOrder: "oldest",
|
|
AlbumTemplate: "",
|
|
AlbumCountry: entity.UnknownID,
|
|
AlbumYear: year,
|
|
AlbumMonth: 0,
|
|
AlbumDay: 0,
|
|
AlbumFavorite: false,
|
|
AlbumPrivate: false,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
DeletedAt: gorm.DeletedAt{},
|
|
}
|
|
Db().Create(&album)
|
|
}
|
|
photoAlbum := entity.PhotoAlbum{
|
|
PhotoUID: photo.PhotoUID,
|
|
AlbumUID: album.AlbumUID,
|
|
Order: 0,
|
|
Hidden: false,
|
|
Missing: false,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
Db().Create(photoAlbum)
|
|
|
|
details := entity.Details{
|
|
PhotoID: photo.ID,
|
|
Keywords: keywordStr,
|
|
KeywordsSrc: entity.SrcMeta,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
Db().Create(details)
|
|
}
|
|
|
|
entity.File{}.RegenerateIndex()
|
|
entity.UpdateCounts()
|
|
|
|
log.Infof("Database Creation completed in %s", time.Since(start))
|
|
code := 0
|
|
os.Exit(code)
|
|
|
|
}
|