-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuilder.go
131 lines (116 loc) · 3.65 KB
/
builder.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
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
124
125
126
127
128
129
130
131
package fcache
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
// Builder configures & builds a new cache.
// cacheDir is the base directory of the cache.
// targetSize is target size of the cache. Depending on the eviction interval and insert load it may grow larger.
func Builder(cacheDir string, targetSize Size) *builder {
return &builder{cacheDir: cacheDir, targetSize: targetSize}
}
type builder struct {
cacheDir string
targetSize Size
evictionConfigured bool
evictionInterval time.Duration
fileMode os.FileMode
backgroundInit bool
initCallback InitCallback
}
// WithEvictionInterval configures how much time has to pass between evictions.
// By default its 10 minutes.
func (b *builder) WithEvictionInterval(evictionInterval time.Duration) *builder {
b.evictionInterval = evictionInterval
b.evictionConfigured = true
return b
}
// WithFileMode configures which file mode is used for the cache files.
// By default 0600 is used.
// Remember to also check your umask when you change this.
func (b *builder) WithFileMode(perm os.FileMode) *builder {
b.fileMode = perm
return b
}
// InitCallback is called after the cache finished restoring all entries from disk.
// cache is the Cache that was doing the init.
// err indicates if the init was successful or not.
type InitCallback func(cache Cache, err error)
// WithBackgroundInit restores the cache state in background instead of in Build.
// If initCallback is not nil it will be called once when all cache entries are restored or an error occurred.
func (b *builder) WithBackgroundInit(initCallback InitCallback) *builder {
b.backgroundInit = true
b.initCallback = initCallback
return b
}
// Build initializes the cache and also loads the state of existing entries from disk.
func (b *builder) Build() (Cache, error) {
if b.targetSize <= 0 {
return nil, errors.New("targetSize has to be > 0")
}
if !b.evictionConfigured {
b.evictionInterval = 10 * time.Minute
}
if b.fileMode == 0 {
b.fileMode = 0600
} else {
if b.fileMode != (b.fileMode | 0600) {
return nil, errors.New("fileMode has to be at least 0600")
}
}
dirMode := b.fileMode | 0700
err := os.MkdirAll(b.cacheDir, dirMode)
if err != nil && !errors.Is(err, os.ErrExist) {
return nil, fmt.Errorf("failed to create cacheDir at %s: %w", b.cacheDir, err)
}
writeTestPath := filepath.Join(b.cacheDir, "test")
f, err := os.OpenFile(writeTestPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, b.fileMode)
if err != nil {
return nil, fmt.Errorf("failed to create test file %s: %w", writeTestPath, err)
}
_, err = f.WriteString("test")
if err != nil {
return nil, fmt.Errorf("failed to write test file %s: %w", writeTestPath, err)
}
err = f.Close()
if err != nil {
return nil, fmt.Errorf("failed to close test file %s: %w", writeTestPath, err)
}
err = os.Remove(writeTestPath)
if err != nil {
return nil, fmt.Errorf("failed to remove test file %s: %w", writeTestPath, err)
}
c := &cache{
cacheDir: b.cacheDir,
targetSize: int64(b.targetSize),
entries: make(map[uint64]*cacheEntry),
evictionInterval: b.evictionInterval,
dirMode: dirMode,
fileMode: b.fileMode,
locker: NewLocker(),
}
err = c.createShardDirs()
if err != nil {
return nil, fmt.Errorf("failed to create shard dirs: %w", err)
}
if b.backgroundInit {
go func() {
err = c.loadEntries()
if b.initCallback != nil {
if err != nil {
err = fmt.Errorf("failed to restore cache: %w", err)
}
b.initCallback(c, err)
}
}()
} else {
err = c.loadEntries()
if err != nil {
return nil, fmt.Errorf("failed to restore cache: %w", err)
}
}
return c, nil
}