summaryrefslogtreecommitdiff
path: root/internal/cache/wrappers.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cache/wrappers.go')
-rw-r--r--internal/cache/wrappers.go214
1 files changed, 214 insertions, 0 deletions
diff --git a/internal/cache/wrappers.go b/internal/cache/wrappers.go
new file mode 100644
index 000000000..edeea9bcd
--- /dev/null
+++ b/internal/cache/wrappers.go
@@ -0,0 +1,214 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package cache
+
+import (
+ "slices"
+
+ "codeberg.org/gruf/go-cache/v3/simple"
+ "codeberg.org/gruf/go-structr"
+)
+
+// SliceCache wraps a simple.Cache to provide simple loader-callback
+// functions for fetching + caching slices of objects (e.g. IDs).
+type SliceCache[T any] struct {
+ cache simple.Cache[string, []T]
+}
+
+// Init initializes the cache with given length + capacity.
+func (c *SliceCache[T]) Init(len, cap int) {
+ c.cache = simple.Cache[string, []T]{}
+ c.cache.Init(len, cap)
+}
+
+// Load will attempt to load an existing slice from cache for key, else calling load function and caching the result.
+func (c *SliceCache[T]) Load(key string, load func() ([]T, error)) ([]T, error) {
+ // Look for cached values.
+ data, ok := c.cache.Get(key)
+
+ if !ok {
+ var err error
+
+ // Not cached, load!
+ data, err = load()
+ if err != nil {
+ return nil, err
+ }
+
+ // Store the data.
+ c.cache.Set(key, data)
+ }
+
+ // Return data clone for safety.
+ return slices.Clone(data), nil
+}
+
+// Invalidate: see simple.Cache{}.InvalidateAll().
+func (c *SliceCache[T]) Invalidate(keys ...string) {
+ _ = c.cache.InvalidateAll(keys...)
+}
+
+// Trim: see simple.Cache{}.Trim().
+func (c *SliceCache[T]) Trim(perc float64) {
+ c.cache.Trim(perc)
+}
+
+// Clear: see simple.Cache{}.Clear().
+func (c *SliceCache[T]) Clear() {
+ c.cache.Clear()
+}
+
+// Len: see simple.Cache{}.Len().
+func (c *SliceCache[T]) Len() int {
+ return c.cache.Len()
+}
+
+// Cap: see simple.Cache{}.Cap().
+func (c *SliceCache[T]) Cap() int {
+ return c.cache.Cap()
+}
+
+// StructCache wraps a structr.Cache{} to simple index caching
+// by name (also to ease update to library version that introduced
+// this). (in the future it may be worth embedding these indexes by
+// name under the main database caches struct which would reduce
+// time required to access cached values).
+type StructCache[StructType any] struct {
+ cache structr.Cache[StructType]
+ index map[string]*structr.Index
+}
+
+// Init initializes the cache with given structr.CacheConfig{}.
+func (c *StructCache[T]) Init(config structr.CacheConfig[T]) {
+ c.index = make(map[string]*structr.Index, len(config.Indices))
+ c.cache = structr.Cache[T]{}
+ c.cache.Init(config)
+ for _, cfg := range config.Indices {
+ c.index[cfg.Fields] = c.cache.Index(cfg.Fields)
+ }
+}
+
+// GetOne calls structr.Cache{}.GetOne(), using a cached structr.Index{} by 'index' name.
+// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}.
+func (c *StructCache[T]) GetOne(index string, key ...any) (T, bool) {
+ i := c.index[index]
+ return c.cache.GetOne(i, i.Key(key...))
+}
+
+// Get calls structr.Cache{}.Get(), using a cached structr.Index{} by 'index' name.
+// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}.
+func (c *StructCache[T]) Get(index string, keys ...[]any) []T {
+ i := c.index[index]
+ return c.cache.Get(i, i.Keys(keys...)...)
+}
+
+// Put: see structr.Cache{}.Put().
+func (c *StructCache[T]) Put(values ...T) {
+ c.cache.Put(values...)
+}
+
+// LoadOne calls structr.Cache{}.LoadOne(), using a cached structr.Index{} by 'index' name.
+// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}.
+func (c *StructCache[T]) LoadOne(index string, load func() (T, error), key ...any) (T, error) {
+ i := c.index[index]
+ return c.cache.LoadOne(i, i.Key(key...), load)
+}
+
+// LoadIDs calls structr.Cache{}.Load(), using a cached structr.Index{} by 'index' name. Note: this also handles
+// conversion of the ID strings to structr.Key{} via structr.Index{}. Strong typing is used for caller convenience.
+//
+// If you need to load multiple cache keys other than by ID strings, please create another convenience wrapper.
+func (c *StructCache[T]) LoadIDs(index string, ids []string, load func([]string) ([]T, error)) ([]T, error) {
+ i := c.index[index]
+ if i == nil {
+ // we only perform this check here as
+ // we're going to use the index before
+ // passing it to cache in main .Load().
+ panic("missing index for cache type")
+ }
+
+ // Generate cache keys for ID types.
+ keys := make([]structr.Key, len(ids))
+ for x, id := range ids {
+ keys[x] = i.Key(id)
+ }
+
+ // Pass loader callback with wrapper onto main cache load function.
+ return c.cache.Load(i, keys, func(uncached []structr.Key) ([]T, error) {
+ uncachedIDs := make([]string, len(uncached))
+ for i := range uncached {
+ uncachedIDs[i] = uncached[i].Values()[0].(string)
+ }
+ return load(uncachedIDs)
+ })
+}
+
+// Store: see structr.Cache{}.Store().
+func (c *StructCache[T]) Store(value T, store func() error) error {
+ return c.cache.Store(value, store)
+}
+
+// Invalidate calls structr.Cache{}.Invalidate(), using a cached structr.Index{} by 'index' name.
+// Note: this also handles conversion of the untyped (any) keys to structr.Key{} via structr.Index{}.
+func (c *StructCache[T]) Invalidate(index string, key ...any) {
+ i := c.index[index]
+ c.cache.Invalidate(i, i.Key(key...))
+}
+
+// InvalidateIDs calls structr.Cache{}.Invalidate(), using a cached structr.Index{} by 'index' name. Note: this also
+// handles conversion of the ID strings to structr.Key{} via structr.Index{}. Strong typing is used for caller convenience.
+//
+// If you need to invalidate multiple cache keys other than by ID strings, please create another convenience wrapper.
+func (c *StructCache[T]) InvalidateIDs(index string, ids []string) {
+ i := c.index[index]
+ if i == nil {
+ // we only perform this check here as
+ // we're going to use the index before
+ // passing it to cache in main .Load().
+ panic("missing index for cache type")
+ }
+
+ // Generate cache keys for ID types.
+ keys := make([]structr.Key, len(ids))
+ for x, id := range ids {
+ keys[x] = i.Key(id)
+ }
+
+ // Pass to main invalidate func.
+ c.cache.Invalidate(i, keys...)
+}
+
+// Trim: see structr.Cache{}.Trim().
+func (c *StructCache[T]) Trim(perc float64) {
+ c.cache.Trim(perc)
+}
+
+// Clear: see structr.Cache{}.Clear().
+func (c *StructCache[T]) Clear() {
+ c.cache.Clear()
+}
+
+// Len: see structr.Cache{}.Len().
+func (c *StructCache[T]) Len() int {
+ return c.cache.Len()
+}
+
+// Cap: see structr.Cache{}.Cap().
+func (c *StructCache[T]) Cap() int {
+ return c.cache.Cap()
+}