Files
musenalm/helpers/collections/session_cache.go
2025-05-24 10:57:31 +02:00

175 lines
3.3 KiB
Go

package collections
import (
"sync"
"time"
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
)
type cacheEntry struct {
user dbmodels.FixedUser
session dbmodels.FixedSession
}
type UserSessionCache struct {
mu sync.RWMutex
capacity int
cache sync.Map
approximateSize int
cleanupInterval time.Duration
stopCleanupSignal chan struct{}
}
func NewUserSessionCache(capacity int, cleanupInterval time.Duration) *UserSessionCache {
if capacity <= 0 {
capacity = 1000
}
if cleanupInterval <= 0 {
cleanupInterval = 5 * time.Minute
}
cache := &UserSessionCache{
capacity: capacity,
cache: sync.Map{},
cleanupInterval: cleanupInterval,
stopCleanupSignal: make(chan struct{}),
}
go cache.startCleanupRoutine()
return cache
}
func (c *UserSessionCache) Set(user *dbmodels.User, session *dbmodels.Session) (*dbmodels.FixedUser, *dbmodels.FixedSession) {
if user == nil || session == nil {
return nil, nil
}
newEntry := &cacheEntry{
user: user.Fixed(),
session: session.Fixed(),
}
_, loaded := c.cache.LoadOrStore(session.Token(), newEntry)
if !loaded {
c.cache.Store(session.Token(), newEntry)
c.mu.Lock()
c.approximateSize++
c.mu.Unlock()
}
return &newEntry.user, &newEntry.session
}
func (c *UserSessionCache) Get(sessionTokenClear string) (*dbmodels.FixedUser, *dbmodels.FixedSession, bool) {
if sessionTokenClear == "" {
return nil, nil, false
}
value, ok := c.cache.Load(sessionTokenClear)
if !ok {
return nil, nil, false
}
entry, ok := value.(*cacheEntry)
if !ok {
c.cache.Delete(sessionTokenClear)
return nil, nil, false
}
if time.Now().After(entry.session.Expires.Time()) {
c.cache.Delete(sessionTokenClear)
c.mu.Lock()
c.approximateSize--
c.mu.Unlock()
return nil, nil, false
}
return &entry.user, &entry.session, true
}
func (c *UserSessionCache) Delete(sessionTokenClear string) {
if sessionTokenClear == "" {
return
}
_, loaded := c.cache.LoadAndDelete(sessionTokenClear)
if loaded {
c.mu.Lock()
c.approximateSize--
c.mu.Unlock()
}
}
func (c *UserSessionCache) DeleteSessionByUserID(uid string) {
if uid == "" {
return
}
c.cache.Range(func(key, value any) bool {
entry, ok := value.(*cacheEntry)
if !ok {
c.cache.Delete(key)
return true
}
if entry.user.Id == uid {
c.cache.Delete(key)
c.mu.Lock()
c.approximateSize--
c.mu.Unlock()
}
return true
})
}
func (c *UserSessionCache) Clear() {
c.cache.Clear()
c.mu.Lock()
c.approximateSize = 0
c.mu.Unlock()
}
func (c *UserSessionCache) startCleanupRoutine() {
ticker := time.NewTicker(c.cleanupInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.cleanupExpiredItems()
case <-c.stopCleanupSignal:
return
}
}
}
func (c *UserSessionCache) cleanupExpiredItems() {
now := time.Now()
var newSize int
c.cache.Range(func(key, value any) bool {
entry, ok := value.(*cacheEntry)
if !ok {
c.cache.Delete(key)
return true
}
if now.After(entry.session.Expires.Time()) {
c.cache.Delete(key)
} else {
newSize++
}
return true
})
c.mu.Lock()
c.approximateSize = newSize
c.mu.Unlock()
}
func (c *UserSessionCache) StopCleanup() {
select {
case <-c.stopCleanupSignal:
default:
close(c.stopCleanupSignal)
}
}