github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/cache/impl_inmemory.go (about)

     1  package cache
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"context"
     7  	"io"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // InMemory implementation of the Cache client.
    14  type InMemory struct {
    15  	m *sync.Map
    16  }
    17  
    18  // NewInMemory instantiates a new in-memory Cache Client.
    19  func NewInMemory() *InMemory {
    20  	return &InMemory{m: new(sync.Map)}
    21  }
    22  
    23  // CheckStatus checks that the cache is ready, or returns an error.
    24  func (c *InMemory) CheckStatus(ctx context.Context) (time.Duration, error) {
    25  	return 0, nil
    26  }
    27  
    28  // Get fetch the cached asset at the given key, and returns true only if the
    29  // asset was found.
    30  func (c *InMemory) Get(key string) ([]byte, bool) {
    31  	value, ok := c.m.Load(key)
    32  	if !ok {
    33  		return nil, false
    34  	}
    35  
    36  	entry := value.(cacheEntry)
    37  	if time.Now().After(entry.expiredAt) {
    38  		// The value is expired. Clean it and return not found
    39  		c.Clear(key)
    40  		return nil, false
    41  	}
    42  
    43  	return entry.payload, true
    44  }
    45  
    46  // MultiGet can be used to fetch several keys at once.
    47  func (c *InMemory) MultiGet(keys []string) [][]byte {
    48  	results := make([][]byte, len(keys))
    49  
    50  	for i, key := range keys {
    51  		results[i], _ = c.Get(key)
    52  	}
    53  
    54  	return results
    55  }
    56  
    57  // Keys returns the list of keys with the given prefix.
    58  func (c *InMemory) Keys(prefix string) []string {
    59  	results := make([]string, 0)
    60  
    61  	c.m.Range(func(key, _ interface{}) bool {
    62  		k := key.(string)
    63  		if strings.HasPrefix(k, prefix) {
    64  			results = append(results, k)
    65  		}
    66  
    67  		return true
    68  	})
    69  
    70  	return results
    71  }
    72  
    73  // Clear removes a key from the cache
    74  func (c *InMemory) Clear(key string) {
    75  	c.m.Delete(key)
    76  }
    77  
    78  // Set stores an asset to the given key.
    79  func (c *InMemory) Set(key string, data []byte, expiration time.Duration) {
    80  	c.m.Store(key, cacheEntry{
    81  		payload:   data,
    82  		expiredAt: time.Now().Add(expiration),
    83  	})
    84  }
    85  
    86  // SetNX stores the data in the cache only if the key doesn't exist yet.
    87  func (c *InMemory) SetNX(key string, data []byte, expiration time.Duration) {
    88  	c.m.LoadOrStore(key, cacheEntry{
    89  		payload:   data,
    90  		expiredAt: time.Now().Add(expiration),
    91  	})
    92  }
    93  
    94  // GetCompressed works like Get but expect a compressed asset that is
    95  // uncompressed.
    96  func (c *InMemory) GetCompressed(key string) (io.Reader, bool) {
    97  	if r, ok := c.Get(key); ok {
    98  		if gr, err := gzip.NewReader(bytes.NewReader(r)); err == nil {
    99  			return gr, true
   100  		}
   101  	}
   102  	return nil, false
   103  }
   104  
   105  // SetCompressed works like Set but compress the asset data before storing it.
   106  func (c *InMemory) SetCompressed(key string, data []byte, expiration time.Duration) {
   107  	dataCompressed := new(bytes.Buffer)
   108  	gw := gzip.NewWriter(dataCompressed)
   109  	defer gw.Close()
   110  	if _, err := io.Copy(gw, bytes.NewReader(data)); err != nil {
   111  		return
   112  	}
   113  	c.Set(key, dataCompressed.Bytes(), expiration)
   114  }
   115  
   116  // RefreshTTL can be used to update the TTL of an existing entry in the cache.
   117  func (c *InMemory) RefreshTTL(key string, expiration time.Duration) {
   118  	value, ok := c.m.Load(key)
   119  	if !ok {
   120  		return
   121  	}
   122  
   123  	entry := value.(cacheEntry)
   124  	entry.expiredAt = time.Now().Add(expiration)
   125  	c.m.Store(key, entry)
   126  }