Skip to content

Commit 7b42192

Browse files
authored
[filecache] Make it typed, add GetOrSet variants (#285)
## Summary Makes filecache better by: * Make it typed * export `Cache` type * Add `GetOrSet` variants. ## How was it tested? builds
1 parent 580a516 commit 7b42192

File tree

1 file changed

+71
-25
lines changed

1 file changed

+71
-25
lines changed

filecache/filecache.go

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ import (
1313
var NotFound = errors.New("not found")
1414
var Expired = errors.New("expired")
1515

16-
type cache struct {
16+
type Cache[T any] struct {
1717
domain string
1818
cacheDir string
1919
}
2020

21-
type data struct {
22-
Val []byte
21+
type data[T any] struct {
22+
Val T
2323
Exp time.Time
2424
}
2525

26-
type Option func(*cache)
26+
type Option[T any] func(*Cache[T])
2727

28-
func New(domain string, opts ...Option) *cache {
29-
result := &cache{domain: domain}
28+
func New[T any](domain string, opts ...Option[T]) *Cache[T] {
29+
result := &Cache[T]{domain: domain}
3030

3131
var err error
3232
result.cacheDir, err = os.UserCacheDir()
@@ -41,56 +41,102 @@ func New(domain string, opts ...Option) *cache {
4141
return result
4242
}
4343

44-
func WithCacheDir(dir string) Option {
45-
return func(c *cache) {
44+
func WithCacheDir[T any](dir string) Option[T] {
45+
return func(c *Cache[T]) {
4646
c.cacheDir = dir
4747
}
4848
}
4949

50-
func (c *cache) Set(key string, val []byte, dur time.Duration) error {
51-
d, err := json.Marshal(data{Val: val, Exp: time.Now().Add(dur)})
50+
// Set stores a value in the cache with the given key and expiration duration.
51+
func (c *Cache[T]) Set(key string, val T, dur time.Duration) error {
52+
d, err := json.Marshal(data[T]{Val: val, Exp: time.Now().Add(dur)})
5253
if err != nil {
5354
return errors.WithStack(err)
5455
}
5556

5657
return errors.WithStack(os.WriteFile(c.filename(key), d, 0644))
5758
}
5859

59-
func (c *cache) SetT(key string, val []byte, t time.Time) error {
60-
d, err := json.Marshal(data{Val: val, Exp: t})
60+
// SetWithTime is like Set but it allows the caller to specify the expiration
61+
// time of the value.
62+
func (c *Cache[T]) SetWithTime(key string, val T, t time.Time) error {
63+
d, err := json.Marshal(data[T]{Val: val, Exp: t})
6164
if err != nil {
6265
return errors.WithStack(err)
6366
}
6467

6568
return errors.WithStack(os.WriteFile(c.filename(key), d, 0644))
6669
}
6770

68-
func (c *cache) Get(key string) ([]byte, error) {
71+
// Get retrieves a value from the cache with the given key.
72+
func (c *Cache[T]) Get(key string) (T, error) {
6973
path := c.filename(key)
74+
resultData := data[T]{}
75+
7076
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
71-
return nil, NotFound
77+
return resultData.Val, NotFound
7278
}
7379

7480
content, err := os.ReadFile(path)
7581
if err != nil {
76-
return nil, errors.WithStack(err)
82+
return resultData.Val, errors.WithStack(err)
7783
}
78-
d := data{}
79-
if err := json.Unmarshal(content, &d); err != nil {
80-
return nil, errors.WithStack(err)
84+
85+
if err := json.Unmarshal(content, &resultData); err != nil {
86+
return resultData.Val, errors.WithStack(err)
8187
}
82-
if time.Now().After(d.Exp) {
83-
return nil, Expired
88+
if time.Now().After(resultData.Exp) {
89+
return resultData.Val, Expired
8490
}
85-
return d.Val, nil
91+
return resultData.Val, nil
8692
}
8793

88-
func (c *cache) filename(key string) string {
89-
dir := filepath.Join(c.cacheDir, c.domain)
90-
_ = os.MkdirAll(dir, 0755)
91-
return filepath.Join(dir, key)
94+
// GetOrSet is a convenience method that gets the value from the cache if it
95+
// exists, otherwise it calls the provided function to get the value and sets
96+
// it in the cache.
97+
// If the function returns an error, the error is returned and the value is not
98+
// cached.
99+
func (c *Cache[T]) GetOrSet(
100+
key string,
101+
f func() (T, time.Duration, error),
102+
) (T, error) {
103+
if val, err := c.Get(key); err == nil || !IsCacheMiss(err) {
104+
return val, err
105+
}
106+
107+
val, dur, err := f()
108+
if err != nil {
109+
return val, err
110+
}
111+
112+
return val, c.Set(key, val, dur)
113+
}
114+
115+
// GetOrSetWithTime is like GetOrSet but it allows the caller to specify the
116+
// expiration time of the value.
117+
func (c *Cache[T]) GetOrSetWithTime(
118+
key string,
119+
f func() (T, time.Time, error),
120+
) (T, error) {
121+
if val, err := c.Get(key); err == nil || !IsCacheMiss(err) {
122+
return val, err
123+
}
124+
125+
val, t, err := f()
126+
if err != nil {
127+
return val, err
128+
}
129+
130+
return val, c.SetWithTime(key, val, t)
92131
}
93132

133+
// IsCacheMiss returns true if the error is NotFound or Expired.
94134
func IsCacheMiss(err error) bool {
95135
return errors.Is(err, NotFound) || errors.Is(err, Expired)
96136
}
137+
138+
func (c *Cache[T]) filename(key string) string {
139+
dir := filepath.Join(c.cacheDir, c.domain)
140+
_ = os.MkdirAll(dir, 0755)
141+
return filepath.Join(dir, key)
142+
}

0 commit comments

Comments
 (0)