mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-06 14:26:22 +00:00
227 lines
4.3 KiB
Go
227 lines
4.3 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// memoryItem represents a cached item in memory.
|
|
type memoryItem struct {
|
|
Value []byte
|
|
Expiration time.Time
|
|
LastAccess time.Time
|
|
HitCount int64
|
|
}
|
|
|
|
// isExpired checks if the item has expired.
|
|
func (m *memoryItem) isExpired() bool {
|
|
if m.Expiration.IsZero() {
|
|
return false
|
|
}
|
|
return time.Now().After(m.Expiration)
|
|
}
|
|
|
|
// MemoryProvider is an in-memory implementation of the Provider interface.
|
|
type MemoryProvider struct {
|
|
mu sync.RWMutex
|
|
items map[string]*memoryItem
|
|
options *Options
|
|
hits int64
|
|
misses int64
|
|
}
|
|
|
|
// NewMemoryProvider creates a new in-memory cache provider.
|
|
func NewMemoryProvider(opts *Options) *MemoryProvider {
|
|
if opts == nil {
|
|
opts = &Options{
|
|
DefaultTTL: 5 * time.Minute,
|
|
MaxSize: 10000,
|
|
}
|
|
}
|
|
|
|
return &MemoryProvider{
|
|
items: make(map[string]*memoryItem),
|
|
options: opts,
|
|
}
|
|
}
|
|
|
|
// Get retrieves a value from the cache by key.
|
|
func (m *MemoryProvider) Get(ctx context.Context, key string) ([]byte, bool) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
item, exists := m.items[key]
|
|
if !exists {
|
|
m.misses++
|
|
return nil, false
|
|
}
|
|
|
|
if item.isExpired() {
|
|
delete(m.items, key)
|
|
m.misses++
|
|
return nil, false
|
|
}
|
|
|
|
item.LastAccess = time.Now()
|
|
item.HitCount++
|
|
m.hits++
|
|
|
|
return item.Value, true
|
|
}
|
|
|
|
// Set stores a value in the cache with the specified TTL.
|
|
func (m *MemoryProvider) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if ttl == 0 {
|
|
ttl = m.options.DefaultTTL
|
|
}
|
|
|
|
var expiration time.Time
|
|
if ttl > 0 {
|
|
expiration = time.Now().Add(ttl)
|
|
}
|
|
|
|
// Check max size and evict if necessary
|
|
if m.options.MaxSize > 0 && len(m.items) >= m.options.MaxSize {
|
|
if _, exists := m.items[key]; !exists {
|
|
m.evictOne()
|
|
}
|
|
}
|
|
|
|
m.items[key] = &memoryItem{
|
|
Value: value,
|
|
Expiration: expiration,
|
|
LastAccess: time.Now(),
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes a key from the cache.
|
|
func (m *MemoryProvider) Delete(ctx context.Context, key string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
delete(m.items, key)
|
|
return nil
|
|
}
|
|
|
|
// DeleteByPattern removes all keys matching the pattern.
|
|
func (m *MemoryProvider) DeleteByPattern(ctx context.Context, pattern string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
re, err := regexp.Compile(pattern)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid pattern: %w", err)
|
|
}
|
|
|
|
for key := range m.items {
|
|
if re.MatchString(key) {
|
|
delete(m.items, key)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Clear removes all items from the cache.
|
|
func (m *MemoryProvider) Clear(ctx context.Context) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.items = make(map[string]*memoryItem)
|
|
m.hits = 0
|
|
m.misses = 0
|
|
return nil
|
|
}
|
|
|
|
// Exists checks if a key exists in the cache.
|
|
func (m *MemoryProvider) Exists(ctx context.Context, key string) bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
item, exists := m.items[key]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
return !item.isExpired()
|
|
}
|
|
|
|
// Close closes the provider and releases any resources.
|
|
func (m *MemoryProvider) Close() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.items = nil
|
|
return nil
|
|
}
|
|
|
|
// Stats returns statistics about the cache provider.
|
|
func (m *MemoryProvider) Stats(ctx context.Context) (*CacheStats, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
// Clean expired items first
|
|
validKeys := 0
|
|
for _, item := range m.items {
|
|
if !item.isExpired() {
|
|
validKeys++
|
|
}
|
|
}
|
|
|
|
return &CacheStats{
|
|
Hits: m.hits,
|
|
Misses: m.misses,
|
|
Keys: int64(validKeys),
|
|
ProviderType: "memory",
|
|
ProviderStats: map[string]any{
|
|
"capacity": m.options.MaxSize,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// evictOne removes one item from the cache using LRU strategy.
|
|
func (m *MemoryProvider) evictOne() {
|
|
var oldestKey string
|
|
var oldestTime time.Time
|
|
|
|
for key, item := range m.items {
|
|
if item.isExpired() {
|
|
delete(m.items, key)
|
|
return
|
|
}
|
|
|
|
if oldestKey == "" || item.LastAccess.Before(oldestTime) {
|
|
oldestKey = key
|
|
oldestTime = item.LastAccess
|
|
}
|
|
}
|
|
|
|
if oldestKey != "" {
|
|
delete(m.items, oldestKey)
|
|
}
|
|
}
|
|
|
|
// CleanExpired removes all expired items from the cache.
|
|
func (m *MemoryProvider) CleanExpired(ctx context.Context) int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
count := 0
|
|
for key, item := range m.items {
|
|
if item.isExpired() {
|
|
delete(m.items, key)
|
|
count++
|
|
}
|
|
}
|
|
|
|
return count
|
|
}
|