code.gitea.io/gitea@v1.22.3/models/user/setting.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package user
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/models/db"
    12  	"code.gitea.io/gitea/modules/cache"
    13  	setting_module "code.gitea.io/gitea/modules/setting"
    14  	"code.gitea.io/gitea/modules/util"
    15  
    16  	"xorm.io/builder"
    17  )
    18  
    19  // Setting is a key value store of user settings
    20  type Setting struct {
    21  	ID           int64  `xorm:"pk autoincr"`
    22  	UserID       int64  `xorm:"index unique(key_userid)"`              // to load all of someone's settings
    23  	SettingKey   string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase
    24  	SettingValue string `xorm:"text"`
    25  }
    26  
    27  // TableName sets the table name for the settings struct
    28  func (s *Setting) TableName() string {
    29  	return "user_setting"
    30  }
    31  
    32  func init() {
    33  	db.RegisterModel(new(Setting))
    34  }
    35  
    36  // ErrUserSettingIsNotExist represents an error that a setting is not exist with special key
    37  type ErrUserSettingIsNotExist struct {
    38  	Key string
    39  }
    40  
    41  // Error implements error
    42  func (err ErrUserSettingIsNotExist) Error() string {
    43  	return fmt.Sprintf("Setting[%s] is not exist", err.Key)
    44  }
    45  
    46  func (err ErrUserSettingIsNotExist) Unwrap() error {
    47  	return util.ErrNotExist
    48  }
    49  
    50  // IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist
    51  func IsErrUserSettingIsNotExist(err error) bool {
    52  	_, ok := err.(ErrUserSettingIsNotExist)
    53  	return ok
    54  }
    55  
    56  // genSettingCacheKey returns the cache key for some configuration
    57  func genSettingCacheKey(userID int64, key string) string {
    58  	return fmt.Sprintf("user_%d.setting.%s", userID, key)
    59  }
    60  
    61  // GetSetting returns the setting value via the key
    62  func GetSetting(ctx context.Context, uid int64, key string) (string, error) {
    63  	return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) {
    64  		res, err := GetSettingNoCache(ctx, uid, key)
    65  		if err != nil {
    66  			return "", err
    67  		}
    68  		return res.SettingValue, nil
    69  	})
    70  }
    71  
    72  // GetSettingNoCache returns specific setting without using the cache
    73  func GetSettingNoCache(ctx context.Context, uid int64, key string) (*Setting, error) {
    74  	v, err := GetSettings(ctx, uid, []string{key})
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	if len(v) == 0 {
    79  		return nil, ErrUserSettingIsNotExist{key}
    80  	}
    81  	return v[key], nil
    82  }
    83  
    84  // GetSettings returns specific settings from user
    85  func GetSettings(ctx context.Context, uid int64, keys []string) (map[string]*Setting, error) {
    86  	settings := make([]*Setting, 0, len(keys))
    87  	if err := db.GetEngine(ctx).
    88  		Where("user_id=?", uid).
    89  		And(builder.In("setting_key", keys)).
    90  		Find(&settings); err != nil {
    91  		return nil, err
    92  	}
    93  	settingsMap := make(map[string]*Setting)
    94  	for _, s := range settings {
    95  		settingsMap[s.SettingKey] = s
    96  	}
    97  	return settingsMap, nil
    98  }
    99  
   100  // GetUserAllSettings returns all settings from user
   101  func GetUserAllSettings(ctx context.Context, uid int64) (map[string]*Setting, error) {
   102  	settings := make([]*Setting, 0, 5)
   103  	if err := db.GetEngine(ctx).
   104  		Where("user_id=?", uid).
   105  		Find(&settings); err != nil {
   106  		return nil, err
   107  	}
   108  	settingsMap := make(map[string]*Setting)
   109  	for _, s := range settings {
   110  		settingsMap[s.SettingKey] = s
   111  	}
   112  	return settingsMap, nil
   113  }
   114  
   115  func validateUserSettingKey(key string) error {
   116  	if len(key) == 0 {
   117  		return fmt.Errorf("setting key must be set")
   118  	}
   119  	if strings.ToLower(key) != key {
   120  		return fmt.Errorf("setting key should be lowercase")
   121  	}
   122  	return nil
   123  }
   124  
   125  // GetUserSetting gets a specific setting for a user
   126  func GetUserSetting(ctx context.Context, userID int64, key string, def ...string) (string, error) {
   127  	if err := validateUserSettingKey(key); err != nil {
   128  		return "", err
   129  	}
   130  
   131  	setting := &Setting{UserID: userID, SettingKey: key}
   132  	has, err := db.GetEngine(ctx).Get(setting)
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  	if !has {
   137  		if len(def) == 1 {
   138  			return def[0], nil
   139  		}
   140  		return "", nil
   141  	}
   142  	return setting.SettingValue, nil
   143  }
   144  
   145  // DeleteUserSetting deletes a specific setting for a user
   146  func DeleteUserSetting(ctx context.Context, userID int64, key string) error {
   147  	if err := validateUserSettingKey(key); err != nil {
   148  		return err
   149  	}
   150  
   151  	cache.Remove(genSettingCacheKey(userID, key))
   152  	_, err := db.GetEngine(ctx).Delete(&Setting{UserID: userID, SettingKey: key})
   153  
   154  	return err
   155  }
   156  
   157  // SetUserSetting updates a users' setting for a specific key
   158  func SetUserSetting(ctx context.Context, userID int64, key, value string) error {
   159  	if err := validateUserSettingKey(key); err != nil {
   160  		return err
   161  	}
   162  
   163  	if err := upsertUserSettingValue(ctx, userID, key, value); err != nil {
   164  		return err
   165  	}
   166  
   167  	cc := cache.GetCache()
   168  	if cc != nil {
   169  		return cc.Put(genSettingCacheKey(userID, key), value, setting_module.CacheService.TTLSeconds())
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func upsertUserSettingValue(ctx context.Context, userID int64, key, value string) error {
   176  	return db.WithTx(ctx, func(ctx context.Context) error {
   177  		e := db.GetEngine(ctx)
   178  
   179  		// here we use a general method to do a safe upsert for different databases (and most transaction levels)
   180  		// 1. try to UPDATE the record and acquire the transaction write lock
   181  		//    if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly
   182  		//    if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist  (b) value is not changed
   183  		// 2. do a SELECT to check if the row exists or not (we already have the transaction lock)
   184  		// 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe)
   185  		//
   186  		// to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1`
   187  		//    to make sure the UPDATE always returns a non-zero value for existing (unchanged) records.
   188  
   189  		res, err := e.Exec("UPDATE user_setting SET setting_value=? WHERE setting_key=? AND user_id=?", value, key, userID)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		rows, _ := res.RowsAffected()
   194  		if rows > 0 {
   195  			// the existing row is updated, so we can return
   196  			return nil
   197  		}
   198  
   199  		// in case the value isn't changed, update would return 0 rows changed, so we need this check
   200  		has, err := e.Exist(&Setting{UserID: userID, SettingKey: key})
   201  		if err != nil {
   202  			return err
   203  		}
   204  		if has {
   205  			return nil
   206  		}
   207  
   208  		// if no existing row, insert a new row
   209  		_, err = e.Insert(&Setting{UserID: userID, SettingKey: key, SettingValue: value})
   210  		return err
   211  	})
   212  }