// Copyright 2021 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package system import ( "context" "math" "sync" "time" "forgejo.org/models/db" "forgejo.org/modules/log" "forgejo.org/modules/setting/config" "forgejo.org/modules/timeutil" "xorm.io/builder") type Setting struct { ID int64 `xorm:"pk autoincr"` SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase SettingValue string `xorm:"text"` Version int Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"`} // TableName sets the table name for the settings structfunc (s *Setting) TableName() string { return "system_setting"} func init() { db.RegisterModel(new(Setting))} const keyRevision = "revision" func GetRevision(ctx context.Context) int { revision, exist, err := db.Get[Setting](ctx, builder.Eq{"setting_key": keyRevision}) if err != nil { return 0 } else if !exist { err = db.Insert(ctx, &Setting{SettingKey: keyRevision, Version: 1}) if err != nil { return 0 } return 1 } 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 version=version+1, 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() cachedDuration := time.Since(d.cacheTime) cachedRevision := d.revision d.mu.RUnlock() if cachedDuration < time.Second { return cachedRevision } d.mu.Lock() defer d.mu.Unlock() if GetRevision(ctx) != d.revision { rev, set, err := GetAllSettings(ctx) if err != nil { log.Error("Unable to get all settings: %v", err) } else { d.revision = rev d.settings = set } } d.cacheTime = time.Now() return d.revision} func (d *dbConfigCachedGetter) InvalidateCache() { d.mu.Lock() d.cacheTime = time.Time{} d.mu.Unlock()} func NewDatabaseDynKeyGetter() config.DynKeyGetter { return &dbConfigCachedGetter{}}
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package system
import (
"context"
"math"
"sync"
"time"
"forgejo.org/models/db"
"forgejo.org/modules/log"
"forgejo.org/modules/setting/config"
"forgejo.org/modules/timeutil"
"xorm.io/builder"
)
type Setting struct {
ID int64 `xorm:"pk autoincr"`
SettingKey string `xorm:"varchar(255) unique"` // key should be lowercase
SettingValue string `xorm:"text"`
Version int
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, exist, err := db.Get[Setting](ctx, builder.Eq{"setting_key": keyRevision})
if err != nil {
return 0
} else if !exist {
err = db.Insert(ctx, &Setting{SettingKey: keyRevision, Version: 1})
if err != nil {
return 0
}
return 1
}
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 version=version+1, 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()
cachedDuration := time.Since(d.cacheTime)
cachedRevision := d.revision
d.mu.RUnlock()
if cachedDuration < time.Second {
return cachedRevision
}
d.mu.Lock()
defer d.mu.Unlock()
if GetRevision(ctx) != d.revision {
rev, set, err := GetAllSettings(ctx)
if err != nil {
log.Error("Unable to get all settings: %v", err)
} else {
d.revision = rev
d.settings = set
}
}
d.cacheTime = time.Now()
return d.revision
}
func (d *dbConfigCachedGetter) InvalidateCache() {
d.mu.Lock()
d.cacheTime = time.Time{}
d.mu.Unlock()
}
func NewDatabaseDynKeyGetter() config.DynKeyGetter {
return &dbConfigCachedGetter{}
}