213 lines · 6.1 KB · Go
RawDownload
// Copyright 2021 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package user import (	"context"	"errors"	"fmt"	"strings" 	"forgejo.org/models/db"	"forgejo.org/modules/cache"	setting_module "forgejo.org/modules/setting"	"forgejo.org/modules/util" 	"xorm.io/builder") // Setting is a key value store of user settingstype Setting struct {	ID           int64  `xorm:"pk autoincr"`	UserID       int64  `xorm:"index unique(key_userid)"`              // to load all of someone's settings	SettingKey   string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase	SettingValue string `xorm:"text"`} // TableName sets the table name for the settings structfunc (s *Setting) TableName() string {	return "user_setting"} func init() {	db.RegisterModel(new(Setting))} // ErrUserSettingIsNotExist represents an error that a setting is not exist with special keytype ErrUserSettingIsNotExist struct {	Key string} // Error implements errorfunc (err ErrUserSettingIsNotExist) Error() string {	return fmt.Sprintf("Setting[%s] is not exist", err.Key)} func (err ErrUserSettingIsNotExist) Unwrap() error {	return util.ErrNotExist} // IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExistfunc IsErrUserSettingIsNotExist(err error) bool {	_, ok := err.(ErrUserSettingIsNotExist)	return ok} // genSettingCacheKey returns the cache key for some configurationfunc genSettingCacheKey(userID int64, key string) string {	return fmt.Sprintf("user_%d.setting.%s", userID, key)} // GetSetting returns the setting value via the keyfunc GetSetting(ctx context.Context, uid int64, key string) (string, error) {	return cache.GetString(genSettingCacheKey(uid, key), func() (string, error) {		res, err := GetSettingNoCache(ctx, uid, key)		if err != nil {			return "", err		}		return res.SettingValue, nil	})} // GetSettingNoCache returns specific setting without using the cachefunc GetSettingNoCache(ctx context.Context, uid int64, key string) (*Setting, error) {	v, err := GetSettings(ctx, uid, []string{key})	if err != nil {		return nil, err	}	if len(v) == 0 {		return nil, ErrUserSettingIsNotExist{key}	}	return v[key], nil} // GetSettings returns specific settings from userfunc GetSettings(ctx context.Context, uid int64, keys []string) (map[string]*Setting, error) {	settings := make([]*Setting, 0, len(keys))	if err := db.GetEngine(ctx).		Where("user_id=?", uid).		And(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} // GetUserAllSettings returns all settings from userfunc GetUserAllSettings(ctx context.Context, uid int64) (map[string]*Setting, error) {	settings := make([]*Setting, 0, 5)	if err := db.GetEngine(ctx).		Where("user_id=?", uid).		Find(&settings); err != nil {		return nil, err	}	settingsMap := make(map[string]*Setting)	for _, s := range settings {		settingsMap[s.SettingKey] = s	}	return settingsMap, nil} func validateUserSettingKey(key string) error {	if len(key) == 0 {		return errors.New("setting key must be set")	}	if strings.ToLower(key) != key {		return errors.New("setting key should be lowercase")	}	return nil} // GetUserSetting gets a specific setting for a userfunc GetUserSetting(ctx context.Context, userID int64, key string, def ...string) (string, error) {	if err := validateUserSettingKey(key); err != nil {		return "", err	} 	setting := &Setting{UserID: userID, SettingKey: key}	has, err := db.GetEngine(ctx).Get(setting)	if err != nil {		return "", err	}	if !has {		if len(def) == 1 {			return def[0], nil		}		return "", nil	}	return setting.SettingValue, nil} // DeleteUserSetting deletes a specific setting for a userfunc DeleteUserSetting(ctx context.Context, userID int64, key string) error {	if err := validateUserSettingKey(key); err != nil {		return err	} 	cache.Remove(genSettingCacheKey(userID, key))	_, err := db.GetEngine(ctx).Delete(&Setting{UserID: userID, SettingKey: key}) 	return err} // SetUserSetting updates a users' setting for a specific keyfunc SetUserSetting(ctx context.Context, userID int64, key, value string) error {	if err := validateUserSettingKey(key); err != nil {		return err	} 	if err := upsertUserSettingValue(ctx, userID, key, value); err != nil {		return err	} 	cc := cache.GetCache()	if cc != nil {		return cc.Put(genSettingCacheKey(userID, key), value, setting_module.CacheService.TTLSeconds())	} 	return nil} func upsertUserSettingValue(ctx context.Context, userID int64, key, value string) error {	return db.WithTx(ctx, 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 user_setting SET setting_value=? WHERE setting_key=? AND user_id=?", value, key, userID)		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{UserID: userID, SettingKey: key})		if err != nil {			return err		}		if has {			return nil		} 		// if no existing row, insert a new row		_, err = e.Insert(&Setting{UserID: userID, SettingKey: key, SettingValue: value})		return err	})}