123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- // Copyright 2021 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package system
-
- import (
- "context"
- "fmt"
- "net/url"
- "strconv"
- "strings"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/cache"
- setting_module "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
-
- "strk.kbt.io/projects/go/libravatar"
- "xorm.io/builder"
- )
-
- // Setting is a key value store of user settings
- type Setting struct {
- ID int64 `xorm:"pk autoincr"`
- SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase
- SettingValue string `xorm:"text"`
- Version int `xorm:"version"` // prevent to override
- 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 (s *Setting) GetValueBool() bool {
- if s == nil {
- return false
- }
-
- b, _ := strconv.ParseBool(s.SettingValue)
- return b
- }
-
- func init() {
- db.RegisterModel(new(Setting))
- }
-
- // ErrSettingIsNotExist represents an error that a setting is not exist with special key
- type ErrSettingIsNotExist struct {
- Key string
- }
-
- // Error implements error
- func (err ErrSettingIsNotExist) Error() string {
- return fmt.Sprintf("System setting[%s] is not exist", err.Key)
- }
-
- // IsErrSettingIsNotExist return true if err is ErrSettingIsNotExist
- func IsErrSettingIsNotExist(err error) bool {
- _, ok := err.(ErrSettingIsNotExist)
- return ok
- }
-
- // ErrDataExpired represents an error that update a record which has been updated by another thread
- type ErrDataExpired struct {
- Key string
- }
-
- // Error implements error
- func (err ErrDataExpired) Error() string {
- return fmt.Sprintf("System setting[%s] has been updated by another thread", err.Key)
- }
-
- // IsErrDataExpired return true if err is ErrDataExpired
- func IsErrDataExpired(err error) bool {
- _, ok := err.(ErrDataExpired)
- return ok
- }
-
- // GetSetting returns specific setting without using the cache
- func GetSetting(ctx context.Context, key string) (*Setting, error) {
- v, err := GetSettings(ctx, []string{key})
- if err != nil {
- return nil, err
- }
- if len(v) == 0 {
- return nil, ErrSettingIsNotExist{key}
- }
- return v[strings.ToLower(key)], nil
- }
-
- const contextCacheKey = "system_setting"
-
- // GetSettingWithCache returns the setting value via the key
- func GetSettingWithCache(ctx context.Context, key string) (string, error) {
- return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
- return cache.GetString(genSettingCacheKey(key), func() (string, error) {
- res, err := GetSetting(ctx, key)
- if err != nil {
- return "", err
- }
- return res.SettingValue, nil
- })
- })
- }
-
- // GetSettingBool return bool value of setting,
- // none existing keys and errors are ignored and result in false
- func GetSettingBool(ctx context.Context, key string) bool {
- s, _ := GetSetting(ctx, key)
- if s == nil {
- return false
- }
- v, _ := strconv.ParseBool(s.SettingValue)
- return v
- }
-
- func GetSettingWithCacheBool(ctx context.Context, key string) bool {
- s, _ := GetSettingWithCache(ctx, key)
- v, _ := strconv.ParseBool(s)
- return v
- }
-
- // GetSettings returns specific settings
- func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error) {
- for i := 0; i < len(keys); i++ {
- keys[i] = strings.ToLower(keys[i])
- }
- settings := make([]*Setting, 0, len(keys))
- if err := db.GetEngine(ctx).
- Where(builder.In("setting_key", keys)).
- Find(&settings); err != nil {
- return nil, err
- }
- settingsMap := make(map[string]*Setting)
- for _, s := range settings {
- settingsMap[s.SettingKey] = s
- }
- return settingsMap, nil
- }
-
- type AllSettings map[string]*Setting
-
- func (settings AllSettings) Get(key string) Setting {
- if v, ok := settings[strings.ToLower(key)]; ok {
- return *v
- }
- return Setting{}
- }
-
- func (settings AllSettings) GetBool(key string) bool {
- b, _ := strconv.ParseBool(settings.Get(key).SettingValue)
- return b
- }
-
- func (settings AllSettings) GetVersion(key string) int {
- return settings.Get(key).Version
- }
-
- // GetAllSettings returns all settings from user
- func GetAllSettings(ctx context.Context) (AllSettings, error) {
- settings := make([]*Setting, 0, 5)
- if err := db.GetEngine(ctx).
- Find(&settings); err != nil {
- return nil, err
- }
- settingsMap := make(map[string]*Setting)
- for _, s := range settings {
- settingsMap[s.SettingKey] = s
- }
- return settingsMap, nil
- }
-
- // DeleteSetting deletes a specific setting for a user
- func DeleteSetting(ctx context.Context, setting *Setting) error {
- cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
- cache.Remove(genSettingCacheKey(setting.SettingKey))
- _, err := db.GetEngine(ctx).Delete(setting)
- return err
- }
-
- func SetSettingNoVersion(ctx context.Context, key, value string) error {
- s, err := GetSetting(ctx, key)
- if IsErrSettingIsNotExist(err) {
- return SetSetting(ctx, &Setting{
- SettingKey: key,
- SettingValue: value,
- })
- }
- if err != nil {
- return err
- }
- s.SettingValue = value
- return SetSetting(ctx, s)
- }
-
- // SetSetting updates a users' setting for a specific key
- func SetSetting(ctx context.Context, setting *Setting) error {
- if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
- return err
- }
-
- setting.Version++
-
- cc := cache.GetCache()
- if cc != nil {
- if err := cc.Put(genSettingCacheKey(setting.SettingKey), setting.SettingValue, setting_module.CacheService.TTLSeconds()); err != nil {
- return err
- }
- }
- cache.SetContextData(ctx, contextCacheKey, setting.SettingKey, setting.SettingValue)
- return nil
- }
-
- func upsertSettingValue(parentCtx context.Context, key, value string, version int) error {
- return db.WithTx(parentCtx, func(ctx context.Context) error {
- e := db.GetEngine(ctx)
-
- // here we use a general method to do a safe upsert for different databases (and most transaction levels)
- // 1. try to UPDATE the record and acquire the transaction write lock
- // if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly
- // if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed
- // 2. do a SELECT to check if the row exists or not (we already have the transaction lock)
- // 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe)
- //
- // to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1`
- // to make sure the UPDATE always returns a non-zero value for existing (unchanged) records.
-
- res, err := e.Exec("UPDATE system_setting SET setting_value=?, version = version+1 WHERE setting_key=? AND version=?", value, key, version)
- if err != nil {
- return err
- }
- rows, _ := res.RowsAffected()
- if rows > 0 {
- // the existing row is updated, so we can return
- return nil
- }
-
- // in case the value isn't changed, update would return 0 rows changed, so we need this check
- has, err := e.Exist(&Setting{SettingKey: key})
- if err != nil {
- return err
- }
- if has {
- return ErrDataExpired{Key: key}
- }
-
- // if no existing row, insert a new row
- _, err = e.Insert(&Setting{SettingKey: key, SettingValue: value})
- return err
- })
- }
-
- var (
- GravatarSourceURL *url.URL
- LibravatarService *libravatar.Libravatar
- )
-
- func Init(ctx context.Context) error {
- var disableGravatar bool
- disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
- if IsErrSettingIsNotExist(err) {
- disableGravatar = setting_module.GetDefaultDisableGravatar()
- disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
- } else if err != nil {
- return err
- } else {
- disableGravatar = disableGravatarSetting.GetValueBool()
- }
-
- var enableFederatedAvatar bool
- enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
- if IsErrSettingIsNotExist(err) {
- enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
- enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
- } else if err != nil {
- return err
- } else {
- enableFederatedAvatar = disableGravatarSetting.GetValueBool()
- }
-
- if setting_module.OfflineMode {
- disableGravatar = true
- enableFederatedAvatar = false
- if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
- if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
- return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
- }
- }
- if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
- if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
- return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
- }
- }
- }
-
- if enableFederatedAvatar || !disableGravatar {
- var err error
- GravatarSourceURL, err = url.Parse(setting_module.GravatarSource)
- if err != nil {
- return fmt.Errorf("Failed to parse Gravatar URL(%s): %w", setting_module.GravatarSource, err)
- }
- }
-
- if GravatarSourceURL != nil && enableFederatedAvatarSetting.GetValueBool() {
- LibravatarService = libravatar.New()
- if GravatarSourceURL.Scheme == "https" {
- LibravatarService.SetUseHTTPS(true)
- LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
- } else {
- LibravatarService.SetUseHTTPS(false)
- LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
- }
- }
- return nil
- }
|