Skip to content

Commit bc430bb

Browse files
committed
feat: adds setter for config.Value and updates forms
Install now submits the proper database name and is properly set using the config.Value class. This extends the getter functionality so now config.Value can be used to both get and set values.
1 parent 589712d commit bc430bb

File tree

9 files changed

+167
-20
lines changed

9 files changed

+167
-20
lines changed

models/system/setting.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,34 @@ func (d *dbConfigCachedGetter) InvalidateCache() {
150150
func NewDatabaseDynKeyGetter() config.DynKeyGetter {
151151
return &dbConfigCachedGetter{}
152152
}
153+
154+
type dbConfigSetter struct {
155+
mu sync.RWMutex
156+
}
157+
158+
var _ config.DynKeySetter = (*dbConfigSetter)(nil)
159+
160+
func (d *dbConfigSetter) SetValue(ctx context.Context, dynKey, value string) error {
161+
d.mu.RLock()
162+
defer d.mu.RUnlock()
163+
_ = GetRevision(ctx) // prepare the "revision" key ahead
164+
return db.WithTx(ctx, func(ctx context.Context) error {
165+
e := db.GetEngine(ctx)
166+
res, err := e.Exec("UPDATE system_setting SET version=version+1, setting_value=? WHERE setting_key=?", value, dynKey)
167+
if err != nil {
168+
return err
169+
}
170+
rows, _ := res.RowsAffected()
171+
if rows == 0 { // if no existing row, insert a new row
172+
if _, err = e.Insert(&Setting{SettingKey: dynKey, SettingValue: value}); err != nil {
173+
return err
174+
}
175+
}
176+
177+
return nil
178+
})
179+
}
180+
181+
func NewDatabaseDynKeySetter() config.DynKeySetter {
182+
return &dbConfigSetter{}
183+
}

modules/setting/config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ type ConfigStruct struct {
5858
}
5959

6060
var (
61-
defaultConfig *ConfigStruct
62-
defaultConfigOnce sync.Once
61+
defaultConfig *ConfigStruct
62+
ConfigOnce sync.Once
6363
)
6464

6565
func initDefaultConfig() {
6666
config.SetCfgSecKeyGetter(&cfgSecKeyGetter{})
6767
defaultConfig = &ConfigStruct{
6868
Picture: &PictureStruct{
69-
EnableGravatar: config.ValueJSON[bool]("picture.disable_gravatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}).Invert(),
69+
EnableGravatar: config.ValueJSON[bool]("picture.enable_gravatar").SelectFrom("picture.disable_gravatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}).Invert(),
7070
EnableFederatedAvatar: config.ValueJSON[bool]("picture.enable_federated_avatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}),
7171
},
7272
Repository: &RepositoryStruct{
@@ -77,7 +77,7 @@ func initDefaultConfig() {
7777
}
7878

7979
func Config() *ConfigStruct {
80-
defaultConfigOnce.Do(initDefaultConfig)
80+
ConfigOnce.Do(initDefaultConfig)
8181
return defaultConfig
8282
}
8383

modules/setting/config/setter.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package config
5+
6+
import (
7+
"context"
8+
"sync"
9+
)
10+
11+
var setterMu sync.RWMutex
12+
13+
type DynKeySetter interface {
14+
SetValue(ctx context.Context, dynKey, value string) error
15+
}
16+
17+
var dynKeySetterInternal DynKeySetter
18+
19+
func SetDynSetter(p DynKeySetter) {
20+
setterMu.Lock()
21+
dynKeySetterInternal = p
22+
setterMu.Unlock()
23+
}
24+
25+
func GetDynSetter() DynKeySetter {
26+
getterMu.RLock()
27+
defer getterMu.RUnlock()
28+
return dynKeySetterInternal
29+
}

modules/setting/config/value.go

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package config
55

66
import (
77
"context"
8+
"fmt"
89
"sync"
910

1011
"code.gitea.io/gitea/modules/json"
@@ -19,8 +20,8 @@ type CfgSecKey struct {
1920
type Value[T any] struct {
2021
mu sync.RWMutex
2122

22-
cfgSecKey CfgSecKey
23-
dynKey string
23+
cfgSecKey CfgSecKey
24+
dynKey, selectFromKey string
2425

2526
def, value T
2627
revision int
@@ -35,19 +36,41 @@ func (value *Value[T]) parse(key, valStr string) (v T) {
3536
}
3637
}
3738

39+
return value.invert(v)
40+
}
41+
42+
func (value *Value[T]) invertBoolStr(val string) (inverted string) {
43+
if val == "true" {
44+
return "false"
45+
}
46+
47+
return "true"
48+
}
49+
50+
func (value *Value[T]) invert(val T) (v T) {
51+
v = val
3852
if value.flipBoolean {
53+
fmt.Printf("Flipping boolean value '%v'...\n", val)
3954
// if value is of type bool
40-
if _, ok := any(v).(bool); ok {
55+
if _, ok := any(val).(bool); ok {
4156
// invert the boolean value upon retrieval
42-
v = any(!any(v).(bool)).(T)
57+
v = any(!any(val).(bool)).(T)
4358
} else {
44-
log.Warn("Ignoring attempt to invert key '%q' for non boolean type", key)
59+
log.Warn("Ignoring attempt to invert key '%q' for non boolean type", value.selectFromKey)
4560
}
4661
}
4762

4863
return v
4964
}
5065

66+
func (value *Value[T]) getKey() string {
67+
if value.selectFromKey != "" {
68+
return value.selectFromKey
69+
}
70+
71+
return value.dynKey
72+
}
73+
5174
func (value *Value[T]) Value(ctx context.Context) (v T) {
5275
dg := GetDynGetter()
5376
if dg == nil {
@@ -69,7 +92,7 @@ func (value *Value[T]) Value(ctx context.Context) (v T) {
6992

7093
// try to parse the config and cache it
7194
var valStr *string
72-
if dynVal, has := dg.GetValue(ctx, value.dynKey); has {
95+
if dynVal, has := dg.GetValue(ctx, value.getKey()); has {
7396
valStr = &dynVal
7497
} else if cfgVal, has := GetCfgSecKeyGetter().GetValue(value.cfgSecKey.Sec, value.cfgSecKey.Key); has {
7598
valStr = &cfgVal
@@ -91,6 +114,10 @@ func (value *Value[T]) DynKey() string {
91114
return value.dynKey
92115
}
93116

117+
func (value *Value[T]) SelectFromKey() string {
118+
return value.selectFromKey
119+
}
120+
94121
func (value *Value[T]) WithDefault(def T) *Value[T] {
95122
value.def = def
96123
return value
@@ -110,6 +137,29 @@ func (value *Value[bool]) Invert() *Value[bool] {
110137
return value
111138
}
112139

140+
func (value *Value[any]) SelectFrom(sectionName string) *Value[any] {
141+
value.selectFromKey = sectionName
142+
return value
143+
}
144+
145+
func (value *Value[any]) SetValue(val string) error {
146+
ctx := context.Background()
147+
ds := GetDynSetter()
148+
if ds == nil {
149+
// this is an edge case: the database is not initialized but the system setting is going to be used
150+
// it should panic to avoid inconsistent config values (from config / system setting) and fix the code
151+
panic("no config dyn value getter")
152+
}
153+
154+
fmt.Printf("Setting value '%s' with old key '%s' using key '%s'\n", val, value.selectFromKey, value.dynKey)
155+
156+
if value.flipBoolean {
157+
return ds.SetValue(ctx, value.getKey(), value.invertBoolStr(val))
158+
}
159+
160+
return ds.SetValue(ctx, value.getKey(), val)
161+
}
162+
113163
func ValueJSON[T any](dynKey string) *Value[T] {
114164
return &Value[T]{dynKey: dynKey}
115165
}

modules/setting/config/value_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,31 @@ func TestValue_parse(t *testing.T) {
3333
})
3434
}
3535
}
36+
37+
func TestValue_getKey(t *testing.T) {
38+
tests := []struct {
39+
name string // description of this test case
40+
valueClass *Value[bool]
41+
want string
42+
}{
43+
{
44+
name: "Custom dynKey name",
45+
valueClass: ValueJSON[bool]("picture.enable_gravatar").SelectFrom("picture.disable_gravatar").WithFileConfig(CfgSecKey{Sec: "", Key: ""}),
46+
want: "picture.enable_gravatar",
47+
},
48+
{
49+
name: "Normal dynKey name",
50+
valueClass: ValueJSON[bool]("picture.disable_gravatar").WithFileConfig(CfgSecKey{Sec: "", Key: ""}),
51+
want: "picture.disable_gravatar",
52+
},
53+
}
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
got := tt.valueClass.getKey()
57+
58+
if got != tt.want {
59+
t.Errorf("getKey() = %v, want %v", got, tt.want)
60+
}
61+
})
62+
}
63+
}

routers/common/db.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func InitDBEngine(ctx context.Context) (err error) {
3838
log.Info("Backing off for %d seconds", int64(setting.Database.DBConnectBackoff/time.Second))
3939
time.Sleep(setting.Database.DBConnectBackoff)
4040
}
41+
config.SetDynSetter(system_model.NewDatabaseDynKeySetter())
4142
config.SetDynGetter(system_model.NewDatabaseDynKeyGetter())
4243
return nil
4344
}

routers/install/install.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,8 @@ func SubmitInstall(ctx *context.Context) {
427427

428428
cfg.Section("server").Key("OFFLINE_MODE").SetValue(strconv.FormatBool(form.OfflineMode))
429429
if err := system_model.SetSettings(ctx, map[string]string{
430-
setting.Config().Picture.EnableGravatar.DynKey(): strconv.FormatBool(!form.EnableGravatar), // invert value as it is stored as disable_gravatar for backwards compatability
430+
// Form is submitted on install and should use the SelectFrom key and inverted this enter
431+
setting.Config().Picture.EnableGravatar.SelectFromKey(): strconv.FormatBool(!form.EnableGravatar),
431432
setting.Config().Picture.EnableFederatedAvatar.DynKey(): strconv.FormatBool(form.EnableFederatedAvatar),
432433
}); err != nil {
433434
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)

routers/web/admin/config.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,13 @@ func ConfigSettings(ctx *context.Context) {
198198
func ChangeConfig(ctx *context.Context) {
199199
cfg := setting.Config()
200200

201-
marshalBool := func(v string) ([]byte, error) {
202-
b, _ := strconv.ParseBool(v)
203-
return json.Marshal(b)
201+
subValueSet := map[string]func(string) error{
202+
cfg.Picture.EnableGravatar.DynKey(): cfg.Picture.EnableGravatar.SetValue,
204203
}
205204

206-
marshalBoolInvert := func(v string) ([]byte, error) {
205+
marshalBool := func(v string) ([]byte, error) {
207206
b, _ := strconv.ParseBool(v)
208-
return json.Marshal(!b)
207+
return json.Marshal(b)
209208
}
210209

211210
marshalString := func(emptyDefault string) func(v string) ([]byte, error) {
@@ -237,7 +236,7 @@ func ChangeConfig(ctx *context.Context) {
237236
}
238237

239238
marshallers := map[string]func(string) ([]byte, error){
240-
cfg.Picture.EnableGravatar.DynKey(): marshalBoolInvert, // Invert for backwards compatability with old database semantics
239+
cfg.Picture.EnableGravatar.DynKey(): marshalBool,
241240
cfg.Picture.EnableFederatedAvatar.DynKey(): marshalBool,
242241
cfg.Repository.OpenWithEditorApps.DynKey(): marshalOpenWithApps,
243242
cfg.Repository.GitGuideRemoteName.DynKey(): marshalString(cfg.Repository.GitGuideRemoteName.DefaultValue()),
@@ -266,7 +265,15 @@ loop:
266265
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
267266
break loop
268267
}
269-
configSettings[key] = string(marshaledValue)
268+
269+
if setter, ok := subValueSet[key]; ok {
270+
if err := setter(string(marshaledValue)); err != nil {
271+
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
272+
break loop
273+
}
274+
} else {
275+
configSettings[key] = string(marshaledValue)
276+
}
270277
}
271278
if ctx.Written() {
272279
return

templates/admin/config_settings/avatars.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
<dt>{{ctx.Locale.Tr "admin.config.enable_gravatar"}}</dt>
77
<dd>
88
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_gravatar"}}">
9-
<input type="checkbox" data-config-dyn-key="picture.disable_gravatar" {{if .SystemConfig.Picture.EnableGravatar.Value ctx}}checked{{end}}><label></label>
9+
<input type="checkbox" data-config-dyn-key="picture.enable_gravatar" {{if .SystemConfig.Picture.EnableGravatar.Value ctx}}checked{{end}}><label></label>
1010
</div>
1111
</dd>
1212
<div class="divider"></div>
1313
<dt>{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}</dt>
1414
<dd>
1515
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}">
16-
<input type="checkbox" data-config-dyn-key="picture.enable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
16+
<input type="checkbox" data-config-dyn-key="picture.disable_federated_avatar" {{if .SystemConfig.Picture.EnableFederatedAvatar.Value ctx}}checked{{end}}><label></label>
1717
</div>
1818
</dd>
1919
</dl>

0 commit comments

Comments
 (0)