mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-29 13:59:15 -05:00
aaf35ee49c
Backport #27000 by @wxiaoguang 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. ![image](https://github.com/go-gitea/gitea/assets/2114189/f8cdd743-1145-41ab-9f8f-3996aa97d440) Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
147 lines
3.6 KiB
Go
147 lines
3.6 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package system
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/setting/config"
|
|
"code.gitea.io/gitea/modules/timeutil"
|
|
)
|
|
|
|
type Setting struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase
|
|
SettingValue string `xorm:"text"`
|
|
Version int `xorm:"version"`
|
|
Created timeutil.TimeStamp `xorm:"created"`
|
|
Updated timeutil.TimeStamp `xorm:"updated"`
|
|
}
|
|
|
|
// TableName sets the table name for the settings struct
|
|
func (s *Setting) TableName() string {
|
|
return "system_setting"
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(Setting))
|
|
}
|
|
|
|
const keyRevision = "revision"
|
|
|
|
func GetRevision(ctx context.Context) int {
|
|
revision := &Setting{SettingKey: keyRevision}
|
|
if has, err := db.GetByBean(ctx, revision); err != nil {
|
|
return 0
|
|
} else if !has {
|
|
err = db.Insert(ctx, &Setting{SettingKey: keyRevision, Version: 1})
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return 1
|
|
} else if revision.Version <= 0 || revision.Version >= math.MaxInt-1 {
|
|
_, err = db.Exec(ctx, "UPDATE system_setting SET version=1 WHERE setting_key=?", keyRevision)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
return revision.Version
|
|
}
|
|
|
|
func GetAllSettings(ctx context.Context) (revision int, res map[string]string, err error) {
|
|
_ = GetRevision(ctx) // prepare the "revision" key ahead
|
|
var settings []*Setting
|
|
if err := db.GetEngine(ctx).
|
|
Find(&settings); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
res = make(map[string]string)
|
|
for _, s := range settings {
|
|
if s.SettingKey == keyRevision {
|
|
revision = s.Version
|
|
}
|
|
res[s.SettingKey] = s.SettingValue
|
|
}
|
|
return revision, res, nil
|
|
}
|
|
|
|
func SetSettings(ctx context.Context, settings map[string]string) error {
|
|
_ = GetRevision(ctx) // prepare the "revision" key ahead
|
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
e := db.GetEngine(ctx)
|
|
_, err := db.Exec(ctx, "UPDATE system_setting SET version=version+1 WHERE setting_key=?", keyRevision)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for k, v := range settings {
|
|
res, err := e.Exec("UPDATE system_setting SET setting_value=? WHERE setting_key=?", v, k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rows, _ := res.RowsAffected()
|
|
if rows == 0 { // if no existing row, insert a new row
|
|
if _, err = e.Insert(&Setting{SettingKey: k, SettingValue: v}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type dbConfigCachedGetter struct {
|
|
mu sync.RWMutex
|
|
|
|
cacheTime time.Time
|
|
revision int
|
|
settings map[string]string
|
|
}
|
|
|
|
var _ config.DynKeyGetter = (*dbConfigCachedGetter)(nil)
|
|
|
|
func (d *dbConfigCachedGetter) GetValue(ctx context.Context, key string) (v string, has bool) {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
v, has = d.settings[key]
|
|
return v, has
|
|
}
|
|
|
|
func (d *dbConfigCachedGetter) GetRevision(ctx context.Context) int {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
if time.Since(d.cacheTime) < time.Second {
|
|
return d.revision
|
|
}
|
|
if GetRevision(ctx) != d.revision {
|
|
d.mu.RUnlock()
|
|
d.mu.Lock()
|
|
rev, set, err := GetAllSettings(ctx)
|
|
if err != nil {
|
|
log.Error("Unable to get all settings: %v", err)
|
|
} else {
|
|
d.cacheTime = time.Now()
|
|
d.revision = rev
|
|
d.settings = set
|
|
}
|
|
d.mu.Unlock()
|
|
d.mu.RLock()
|
|
}
|
|
return d.revision
|
|
}
|
|
|
|
func (d *dbConfigCachedGetter) InvalidateCache() {
|
|
d.mu.Lock()
|
|
d.cacheTime = time.Time{}
|
|
d.mu.Unlock()
|
|
}
|
|
|
|
func NewDatabaseDynKeyGetter() config.DynKeyGetter {
|
|
return &dbConfigCachedGetter{}
|
|
}
|