| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package admin
- import (
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "strings"
- "sync"
- "time"
- "golang.org/x/sync/singleflight"
- )
- type snapshotCacheEntry struct {
- ETag string
- Payload any
- ExpiresAt time.Time
- }
- type snapshotCache struct {
- mu sync.RWMutex
- ttl time.Duration
- items map[string]snapshotCacheEntry
- sf singleflight.Group
- }
- type snapshotCacheLoadResult struct {
- Entry snapshotCacheEntry
- Hit bool
- }
- func newSnapshotCache(ttl time.Duration) *snapshotCache {
- if ttl <= 0 {
- ttl = 30 * time.Second
- }
- return &snapshotCache{
- ttl: ttl,
- items: make(map[string]snapshotCacheEntry),
- }
- }
- func (c *snapshotCache) Get(key string) (snapshotCacheEntry, bool) {
- if c == nil || key == "" {
- return snapshotCacheEntry{}, false
- }
- now := time.Now()
- c.mu.RLock()
- entry, ok := c.items[key]
- c.mu.RUnlock()
- if !ok {
- return snapshotCacheEntry{}, false
- }
- if now.After(entry.ExpiresAt) {
- c.mu.Lock()
- delete(c.items, key)
- c.mu.Unlock()
- return snapshotCacheEntry{}, false
- }
- return entry, true
- }
- func (c *snapshotCache) Set(key string, payload any) snapshotCacheEntry {
- if c == nil {
- return snapshotCacheEntry{}
- }
- entry := snapshotCacheEntry{
- ETag: buildETagFromAny(payload),
- Payload: payload,
- ExpiresAt: time.Now().Add(c.ttl),
- }
- if key == "" {
- return entry
- }
- c.mu.Lock()
- c.items[key] = entry
- c.mu.Unlock()
- return entry
- }
- func (c *snapshotCache) GetOrLoad(key string, load func() (any, error)) (snapshotCacheEntry, bool, error) {
- if load == nil {
- return snapshotCacheEntry{}, false, nil
- }
- if entry, ok := c.Get(key); ok {
- return entry, true, nil
- }
- if c == nil || key == "" {
- payload, err := load()
- if err != nil {
- return snapshotCacheEntry{}, false, err
- }
- return c.Set(key, payload), false, nil
- }
- value, err, _ := c.sf.Do(key, func() (any, error) {
- if entry, ok := c.Get(key); ok {
- return snapshotCacheLoadResult{Entry: entry, Hit: true}, nil
- }
- payload, err := load()
- if err != nil {
- return nil, err
- }
- return snapshotCacheLoadResult{Entry: c.Set(key, payload), Hit: false}, nil
- })
- if err != nil {
- return snapshotCacheEntry{}, false, err
- }
- result, ok := value.(snapshotCacheLoadResult)
- if !ok {
- return snapshotCacheEntry{}, false, nil
- }
- return result.Entry, result.Hit, nil
- }
- func buildETagFromAny(payload any) string {
- raw, err := json.Marshal(payload)
- if err != nil {
- return ""
- }
- sum := sha256.Sum256(raw)
- return "\"" + hex.EncodeToString(sum[:]) + "\""
- }
- func parseBoolQueryWithDefault(raw string, def bool) bool {
- value := strings.TrimSpace(strings.ToLower(raw))
- if value == "" {
- return def
- }
- switch value {
- case "1", "true", "yes", "on":
- return true
- case "0", "false", "no", "off":
- return false
- default:
- return def
- }
- }
|