People: Disable updates if the worker does not run often enough #2182
This commit is contained in:
parent
cb0f37c4af
commit
4c583f7f1d
12 changed files with 194 additions and 120 deletions
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize/english"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -17,6 +18,18 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/service"
|
||||
)
|
||||
|
||||
// Checks if background worker runs less than once per hour.
|
||||
func wakeupIntervalTooHigh(c *gin.Context) bool {
|
||||
if conf := service.Config(); conf.Unsafe() {
|
||||
return false
|
||||
} else if i := conf.WakeupInterval(); i > time.Hour {
|
||||
Abort(c, http.StatusForbidden, i18n.ErrWakeupInterval, i.String())
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// findFileMarker returns a file and marker entity matching the api request.
|
||||
func findFileMarker(c *gin.Context) (file *entity.File, marker *entity.Marker, err error) {
|
||||
// Check authorization.
|
||||
|
@ -67,6 +80,12 @@ func findFileMarker(c *gin.Context) (file *entity.File, marker *entity.Marker, e
|
|||
// id: int Marker ID as returned by the API
|
||||
func UpdateMarker(router *gin.RouterGroup) {
|
||||
router.PUT("/markers/:marker_uid", func(c *gin.Context) {
|
||||
// Abort if workers runs less than once per hour.
|
||||
if wakeupIntervalTooHigh(c) {
|
||||
return
|
||||
}
|
||||
|
||||
// Abort if another update is running.
|
||||
if err := mutex.People.Start(); err != nil {
|
||||
AbortBusy(c)
|
||||
return
|
||||
|
@ -145,6 +164,12 @@ func UpdateMarker(router *gin.RouterGroup) {
|
|||
// id: int Marker ID as returned by the API
|
||||
func ClearMarkerSubject(router *gin.RouterGroup) {
|
||||
router.DELETE("/markers/:marker_uid/subject", func(c *gin.Context) {
|
||||
// Abort if workers runs less than once per hour.
|
||||
if wakeupIntervalTooHigh(c) {
|
||||
return
|
||||
}
|
||||
|
||||
// Abort if another update is running.
|
||||
if err := mutex.People.Start(); err != nil {
|
||||
AbortBusy(c)
|
||||
return
|
||||
|
|
|
@ -62,7 +62,7 @@ func configAction(ctx *cli.Context) error {
|
|||
|
||||
// Workers.
|
||||
fmt.Printf("%-25s %d\n", "workers", conf.Workers())
|
||||
fmt.Printf("%-25s %d\n", "wakeup-interval", conf.WakeupInterval()/time.Second)
|
||||
fmt.Printf("%-25s %s\n", "wakeup-interval", conf.WakeupInterval().String())
|
||||
fmt.Printf("%-25s %d\n", "auto-index", conf.AutoIndex()/time.Second)
|
||||
fmt.Printf("%-25s %d\n", "auto-import", conf.AutoImport()/time.Second)
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ const DefaultAutoIndexDelay = int(5 * 60) // 5 Minutes
|
|||
const DefaultAutoImportDelay = int(3 * 60) // 3 Minutes
|
||||
|
||||
const DefaultWakeupIntervalSeconds = int(15 * 60) // 15 Minutes
|
||||
const DefaultWakeupInterval = time.Second * time.Duration(DefaultWakeupIntervalSeconds)
|
||||
const MaxWakeupInterval = time.Hour * 24 // 1 Day
|
||||
|
||||
// Megabyte in bytes.
|
||||
const Megabyte = 1000 * 1000
|
||||
|
@ -133,6 +135,11 @@ func NewConfig(ctx *cli.Context) *Config {
|
|||
return c
|
||||
}
|
||||
|
||||
// Unsafe checks if unsafe settings are allowed.
|
||||
func (c *Config) Unsafe() bool {
|
||||
return c.options.Unsafe
|
||||
}
|
||||
|
||||
// Options returns the raw config options.
|
||||
func (c *Config) Options() *Options {
|
||||
if c.options == nil {
|
||||
|
@ -212,6 +219,12 @@ func (c *Config) Init() error {
|
|||
log.Infof("config: make sure your server has enough swap configured to prevent restarts when there are memory usage spikes")
|
||||
}
|
||||
|
||||
// Show wakeup interval warning if face recognition is enabled
|
||||
// and the worker runs less than once per hour.
|
||||
if !c.DisableFaces() && !c.Unsafe() && c.WakeupInterval() > time.Hour {
|
||||
log.Warnf("config: the wakeup interval is %s, but must be 1h or less for face recognition to work", c.WakeupInterval().String())
|
||||
}
|
||||
|
||||
// Set HTTP user agent.
|
||||
places.UserAgent = c.UserAgent()
|
||||
|
||||
|
@ -534,17 +547,23 @@ func (c *Config) Workers() int {
|
|||
return 1
|
||||
}
|
||||
|
||||
// WakeupInterval returns the metadata, share & sync background worker wakeup interval duration (1 - 604800 seconds).
|
||||
// WakeupInterval returns the duration between background worker runs
|
||||
// required for face recognition and index maintenance(1-86400s).
|
||||
func (c *Config) WakeupInterval() time.Duration {
|
||||
if c.options.Unsafe && c.options.WakeupInterval < 0 {
|
||||
// Background worker can be disabled in unsafe mode.
|
||||
return time.Duration(0)
|
||||
} else if c.options.WakeupInterval <= 0 || c.options.WakeupInterval > 604800 {
|
||||
// Default if out of range.
|
||||
return time.Duration(DefaultWakeupIntervalSeconds) * time.Second
|
||||
if c.options.WakeupInterval <= 0 {
|
||||
if c.options.Unsafe {
|
||||
// Worker can be disabled only in unsafe mode.
|
||||
return time.Duration(0)
|
||||
} else {
|
||||
// Default to 15 minutes if no interval is set.
|
||||
return DefaultWakeupInterval
|
||||
}
|
||||
} else if c.options.WakeupInterval > MaxWakeupInterval {
|
||||
// Max interval is one day.
|
||||
return MaxWakeupInterval
|
||||
}
|
||||
|
||||
return time.Duration(c.options.WakeupInterval) * time.Second
|
||||
return c.options.WakeupInterval
|
||||
}
|
||||
|
||||
// AutoIndex returns the auto index delay duration.
|
||||
|
|
|
@ -282,7 +282,7 @@ func TestConfig_Workers(t *testing.T) {
|
|||
|
||||
func TestConfig_WakeupInterval(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.Equal(t, time.Duration(900000000000), c.WakeupInterval())
|
||||
assert.Equal(t, "1h34m9s", c.WakeupInterval().String())
|
||||
}
|
||||
|
||||
func TestConfig_AutoIndex(t *testing.T) {
|
||||
|
|
|
@ -139,10 +139,10 @@ var GlobalFlags = []cli.Flag{
|
|||
EnvVar: "PHOTOPRISM_WORKERS",
|
||||
Value: cpuid.CPU.PhysicalCores / 2,
|
||||
},
|
||||
cli.IntFlag{
|
||||
cli.StringFlag{
|
||||
Name: "wakeup-interval, i",
|
||||
Usage: "metadata, share & sync background worker wakeup interval in `SECONDS` (1-604800)",
|
||||
Value: DefaultWakeupIntervalSeconds,
|
||||
Usage: "`DURATION` between worker runs required for face recognition and index maintenance (1-86400s)",
|
||||
Value: DefaultWakeupInterval.String(),
|
||||
EnvVar: "PHOTOPRISM_WAKEUP_INTERVAL",
|
||||
},
|
||||
cli.IntFlag{
|
||||
|
|
|
@ -5,6 +5,9 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
|
@ -34,110 +37,110 @@ const (
|
|||
//
|
||||
// See https://github.com/photoprism/photoprism/issues/50#issuecomment-433856358
|
||||
type Options struct {
|
||||
Name string `json:"-"`
|
||||
Version string `json:"-"`
|
||||
Copyright string `json:"-"`
|
||||
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
Test bool `yaml:"-" json:"Test,omitempty" flag:"test"`
|
||||
Unsafe bool `yaml:"-" json:"-" flag:"unsafe"`
|
||||
Demo bool `yaml:"Demo" json:"-" flag:"demo"`
|
||||
Sponsor bool `yaml:"-" json:"-" flag:"sponsor"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"`
|
||||
Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`
|
||||
ConfigPath string `yaml:"ConfigPath" json:"-" flag:"config-path"`
|
||||
ConfigFile string `json:"-"`
|
||||
OriginalsPath string `yaml:"OriginalsPath" json:"-" flag:"originals-path"`
|
||||
OriginalsLimit int `yaml:"OriginalsLimit" json:"OriginalsLimit" flag:"originals-limit"`
|
||||
ResolutionLimit int `yaml:"ResolutionLimit" json:"ResolutionLimit" flag:"resolution-limit"`
|
||||
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
|
||||
TempPath string `yaml:"TempPath" json:"-" flag:"temp-path"`
|
||||
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
||||
AssetsPath string `yaml:"AssetsPath" json:"-" flag:"assets-path"`
|
||||
Workers int `yaml:"Workers" json:"Workers" flag:"workers"`
|
||||
WakeupInterval int `yaml:"WakeupInterval" json:"WakeupInterval" flag:"wakeup-interval"`
|
||||
AutoIndex int `yaml:"AutoIndex" json:"AutoIndex" flag:"auto-index"`
|
||||
AutoImport int `yaml:"AutoImport" json:"AutoImport" flag:"auto-import"`
|
||||
DisableWebDAV bool `yaml:"DisableWebDAV" json:"DisableWebDAV" flag:"disable-webdav"`
|
||||
DisableBackups bool `yaml:"DisableBackups" json:"DisableBackups" flag:"disable-backups"`
|
||||
DisableSettings bool `yaml:"DisableSettings" json:"-" flag:"disable-settings"`
|
||||
DisablePlaces bool `yaml:"DisablePlaces" json:"DisablePlaces" flag:"disable-places"`
|
||||
DisableDarktable bool `yaml:"DisableDarktable" json:"DisableDarktable" flag:"disable-darktable"`
|
||||
DisableRawtherapee bool `yaml:"DisableRawtherapee" json:"DisableRawtherapee" flag:"disable-rawtherapee"`
|
||||
DisableSips bool `yaml:"DisableSips" json:"DisableSips" flag:"disable-sips"`
|
||||
DisableHeifConvert bool `yaml:"DisableHeifConvert" json:"DisableHeifConvert" flag:"disable-heifconvert"`
|
||||
DisableTensorFlow bool `yaml:"DisableTensorFlow" json:"DisableTensorFlow" flag:"disable-tensorflow"`
|
||||
DisableFaces bool `yaml:"DisableFaces" json:"DisableFaces" flag:"disable-faces"`
|
||||
DisableClassification bool `yaml:"DisableClassification" json:"DisableClassification" flag:"disable-classification"`
|
||||
DisableFFmpeg bool `yaml:"DisableFFmpeg" json:"DisableFFmpeg" flag:"disable-ffmpeg"`
|
||||
DisableExifTool bool `yaml:"DisableExifTool" json:"DisableExifTool" flag:"disable-exiftool"`
|
||||
ExifBruteForce bool `yaml:"ExifBruteForce" json:"ExifBruteForce" flag:"exif-bruteforce"`
|
||||
RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"`
|
||||
DetectNSFW bool `yaml:"DetectNSFW" json:"DetectNSFW" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"UploadNSFW" json:"-" flag:"upload-nsfw"`
|
||||
DefaultTheme string `yaml:"DefaultTheme" json:"DefaultTheme" flag:"default-theme"`
|
||||
DefaultLocale string `yaml:"DefaultLocale" json:"DefaultLocale" flag:"default-locale"`
|
||||
AppIcon string `yaml:"AppIcon" json:"AppIcon" flag:"app-icon"`
|
||||
AppName string `yaml:"AppName" json:"AppName" flag:"app-name"`
|
||||
AppMode string `yaml:"AppMode" json:"AppMode" flag:"app-mode"`
|
||||
CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"`
|
||||
SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"`
|
||||
SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"`
|
||||
SiteTitle string `yaml:"SiteTitle" json:"SiteTitle" flag:"site-title"`
|
||||
SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"`
|
||||
SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"`
|
||||
SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"`
|
||||
Imprint string `yaml:"Imprint" json:"Imprint" flag:"imprint"`
|
||||
ImprintUrl string `yaml:"ImprintUrl" json:"ImprintUrl" flag:"imprint-url"`
|
||||
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
|
||||
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
||||
DatabaseServer string `yaml:"DatabaseServer" json:"-" flag:"database-server"`
|
||||
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
||||
DatabaseUser string `yaml:"DatabaseUser" json:"-" flag:"database-user"`
|
||||
DatabasePassword string `yaml:"DatabasePassword" json:"-" flag:"database-password"`
|
||||
DatabaseConns int `yaml:"DatabaseConns" json:"-" flag:"database-conns"`
|
||||
DatabaseConnsIdle int `yaml:"DatabaseConnsIdle" json:"-" flag:"database-conns-idle"`
|
||||
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
||||
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
|
||||
HttpMode string `yaml:"HttpMode" json:"-" flag:"http-mode"`
|
||||
HttpCompression string `yaml:"HttpCompression" json:"-" flag:"http-compression"`
|
||||
DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"`
|
||||
DarktableBlacklist string `yaml:"DarktableBlacklist" json:"-" flag:"darktable-blacklist"`
|
||||
RawtherapeeBin string `yaml:"RawtherapeeBin" json:"-" flag:"rawtherapee-bin"`
|
||||
RawtherapeeBlacklist string `yaml:"RawtherapeeBlacklist" json:"-" flag:"rawtherapee-blacklist"`
|
||||
SipsBin string `yaml:"SipsBin" json:"-" flag:"sips-bin"`
|
||||
HeifConvertBin string `yaml:"HeifConvertBin" json:"-" flag:"heifconvert-bin"`
|
||||
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
|
||||
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
|
||||
FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"`
|
||||
FFmpegBuffers int `yaml:"FFmpegBuffers" json:"FFmpegBuffers" flag:"ffmpeg-buffers"`
|
||||
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||
DetachServer bool `yaml:"DetachServer" json:"-" flag:"detach-server"`
|
||||
DownloadToken string `yaml:"DownloadToken" json:"-" flag:"download-token"`
|
||||
PreviewToken string `yaml:"PreviewToken" json:"-" flag:"preview-token"`
|
||||
ThumbFilter string `yaml:"ThumbFilter" json:"ThumbFilter" flag:"thumb-filter"`
|
||||
ThumbColorspace string `yaml:"ThumbColorspace" json:"ThumbColorspace" flag:"thumb-colorspace"`
|
||||
ThumbUncached bool `yaml:"ThumbUncached" json:"ThumbUncached" flag:"thumb-uncached"`
|
||||
ThumbSize int `yaml:"ThumbSize" json:"ThumbSize" flag:"thumb-size"`
|
||||
ThumbSizeUncached int `yaml:"ThumbSizeUncached" json:"ThumbSizeUncached" flag:"thumb-size-uncached"`
|
||||
JpegSize int `yaml:"JpegSize" json:"JpegSize" flag:"jpeg-size"`
|
||||
JpegQuality string `yaml:"JpegQuality" json:"JpegQuality" flag:"jpeg-quality"`
|
||||
FaceSize int `yaml:"-" json:"-" flag:"face-size"`
|
||||
FaceScore float64 `yaml:"-" json:"-" flag:"face-score"`
|
||||
FaceOverlap int `yaml:"-" json:"-" flag:"face-overlap"`
|
||||
FaceClusterSize int `yaml:"-" json:"-" flag:"face-cluster-size"`
|
||||
FaceClusterScore int `yaml:"-" json:"-" flag:"face-cluster-score"`
|
||||
FaceClusterCore int `yaml:"-" json:"-" flag:"face-cluster-core"`
|
||||
FaceClusterDist float64 `yaml:"-" json:"-" flag:"face-cluster-dist"`
|
||||
FaceMatchDist float64 `yaml:"-" json:"-" flag:"face-match-dist"`
|
||||
PIDFilename string `yaml:"PIDFilename" json:"-" flag:"pid-filename"`
|
||||
LogFilename string `yaml:"LogFilename" json:"-" flag:"log-filename"`
|
||||
Name string `json:"-"`
|
||||
Version string `json:"-"`
|
||||
Copyright string `json:"-"`
|
||||
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
Test bool `yaml:"-" json:"Test,omitempty" flag:"test"`
|
||||
Unsafe bool `yaml:"-" json:"-" flag:"unsafe"`
|
||||
Demo bool `yaml:"Demo" json:"-" flag:"demo"`
|
||||
Sponsor bool `yaml:"-" json:"-" flag:"sponsor"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"`
|
||||
Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`
|
||||
ConfigPath string `yaml:"ConfigPath" json:"-" flag:"config-path"`
|
||||
ConfigFile string `json:"-"`
|
||||
OriginalsPath string `yaml:"OriginalsPath" json:"-" flag:"originals-path"`
|
||||
OriginalsLimit int `yaml:"OriginalsLimit" json:"OriginalsLimit" flag:"originals-limit"`
|
||||
ResolutionLimit int `yaml:"ResolutionLimit" json:"ResolutionLimit" flag:"resolution-limit"`
|
||||
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
|
||||
TempPath string `yaml:"TempPath" json:"-" flag:"temp-path"`
|
||||
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
||||
AssetsPath string `yaml:"AssetsPath" json:"-" flag:"assets-path"`
|
||||
Workers int `yaml:"Workers" json:"Workers" flag:"workers"`
|
||||
WakeupInterval time.Duration `yaml:"WakeupInterval" json:"WakeupInterval" flag:"wakeup-interval"`
|
||||
AutoIndex int `yaml:"AutoIndex" json:"AutoIndex" flag:"auto-index"`
|
||||
AutoImport int `yaml:"AutoImport" json:"AutoImport" flag:"auto-import"`
|
||||
DisableWebDAV bool `yaml:"DisableWebDAV" json:"DisableWebDAV" flag:"disable-webdav"`
|
||||
DisableBackups bool `yaml:"DisableBackups" json:"DisableBackups" flag:"disable-backups"`
|
||||
DisableSettings bool `yaml:"DisableSettings" json:"-" flag:"disable-settings"`
|
||||
DisablePlaces bool `yaml:"DisablePlaces" json:"DisablePlaces" flag:"disable-places"`
|
||||
DisableDarktable bool `yaml:"DisableDarktable" json:"DisableDarktable" flag:"disable-darktable"`
|
||||
DisableRawtherapee bool `yaml:"DisableRawtherapee" json:"DisableRawtherapee" flag:"disable-rawtherapee"`
|
||||
DisableSips bool `yaml:"DisableSips" json:"DisableSips" flag:"disable-sips"`
|
||||
DisableHeifConvert bool `yaml:"DisableHeifConvert" json:"DisableHeifConvert" flag:"disable-heifconvert"`
|
||||
DisableTensorFlow bool `yaml:"DisableTensorFlow" json:"DisableTensorFlow" flag:"disable-tensorflow"`
|
||||
DisableFaces bool `yaml:"DisableFaces" json:"DisableFaces" flag:"disable-faces"`
|
||||
DisableClassification bool `yaml:"DisableClassification" json:"DisableClassification" flag:"disable-classification"`
|
||||
DisableFFmpeg bool `yaml:"DisableFFmpeg" json:"DisableFFmpeg" flag:"disable-ffmpeg"`
|
||||
DisableExifTool bool `yaml:"DisableExifTool" json:"DisableExifTool" flag:"disable-exiftool"`
|
||||
ExifBruteForce bool `yaml:"ExifBruteForce" json:"ExifBruteForce" flag:"exif-bruteforce"`
|
||||
RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"`
|
||||
DetectNSFW bool `yaml:"DetectNSFW" json:"DetectNSFW" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"UploadNSFW" json:"-" flag:"upload-nsfw"`
|
||||
DefaultTheme string `yaml:"DefaultTheme" json:"DefaultTheme" flag:"default-theme"`
|
||||
DefaultLocale string `yaml:"DefaultLocale" json:"DefaultLocale" flag:"default-locale"`
|
||||
AppIcon string `yaml:"AppIcon" json:"AppIcon" flag:"app-icon"`
|
||||
AppName string `yaml:"AppName" json:"AppName" flag:"app-name"`
|
||||
AppMode string `yaml:"AppMode" json:"AppMode" flag:"app-mode"`
|
||||
CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"`
|
||||
SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"`
|
||||
SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"`
|
||||
SiteTitle string `yaml:"SiteTitle" json:"SiteTitle" flag:"site-title"`
|
||||
SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"`
|
||||
SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"`
|
||||
SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"`
|
||||
Imprint string `yaml:"Imprint" json:"Imprint" flag:"imprint"`
|
||||
ImprintUrl string `yaml:"ImprintUrl" json:"ImprintUrl" flag:"imprint-url"`
|
||||
DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"`
|
||||
DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"`
|
||||
DatabaseServer string `yaml:"DatabaseServer" json:"-" flag:"database-server"`
|
||||
DatabaseName string `yaml:"DatabaseName" json:"-" flag:"database-name"`
|
||||
DatabaseUser string `yaml:"DatabaseUser" json:"-" flag:"database-user"`
|
||||
DatabasePassword string `yaml:"DatabasePassword" json:"-" flag:"database-password"`
|
||||
DatabaseConns int `yaml:"DatabaseConns" json:"-" flag:"database-conns"`
|
||||
DatabaseConnsIdle int `yaml:"DatabaseConnsIdle" json:"-" flag:"database-conns-idle"`
|
||||
HttpHost string `yaml:"HttpHost" json:"-" flag:"http-host"`
|
||||
HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"`
|
||||
HttpMode string `yaml:"HttpMode" json:"-" flag:"http-mode"`
|
||||
HttpCompression string `yaml:"HttpCompression" json:"-" flag:"http-compression"`
|
||||
DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"`
|
||||
DarktableBlacklist string `yaml:"DarktableBlacklist" json:"-" flag:"darktable-blacklist"`
|
||||
RawtherapeeBin string `yaml:"RawtherapeeBin" json:"-" flag:"rawtherapee-bin"`
|
||||
RawtherapeeBlacklist string `yaml:"RawtherapeeBlacklist" json:"-" flag:"rawtherapee-blacklist"`
|
||||
SipsBin string `yaml:"SipsBin" json:"-" flag:"sips-bin"`
|
||||
HeifConvertBin string `yaml:"HeifConvertBin" json:"-" flag:"heifconvert-bin"`
|
||||
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
|
||||
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
|
||||
FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"`
|
||||
FFmpegBuffers int `yaml:"FFmpegBuffers" json:"FFmpegBuffers" flag:"ffmpeg-buffers"`
|
||||
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||
DetachServer bool `yaml:"DetachServer" json:"-" flag:"detach-server"`
|
||||
DownloadToken string `yaml:"DownloadToken" json:"-" flag:"download-token"`
|
||||
PreviewToken string `yaml:"PreviewToken" json:"-" flag:"preview-token"`
|
||||
ThumbFilter string `yaml:"ThumbFilter" json:"ThumbFilter" flag:"thumb-filter"`
|
||||
ThumbColorspace string `yaml:"ThumbColorspace" json:"ThumbColorspace" flag:"thumb-colorspace"`
|
||||
ThumbUncached bool `yaml:"ThumbUncached" json:"ThumbUncached" flag:"thumb-uncached"`
|
||||
ThumbSize int `yaml:"ThumbSize" json:"ThumbSize" flag:"thumb-size"`
|
||||
ThumbSizeUncached int `yaml:"ThumbSizeUncached" json:"ThumbSizeUncached" flag:"thumb-size-uncached"`
|
||||
JpegSize int `yaml:"JpegSize" json:"JpegSize" flag:"jpeg-size"`
|
||||
JpegQuality string `yaml:"JpegQuality" json:"JpegQuality" flag:"jpeg-quality"`
|
||||
FaceSize int `yaml:"-" json:"-" flag:"face-size"`
|
||||
FaceScore float64 `yaml:"-" json:"-" flag:"face-score"`
|
||||
FaceOverlap int `yaml:"-" json:"-" flag:"face-overlap"`
|
||||
FaceClusterSize int `yaml:"-" json:"-" flag:"face-cluster-size"`
|
||||
FaceClusterScore int `yaml:"-" json:"-" flag:"face-cluster-score"`
|
||||
FaceClusterCore int `yaml:"-" json:"-" flag:"face-cluster-core"`
|
||||
FaceClusterDist float64 `yaml:"-" json:"-" flag:"face-cluster-dist"`
|
||||
FaceMatchDist float64 `yaml:"-" json:"-" flag:"face-match-dist"`
|
||||
PIDFilename string `yaml:"PIDFilename" json:"-" flag:"pid-filename"`
|
||||
LogFilename string `yaml:"LogFilename" json:"-" flag:"log-filename"`
|
||||
}
|
||||
|
||||
// NewOptions creates a new configuration entity by using two methods:
|
||||
|
@ -216,6 +219,24 @@ func (c *Options) SetContext(ctx *cli.Context) error {
|
|||
// Assign value to field with "flag" tag.
|
||||
if tagValue != "" {
|
||||
switch t := fieldValue.Interface().(type) {
|
||||
case time.Duration:
|
||||
var s string
|
||||
|
||||
// Get duration string.
|
||||
if ctx.IsSet(tagValue) {
|
||||
s = ctx.String(tagValue)
|
||||
} else if ctx.GlobalIsSet(tagValue) || fieldValue.Interface().(time.Duration) == 0 {
|
||||
s = ctx.GlobalString(tagValue)
|
||||
}
|
||||
|
||||
// Parse duration string.
|
||||
if s == "" {
|
||||
// Omit.
|
||||
} else if sec := txt.UInt(s); sec > 0 {
|
||||
fieldValue.Set(reflect.ValueOf(time.Duration(sec) * time.Second))
|
||||
} else if d, err := time.ParseDuration(s); err == nil {
|
||||
fieldValue.Set(reflect.ValueOf(d))
|
||||
}
|
||||
case float64:
|
||||
// Only if explicitly set or current value is empty (use default).
|
||||
if ctx.IsSet(tagValue) {
|
||||
|
|
|
@ -18,6 +18,7 @@ func TestNewOptions(t *testing.T) {
|
|||
assert.IsType(t, new(Options), c)
|
||||
|
||||
assert.Equal(t, fs.Abs("../../assets"), c.AssetsPath)
|
||||
assert.Equal(t, "1h34m9s", c.WakeupInterval.String())
|
||||
assert.False(t, c.Debug)
|
||||
assert.False(t, c.ReadOnly)
|
||||
}
|
||||
|
@ -36,6 +37,7 @@ func TestOptions_SetOptionsFromFile(t *testing.T) {
|
|||
assert.Equal(t, "/srv/photoprism/photos/originals", c.OriginalsPath)
|
||||
assert.Equal(t, "/srv/photoprism/photos/import", c.ImportPath)
|
||||
assert.Equal(t, "/srv/photoprism/temp", c.TempPath)
|
||||
assert.Equal(t, "1h34m9s", c.WakeupInterval.String())
|
||||
assert.NotEmpty(t, c.DatabaseDriver)
|
||||
assert.NotEmpty(t, c.DatabaseDsn)
|
||||
assert.Equal(t, 81, c.HttpPort)
|
||||
|
|
|
@ -198,6 +198,7 @@ func CliTestContext() *cli.Context {
|
|||
globalSet.String("darktable-cli", config.DarktableBin, "doc")
|
||||
globalSet.String("darktable-blacklist", config.DarktableBlacklist, "doc")
|
||||
globalSet.String("admin-password", config.DarktableBin, "doc")
|
||||
globalSet.String("wakeup-interval", "1h34m9s", "doc")
|
||||
globalSet.Bool("detect-nsfw", config.DetectNSFW, "doc")
|
||||
globalSet.Int("auto-index", config.AutoIndex, "doc")
|
||||
globalSet.Int("auto-import", config.AutoImport, "doc")
|
||||
|
@ -219,6 +220,7 @@ func CliTestContext() *cli.Context {
|
|||
LogError(c.Set("darktable-cli", config.DarktableBin))
|
||||
LogError(c.Set("darktable-blacklist", "raf,cr3"))
|
||||
LogError(c.Set("admin-password", config.AdminPassword))
|
||||
LogError(c.Set("wakeup-interval", "1h34m9s"))
|
||||
LogError(c.Set("detect-nsfw", "true"))
|
||||
LogError(c.Set("auto-index", strconv.Itoa(config.AutoIndex)))
|
||||
LogError(c.Set("auto-import", strconv.Itoa(config.AutoImport)))
|
||||
|
|
|
@ -43,9 +43,8 @@ func gettext(s string) string {
|
|||
return gotext.Get(s)
|
||||
}
|
||||
|
||||
func Msg(id Message, params ...interface{}) string {
|
||||
msg := gotext.Get(Messages[id])
|
||||
|
||||
// msgParams replaces message params with the actual values.
|
||||
func msgParams(msg string, params ...interface{}) string {
|
||||
if strings.Contains(msg, "%") {
|
||||
msg = fmt.Sprintf(msg, params...)
|
||||
}
|
||||
|
@ -53,6 +52,10 @@ func Msg(id Message, params ...interface{}) string {
|
|||
return msg
|
||||
}
|
||||
|
||||
func Msg(id Message, params ...interface{}) string {
|
||||
return msgParams(gotext.Get(Messages[id]), params...)
|
||||
}
|
||||
|
||||
func Error(id Message, params ...interface{}) error {
|
||||
return errors.New(strings.ToLower(Msg(id, params...)))
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ const (
|
|||
ErrInvalidLink
|
||||
ErrInvalidName
|
||||
ErrBusy
|
||||
ErrWakeupInterval
|
||||
|
||||
MsgChangesSaved
|
||||
MsgAlbumCreated
|
||||
|
@ -114,6 +115,7 @@ var Messages = MessageMap{
|
|||
ErrInvalidLink: gettext("Invalid link"),
|
||||
ErrInvalidName: gettext("Invalid name"),
|
||||
ErrBusy: gettext("Busy, please try again later"),
|
||||
ErrWakeupInterval: gettext("The wakeup interval is %s, but must be 1h or less"),
|
||||
|
||||
// Info and confirmation messages:
|
||||
MsgChangesSaved: gettext("Changes successfully saved"),
|
||||
|
|
Loading…
Reference in a new issue