mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-27 20:41:01 +00:00 
			
		
		
		
	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.  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{}
 | |
| }
 |