1
Fork 0
photoview/api/database/database.go

254 lines
6.1 KiB
Go
Raw Normal View History

2020-01-30 14:49:39 +01:00
package database
import (
2021-01-12 19:12:56 +01:00
"context"
"fmt"
2020-01-30 14:49:39 +01:00
"log"
2020-01-31 15:22:58 +01:00
"net/url"
2021-01-12 19:12:56 +01:00
"time"
2020-01-30 14:49:39 +01:00
"github.com/photoview/photoview/api/database/drivers"
"github.com/photoview/photoview/api/database/migrations"
2020-12-17 23:18:00 +01:00
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/utils"
"github.com/pkg/errors"
"github.com/go-sql-driver/mysql"
gorm_mysql "gorm.io/driver/mysql"
2021-01-31 17:06:25 +01:00
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
2020-11-30 21:29:49 +01:00
"gorm.io/gorm/logger"
2020-01-30 14:49:39 +01:00
)
2021-04-23 15:10:23 +02:00
func GetMysqlAddress(addressString string) (string, error) {
if addressString == "" {
return "", errors.New(fmt.Sprintf("Environment variable %s missing, exiting", utils.EnvMysqlURL.GetName()))
2020-01-31 15:22:58 +01:00
}
config, err := mysql.ParseDSN(addressString)
if err != nil {
return "", errors.Wrap(err, "Could not parse mysql url")
}
config.MultiStatements = true
config.ParseTime = true
2020-01-31 15:22:58 +01:00
return config.FormatDSN(), nil
}
2020-01-31 15:22:58 +01:00
2021-04-23 15:10:23 +02:00
func GetPostgresAddress(addressString string) (*url.URL, error) {
2021-01-31 17:06:25 +01:00
if addressString == "" {
return nil, errors.New(fmt.Sprintf("Environment variable %s missing, exiting", utils.EnvPostgresURL.GetName()))
}
address, err := url.Parse(addressString)
if err != nil {
return nil, errors.Wrap(err, "Could not parse postgres url")
}
return address, nil
}
2021-04-23 15:10:23 +02:00
func GetSqliteAddress(path string) (*url.URL, error) {
2021-01-17 12:45:23 +01:00
if path == "" {
path = "photoview.db"
}
address, err := url.Parse(path)
if err != nil {
return nil, errors.Wrapf(err, "Could not parse sqlite url (%s)", path)
}
queryValues := address.Query()
queryValues.Add("cache", "shared")
queryValues.Add("mode", "rwc")
// queryValues.Add("_busy_timeout", "60000") // 1 minute
queryValues.Add("_journal_mode", "WAL") // Write-Ahead Logging (WAL) mode
queryValues.Add("_locking_mode", "NORMAL") // allows concurrent reads and writes
2021-01-17 12:45:23 +01:00
address.RawQuery = queryValues.Encode()
// log.Panicf("%s", address.String())
return address, nil
}
2021-04-23 15:10:23 +02:00
func ConfigureDatabase(config *gorm.Config) (*gorm.DB, error) {
var databaseDialect gorm.Dialector
driver := drivers.DatabaseDriverFromEnv()
log.Printf("Utilizing %s database driver based on environment variables", driver)
switch driver {
case drivers.MYSQL:
2021-04-23 15:10:23 +02:00
mysqlAddress, err := GetMysqlAddress(utils.EnvMysqlURL.GetValue())
if err != nil {
return nil, err
}
databaseDialect = gorm_mysql.Open(mysqlAddress)
case drivers.SQLITE:
2021-04-23 15:10:23 +02:00
sqliteAddress, err := GetSqliteAddress(utils.EnvSqlitePath.GetValue())
2021-01-17 12:45:23 +01:00
if err != nil {
return nil, err
}
2021-01-17 12:45:23 +01:00
databaseDialect = sqlite.Open(sqliteAddress.String())
2021-01-31 17:06:25 +01:00
case drivers.POSTGRES:
2021-04-23 15:10:23 +02:00
postgresAddress, err := GetPostgresAddress(utils.EnvPostgresURL.GetValue())
2021-01-31 17:06:25 +01:00
if err != nil {
return nil, err
}
databaseDialect = postgres.Open(postgresAddress.String())
}
db, err := gorm.Open(databaseDialect, config)
if err != nil {
return nil, err
}
2021-11-06 13:23:59 +01:00
// Manually enable foreign keys for sqlite, as this isn't done by default
if drivers.SQLITE.MatchDatabase(db) {
2021-11-06 13:23:59 +01:00
db.Exec("PRAGMA foreign_keys = ON")
}
return db, nil
}
2021-01-12 19:12:56 +01:00
// SetupDatabase connects to the database using environment variables
func SetupDatabase() (*gorm.DB, error) {
config := gorm.Config{}
// Configure database logging
if utils.DevelopmentMode() {
config.Logger = logger.Default.LogMode(logger.Info)
} else {
config.Logger = logger.Default.LogMode(logger.Warn)
}
var db *gorm.DB
for retryCount := 1; retryCount <= 5; retryCount++ {
var err error
2021-04-23 15:10:23 +02:00
db, err = ConfigureDatabase(&config)
if err == nil {
sqlDB, dbErr := db.DB()
if dbErr != nil {
return nil, dbErr
2021-01-12 19:12:56 +01:00
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err = sqlDB.PingContext(ctx)
cancel()
2021-01-19 16:59:10 +01:00
sqlDB.SetMaxOpenConns(80)
if err == nil {
return db, nil
}
2021-01-12 19:12:56 +01:00
}
log.Printf("WARN: Could not ping database: %s. Will retry after 5 seconds\n", err)
time.Sleep(time.Duration(5) * time.Second)
2021-01-12 19:12:56 +01:00
}
return db, nil
2020-01-30 14:49:39 +01:00
}
2020-01-31 15:22:58 +01:00
2021-04-23 15:10:23 +02:00
var database_models []interface{} = []interface{}{
&models.User{},
&models.AccessToken{},
&models.SiteInfo{},
&models.Media{},
&models.MediaURL{},
&models.Album{},
&models.MediaEXIF{},
&models.VideoMetadata{},
&models.ShareToken{},
&models.UserMediaData{},
&models.UserAlbums{},
2021-04-23 15:10:23 +02:00
&models.UserPreferences{},
// Face detection
&models.FaceGroup{},
&models.ImageFace{},
}
func MigrateDatabase(db *gorm.DB) error {
2020-01-31 15:22:58 +01:00
if err := db.SetupJoinTable(&models.User{}, "Albums", &models.UserAlbums{}); err != nil {
log.Printf("Setup UserAlbums join table failed: %v\n", err)
}
if err := db.AutoMigrate(database_models...); err != nil {
2021-04-13 22:15:47 +02:00
log.Printf("Auto migration failed: %v\n", err)
}
// v2.1.0 - Replaced by Media.CreatedAt
if db.Migrator().HasColumn(&models.Media{}, "date_imported") {
db.Migrator().DropColumn(&models.Media{}, "date_imported")
}
// v2.3.0 - Changed type of MediaEXIF.Exposure and MediaEXIF.Flash
// from string values to decimal and int respectively
if err := migrate_exif_fields(db); err != nil {
2021-04-13 22:15:47 +02:00
log.Printf("Failed to run exif fields migration: %v\n", err)
}
// Remove invalid GPS data from DB
if err := migrations.MigrateForExifGPSCorrection(db); err != nil {
log.Printf("Failed to run exif GPS correction migration: %v\n", err)
}
2020-01-31 15:22:58 +01:00
return nil
}
2021-04-23 15:10:23 +02:00
func ClearDatabase(db *gorm.DB) error {
err := db.Transaction(func(tx *gorm.DB) error {
2021-04-24 18:51:21 +02:00
db_driver := drivers.DatabaseDriverFromEnv()
2021-04-24 19:28:20 +02:00
if db_driver == drivers.MYSQL {
2021-04-24 19:28:20 +02:00
if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 0;").Error; err != nil {
return err
}
}
dry_run := tx.Session(&gorm.Session{DryRun: true})
2021-04-23 15:10:23 +02:00
for _, model := range database_models {
// get table name of model structure
table := dry_run.Find(model).Statement.Table
2021-04-24 19:28:20 +02:00
switch db_driver {
case drivers.POSTGRES:
2021-04-24 19:21:10 +02:00
if err := tx.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)).Error; err != nil {
return err
}
case drivers.MYSQL:
2021-04-24 19:21:10 +02:00
if err := tx.Exec(fmt.Sprintf("TRUNCATE TABLE %s", table)).Error; err != nil {
return err
}
case drivers.SQLITE:
2021-04-24 19:21:10 +02:00
if err := tx.Exec(fmt.Sprintf("DELETE FROM %s", table)).Error; err != nil {
return err
}
2021-04-23 15:10:23 +02:00
}
2021-04-24 19:21:10 +02:00
2021-04-23 15:10:23 +02:00
}
if db_driver == drivers.MYSQL {
2021-04-24 19:28:20 +02:00
if err := tx.Exec("SET FOREIGN_KEY_CHECKS = 1;").Error; err != nil {
return err
}
}
2021-04-23 15:10:23 +02:00
return nil
})
if err != nil {
return err
}
return nil
}