photoprism/internal/context/config.go
2018-11-17 06:21:39 +01:00

419 lines
11 KiB
Go

package context
import (
"log"
"os"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/kylelemons/go-gypsy/yaml"
"github.com/photoprism/photoprism/internal/fsutil"
"github.com/photoprism/photoprism/internal/models"
"github.com/urfave/cli"
)
// Config provides a struct in which application configuration is stored.
// Application code must use functions to get config values, for two reasons:
//
// 1. Some values are computed and we don't want to leak implementation details (aims at reducing refactoring overhead).
//
// 2. Paths might actually be dynamic later (if we build a multi-user version).
//
// See https://github.com/photoprism/photoprism/issues/50#issuecomment-433856358
type Config struct {
appName string
appVersion string
appCopyright string
debug bool
configFile string
serverIP string
serverPort int
serverMode string
assetsPath string
cachePath string
originalsPath string
importPath string
exportPath string
darktableCli string
databaseDriver string
databaseDsn string
db *gorm.DB
}
type ClientConfig map[string]interface{}
// NewConfig() creates a new configuration entity by using two methods:
//
// 1. SetValuesFromFile: This will initialize values from a yaml config file.
//
// 2. SetValuesFromCliContext: Which comes after SetValuesFromFile and overrides
// any previous values giving an option two override file configs through the CLI.
func NewConfig(ctx *cli.Context) *Config {
c := &Config{}
c.appName = ctx.App.Name
c.appCopyright = ctx.App.Copyright
c.appVersion = ctx.App.Version
c.SetValuesFromFile(fsutil.ExpandedFilename(ctx.GlobalString("config-file")))
c.SetValuesFromCliContext(ctx)
return c
}
// SetValuesFromFile uses a yaml config file to initiate the configuration entity.
func (c *Config) SetValuesFromFile(fileName string) error {
yamlConfig, err := yaml.ReadFile(fileName)
if err != nil {
return err
}
c.configFile = fileName
if debug, err := yamlConfig.GetBool("debug"); err == nil {
c.debug = debug
}
if serverIP, err := yamlConfig.Get("server-host"); err == nil {
c.serverIP = serverIP
}
if serverPort, err := yamlConfig.GetInt("server-port"); err == nil {
c.serverPort = int(serverPort)
}
if serverMode, err := yamlConfig.Get("server-mode"); err == nil {
c.serverMode = serverMode
}
if assetsPath, err := yamlConfig.Get("assets-path"); err == nil {
c.assetsPath = fsutil.ExpandedFilename(assetsPath)
}
if cachePath, err := yamlConfig.Get("cache-path"); err == nil {
c.cachePath = fsutil.ExpandedFilename(cachePath)
}
if originalsPath, err := yamlConfig.Get("originals-path"); err == nil {
c.originalsPath = fsutil.ExpandedFilename(originalsPath)
}
if importPath, err := yamlConfig.Get("import-path"); err == nil {
c.importPath = fsutil.ExpandedFilename(importPath)
}
if exportPath, err := yamlConfig.Get("export-path"); err == nil {
c.exportPath = fsutil.ExpandedFilename(exportPath)
}
if darktableCli, err := yamlConfig.Get("darktable-cli"); err == nil {
c.darktableCli = fsutil.ExpandedFilename(darktableCli)
}
if databaseDriver, err := yamlConfig.Get("database-driver"); err == nil {
c.databaseDriver = databaseDriver
}
if databaseDsn, err := yamlConfig.Get("database-dsn"); err == nil {
c.databaseDsn = databaseDsn
}
return nil
}
// SetValuesFromCliContext uses values from the CLI to setup configuration overrides
// for the entity.
func (c *Config) SetValuesFromCliContext(ctx *cli.Context) error {
if ctx.GlobalBool("debug") {
c.debug = ctx.GlobalBool("debug")
}
if ctx.GlobalIsSet("assets-path") || c.assetsPath == "" {
c.assetsPath = fsutil.ExpandedFilename(ctx.GlobalString("assets-path"))
}
if ctx.GlobalIsSet("cache-path") || c.cachePath == "" {
c.cachePath = fsutil.ExpandedFilename(ctx.GlobalString("cache-path"))
}
if ctx.GlobalIsSet("originals-path") || c.originalsPath == "" {
c.originalsPath = fsutil.ExpandedFilename(ctx.GlobalString("originals-path"))
}
if ctx.GlobalIsSet("import-path") || c.importPath == "" {
c.importPath = fsutil.ExpandedFilename(ctx.GlobalString("import-path"))
}
if ctx.GlobalIsSet("export-path") || c.exportPath == "" {
c.exportPath = fsutil.ExpandedFilename(ctx.GlobalString("export-path"))
}
if ctx.GlobalIsSet("darktable-cli") || c.darktableCli == "" {
c.darktableCli = fsutil.ExpandedFilename(ctx.GlobalString("darktable-cli"))
}
if ctx.GlobalIsSet("database-driver") || c.databaseDriver == "" {
c.databaseDriver = ctx.GlobalString("database-driver")
}
if ctx.GlobalIsSet("database-dsn") || c.databaseDsn == "" {
c.databaseDsn = ctx.GlobalString("database-dsn")
}
if ctx.IsSet("server-host") || c.serverIP == "" {
c.serverIP = ctx.String("server-host")
}
if ctx.IsSet("server-port") || c.serverPort == 0 {
c.serverPort = ctx.Int("server-port")
}
if ctx.IsSet("server-mode") || c.serverMode == "" {
c.serverMode = ctx.String("server-mode")
}
return nil
}
// CreateDirectories creates all the folders that photoprism needs. These are:
// originalsPath
// ThumbnailsPath
// importPath
// exportPath
func (c *Config) CreateDirectories() error {
if err := os.MkdirAll(c.GetOriginalsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.GetImportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.GetExportPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.GetThumbnailsPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.GetDatabasePath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.GetTensorFlowModelPath(), os.ModePerm); err != nil {
return err
}
if err := os.MkdirAll(c.GetPublicBuildPath(), os.ModePerm); err != nil {
return err
}
return nil
}
// connectToDatabase estabilishes a connection to a database given a driver.
// It tries to do this 12 times with a 5 second sleep intervall in between.
func (c *Config) connectToDatabase() error {
db, err := gorm.Open(c.databaseDriver, c.databaseDsn)
if err != nil || db == nil {
for i := 1; i <= 12; i++ {
time.Sleep(5 * time.Second)
db, err = gorm.Open(c.databaseDriver, c.databaseDsn)
if db != nil && err == nil {
break
}
}
if err != nil || db == nil {
log.Fatal(err)
}
}
c.db = db
return err
}
// GetAppName returns the application name.
func (c *Config) GetAppName() string {
return c.appName
}
// GetAppVersion returns the application version.
func (c *Config) GetAppVersion() string {
return c.appVersion
}
// GetAppCopyright returns the application copyright.
func (c *Config) GetAppCopyright() string {
return c.appCopyright
}
// IsDebug returns true if debug mode is on.
func (c *Config) IsDebug() bool {
return c.debug
}
// GetConfigFile returns the config file name.
func (c *Config) GetConfigFile() string {
return c.configFile
}
// GetServerIP returns the server IP address (empty for all).
func (c *Config) GetServerIP() string {
return c.serverIP
}
// GetServerPort returns the server port.
func (c *Config) GetServerPort() int {
return c.serverPort
}
// GetServerMode returns the server mode.
func (c *Config) GetServerMode() string {
return c.serverMode
}
// GetOriginalsPath returns the originals.
func (c *Config) GetOriginalsPath() string {
return c.originalsPath
}
// GetImportPath returns the import directory.
func (c *Config) GetImportPath() string {
return c.importPath
}
// GetExportPath returns the export directory.
func (c *Config) GetExportPath() string {
return c.exportPath
}
// GetDarktableCli returns the darktable-cli binary file name.
func (c *Config) GetDarktableCli() string {
return c.darktableCli
}
// GetDatabaseDriver returns the database driver name.
func (c *Config) GetDatabaseDriver() string {
return c.databaseDriver
}
// GetDatabaseDsn returns the database data source name (DSN).
func (c *Config) GetDatabaseDsn() string {
return c.databaseDsn
}
// GetCachePath returns the path to the cache.
func (c *Config) GetCachePath() string {
return c.cachePath
}
// GetThumbnailsPath returns the path to the cached thumbnails.
func (c *Config) GetThumbnailsPath() string {
return c.GetCachePath() + "/thumbnails"
}
// GetAssetsPath returns the path to the assets.
func (c *Config) GetAssetsPath() string {
return c.assetsPath
}
// GetTensorFlowModelPath returns the tensorflow model path.
func (c *Config) GetTensorFlowModelPath() string {
return c.GetAssetsPath() + "/tensorflow"
}
// GetDatabasePath returns the database storage path (e.g. for SQLite or Bleve).
func (c *Config) GetDatabasePath() string {
return c.GetAssetsPath() + "/database"
}
// GetServerAssetsPath returns the server assets path (public files, favicons, templates,...).
func (c *Config) GetServerAssetsPath() string {
return c.GetAssetsPath() + "/server"
}
// GetTemplatesPath returns the server templates path.
func (c *Config) GetTemplatesPath() string {
return c.GetServerAssetsPath() + "/templates"
}
// GetFaviconsPath returns the favicons path.
func (c *Config) GetFaviconsPath() string {
return c.GetServerAssetsPath() + "/favicons"
}
// GetPublicPath returns the public server path (//server/assets/*).
func (c *Config) GetPublicPath() string {
return c.GetServerAssetsPath() + "/public"
}
// GetPublicBuildPath returns the public build path (//server/assets/build/*).
func (c *Config) GetPublicBuildPath() string {
return c.GetPublicPath() + "/build"
}
// GetDb gets a db connection. If it already is estabilished it will return that.
func (c *Config) GetDb() *gorm.DB {
if c.db == nil {
c.connectToDatabase()
}
return c.db
}
// MigrateDb will start a migration process.
func (c *Config) MigrateDb() {
db := c.GetDb()
db.AutoMigrate(&models.File{},
&models.Photo{},
&models.Tag{},
&models.Album{},
&models.Location{},
&models.Camera{},
&models.Lens{},
&models.Country{})
if !db.Dialect().HasIndex("photos", "photos_fulltext") {
db.Exec("CREATE FULLTEXT INDEX photos_fulltext ON photos (photo_title, photo_description, photo_artist, photo_colors)")
}
}
// GetClientConfig returns a loaded and set configuration entity.
func (c *Config) GetClientConfig() map[string]interface{} {
db := c.GetDb()
var cameras []*models.Camera
type country struct {
LocCountry string
LocCountryCode string
}
var countries []country
db.Model(&models.Location{}).Select("DISTINCT loc_country_code, loc_country").Scan(&countries)
db.Where("deleted_at IS NULL").Limit(1000).Order("camera_model").Find(&cameras)
jsHash := fsutil.Hash(c.GetPublicBuildPath() + "/app.js")
cssHash := fsutil.Hash(c.GetPublicBuildPath() + "/app.css")
result := ClientConfig{
"appName": c.GetAppName(),
"appVersion": c.GetAppVersion(),
"debug": c.IsDebug(),
"cameras": cameras,
"countries": countries,
"jsHash": jsHash,
"cssHash": cssHash,
}
return result
}