Refactor system setting (#27000)
This PR reduces the complexity of the system setting system. It only needs one line to introduce a new option, and the option can be used anywhere out-of-box. It is still high-performant (and more performant) because the config values are cached in the config system.
This commit is contained in:
parent
976d1760ac
commit
9f8d59858a
21 changed files with 429 additions and 525 deletions
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -103,12 +102,6 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
|
|||
assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified)
|
||||
}
|
||||
|
||||
func initGravatarSource(t *testing.T) {
|
||||
setting.GravatarSource = "https://secure.gravatar.com/avatar"
|
||||
err := system_model.Init(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPushCommits_AvatarLink(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
@ -132,7 +125,7 @@ func TestPushCommits_AvatarLink(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
initGravatarSource(t)
|
||||
setting.GravatarSource = "https://secure.gravatar.com/avatar"
|
||||
|
||||
assert.Equal(t,
|
||||
"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor),
|
||||
|
|
55
modules/setting/config.go
Normal file
55
modules/setting/config.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting/config"
|
||||
)
|
||||
|
||||
type PictureStruct struct {
|
||||
DisableGravatar *config.Value[bool]
|
||||
EnableFederatedAvatar *config.Value[bool]
|
||||
}
|
||||
|
||||
type ConfigStruct struct {
|
||||
Picture *PictureStruct
|
||||
}
|
||||
|
||||
var (
|
||||
defaultConfig *ConfigStruct
|
||||
defaultConfigOnce sync.Once
|
||||
)
|
||||
|
||||
func initDefaultConfig() {
|
||||
config.SetCfgSecKeyGetter(&cfgSecKeyGetter{})
|
||||
defaultConfig = &ConfigStruct{
|
||||
Picture: &PictureStruct{
|
||||
DisableGravatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}, "picture.disable_gravatar"),
|
||||
EnableFederatedAvatar: config.Bool(false, config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}, "picture.enable_federated_avatar"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Config() *ConfigStruct {
|
||||
defaultConfigOnce.Do(initDefaultConfig)
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
type cfgSecKeyGetter struct{}
|
||||
|
||||
func (c cfgSecKeyGetter) GetValue(sec, key string) (v string, has bool) {
|
||||
cfgSec, err := CfgProvider.GetSection(sec)
|
||||
if err != nil {
|
||||
log.Error("Unable to get config section: %q", sec)
|
||||
return "", false
|
||||
}
|
||||
cfgKey := ConfigSectionKey(cfgSec, key)
|
||||
if cfgKey == nil {
|
||||
return "", false
|
||||
}
|
||||
return cfgKey.Value(), true
|
||||
}
|
49
modules/setting/config/getter.go
Normal file
49
modules/setting/config/getter.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var getterMu sync.RWMutex
|
||||
|
||||
type CfgSecKeyGetter interface {
|
||||
GetValue(sec, key string) (v string, has bool)
|
||||
}
|
||||
|
||||
var cfgSecKeyGetterInternal CfgSecKeyGetter
|
||||
|
||||
func SetCfgSecKeyGetter(p CfgSecKeyGetter) {
|
||||
getterMu.Lock()
|
||||
cfgSecKeyGetterInternal = p
|
||||
getterMu.Unlock()
|
||||
}
|
||||
|
||||
func GetCfgSecKeyGetter() CfgSecKeyGetter {
|
||||
getterMu.RLock()
|
||||
defer getterMu.RUnlock()
|
||||
return cfgSecKeyGetterInternal
|
||||
}
|
||||
|
||||
type DynKeyGetter interface {
|
||||
GetValue(ctx context.Context, key string) (v string, has bool)
|
||||
GetRevision(ctx context.Context) int
|
||||
InvalidateCache()
|
||||
}
|
||||
|
||||
var dynKeyGetterInternal DynKeyGetter
|
||||
|
||||
func SetDynGetter(p DynKeyGetter) {
|
||||
getterMu.Lock()
|
||||
dynKeyGetterInternal = p
|
||||
getterMu.Unlock()
|
||||
}
|
||||
|
||||
func GetDynGetter() DynKeyGetter {
|
||||
getterMu.RLock()
|
||||
defer getterMu.RUnlock()
|
||||
return dynKeyGetterInternal
|
||||
}
|
81
modules/setting/config/value.go
Normal file
81
modules/setting/config/value.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type CfgSecKey struct {
|
||||
Sec, Key string
|
||||
}
|
||||
|
||||
type Value[T any] struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
cfgSecKey CfgSecKey
|
||||
dynKey string
|
||||
|
||||
def, value T
|
||||
revision int
|
||||
}
|
||||
|
||||
func (value *Value[T]) parse(s string) (v T) {
|
||||
switch any(v).(type) {
|
||||
case bool:
|
||||
b, _ := strconv.ParseBool(s)
|
||||
return any(b).(T)
|
||||
default:
|
||||
panic("unsupported config type, please complete the code")
|
||||
}
|
||||
}
|
||||
|
||||
func (value *Value[T]) Value(ctx context.Context) (v T) {
|
||||
dg := GetDynGetter()
|
||||
if dg == nil {
|
||||
// this is an edge case: the database is not initialized but the system setting is going to be used
|
||||
// it should panic to avoid inconsistent config values (from config / system setting) and fix the code
|
||||
panic("no config dyn value getter")
|
||||
}
|
||||
|
||||
rev := dg.GetRevision(ctx)
|
||||
|
||||
// if the revision in database doesn't change, use the last value
|
||||
value.mu.RLock()
|
||||
if rev == value.revision {
|
||||
v = value.value
|
||||
value.mu.RUnlock()
|
||||
return v
|
||||
}
|
||||
value.mu.RUnlock()
|
||||
|
||||
// try to parse the config and cache it
|
||||
var valStr *string
|
||||
if dynVal, has := dg.GetValue(ctx, value.dynKey); has {
|
||||
valStr = &dynVal
|
||||
} else if cfgVal, has := GetCfgSecKeyGetter().GetValue(value.cfgSecKey.Sec, value.cfgSecKey.Key); has {
|
||||
valStr = &cfgVal
|
||||
}
|
||||
if valStr == nil {
|
||||
v = value.def
|
||||
} else {
|
||||
v = value.parse(*valStr)
|
||||
}
|
||||
|
||||
value.mu.Lock()
|
||||
value.value = v
|
||||
value.revision = rev
|
||||
value.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (value *Value[T]) DynKey() string {
|
||||
return value.dynKey
|
||||
}
|
||||
|
||||
func Bool(def bool, cfgSecKey CfgSecKey, dynKey string) *Value[bool] {
|
||||
return &Value[bool]{def: def, cfgSecKey: cfgSecKey, dynKey: dynKey}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue