-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfilecache.go
80 lines (65 loc) · 1.68 KB
/
filecache.go
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
package rust2go
import (
"fmt"
"os"
"strings"
"sync"
)
// Filecache is a cache for known input output
// cache write to file in line mode, each line is a input output pair(K, V)
// cache file is append only, each time new cache appended to the end of the file
type FileCache[K comparable, V any] struct {
file *os.File
cache map[K]V
mu sync.RWMutex
}
func LoadFileCache[K comparable, V any](path string, lineParser func(string) (K, V, error)) (*FileCache[K, V], error) {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}
lines := strings.Split(string(content), "\n")
cache := make(map[K]V, len(lines))
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
k, v, err := lineParser(line)
if err != nil {
return nil, err
}
cache[k] = v
}
return &FileCache[K, V]{
file: f,
cache: cache,
}, nil
}
// Load load value from cache, if not found, call getter to get value and save to cache
func (fc *FileCache[K, V]) Load(k K, getter func(K) (V, error), lineFmt func(k K, v V) string) (V, error) {
fc.mu.RLock()
if v, ok := fc.cache[k]; ok {
defer fc.mu.RUnlock()
return v, nil
}
fc.mu.RUnlock()
v, err := getter(k)
fc.mu.Lock()
defer fc.mu.Unlock()
if err != nil {
return v, err
}
fc.cache[k] = v
_, err = fc.file.WriteString(strings.TrimSpace(lineFmt(k, v)) + "\n") // write line
if err != nil {
fmt.Println("[FileCache] write file cache failed", err) // TODO now just ignore the error, since it's not critical
}
return v, nil
}
func (fc *FileCache[K, V]) Close() error {
return fc.file.Close()
}