0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-11-04 15:04:00 +01:00
2025-10-16 21:01:44 -04:00

162 lines
3.5 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package config
import (
"context"
"sync"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
type CfgSecKey struct {
Sec, Key string
}
type Value[T any] struct {
mu sync.RWMutex
cfgSecKey CfgSecKey
dynKey, selectFromKey string
def, value T
revision int
flipBoolean bool
}
func (value *Value[T]) parse(key, valStr string) (v T) {
v = value.def
if valStr != "" {
if err := json.Unmarshal(util.UnsafeStringToBytes(valStr), &v); err != nil {
log.Error("Unable to unmarshal json config for key %q, err: %v", key, err)
}
}
return value.invert(v)
}
func (value *Value[T]) invertBoolStr(val string) (inverted string) {
if val == "true" {
return "false"
}
return "true"
}
func (value *Value[T]) invert(val T) (v T) {
v = val
if value.flipBoolean {
// if value is of type bool
if _, ok := any(val).(bool); ok {
// invert the boolean value upon retrieval
v = any(!any(val).(bool)).(T)
} else {
log.Warn("Ignoring attempt to invert key '%q' for non boolean type", value.selectFromKey)
}
}
return v
}
func (value *Value[T]) getKey() string {
if value.selectFromKey != "" {
return value.selectFromKey
}
return value.dynKey
}
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 the 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.getKey()); 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(value.dynKey, *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 (value *Value[T]) SelectFromKey() string {
return value.selectFromKey
}
func (value *Value[T]) WithDefault(def T) *Value[T] {
value.def = def
return value
}
func (value *Value[T]) DefaultValue() T {
return value.def
}
func (value *Value[T]) WithFileConfig(cfgSecKey CfgSecKey) *Value[T] {
value.cfgSecKey = cfgSecKey
return value
}
func (value *Value[bool]) Invert() *Value[bool] {
value.flipBoolean = true
return value
}
func (value *Value[any]) SelectFrom(sectionName string) *Value[any] {
value.selectFromKey = sectionName
return value
}
func (value *Value[any]) SetValue(val string) error {
ctx := context.Background()
ds := GetDynSetter()
if ds == 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")
}
if value.flipBoolean {
return ds.SetValue(ctx, value.getKey(), value.invertBoolStr(val))
}
return ds.SetValue(ctx, value.getKey(), val)
}
func ValueJSON[T any](dynKey string) *Value[T] {
return &Value[T]{dynKey: dynKey}
}