Files
navidrome/plugins/host_cache.go
2026-01-12 08:16:04 -05:00

154 lines
4.2 KiB
Go

package plugins
import (
"context"
"time"
"github.com/jellydator/ttlcache/v3"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/plugins/host"
)
const (
defaultCacheTTL = 24 * time.Hour
)
// cacheServiceImpl implements the host.CacheService interface.
// Each plugin gets its own cache instance for isolation.
type cacheServiceImpl struct {
pluginName string
cache *ttlcache.Cache[string, any]
defaultTTL time.Duration
}
// newCacheService creates a new cacheServiceImpl instance with its own cache.
func newCacheService(pluginName string) *cacheServiceImpl {
cache := ttlcache.New[string, any](
ttlcache.WithTTL[string, any](defaultCacheTTL),
)
// Start the janitor goroutine to clean up expired entries
go cache.Start()
return &cacheServiceImpl{
pluginName: pluginName,
cache: cache,
defaultTTL: defaultCacheTTL,
}
}
// getTTL converts seconds to a duration, using default if 0 or negative
func (s *cacheServiceImpl) getTTL(seconds int64) time.Duration {
if seconds <= 0 {
return s.defaultTTL
}
return time.Duration(seconds) * time.Second
}
// SetString stores a string value in the cache.
func (s *cacheServiceImpl) SetString(ctx context.Context, key string, value string, ttlSeconds int64) error {
s.cache.Set(key, value, s.getTTL(ttlSeconds))
return nil
}
// GetString retrieves a string value from the cache.
func (s *cacheServiceImpl) GetString(ctx context.Context, key string) (string, bool, error) {
item := s.cache.Get(key)
if item == nil {
return "", false, nil
}
value, ok := item.Value().(string)
if !ok {
log.Debug(ctx, "Cache type mismatch", "plugin", s.pluginName, "key", key, "expected", "string")
return "", false, nil
}
return value, true, nil
}
// SetInt stores an integer value in the cache.
func (s *cacheServiceImpl) SetInt(ctx context.Context, key string, value int64, ttlSeconds int64) error {
s.cache.Set(key, value, s.getTTL(ttlSeconds))
return nil
}
// GetInt retrieves an integer value from the cache.
func (s *cacheServiceImpl) GetInt(ctx context.Context, key string) (int64, bool, error) {
item := s.cache.Get(key)
if item == nil {
return 0, false, nil
}
value, ok := item.Value().(int64)
if !ok {
log.Debug(ctx, "Cache type mismatch", "plugin", s.pluginName, "key", key, "expected", "int64")
return 0, false, nil
}
return value, true, nil
}
// SetFloat stores a float value in the cache.
func (s *cacheServiceImpl) SetFloat(ctx context.Context, key string, value float64, ttlSeconds int64) error {
s.cache.Set(key, value, s.getTTL(ttlSeconds))
return nil
}
// GetFloat retrieves a float value from the cache.
func (s *cacheServiceImpl) GetFloat(ctx context.Context, key string) (float64, bool, error) {
item := s.cache.Get(key)
if item == nil {
return 0, false, nil
}
value, ok := item.Value().(float64)
if !ok {
log.Debug(ctx, "Cache type mismatch", "plugin", s.pluginName, "key", key, "expected", "float64")
return 0, false, nil
}
return value, true, nil
}
// SetBytes stores a byte slice in the cache.
func (s *cacheServiceImpl) SetBytes(ctx context.Context, key string, value []byte, ttlSeconds int64) error {
s.cache.Set(key, value, s.getTTL(ttlSeconds))
return nil
}
// GetBytes retrieves a byte slice from the cache.
func (s *cacheServiceImpl) GetBytes(ctx context.Context, key string) ([]byte, bool, error) {
item := s.cache.Get(key)
if item == nil {
return nil, false, nil
}
value, ok := item.Value().([]byte)
if !ok {
log.Debug(ctx, "Cache type mismatch", "plugin", s.pluginName, "key", key, "expected", "[]byte")
return nil, false, nil
}
return value, true, nil
}
// Has checks if a key exists in the cache.
func (s *cacheServiceImpl) Has(ctx context.Context, key string) (bool, error) {
item := s.cache.Get(key)
return item != nil, nil
}
// Remove deletes a value from the cache.
func (s *cacheServiceImpl) Remove(ctx context.Context, key string) error {
s.cache.Delete(key)
return nil
}
// Close stops the cache's janitor goroutine and clears all entries.
// This is called when the plugin is unloaded.
func (s *cacheServiceImpl) Close() error {
s.cache.Stop()
s.cache.DeleteAll()
log.Debug("Closed plugin cache", "plugin", s.pluginName)
return nil
}
// Ensure cacheServiceImpl implements host.CacheService
var _ host.CacheService = (*cacheServiceImpl)(nil)