-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.go
More file actions
123 lines (112 loc) · 2.6 KB
/
Copy pathcache.go
File metadata and controls
123 lines (112 loc) · 2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package main
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
const durableTTL = 30 * 24 * time.Hour
type cacheEntry struct {
data any
expiresAt time.Time
}
type cacheStore struct {
mu sync.RWMutex
store map[string]cacheEntry
dir string
}
func newCache(dir string) *cacheStore {
return &cacheStore{store: make(map[string]cacheEntry), dir: dir}
}
func (c *cacheStore) get(key string, now time.Time) (any, bool) {
c.mu.RLock()
entry, ok := c.store[key]
c.mu.RUnlock()
if !ok || !now.Before(entry.expiresAt) {
if ok {
c.mu.Lock()
delete(c.store, key)
c.mu.Unlock()
}
return nil, false
}
return entry.data, true
}
func (c *cacheStore) set(key string, value any, expiresAt time.Time) {
c.mu.Lock()
c.store[key] = cacheEntry{data: value, expiresAt: expiresAt}
c.mu.Unlock()
}
// getDurable/setDurable: data that never changes (finished sessions), mirrored
// to disk when a dir is set, since OpenF1 can be unreachable for long stretches.
func (c *cacheStore) getDurable(key string, now time.Time) (any, bool) {
if value, ok := c.get(key, now); ok {
return value, true
}
if c.dir == "" {
return nil, false
}
path := c.path(key)
info, err := os.Stat(path)
if err != nil || now.Sub(info.ModTime()) > durableTTL {
return nil, false
}
data, err := os.ReadFile(path)
if err != nil {
return nil, false
}
var value any
if json.Unmarshal(data, &value) != nil {
return nil, false
}
c.set(key, value, now.Add(durableTTL))
return value, true
}
func (c *cacheStore) setDurable(key string, value any, now time.Time) error {
c.set(key, value, now.Add(durableTTL))
if c.dir == "" {
return nil
}
data, err := json.Marshal(value)
if err != nil {
return err
}
if err := os.MkdirAll(c.dir, 0o755); err != nil {
return err
}
tmp, err := os.CreateTemp(c.dir, ".tmp-*")
if err != nil {
return err
}
_, writeErr := tmp.Write(data)
closeErr := tmp.Close()
if err := errors.Join(writeErr, closeErr); err != nil {
os.Remove(tmp.Name())
return err
}
return os.Rename(tmp.Name(), c.path(key))
}
func (c *cacheStore) path(key string) string {
safe := strings.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' {
return r
}
return '_'
}, key)
return filepath.Join(c.dir, safe+".json")
}
// probe checks the cache directory can actually be written to.
func (c *cacheStore) probe() error {
if err := os.MkdirAll(c.dir, 0o755); err != nil {
return err
}
f, err := os.CreateTemp(c.dir, ".probe-*")
if err != nil {
return err
}
f.Close()
return os.Remove(f.Name())
}