github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/cache/cache_redis.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package cache
     7  
     8  import (
     9  	"fmt"
    10  	"strconv"
    11  	"time"
    12  
    13  	"github.com/gitbundle/modules/graceful"
    14  	"github.com/gitbundle/modules/nosql"
    15  
    16  	"gitea.com/go-chi/cache"
    17  	"github.com/redis/go-redis/v9"
    18  )
    19  
    20  // RedisCacher represents a redis cache adapter implementation.
    21  type RedisCacher struct {
    22  	c          redis.UniversalClient
    23  	prefix     string
    24  	hsetName   string
    25  	occupyMode bool
    26  }
    27  
    28  // toStr convert string/int/int64 interface to string. it's only used by the RedisCacher.Put internally
    29  func toStr(v interface{}) string {
    30  	if v == nil {
    31  		return ""
    32  	}
    33  	switch v := v.(type) {
    34  	case string:
    35  		return v
    36  	case []byte:
    37  		return string(v)
    38  	case int:
    39  		return strconv.FormatInt(int64(v), 10)
    40  	case int64:
    41  		return strconv.FormatInt(v, 10)
    42  	default:
    43  		return fmt.Sprint(v) // as what the old com.ToStr does in most cases
    44  	}
    45  }
    46  
    47  // Put puts value (string type) into cache with key and expire time.
    48  // If expired is 0, it lives forever.
    49  func (c *RedisCacher) Put(key string, val interface{}, expire int64) error {
    50  	// this function is not well-designed, it only puts string values into cache
    51  	key = c.prefix + key
    52  	if expire == 0 {
    53  		if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), 0).Err(); err != nil {
    54  			return err
    55  		}
    56  	} else {
    57  		dur := time.Duration(expire) * time.Second
    58  		if err := c.c.Set(graceful.GetManager().HammerContext(), key, toStr(val), dur).Err(); err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	if c.occupyMode {
    64  		return nil
    65  	}
    66  	return c.c.HSet(graceful.GetManager().HammerContext(), c.hsetName, key, "0").Err()
    67  }
    68  
    69  // Get gets cached value by given key.
    70  func (c *RedisCacher) Get(key string) interface{} {
    71  	val, err := c.c.Get(graceful.GetManager().HammerContext(), c.prefix+key).Result()
    72  	if err != nil {
    73  		return nil
    74  	}
    75  	return val
    76  }
    77  
    78  // Delete deletes cached value by given key.
    79  func (c *RedisCacher) Delete(key string) error {
    80  	key = c.prefix + key
    81  	if err := c.c.Del(graceful.GetManager().HammerContext(), key).Err(); err != nil {
    82  		return err
    83  	}
    84  
    85  	if c.occupyMode {
    86  		return nil
    87  	}
    88  	return c.c.HDel(graceful.GetManager().HammerContext(), c.hsetName, key).Err()
    89  }
    90  
    91  // Incr increases cached int-type value by given key as a counter.
    92  func (c *RedisCacher) Incr(key string) error {
    93  	if !c.IsExist(key) {
    94  		return fmt.Errorf("key '%s' not exist", key)
    95  	}
    96  	return c.c.Incr(graceful.GetManager().HammerContext(), c.prefix+key).Err()
    97  }
    98  
    99  // Decr decreases cached int-type value by given key as a counter.
   100  func (c *RedisCacher) Decr(key string) error {
   101  	if !c.IsExist(key) {
   102  		return fmt.Errorf("key '%s' not exist", key)
   103  	}
   104  	return c.c.Decr(graceful.GetManager().HammerContext(), c.prefix+key).Err()
   105  }
   106  
   107  // IsExist returns true if cached value exists.
   108  func (c *RedisCacher) IsExist(key string) bool {
   109  	if c.c.Exists(graceful.GetManager().HammerContext(), c.prefix+key).Val() == 1 {
   110  		return true
   111  	}
   112  
   113  	if !c.occupyMode {
   114  		c.c.HDel(graceful.GetManager().HammerContext(), c.hsetName, c.prefix+key)
   115  	}
   116  	return false
   117  }
   118  
   119  // Flush deletes all cached data.
   120  func (c *RedisCacher) Flush() error {
   121  	if c.occupyMode {
   122  		return c.c.FlushDB(graceful.GetManager().HammerContext()).Err()
   123  	}
   124  
   125  	keys, err := c.c.HKeys(graceful.GetManager().HammerContext(), c.hsetName).Result()
   126  	if err != nil {
   127  		return err
   128  	}
   129  	if err = c.c.Del(graceful.GetManager().HammerContext(), keys...).Err(); err != nil {
   130  		return err
   131  	}
   132  	return c.c.Del(graceful.GetManager().HammerContext(), c.hsetName).Err()
   133  }
   134  
   135  // StartAndGC starts GC routine based on config string settings.
   136  // AdapterConfig: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,hset_name=MacaronCache,prefix=cache:
   137  func (c *RedisCacher) StartAndGC(opts cache.Options) error {
   138  	c.hsetName = "MacaronCache"
   139  	c.occupyMode = opts.OccupyMode
   140  
   141  	uri := nosql.ToRedisURI(opts.AdapterConfig)
   142  
   143  	c.c = nosql.GetManager().GetRedisClient(uri.String())
   144  
   145  	for k, v := range uri.Query() {
   146  		switch k {
   147  		case "hset_name":
   148  			c.hsetName = v[0]
   149  		case "prefix":
   150  			c.prefix = v[0]
   151  		}
   152  	}
   153  
   154  	return c.c.Ping(graceful.GetManager().HammerContext()).Err()
   155  }
   156  
   157  // Ping tests if the cache is alive.
   158  func (c *RedisCacher) Ping() error {
   159  	return c.c.Ping(graceful.GetManager().HammerContext()).Err()
   160  }
   161  
   162  func init() {
   163  	cache.Register("redis", &RedisCacher{})
   164  }