162 lines
2.6 KiB
Go

package session
import (
"encoding/json"
"maps"
"os"
"sync"
"time"
)
type Store struct {
mu sync.RWMutex
sessions map[string]*Session
filePath string
saveInterval time.Duration
stopChan chan struct{}
}
type persistedData struct {
Sessions map[string]*Session `json:"sessions"`
SavedAt time.Time `json:"saved_at"`
}
func NewStore(filePath string) *Store {
store := &Store{
sessions: make(map[string]*Session),
filePath: filePath,
saveInterval: 5 * time.Minute,
stopChan: make(chan struct{}),
}
store.loadFromFile()
store.startPeriodicSave()
return store
}
func (s *Store) Save(session *Session) {
s.mu.Lock()
defer s.mu.Unlock()
s.sessions[session.ID] = session
}
func (s *Store) Get(sessionID string) (*Session, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
session, exists := s.sessions[sessionID]
if !exists {
return nil, false
}
if session.IsExpired() {
return nil, false
}
return session, true
}
func (s *Store) Delete(sessionID string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.sessions, sessionID)
}
func (s *Store) Cleanup() {
s.mu.Lock()
defer s.mu.Unlock()
for id, session := range s.sessions {
if session.IsExpired() {
delete(s.sessions, id)
}
}
}
func (s *Store) Stats() (total, active int) {
s.mu.RLock()
defer s.mu.RUnlock()
total = len(s.sessions)
for _, session := range s.sessions {
if !session.IsExpired() {
active++
}
}
return
}
func (s *Store) loadFromFile() {
if s.filePath == "" {
return
}
data, err := os.ReadFile(s.filePath)
if err != nil {
return
}
var persisted persistedData
if err := json.Unmarshal(data, &persisted); err != nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
for id, session := range persisted.Sessions {
if !session.IsExpired() {
session.ID = id
s.sessions[id] = session
}
}
}
func (s *Store) saveToFile() error {
if s.filePath == "" {
return nil
}
s.mu.RLock()
sessionsCopy := make(map[string]*Session, len(s.sessions))
maps.Copy(sessionsCopy, s.sessions)
s.mu.RUnlock()
data := persistedData{
Sessions: sessionsCopy,
SavedAt: time.Now(),
}
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
return os.WriteFile(s.filePath, jsonData, 0600)
}
func (s *Store) startPeriodicSave() {
go func() {
ticker := time.NewTicker(s.saveInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.Cleanup()
s.saveToFile()
case <-s.stopChan:
s.saveToFile()
return
}
}
}()
}
func (s *Store) Close() error {
close(s.stopChan)
return s.saveToFile()
}