-
Notifications
You must be signed in to change notification settings - Fork 1
/
cachegob.go
executable file
·178 lines (154 loc) · 3.6 KB
/
cachegob.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
package fscache
import (
"bytes"
"compress/gzip"
"encoding/gob"
"errors"
"fmt"
"io"
"os"
"reflect"
"time"
)
// The default compression level of new CacheDir objects.
const DefaultCompressionLevel = gzip.BestCompression
func (cd *CacheDir) SetCompressionLevel(level int) {
cd.mutex.Lock()
defer cd.mutex.Unlock()
cd.compressionLevel = level
}
// Retrieves the current gzip compression level.
func (cd *CacheDir) GetCompressionLevel() int {
cd.mutex.Lock()
defer cd.mutex.Unlock()
return cd.compressionLevel
}
// Calls Get to retrieve the requested key from the cache.
//
// If the key is expired, then it is removed from the cache.
func (cd *CacheDir) GetAndExpire(v interface{}, max time.Duration, key ...CacheKey) (mtime time.Time, expired bool, err error) {
mtime, err = cd.Get(v, key...)
if err != nil && time.Now().Sub(mtime) > max {
expired = true
err = cd.Delete(key...)
}
return
}
// Gets the requested key from the cache. The given interface{} must be a pointer
// or otherwise be modifiable; otherwise Get will panic.
func (cd *CacheDir) Get(v interface{}, key ...CacheKey) (mtime time.Time, err error) {
val := reflect.ValueOf(v)
if k := val.Kind(); k == reflect.Ptr || k == reflect.Interface {
val = val.Elem()
}
if !val.CanSet() {
// API caller error
panic("(*CacheDir).Get(): given interface{} is not setable")
}
lock, err := cd.Lock(key...)
if err != nil {
return
}
defer func() {
// We may unlock it early
if lock != nil {
lock.Unlock()
}
}()
fh, err := cd.Open(key...)
if err != nil {
return
}
stat, err := fh.Stat()
if err != nil {
return
}
mtime = stat.ModTime()
buf := bytes.Buffer{}
if _, err = io.Copy(&buf, fh); err != nil {
fh.Close()
return
}
if err = fh.Close(); err != nil {
return
}
if lock != nil {
// early unlock
lock.Unlock()
lock = nil
}
gz, err := gzip.NewReader(&buf)
if err != nil {
return
}
defer func() {
if e := gz.Close(); err == nil {
err = e
}
}()
switch f := gz.Header.Comment; f {
case "encoding/gob":
dec := gob.NewDecoder(gz)
err = dec.Decode(v)
default:
err = errors.New(fmt.Sprintf("Cached data (format %q) is not in a known format", f))
}
return
}
// Stores the given interface{} in the cache. Returns the size of the resulting file and the error, if any.
//
// Compresses the resulting data using gzip with the compression level set by SetCompressionLevel().
func (cd *CacheDir) Set(v interface{}, key ...CacheKey) (n int64, err error) {
if v := reflect.ValueOf(v); !v.IsValid() {
panic("reflect.ValueOf() returned invaled value")
} else if k := v.Kind(); k == reflect.Ptr || k == reflect.Interface {
if v.IsNil() {
return // no point in saving nil
}
}
// First we encode to memory -- we don't want to create/truncate a file and put bad data in it.
buf := bytes.Buffer{}
gz, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return 0, err
}
gz.Header.Comment = "encoding/gob"
enc := gob.NewEncoder(gz)
err = enc.Encode(v)
if e := gz.Close(); err == nil {
err = e
}
if err != nil {
return 0, err
}
// We have good data, time to actually put it in the cache
lock, err := cd.Lock(key...)
switch {
case err == nil:
// AOK
defer lock.Unlock()
case os.IsNotExist(err):
// new file
default:
return 0, err
}
fh, err := cd.Create(key...)
if err != nil {
return 0, err
}
if lock == nil {
// the file didn't exist before, but it does now
lock, err = cd.Lock(key...)
if err != nil {
return 0, err
}
defer lock.Unlock()
}
defer func() {
if e := fh.Close(); err == nil {
err = e
}
}()
n, err = io.Copy(fh, &buf)
return
}