forked from andeya/lessgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfilecache.go
239 lines (218 loc) · 5.05 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
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package lessgo
import (
"bytes"
"io"
"os"
"sync"
"time"
)
type (
MemoryCache struct {
usedSize int64 // 已用容量
singleFileAllow int64 // 允许的最大文件
maxCap int64 // 最大缓存总量
enable *bool
gc time.Duration // 缓存更新检查时长及动态过期时长
filemap map[string]*Cachefile
once sync.Once
sync.RWMutex
}
Cachefile struct {
fname string
info os.FileInfo
bytes []byte
last time.Time
exist bool
}
)
const (
_cancache = iota // 文件可被缓存
_canupdate // 文件缓存可被更新
_unknown // 文件状态为知
_notexist // 文件不存在
_notallowed // 文件大小超出允许范围
_willoverflow // 将超出缓存空间
)
func NewMemoryCache(singleFileAllow, maxCap int64, gc time.Duration) *MemoryCache {
return &MemoryCache{
singleFileAllow: singleFileAllow,
maxCap: maxCap,
gc: gc,
enable: new(bool),
filemap: map[string]*Cachefile{},
}
}
func (c *Cachefile) Size() int64 {
return int64(len(c.bytes))
}
func (m *MemoryCache) Enable() bool {
m.RLock()
defer m.RUnlock()
return *m.enable
}
func (m *MemoryCache) SetEnable(bl bool) {
m.Lock()
defer m.Unlock()
if m.enable == nil {
m.enable = &bl
if bl {
m.memoryCacheMonitor()
return
}
}
_bl := *m.enable
if bl && _bl {
return
}
*m.enable = bl
if _bl {
return
}
m.enable = &bl
m.once = sync.Once{}
m.memoryCacheMonitor()
}
func (m *MemoryCache) GetCacheFile(fname string) (*bytes.Reader, os.FileInfo, bool) {
m.RLock()
cfile, ok := m.filemap[fname]
if ok {
m.RUnlock()
return bytes.NewReader(cfile.bytes), cfile.info, cfile.exist
}
m.RUnlock()
m.Lock()
defer m.Unlock()
cfile, ok = m.filemap[fname]
if ok {
return bytes.NewReader(cfile.bytes), cfile.info, cfile.exist
}
file, err := os.Open(fname)
if err != nil {
return nil, nil, false
}
defer file.Close()
info, _ := file.Stat()
var bufferWriter bytes.Buffer
io.Copy(&bufferWriter, file)
// 检查是否加入缓存
if size := m.usedSize + info.Size(); size <= m.maxCap {
m.filemap[fname] = &Cachefile{
fname: fname,
bytes: bufferWriter.Bytes(),
info: info,
exist: true,
last: time.Now(),
}
m.usedSize = size
}
return bytes.NewReader(bufferWriter.Bytes()), info, true
}
func (m *MemoryCache) memoryCacheMonitor() {
enable := m.enable
go m.once.Do(func() {
for *enable {
time.Sleep(m.gc)
m.RLock()
expired, mustExpired := []string{}, []string{}
for fname, cfile := range m.filemap {
// 检查缓存超时,超时则加入过期列表
if cfile.last.Add(m.gc).Before(time.Now()) {
expired = append(expired, fname)
continue
}
// 获取本地文件状态
info, status := m.check(fname)
// 缓存内容被标记不存在时
if !cfile.exist {
// 检查本地文件是否可被缓存
switch status {
case _notexist:
case _notallowed, _willoverflow:
// 本地文件无法缓存时,移除缓存
expired = append(expired, fname)
case _canupdate, _cancache:
// 本地文件可被缓存时,更新缓存
m.update(cfile)
}
continue
}
switch status {
case _notexist:
// 本地文件被移除后,标记不存在
cfile.exist = false
cfile.bytes = nil
cfile.info = nil
continue
case _notallowed, _willoverflow:
// 本地文件不可缓存时,强制移除缓存
mustExpired = append(mustExpired, fname)
continue
case _cancache, _canupdate:
if info.ModTime().Equal(cfile.info.ModTime()) {
// 本地文件未更新时缓存不变
continue
}
// 本地文件已更新时更新缓存
m.update(cfile)
continue
}
}
// 移除过期缓存
m.RUnlock()
if len(expired) > 0 {
m.Lock()
for _, fname := range mustExpired {
m.usedSize -= m.filemap[fname].Size()
delete(m.filemap, fname)
}
for _, fname := range expired {
if m.filemap[fname].last.Add(m.gc).Before(time.Now()) {
m.usedSize -= m.filemap[fname].Size()
delete(m.filemap, fname)
}
}
m.Unlock()
}
}
})
}
func (m *MemoryCache) check(fname string) (os.FileInfo, int) {
info, err := os.Stat(fname)
if err != nil {
if os.IsNotExist(err) {
return info, _notexist
}
return info, _unknown
}
if info.Size() > m.singleFileAllow {
return info, _notallowed
}
cfile, ok := m.filemap[fname]
var currSize int64 = 0
if ok {
currSize = cfile.Size()
}
if m.usedSize-currSize+info.Size() > m.maxCap {
return info, _willoverflow
}
if m.usedSize+info.Size() <= m.maxCap {
return info, _cancache
}
return info, _canupdate
}
func (m *MemoryCache) update(c *Cachefile) {
oldsize := c.Size()
file, err := os.Open(c.fname)
if err != nil {
return
}
defer file.Close()
info, _ := file.Stat()
var bufferWriter bytes.Buffer
io.Copy(&bufferWriter, file)
c.bytes = bufferWriter.Bytes()
c.exist = true
c.info = info
c.last = time.Now()
m.usedSize = m.usedSize - oldsize + c.Size()
}