mirror of
https://github.com/stashapp/stash.git
synced 2026-01-06 07:38:49 +01:00
Fix: config race conditions with RWMutex (#1645)
* Fix: config race conditions with RWMutex Added RWMutex to config.Instance which read or write locks all instances where viper is used. Refactored checksum manager to only use config and not viper directly anymore. All stash viper operations are now "behind" the config.Instance and thus mutex "protected".
This commit is contained in:
parent
da8803925c
commit
cb6dab3c5f
4 changed files with 272 additions and 13 deletions
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
|
|
@ -26,16 +25,12 @@ func setInitialMD5Config(txnManager models.TransactionManager) {
|
|||
|
||||
usingMD5 := count != 0
|
||||
defaultAlgorithm := models.HashAlgorithmOshash
|
||||
|
||||
if usingMD5 {
|
||||
defaultAlgorithm = models.HashAlgorithmMd5
|
||||
}
|
||||
|
||||
// TODO - this should use the config instance
|
||||
viper.SetDefault(config.VideoFileNamingAlgorithm, defaultAlgorithm)
|
||||
viper.SetDefault(config.CalculateMD5, usingMD5)
|
||||
|
||||
config := config.GetInstance()
|
||||
config.SetChecksumDefaultValues(defaultAlgorithm, usingMD5)
|
||||
if err := config.Write(); err != nil {
|
||||
logger.Errorf("Error while writing configuration file: %s", err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"sync"
|
||||
//"github.com/sasha-s/go-deadlock" // if you have deadlock issues
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
|
@ -165,6 +168,8 @@ func HasTLSConfig() bool {
|
|||
type Instance struct {
|
||||
cpuProfilePath string
|
||||
isNewSystem bool
|
||||
sync.RWMutex
|
||||
//deadlock.RWMutex // for deadlock testing/issues
|
||||
}
|
||||
|
||||
var instance *Instance
|
||||
|
|
@ -181,6 +186,8 @@ func (i *Instance) IsNewSystem() bool {
|
|||
}
|
||||
|
||||
func (i *Instance) SetConfigFile(fn string) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetConfigFile(fn)
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +199,8 @@ func (i *Instance) GetCPUProfilePath() string {
|
|||
}
|
||||
|
||||
func (i *Instance) Set(key string, value interface{}) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.Set(key, value)
|
||||
}
|
||||
|
||||
|
|
@ -205,11 +214,15 @@ func (i *Instance) SetPassword(value string) {
|
|||
}
|
||||
|
||||
func (i *Instance) Write() error {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
// GetConfigFile returns the full path to the used configuration file.
|
||||
func (i *Instance) GetConfigFile() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.ConfigFileUsed()
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +239,8 @@ func (i *Instance) GetDefaultDatabaseFilePath() string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetStashPaths() []*models.StashConfig {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
var ret []*models.StashConfig
|
||||
if err := viper.UnmarshalKey(Stash, &ret); err != nil || len(ret) == 0 {
|
||||
// fallback to legacy format
|
||||
|
|
@ -243,30 +258,44 @@ func (i *Instance) GetStashPaths() []*models.StashConfig {
|
|||
}
|
||||
|
||||
func (i *Instance) GetConfigFilePath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.ConfigFileUsed()
|
||||
}
|
||||
|
||||
func (i *Instance) GetCachePath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Cache)
|
||||
}
|
||||
|
||||
func (i *Instance) GetGeneratedPath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Generated)
|
||||
}
|
||||
|
||||
func (i *Instance) GetMetadataPath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Metadata)
|
||||
}
|
||||
|
||||
func (i *Instance) GetDatabasePath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Database)
|
||||
}
|
||||
|
||||
func (i *Instance) GetJWTSignKey() []byte {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return []byte(viper.GetString(JWTSignKey))
|
||||
}
|
||||
|
||||
func (i *Instance) GetSessionStoreKey() []byte {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return []byte(viper.GetString(SessionStoreKey))
|
||||
}
|
||||
|
||||
|
|
@ -279,14 +308,20 @@ func (i *Instance) GetDefaultScrapersPath() string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetExcludes() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetStringSlice(Exclude)
|
||||
}
|
||||
|
||||
func (i *Instance) GetImageExcludes() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetStringSlice(ImageExclude)
|
||||
}
|
||||
|
||||
func (i *Instance) GetVideoExtensions() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetStringSlice(VideoExtensions)
|
||||
if ret == nil {
|
||||
ret = defaultVideoExtensions
|
||||
|
|
@ -295,6 +330,8 @@ func (i *Instance) GetVideoExtensions() []string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetImageExtensions() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetStringSlice(ImageExtensions)
|
||||
if ret == nil {
|
||||
ret = defaultImageExtensions
|
||||
|
|
@ -303,6 +340,8 @@ func (i *Instance) GetImageExtensions() []string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetGalleryExtensions() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetStringSlice(GalleryExtensions)
|
||||
if ret == nil {
|
||||
ret = defaultGalleryExtensions
|
||||
|
|
@ -311,10 +350,14 @@ func (i *Instance) GetGalleryExtensions() []string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetCreateGalleriesFromFolders() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetBool(CreateGalleriesFromFolders)
|
||||
}
|
||||
|
||||
func (i *Instance) GetLanguage() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetString(Language)
|
||||
|
||||
// default to English
|
||||
|
|
@ -328,12 +371,16 @@ func (i *Instance) GetLanguage() string {
|
|||
// IsCalculateMD5 returns true if MD5 checksums should be generated for
|
||||
// scene video files.
|
||||
func (i *Instance) IsCalculateMD5() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetBool(CalculateMD5)
|
||||
}
|
||||
|
||||
// GetVideoFileNamingAlgorithm returns what hash algorithm should be used for
|
||||
// naming generated scene video files.
|
||||
func (i *Instance) GetVideoFileNamingAlgorithm() models.HashAlgorithm {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetString(VideoFileNamingAlgorithm)
|
||||
|
||||
// default to oshash
|
||||
|
|
@ -345,22 +392,30 @@ func (i *Instance) GetVideoFileNamingAlgorithm() models.HashAlgorithm {
|
|||
}
|
||||
|
||||
func (i *Instance) GetScrapersPath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(ScrapersPath)
|
||||
}
|
||||
|
||||
func (i *Instance) GetScraperUserAgent() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(ScraperUserAgent)
|
||||
}
|
||||
|
||||
// GetScraperCDPPath gets the path to the Chrome executable or remote address
|
||||
// to an instance of Chrome.
|
||||
func (i *Instance) GetScraperCDPPath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(ScraperCDPPath)
|
||||
}
|
||||
|
||||
// GetScraperCertCheck returns true if the scraper should check for insecure
|
||||
// certificates when fetching an image or a page.
|
||||
func (i *Instance) GetScraperCertCheck() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := true
|
||||
if viper.IsSet(ScraperCertCheck) {
|
||||
ret = viper.GetBool(ScraperCertCheck)
|
||||
|
|
@ -370,6 +425,8 @@ func (i *Instance) GetScraperCertCheck() bool {
|
|||
}
|
||||
|
||||
func (i *Instance) GetScraperExcludeTagPatterns() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
var ret []string
|
||||
if viper.IsSet(ScraperExcludeTagPatterns) {
|
||||
ret = viper.GetStringSlice(ScraperExcludeTagPatterns)
|
||||
|
|
@ -379,6 +436,8 @@ func (i *Instance) GetScraperExcludeTagPatterns() []string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetStashBoxes() []*models.StashBox {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
var boxes []*models.StashBox
|
||||
viper.UnmarshalKey(StashBoxes, &boxes)
|
||||
return boxes
|
||||
|
|
@ -392,34 +451,48 @@ func (i *Instance) GetDefaultPluginsPath() string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetPluginsPath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(PluginsPath)
|
||||
}
|
||||
|
||||
func (i *Instance) GetHost() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Host)
|
||||
}
|
||||
|
||||
func (i *Instance) GetPort() int {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetInt(Port)
|
||||
}
|
||||
|
||||
func (i *Instance) GetExternalHost() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(ExternalHost)
|
||||
}
|
||||
|
||||
// GetPreviewSegmentDuration returns the duration of a single segment in a
|
||||
// scene preview file, in seconds.
|
||||
func (i *Instance) GetPreviewSegmentDuration() float64 {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetFloat64(PreviewSegmentDuration)
|
||||
}
|
||||
|
||||
// GetParallelTasks returns the number of parallel tasks that should be started
|
||||
// by scan or generate task.
|
||||
func (i *Instance) GetParallelTasks() int {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetInt(ParallelTasks)
|
||||
}
|
||||
|
||||
func (i *Instance) GetParallelTasksWithAutoDetection() int {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
parallelTasks := viper.GetInt(ParallelTasks)
|
||||
if parallelTasks <= 0 {
|
||||
parallelTasks = (runtime.NumCPU() / 4) + 1
|
||||
|
|
@ -428,11 +501,15 @@ func (i *Instance) GetParallelTasksWithAutoDetection() int {
|
|||
}
|
||||
|
||||
func (i *Instance) GetPreviewAudio() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetBool(PreviewAudio)
|
||||
}
|
||||
|
||||
// GetPreviewSegments returns the amount of segments in a scene preview file.
|
||||
func (i *Instance) GetPreviewSegments() int {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetInt(PreviewSegments)
|
||||
}
|
||||
|
||||
|
|
@ -443,6 +520,8 @@ func (i *Instance) GetPreviewSegments() int {
|
|||
// in the preview. If the value is suffixed with a '%' character (for example
|
||||
// '2%'), then it is interpreted as a proportion of the total video duration.
|
||||
func (i *Instance) GetPreviewExcludeStart() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(PreviewExcludeStart)
|
||||
}
|
||||
|
||||
|
|
@ -452,12 +531,16 @@ func (i *Instance) GetPreviewExcludeStart() string {
|
|||
// when generating previews. If the value is suffixed with a '%' character,
|
||||
// then it is interpreted as a proportion of the total video duration.
|
||||
func (i *Instance) GetPreviewExcludeEnd() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(PreviewExcludeEnd)
|
||||
}
|
||||
|
||||
// GetPreviewPreset returns the preset when generating previews. Defaults to
|
||||
// Slow.
|
||||
func (i *Instance) GetPreviewPreset() models.PreviewPreset {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetString(PreviewPreset)
|
||||
|
||||
// default to slow
|
||||
|
|
@ -469,6 +552,8 @@ func (i *Instance) GetPreviewPreset() models.PreviewPreset {
|
|||
}
|
||||
|
||||
func (i *Instance) GetMaxTranscodeSize() models.StreamingResolutionEnum {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetString(MaxTranscodeSize)
|
||||
|
||||
// default to original
|
||||
|
|
@ -480,6 +565,8 @@ func (i *Instance) GetMaxTranscodeSize() models.StreamingResolutionEnum {
|
|||
}
|
||||
|
||||
func (i *Instance) GetMaxStreamingTranscodeSize() models.StreamingResolutionEnum {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := viper.GetString(MaxStreamingTranscodeSize)
|
||||
|
||||
// default to original
|
||||
|
|
@ -491,19 +578,27 @@ func (i *Instance) GetMaxStreamingTranscodeSize() models.StreamingResolutionEnum
|
|||
}
|
||||
|
||||
func (i *Instance) GetAPIKey() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(ApiKey)
|
||||
}
|
||||
|
||||
func (i *Instance) GetUsername() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Username)
|
||||
}
|
||||
|
||||
func (i *Instance) GetPasswordHash() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Password)
|
||||
}
|
||||
|
||||
func (i *Instance) GetCredentials() (string, string) {
|
||||
if i.HasCredentials() {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(Username), viper.GetString(Password)
|
||||
}
|
||||
|
||||
|
|
@ -511,12 +606,14 @@ func (i *Instance) GetCredentials() (string, string) {
|
|||
}
|
||||
|
||||
func (i *Instance) HasCredentials() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
if !viper.IsSet(Username) || !viper.IsSet(Password) {
|
||||
return false
|
||||
}
|
||||
|
||||
username := i.GetUsername()
|
||||
pwHash := i.GetPasswordHash()
|
||||
username := viper.GetString(Username)
|
||||
pwHash := viper.GetString(Password)
|
||||
|
||||
return username != "" && pwHash != ""
|
||||
}
|
||||
|
|
@ -565,6 +662,8 @@ func (i *Instance) ValidateStashBoxes(boxes []*models.StashBoxInput) error {
|
|||
// GetMaxSessionAge gets the maximum age for session cookies, in seconds.
|
||||
// Session cookie expiry times are refreshed every request.
|
||||
func (i *Instance) GetMaxSessionAge() int {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(MaxSessionAge, DefaultMaxSessionAge)
|
||||
return viper.GetInt(MaxSessionAge)
|
||||
}
|
||||
|
|
@ -572,15 +671,21 @@ func (i *Instance) GetMaxSessionAge() int {
|
|||
// GetCustomServedFolders gets the map of custom paths to their applicable
|
||||
// filesystem locations
|
||||
func (i *Instance) GetCustomServedFolders() URLMap {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetStringMapString(CustomServedFolders)
|
||||
}
|
||||
|
||||
func (i *Instance) GetCustomUILocation() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(CustomUILocation)
|
||||
}
|
||||
|
||||
// Interface options
|
||||
func (i *Instance) GetMenuItems() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
if viper.IsSet(MenuItems) {
|
||||
return viper.GetStringSlice(MenuItems)
|
||||
}
|
||||
|
|
@ -588,46 +693,63 @@ func (i *Instance) GetMenuItems() []string {
|
|||
}
|
||||
|
||||
func (i *Instance) GetSoundOnPreview() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetBool(SoundOnPreview)
|
||||
}
|
||||
|
||||
func (i *Instance) GetWallShowTitle() bool {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(WallShowTitle, true)
|
||||
return viper.GetBool(WallShowTitle)
|
||||
}
|
||||
|
||||
func (i *Instance) GetCustomPerformerImageLocation() string {
|
||||
// don't set the default, as it causes race condition crashes
|
||||
// viper.SetDefault(CustomPerformerImageLocation, "")
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(CustomPerformerImageLocation, "")
|
||||
return viper.GetString(CustomPerformerImageLocation)
|
||||
}
|
||||
|
||||
func (i *Instance) GetWallPlayback() string {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(WallPlayback, "video")
|
||||
return viper.GetString(WallPlayback)
|
||||
}
|
||||
|
||||
func (i *Instance) GetMaximumLoopDuration() int {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(MaximumLoopDuration, 0)
|
||||
return viper.GetInt(MaximumLoopDuration)
|
||||
}
|
||||
|
||||
func (i *Instance) GetAutostartVideo() bool {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(AutostartVideo, false)
|
||||
return viper.GetBool(AutostartVideo)
|
||||
}
|
||||
|
||||
func (i *Instance) GetShowStudioAsText() bool {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(ShowStudioAsText, false)
|
||||
return viper.GetBool(ShowStudioAsText)
|
||||
}
|
||||
|
||||
func (i *Instance) GetSlideshowDelay() int {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(SlideshowDelay, 5000)
|
||||
return viper.GetInt(SlideshowDelay)
|
||||
}
|
||||
|
||||
func (i *Instance) GetCSSPath() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
// use custom.css in the same directory as the config file
|
||||
configFileUsed := viper.ConfigFileUsed()
|
||||
configDir := filepath.Dir(configFileUsed)
|
||||
|
|
@ -655,6 +777,8 @@ func (i *Instance) GetCSS() string {
|
|||
}
|
||||
|
||||
func (i *Instance) SetCSS(css string) {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
fn := i.GetCSSPath()
|
||||
|
||||
buf := []byte(css)
|
||||
|
|
@ -663,39 +787,53 @@ func (i *Instance) SetCSS(css string) {
|
|||
}
|
||||
|
||||
func (i *Instance) GetCSSEnabled() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetBool(CSSEnabled)
|
||||
}
|
||||
|
||||
func (i *Instance) GetHandyKey() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(HandyKey)
|
||||
}
|
||||
|
||||
// GetDLNAServerName returns the visible name of the DLNA server. If empty,
|
||||
// "stash" will be used.
|
||||
func (i *Instance) GetDLNAServerName() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(DLNAServerName)
|
||||
}
|
||||
|
||||
// GetDLNADefaultEnabled returns true if the DLNA is enabled by default.
|
||||
func (i *Instance) GetDLNADefaultEnabled() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetBool(DLNADefaultEnabled)
|
||||
}
|
||||
|
||||
// GetDLNADefaultIPWhitelist returns a list of IP addresses/wildcards that
|
||||
// are allowed to use the DLNA service.
|
||||
func (i *Instance) GetDLNADefaultIPWhitelist() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetStringSlice(DLNADefaultIPWhitelist)
|
||||
}
|
||||
|
||||
// GetDLNAInterfaces returns a list of interface names to expose DLNA on. If
|
||||
// empty, runs on all interfaces.
|
||||
func (i *Instance) GetDLNAInterfaces() []string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetStringSlice(DLNAInterfaces)
|
||||
}
|
||||
|
||||
// GetLogFile returns the filename of the file to output logs to.
|
||||
// An empty string means that file logging will be disabled.
|
||||
func (i *Instance) GetLogFile() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
return viper.GetString(LogFile)
|
||||
}
|
||||
|
||||
|
|
@ -703,6 +841,8 @@ func (i *Instance) GetLogFile() string {
|
|||
// in addition to writing to a log file. Logging will be output to the
|
||||
// terminal if file logging is disabled. Defaults to true.
|
||||
func (i *Instance) GetLogOut() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := true
|
||||
if viper.IsSet(LogOut) {
|
||||
ret = viper.GetBool(LogOut)
|
||||
|
|
@ -714,6 +854,8 @@ func (i *Instance) GetLogOut() bool {
|
|||
// GetLogLevel returns the lowest log level to write to the log.
|
||||
// Should be one of "Debug", "Info", "Warning", "Error"
|
||||
func (i *Instance) GetLogLevel() string {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
const defaultValue = "Info"
|
||||
|
||||
value := viper.GetString(LogLevel)
|
||||
|
|
@ -727,6 +869,8 @@ func (i *Instance) GetLogLevel() string {
|
|||
// GetLogAccess returns true if http requests should be logged to the terminal.
|
||||
// HTTP requests are not logged to the log file. Defaults to true.
|
||||
func (i *Instance) GetLogAccess() bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := true
|
||||
if viper.IsSet(LogAccess) {
|
||||
ret = viper.GetBool(LogAccess)
|
||||
|
|
@ -737,6 +881,8 @@ func (i *Instance) GetLogAccess() bool {
|
|||
|
||||
// Max allowed graphql upload size in megabytes
|
||||
func (i *Instance) GetMaxUploadSize() int64 {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
ret := int64(1024)
|
||||
if viper.IsSet(MaxUploadSize) {
|
||||
ret = viper.GetInt64(MaxUploadSize)
|
||||
|
|
@ -745,6 +891,8 @@ func (i *Instance) GetMaxUploadSize() int64 {
|
|||
}
|
||||
|
||||
func (i *Instance) Validate() error {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
mandatoryPaths := []string{
|
||||
Database,
|
||||
Generated,
|
||||
|
|
@ -767,7 +915,22 @@ func (i *Instance) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *Instance) SetChecksumDefaultValues(defaultAlgorithm models.HashAlgorithm, usingMD5 bool) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(VideoFileNamingAlgorithm, defaultAlgorithm)
|
||||
viper.SetDefault(CalculateMD5, usingMD5)
|
||||
}
|
||||
|
||||
func (i *Instance) setDefaultValues() error {
|
||||
|
||||
// read data before write lock scope
|
||||
defaultDatabaseFilePath := i.GetDefaultDatabaseFilePath()
|
||||
defaultScrapersPath := i.GetDefaultScrapersPath()
|
||||
defaultPluginsPath := i.GetDefaultPluginsPath()
|
||||
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
viper.SetDefault(ParallelTasks, parallelTasksDefault)
|
||||
viper.SetDefault(PreviewSegmentDuration, previewSegmentDurationDefault)
|
||||
viper.SetDefault(PreviewSegments, previewSegmentsDefault)
|
||||
|
|
@ -776,14 +939,14 @@ func (i *Instance) setDefaultValues() error {
|
|||
viper.SetDefault(PreviewAudio, previewAudioDefault)
|
||||
viper.SetDefault(SoundOnPreview, false)
|
||||
|
||||
viper.SetDefault(Database, i.GetDefaultDatabaseFilePath())
|
||||
viper.SetDefault(Database, defaultDatabaseFilePath)
|
||||
|
||||
// Set generated to the metadata path for backwards compat
|
||||
viper.SetDefault(Generated, viper.GetString(Metadata))
|
||||
|
||||
// Set default scrapers and plugins paths
|
||||
viper.SetDefault(ScrapersPath, i.GetDefaultScrapersPath())
|
||||
viper.SetDefault(PluginsPath, i.GetDefaultPluginsPath())
|
||||
viper.SetDefault(ScrapersPath, defaultScrapersPath)
|
||||
viper.SetDefault(PluginsPath, defaultPluginsPath)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
|
|
|
|||
100
pkg/manager/config/config_concurrency_test.go
Normal file
100
pkg/manager/config/config_concurrency_test.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// should be run with -race
|
||||
func TestConcurrentConfigAccess(t *testing.T) {
|
||||
i := GetInstance()
|
||||
|
||||
const workers = 8
|
||||
//const loops = 1000
|
||||
const loops = 200
|
||||
var wg sync.WaitGroup
|
||||
for t := 0; t < workers; t++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for l := 0; l < loops; l++ {
|
||||
i.SetInitialConfig()
|
||||
|
||||
i.HasCredentials()
|
||||
i.GetCPUProfilePath()
|
||||
i.GetConfigFile()
|
||||
i.GetConfigPath()
|
||||
i.GetDefaultDatabaseFilePath()
|
||||
i.GetStashPaths()
|
||||
i.GetConfigFilePath()
|
||||
i.Set(Cache, i.GetCachePath())
|
||||
i.Set(Generated, i.GetGeneratedPath())
|
||||
i.Set(Metadata, i.GetMetadataPath())
|
||||
i.Set(Database, i.GetDatabasePath())
|
||||
i.Set(JWTSignKey, i.GetJWTSignKey())
|
||||
i.Set(SessionStoreKey, i.GetSessionStoreKey())
|
||||
i.GetDefaultScrapersPath()
|
||||
i.Set(Exclude, i.GetExcludes())
|
||||
i.Set(ImageExclude, i.GetImageExcludes())
|
||||
i.Set(VideoExtensions, i.GetVideoExtensions())
|
||||
i.Set(ImageExtensions, i.GetImageExtensions())
|
||||
i.Set(GalleryExtensions, i.GetGalleryExtensions())
|
||||
i.Set(CreateGalleriesFromFolders, i.GetCreateGalleriesFromFolders())
|
||||
i.Set(Language, i.GetLanguage())
|
||||
i.Set(VideoFileNamingAlgorithm, i.GetVideoFileNamingAlgorithm())
|
||||
i.Set(ScrapersPath, i.GetScrapersPath())
|
||||
i.Set(ScraperUserAgent, i.GetScraperUserAgent())
|
||||
i.Set(ScraperCDPPath, i.GetScraperCDPPath())
|
||||
i.Set(ScraperCertCheck, i.GetScraperCertCheck())
|
||||
i.Set(ScraperExcludeTagPatterns, i.GetScraperExcludeTagPatterns())
|
||||
i.Set(StashBoxes, i.GetStashBoxes())
|
||||
i.GetDefaultPluginsPath()
|
||||
i.Set(PluginsPath, i.GetPluginsPath())
|
||||
i.Set(Host, i.GetHost())
|
||||
i.Set(Port, i.GetPort())
|
||||
i.Set(ExternalHost, i.GetExternalHost())
|
||||
i.Set(PreviewSegmentDuration, i.GetPreviewSegmentDuration())
|
||||
i.Set(ParallelTasks, i.GetParallelTasks())
|
||||
i.Set(ParallelTasks, i.GetParallelTasksWithAutoDetection())
|
||||
i.Set(PreviewAudio, i.GetPreviewAudio())
|
||||
i.Set(PreviewSegments, i.GetPreviewSegments())
|
||||
i.Set(PreviewExcludeStart, i.GetPreviewExcludeStart())
|
||||
i.Set(PreviewExcludeEnd, i.GetPreviewExcludeEnd())
|
||||
i.Set(PreviewPreset, i.GetPreviewPreset())
|
||||
i.Set(MaxTranscodeSize, i.GetMaxTranscodeSize())
|
||||
i.Set(MaxStreamingTranscodeSize, i.GetMaxStreamingTranscodeSize())
|
||||
i.Set(ApiKey, i.GetAPIKey())
|
||||
i.Set(Username, i.GetUsername())
|
||||
i.Set(Password, i.GetPasswordHash())
|
||||
i.GetCredentials()
|
||||
i.Set(MaxSessionAge, i.GetMaxSessionAge())
|
||||
i.Set(CustomServedFolders, i.GetCustomServedFolders())
|
||||
i.Set(CustomUILocation, i.GetCustomUILocation())
|
||||
i.Set(MenuItems, i.GetMenuItems())
|
||||
i.Set(SoundOnPreview, i.GetSoundOnPreview())
|
||||
i.Set(WallShowTitle, i.GetWallShowTitle())
|
||||
i.Set(CustomPerformerImageLocation, i.GetCustomPerformerImageLocation())
|
||||
i.Set(WallPlayback, i.GetWallPlayback())
|
||||
i.Set(MaximumLoopDuration, i.GetMaximumLoopDuration())
|
||||
i.Set(AutostartVideo, i.GetAutostartVideo())
|
||||
i.Set(ShowStudioAsText, i.GetShowStudioAsText())
|
||||
i.Set(SlideshowDelay, i.GetSlideshowDelay())
|
||||
i.GetCSSPath()
|
||||
i.GetCSS()
|
||||
i.Set(CSSEnabled, i.GetCSSEnabled())
|
||||
i.Set(HandyKey, i.GetHandyKey())
|
||||
i.Set(DLNAServerName, i.GetDLNAServerName())
|
||||
i.Set(DLNADefaultEnabled, i.GetDLNADefaultEnabled())
|
||||
i.Set(DLNADefaultIPWhitelist, i.GetDLNADefaultIPWhitelist())
|
||||
i.Set(DLNAInterfaces, i.GetDLNAInterfaces())
|
||||
i.Set(LogFile, i.GetLogFile())
|
||||
i.Set(LogOut, i.GetLogOut())
|
||||
i.Set(LogLevel, i.GetLogLevel())
|
||||
i.Set(LogAccess, i.GetLogAccess())
|
||||
i.Set(MaxUploadSize, i.GetMaxUploadSize())
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
* Added de-DE language option. ([#1578](https://github.com/stashapp/stash/pull/1578))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
* Fix race condition panic when reading and writing config concurrently. ([#1645](https://github.com/stashapp/stash/issues/1343))
|
||||
* Fix performance issue on Studios page getting studio image count. ([#1643](https://github.com/stashapp/stash/pull/1643))
|
||||
* Regenerate scene phash if overwrite flag is set. ([#1633](https://github.com/stashapp/stash/pull/1633))
|
||||
* Create .stash directory in $HOME only if required. ([#1623](https://github.com/stashapp/stash/pull/1623))
|
||||
|
|
|
|||
Loading…
Reference in a new issue