ezhik.

Veil Browser

Прототип
GitHub ↗ Скачать ZIP
store.go2.6 KB · goraw
package session

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"sync"
	"time"
)

var ErrNotFound = errors.New("session not found")

type Store struct {
	mu       sync.RWMutex
	sessions map[string]Session
	ttl      time.Duration
	now      func() time.Time
}

func NewStore(ttl time.Duration) *Store {
	return &Store{
		sessions: make(map[string]Session),
		ttl:      ttl,
		now:      time.Now,
	}
}

func (store *Store) Create() (Session, error) {
	id, err := randomID()
	if err != nil {
		return Session{}, err
	}
	tabID, err := randomID()
	if err != nil {
		return Session{}, err
	}
	now := store.now().UTC()
	created := Session{
		ID:        id,
		State:     DefaultState(tabID),
		CreatedAt: now,
		UpdatedAt: now,
		ExpiresAt: now.Add(store.ttl),
	}

	store.mu.Lock()
	store.sessions[id] = cloneSession(created)
	store.mu.Unlock()
	return cloneSession(created), nil
}

func (store *Store) Get(id string) (Session, error) {
	store.mu.RLock()
	current, ok := store.sessions[id]
	store.mu.RUnlock()
	if !ok {
		return Session{}, ErrNotFound
	}
	if !current.ExpiresAt.After(store.now()) {
		store.Delete(id)
		return Session{}, ErrNotFound
	}
	return cloneSession(current), nil
}

func (store *Store) Update(id string, state State) (Session, error) {
	if err := state.Validate(); err != nil {
		return Session{}, err
	}

	store.mu.Lock()
	defer store.mu.Unlock()
	current, ok := store.sessions[id]
	if !ok || !current.ExpiresAt.After(store.now()) {
		delete(store.sessions, id)
		return Session{}, ErrNotFound
	}
	now := store.now().UTC()
	current.State = cloneState(state)
	current.UpdatedAt = now
	current.ExpiresAt = now.Add(store.ttl)
	store.sessions[id] = current
	return cloneSession(current), nil
}

func (store *Store) Delete(id string) {
	store.mu.Lock()
	delete(store.sessions, id)
	store.mu.Unlock()
}

func (store *Store) DeleteExpired() int {
	now := store.now()
	deleted := 0
	store.mu.Lock()
	for id, current := range store.sessions {
		if !current.ExpiresAt.After(now) {
			delete(store.sessions, id)
			deleted++
		}
	}
	store.mu.Unlock()
	return deleted
}

func randomID() (string, error) {
	buffer := make([]byte, 32)
	if _, err := rand.Read(buffer); err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(buffer), nil
}

func cloneSession(current Session) Session {
	current.State = cloneState(current.State)
	return current
}

func cloneState(state State) State {
	copyState := state
	copyState.Tabs = make([]Tab, len(state.Tabs))
	for index, tab := range state.Tabs {
		copyState.Tabs[index] = tab
		copyState.Tabs[index].History = append([]string(nil), tab.History...)
	}
	return copyState
}