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 }