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() }