-
Notifications
You must be signed in to change notification settings - Fork 1
/
cache.go
142 lines (115 loc) · 2.68 KB
/
cache.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
package ublock
import (
"crypto/md5"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
// ASSET CACHE
type assetCacheEntry struct {
filepath string // path and filename
exp time.Time // expiration date
}
type assetCache struct {
entries map[string]*assetCacheEntry // file list
ttl time.Duration // default TTL for newly created files
dir string // working directory
m sync.Mutex
}
func newAssetCache(dir string, ttl time.Duration) (*assetCache, error) {
c := &assetCache{
ttl: ttl,
dir: dir,
}
if err := c.refresh(); err != nil {
return nil, err
}
return c, nil
}
// refresh lists all the files from `dir` and creates a new index.
// is a file is expired it will be removed from filesystem
func (c *assetCache) refresh() error {
c.m.Lock()
defer c.m.Unlock()
fid, e := ioutil.ReadDir(c.dir)
if e != nil {
log("readdir err [%s]\n", e.Error())
return e
}
entries := make(map[string]*assetCacheEntry)
for _, fi := range fid {
if fi.IsDir() {
continue
}
// file names must follow the format: <md5_sum>_<unix_timestamp>
sub := strings.Split(fi.Name(), "_")
if len(sub) != 2 {
continue
}
// check for valid md5
if !isMD5(sub[0]) {
continue
}
// validate timestamp
i, err := strconv.ParseInt(sub[1], 10, 64)
if err != nil {
continue
}
// check if file is expired
t := time.Unix(i, 0)
if time.Now().After(t) {
log("asset cache: removing expired cache file [%s]\n", fi.Name())
os.Remove(filepath.Join(c.dir, fi.Name()))
continue
}
entries[sub[0]] = &assetCacheEntry{
filepath: filepath.Join(c.dir, fi.Name()),
exp: t,
}
}
c.entries = entries
return nil
}
// addCachesAsset adds the file to index and writes the data to filesystem
func (c *assetCache) add(name string, data []byte) error {
c.m.Lock()
defer c.m.Unlock()
k := md5Sum(name)
e := &assetCacheEntry{
exp: time.Now().Add(c.ttl),
}
e.filepath = filepath.Join(c.dir, fmt.Sprintf("%s_%d", k, e.exp.Unix()))
c.entries[k] = e
return ioutil.WriteFile(e.filepath, data, 0755)
}
// get returns the file's content.
// If the file is found to be expired, it will be removed from filesystem
func (c *assetCache) get(name string) ([]byte, error) {
k := md5Sum(name)
e, ok := c.entries[k]
if !ok {
return nil, nil
}
if time.Now().After(e.exp) {
delete(c.entries, k)
os.Remove(e.filepath)
return nil, nil
}
return ioutil.ReadFile(e.filepath)
}
func isMD5(s string) (ret bool) {
ret, _ = regexp.MatchString(`^[a-f0-9]{32}$`, s)
return
}
func md5Sum(s string) string {
h := md5.New()
io.WriteString(h, s)
return fmt.Sprintf("%x", h.Sum(nil))
}