2019-05-06 23:18:10 +02:00
|
|
|
package config
|
2019-05-03 18:57:28 +02:00
|
|
|
|
|
|
|
import (
|
2020-09-06 14:18:40 +02:00
|
|
|
"fmt"
|
2020-12-05 06:21:16 +01:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-01-02 00:03:07 +01:00
|
|
|
"runtime"
|
2020-06-26 14:26:36 +02:00
|
|
|
"strings"
|
2020-01-31 15:29:06 +01:00
|
|
|
"sync"
|
2019-05-03 18:57:28 +02:00
|
|
|
"time"
|
|
|
|
|
2020-12-05 00:13:44 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
|
|
|
2019-05-03 18:57:28 +02:00
|
|
|
"github.com/jinzhu/gorm"
|
|
|
|
_ "github.com/jinzhu/gorm/dialects/mysql"
|
|
|
|
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
2019-12-02 00:30:58 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/event"
|
2020-12-04 13:10:32 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/hub"
|
|
|
|
"github.com/photoprism/photoprism/internal/hub/places"
|
2020-01-19 12:50:44 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/mutex"
|
2020-01-06 14:32:15 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/thumb"
|
2020-05-27 19:38:40 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/rnd"
|
2019-12-02 00:30:58 +01:00
|
|
|
"github.com/sirupsen/logrus"
|
2019-05-03 18:57:28 +02:00
|
|
|
"github.com/urfave/cli"
|
|
|
|
)
|
|
|
|
|
2019-12-02 00:30:58 +01:00
|
|
|
var log = event.Log
|
2020-04-13 18:08:21 +02:00
|
|
|
var once sync.Once
|
2019-12-02 00:30:58 +01:00
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// Config holds database, cache and all parameters of photoprism
|
2019-05-06 23:18:10 +02:00
|
|
|
type Config struct {
|
2020-10-03 13:50:30 +02:00
|
|
|
once sync.Once
|
|
|
|
db *gorm.DB
|
|
|
|
params *Params
|
|
|
|
settings *Settings
|
2020-12-04 13:10:32 +01:00
|
|
|
hub *hub.Config
|
2020-10-03 13:50:30 +02:00
|
|
|
token string
|
2020-12-05 06:21:16 +01:00
|
|
|
serial string
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
2020-01-06 14:32:15 +01:00
|
|
|
func init() {
|
2020-07-13 15:23:54 +02:00
|
|
|
// Init public thumb sizes for use in client apps.
|
|
|
|
for i := len(thumb.DefaultTypes) - 1; i >= 0; i-- {
|
|
|
|
size := thumb.DefaultTypes[i]
|
|
|
|
t := thumb.Types[size]
|
|
|
|
|
2020-01-06 14:32:15 +01:00
|
|
|
if t.Public {
|
2020-07-13 17:25:27 +02:00
|
|
|
Thumbs = append(Thumbs, Thumb{Size: size, Use: t.Use, Width: t.Width, Height: t.Height})
|
2020-01-06 14:32:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 09:11:33 +02:00
|
|
|
func initLogger(debug bool) {
|
2020-01-31 15:29:06 +01:00
|
|
|
once.Do(func() {
|
|
|
|
log.SetFormatter(&logrus.TextFormatter{
|
|
|
|
DisableColors: false,
|
|
|
|
FullTimestamp: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if debug {
|
|
|
|
log.SetLevel(logrus.DebugLevel)
|
|
|
|
} else {
|
|
|
|
log.SetLevel(logrus.InfoLevel)
|
|
|
|
}
|
2019-05-03 18:57:28 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-02-21 01:14:45 +01:00
|
|
|
// NewConfig initialises a new configuration file
|
2019-05-06 23:18:10 +02:00
|
|
|
func NewConfig(ctx *cli.Context) *Config {
|
2019-05-04 05:25:00 +02:00
|
|
|
initLogger(ctx.GlobalBool("debug"))
|
2019-05-03 18:57:28 +02:00
|
|
|
|
2019-05-14 16:04:17 +02:00
|
|
|
c := &Config{
|
2020-04-13 18:08:21 +02:00
|
|
|
params: NewParams(ctx),
|
2020-05-27 19:38:40 +02:00
|
|
|
token: rnd.Token(8),
|
2019-05-14 16:04:17 +02:00
|
|
|
}
|
2019-05-03 18:57:28 +02:00
|
|
|
|
2020-04-13 18:08:21 +02:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// Propagate updates config values in other packages as needed.
|
|
|
|
func (c *Config) Propagate() {
|
2019-05-03 18:57:28 +02:00
|
|
|
log.SetLevel(c.LogLevel())
|
|
|
|
|
2020-05-05 17:04:13 +02:00
|
|
|
thumb.Size = c.ThumbSize()
|
2020-07-18 17:33:02 +02:00
|
|
|
thumb.SizeUncached = c.ThumbSizeUncached()
|
2020-05-05 17:17:19 +02:00
|
|
|
thumb.Filter = c.ThumbFilter()
|
2020-05-05 15:42:54 +02:00
|
|
|
thumb.JpegQuality = c.JpegQuality()
|
2020-09-06 14:18:40 +02:00
|
|
|
places.UserAgent = c.UserAgent()
|
2020-12-05 00:13:44 +01:00
|
|
|
entity.GeoApi = c.GeoApi()
|
2020-01-06 14:32:15 +01:00
|
|
|
|
2020-04-13 18:08:21 +02:00
|
|
|
c.Settings().Propagate()
|
2020-12-04 13:10:32 +01:00
|
|
|
c.Hub().Propagate()
|
2020-04-13 18:08:21 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 08:52:03 +02:00
|
|
|
// Init creates directories, parses additional config files, opens a database connection and initializes dependencies.
|
|
|
|
func (c *Config) Init() error {
|
|
|
|
if err := c.CreateDirectories(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-12-05 06:21:16 +01:00
|
|
|
if err := c.initStorage(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-08 08:52:03 +02:00
|
|
|
c.initSettings()
|
2020-12-04 13:10:32 +01:00
|
|
|
c.initHub()
|
2020-10-08 08:52:03 +02:00
|
|
|
|
2020-04-13 18:08:21 +02:00
|
|
|
c.Propagate()
|
2020-10-08 08:52:03 +02:00
|
|
|
|
2020-07-13 10:41:45 +02:00
|
|
|
return c.connectDb()
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
2020-12-05 06:21:16 +01:00
|
|
|
// initStorage initializes storage directories with a random serial.
|
|
|
|
func (c *Config) initStorage() error {
|
|
|
|
const serialName = "serial"
|
|
|
|
|
|
|
|
c.serial = rnd.PPID('z')
|
|
|
|
|
|
|
|
storageName := filepath.Join(c.StoragePath(), serialName)
|
|
|
|
backupName := filepath.Join(c.BackupPath(), serialName)
|
|
|
|
|
|
|
|
if data, err := ioutil.ReadFile(storageName); err == nil {
|
|
|
|
c.serial = string(data)
|
|
|
|
} else if data, err := ioutil.ReadFile(backupName); err == nil {
|
|
|
|
c.serial = string(data)
|
|
|
|
} else if err := ioutil.WriteFile(storageName, []byte(c.serial), os.ModePerm); err != nil {
|
|
|
|
return fmt.Errorf("failed creating %s: %s", storageName, err)
|
|
|
|
} else if err := ioutil.WriteFile(backupName, []byte(c.serial), os.ModePerm); err != nil {
|
|
|
|
return fmt.Errorf("failed creating %s: %s", backupName, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// Name returns the application name ("PhotoPrism").
|
2019-05-06 23:18:10 +02:00
|
|
|
func (c *Config) Name() string {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.Name
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// Version returns the application version.
|
|
|
|
func (c *Config) Version() string {
|
|
|
|
return c.params.Version
|
|
|
|
}
|
2019-12-11 14:10:20 +01:00
|
|
|
|
2020-09-06 14:18:40 +02:00
|
|
|
// UserAgent returns a HTTP user agent string based on app name & version.
|
|
|
|
func (c *Config) UserAgent() string {
|
|
|
|
return fmt.Sprintf("%s/%s", c.Name(), c.Version())
|
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// Copyright returns the application copyright.
|
|
|
|
func (c *Config) Copyright() string {
|
|
|
|
return c.params.Copyright
|
2019-12-11 14:10:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// SiteUrl returns the public server URL (default is "http://localhost:2342/").
|
|
|
|
func (c *Config) SiteUrl() string {
|
|
|
|
if c.params.SiteUrl == "" {
|
|
|
|
return "http://localhost:2342/"
|
2019-12-11 14:10:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
return c.params.SiteUrl
|
2019-12-11 14:10:20 +01:00
|
|
|
}
|
|
|
|
|
2020-06-26 14:26:36 +02:00
|
|
|
// SitePreview returns the site preview image URL for sharing.
|
|
|
|
func (c *Config) SitePreview() string {
|
|
|
|
if c.params.SitePreview == "" {
|
|
|
|
return c.SiteUrl() + "static/img/preview.jpg"
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasPrefix(c.params.SitePreview, "http") {
|
|
|
|
return c.SiteUrl() + c.params.SitePreview
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.params.SitePreview
|
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// SiteTitle returns the main site title (default is application name).
|
|
|
|
func (c *Config) SiteTitle() string {
|
|
|
|
if c.params.SiteTitle == "" {
|
|
|
|
return c.Name()
|
|
|
|
}
|
2019-12-11 14:10:20 +01:00
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
return c.params.SiteTitle
|
2019-12-11 14:10:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// SiteCaption returns a short site caption.
|
|
|
|
func (c *Config) SiteCaption() string {
|
|
|
|
return c.params.SiteCaption
|
2019-12-11 14:10:20 +01:00
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// SiteDescription returns a long site description.
|
|
|
|
func (c *Config) SiteDescription() string {
|
|
|
|
return c.params.SiteDescription
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
2020-05-31 02:09:52 +02:00
|
|
|
// SiteAuthor returns the site author / copyright.
|
|
|
|
func (c *Config) SiteAuthor() string {
|
|
|
|
return c.params.SiteAuthor
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Debug returns true if Debug mode is on.
|
2019-05-06 23:18:10 +02:00
|
|
|
func (c *Config) Debug() bool {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.Debug
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
2019-11-11 21:10:41 +01:00
|
|
|
// Public returns true if app requires no authentication.
|
|
|
|
func (c *Config) Public() bool {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.Public
|
2019-11-11 21:10:41 +01:00
|
|
|
}
|
|
|
|
|
2019-12-30 12:38:11 +01:00
|
|
|
// Experimental returns true if experimental features should be enabled.
|
|
|
|
func (c *Config) Experimental() bool {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.Experimental
|
2019-12-30 12:38:11 +01:00
|
|
|
}
|
|
|
|
|
2019-05-04 09:11:33 +02:00
|
|
|
// ReadOnly returns true if photo directories are write protected.
|
2019-05-06 23:18:10 +02:00
|
|
|
func (c *Config) ReadOnly() bool {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.ReadOnly
|
2019-05-04 09:11:33 +02:00
|
|
|
}
|
|
|
|
|
2020-01-13 16:48:32 +01:00
|
|
|
// DetectNSFW returns true if NSFW photos should be detected and flagged.
|
|
|
|
func (c *Config) DetectNSFW() bool {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.DetectNSFW
|
2019-12-15 17:19:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// UploadNSFW returns true if NSFW photos can be uploaded.
|
|
|
|
func (c *Config) UploadNSFW() bool {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.UploadNSFW
|
2019-12-15 17:19:16 +01:00
|
|
|
}
|
|
|
|
|
2020-06-29 21:14:34 +02:00
|
|
|
// AdminPassword returns the initial admin password.
|
2019-11-08 06:53:40 +01:00
|
|
|
func (c *Config) AdminPassword() string {
|
2020-04-13 18:08:21 +02:00
|
|
|
return c.params.AdminPassword
|
2019-11-08 06:53:40 +01:00
|
|
|
}
|
|
|
|
|
2019-05-03 18:57:28 +02:00
|
|
|
// LogLevel returns the logrus log level.
|
2019-12-02 00:30:58 +01:00
|
|
|
func (c *Config) LogLevel() logrus.Level {
|
2019-05-03 18:57:28 +02:00
|
|
|
if c.Debug() {
|
2020-04-13 18:08:21 +02:00
|
|
|
c.params.LogLevel = "debug"
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
|
2020-04-13 18:08:21 +02:00
|
|
|
if logLevel, err := logrus.ParseLevel(c.params.LogLevel); err == nil {
|
2019-05-03 18:57:28 +02:00
|
|
|
return logLevel
|
|
|
|
} else {
|
2019-12-02 00:30:58 +01:00
|
|
|
return logrus.InfoLevel
|
2019-05-03 18:57:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-19 12:50:44 +01:00
|
|
|
// Shutdown services and workers.
|
2019-05-06 23:18:10 +02:00
|
|
|
func (c *Config) Shutdown() {
|
2020-05-26 15:15:14 +02:00
|
|
|
mutex.MainWorker.Cancel()
|
|
|
|
mutex.ShareWorker.Cancel()
|
|
|
|
mutex.SyncWorker.Cancel()
|
2020-06-29 13:35:38 +02:00
|
|
|
mutex.MetaWorker.Cancel()
|
2020-01-19 12:50:44 +01:00
|
|
|
|
2019-05-04 17:34:51 +02:00
|
|
|
if err := c.CloseDb(); err != nil {
|
|
|
|
log.Errorf("could not close database connection: %s", err)
|
|
|
|
} else {
|
|
|
|
log.Info("closed database connection")
|
|
|
|
}
|
|
|
|
}
|
2019-11-12 04:34:37 +01:00
|
|
|
|
2020-01-02 00:03:07 +01:00
|
|
|
// Workers returns the number of workers e.g. for indexing files.
|
2020-01-02 04:08:33 +01:00
|
|
|
func (c *Config) Workers() int {
|
2020-03-09 00:51:10 +01:00
|
|
|
numCPU := runtime.NumCPU()
|
|
|
|
|
2020-10-21 07:33:24 +02:00
|
|
|
// Limit number of workers when using SQLite to avoid database locking issues.
|
2020-12-07 16:20:35 +01:00
|
|
|
if c.DatabaseDriver() == SQLite && numCPU > 4 && c.params.Workers <= 0 {
|
|
|
|
return 4
|
2020-10-21 07:33:24 +02:00
|
|
|
}
|
|
|
|
|
2020-04-13 18:08:21 +02:00
|
|
|
if c.params.Workers > 0 && c.params.Workers <= numCPU {
|
|
|
|
return c.params.Workers
|
2020-01-06 23:43:19 +01:00
|
|
|
}
|
|
|
|
|
2020-03-09 00:51:10 +01:00
|
|
|
if numCPU > 1 {
|
|
|
|
return numCPU - 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1
|
2019-11-12 04:34:37 +01:00
|
|
|
}
|
2020-01-06 06:59:35 +01:00
|
|
|
|
2020-04-06 22:09:45 +02:00
|
|
|
// WakeupInterval returns the background worker wakeup interval.
|
|
|
|
func (c *Config) WakeupInterval() time.Duration {
|
2020-04-13 18:08:21 +02:00
|
|
|
if c.params.WakeupInterval <= 0 {
|
2020-05-30 21:11:56 +02:00
|
|
|
return 15 * time.Minute
|
2020-04-06 22:09:45 +02:00
|
|
|
}
|
|
|
|
|
2020-04-13 18:08:21 +02:00
|
|
|
return time.Duration(c.params.WakeupInterval) * time.Second
|
2020-04-06 22:09:45 +02:00
|
|
|
}
|
|
|
|
|
2020-12-05 00:13:44 +01:00
|
|
|
// GeoApi returns the preferred geo coding api (none, osm or places).
|
|
|
|
func (c *Config) GeoApi() string {
|
|
|
|
switch c.params.GeoApi {
|
2020-01-06 06:59:35 +01:00
|
|
|
case "places":
|
|
|
|
return "places"
|
|
|
|
case "osm":
|
|
|
|
return "osm"
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2020-05-25 19:10:44 +02:00
|
|
|
|
|
|
|
// OriginalsLimit returns the file size limit for originals.
|
|
|
|
func (c *Config) OriginalsLimit() int64 {
|
|
|
|
if c.params.OriginalsLimit <= 0 || c.params.OriginalsLimit > 100000 {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Megabyte.
|
|
|
|
return c.params.OriginalsLimit * 1024 * 1024
|
|
|
|
}
|
2020-10-03 13:50:30 +02:00
|
|
|
|
2020-12-04 13:10:32 +01:00
|
|
|
// UpdateHub updates backend api credentials for maps & places.
|
|
|
|
func (c *Config) UpdateHub() {
|
|
|
|
if err := c.hub.Refresh(); err != nil {
|
2020-10-08 08:52:03 +02:00
|
|
|
log.Debugf("config: %s", err)
|
2020-12-04 13:10:32 +01:00
|
|
|
} else if err := c.hub.Save(); err != nil {
|
2020-10-08 08:52:03 +02:00
|
|
|
log.Debugf("config: %s", err)
|
2020-10-04 22:22:53 +02:00
|
|
|
} else {
|
2020-12-04 13:10:32 +01:00
|
|
|
c.hub.Propagate()
|
2020-10-04 22:22:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 13:10:32 +01:00
|
|
|
// initHub initializes PhotoPrism hub config.
|
|
|
|
func (c *Config) initHub() {
|
2020-12-05 06:21:16 +01:00
|
|
|
c.hub = hub.NewConfig(c.Version(), c.HubConfigFile(), c.serial)
|
2020-10-03 13:50:30 +02:00
|
|
|
|
2020-12-04 13:10:32 +01:00
|
|
|
if err := c.hub.Load(); err == nil {
|
2020-10-03 13:50:30 +02:00
|
|
|
// Do nothing.
|
2020-12-04 13:10:32 +01:00
|
|
|
} else if err := c.hub.Refresh(); err != nil {
|
2020-10-08 08:52:03 +02:00
|
|
|
log.Debugf("config: %s", err)
|
2020-12-04 13:10:32 +01:00
|
|
|
} else if err := c.hub.Save(); err != nil {
|
2020-10-08 08:52:03 +02:00
|
|
|
log.Debugf("config: %s", err)
|
2020-10-03 13:50:30 +02:00
|
|
|
}
|
|
|
|
|
2020-12-04 13:10:32 +01:00
|
|
|
c.hub.Propagate()
|
2020-10-04 04:47:54 +02:00
|
|
|
|
|
|
|
ticker := time.NewTicker(time.Hour * 24)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2020-12-04 13:10:32 +01:00
|
|
|
c.UpdateHub()
|
2020-10-04 04:47:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2020-10-03 13:50:30 +02:00
|
|
|
}
|
|
|
|
|
2020-12-04 13:10:32 +01:00
|
|
|
// Hub returns the PhotoPrism hub config.
|
|
|
|
func (c *Config) Hub() *hub.Config {
|
|
|
|
if c.hub == nil {
|
|
|
|
c.initHub()
|
2020-10-08 08:52:03 +02:00
|
|
|
}
|
|
|
|
|
2020-12-04 13:10:32 +01:00
|
|
|
return c.hub
|
2020-10-03 13:50:30 +02:00
|
|
|
}
|