]> git.lizzy.rs Git - go-fscache.git/blob - cachegob.go
Replace obsolete lock package
[go-fscache.git] / cachegob.go
1 package fscache
2
3 import (
4         "bytes"
5         "compress/gzip"
6         "encoding/gob"
7         "errors"
8         "fmt"
9         "io"
10         "os"
11         "reflect"
12         "time"
13 )
14
15 // The default compression level of new CacheDir objects.
16 const DefaultCompressionLevel = gzip.BestCompression
17
18 func (cd *CacheDir) SetCompressionLevel(level int) {
19         cd.mutex.Lock()
20         defer cd.mutex.Unlock()
21
22         cd.compressionLevel = level
23 }
24
25 // Retrieves the current gzip compression level.
26 func (cd *CacheDir) GetCompressionLevel() int {
27         cd.mutex.Lock()
28         defer cd.mutex.Unlock()
29
30         return cd.compressionLevel
31 }
32
33 // Calls Get to retrieve the requested key from the cache.
34 //
35 // If the key is expired, then it is removed from the cache.
36 func (cd *CacheDir) GetAndExpire(v interface{}, max time.Duration, key ...CacheKey) (mtime time.Time, expired bool, err error) {
37         mtime, err = cd.Get(v, key...)
38
39         if err != nil && time.Now().Sub(mtime) > max {
40                 expired = true
41                 err = cd.Delete(key...)
42         }
43         return
44 }
45
46 // Gets the requested key from the cache. The given interface{} must be a pointer
47 // or otherwise be modifiable; otherwise Get will panic.
48 func (cd *CacheDir) Get(v interface{}, key ...CacheKey) (mtime time.Time, err error) {
49         val := reflect.ValueOf(v)
50         if k := val.Kind(); k == reflect.Ptr || k == reflect.Interface {
51                 val = val.Elem()
52         }
53         if !val.CanSet() {
54                 // API caller error
55                 panic("(*CacheDir).Get(): given interface{} is not setable")
56         }
57
58         lock, err := cd.Lock(key...)
59         if err != nil {
60                 return
61         }
62         defer func() {
63                 // We may unlock it early
64                 if lock != nil {
65                         lock.Unlock()
66                 }
67         }()
68
69         fh, err := cd.Open(key...)
70         if err != nil {
71                 return
72         }
73         stat, err := fh.Stat()
74         if err != nil {
75                 return
76         }
77         mtime = stat.ModTime()
78
79         buf := bytes.Buffer{}
80         if _, err = io.Copy(&buf, fh); err != nil {
81                 fh.Close()
82                 return
83         }
84         if err = fh.Close(); err != nil {
85                 return
86         }
87
88         if lock != nil {
89                 // early unlock
90                 lock.Unlock()
91                 lock = nil
92         }
93
94         gz, err := gzip.NewReader(&buf)
95         if err != nil {
96                 return
97         }
98         defer func() {
99                 if e := gz.Close(); err == nil {
100                         err = e
101                 }
102         }()
103
104         switch f := gz.Header.Comment; f {
105         case "encoding/gob":
106                 dec := gob.NewDecoder(gz)
107                 err = dec.Decode(v)
108         default:
109                 err = errors.New(fmt.Sprintf("Cached data (format %q) is not in a known format", f))
110         }
111
112         return
113 }
114
115 // Stores the given interface{} in the cache. Returns the size of the resulting file and the error, if any.
116 //
117 // Compresses the resulting data using gzip with the compression level set by SetCompressionLevel().
118 func (cd *CacheDir) Set(v interface{}, key ...CacheKey) (n int64, err error) {
119         if v := reflect.ValueOf(v); !v.IsValid() {
120                 panic("reflect.ValueOf() returned invaled value")
121         } else if k := v.Kind(); k == reflect.Ptr || k == reflect.Interface {
122                 if v.IsNil() {
123                         return // no point in saving nil
124                 }
125         }
126
127         // First we encode to memory -- we don't want to create/truncate a file and put bad data in it.
128         buf := bytes.Buffer{}
129         gz, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
130         if err != nil {
131                 return 0, err
132         }
133         gz.Header.Comment = "encoding/gob"
134
135         enc := gob.NewEncoder(gz)
136         err = enc.Encode(v)
137
138         if e := gz.Close(); err == nil {
139                 err = e
140         }
141
142         if err != nil {
143                 return 0, err
144         }
145
146         // We have good data, time to actually put it in the cache
147         lock, err := cd.Lock(key...)
148         switch {
149         case err == nil:
150                 // AOK
151                 defer lock.Unlock()
152         case os.IsNotExist(err):
153                 // new file
154         default:
155                 return 0, err
156         }
157
158         fh, err := cd.Create(key...)
159         if err != nil {
160                 return 0, err
161         }
162         if lock == nil {
163                 // the file didn't exist before, but it does now
164                 lock, err = cd.Lock(key...)
165                 if err != nil {
166                         return 0, err
167                 }
168                 defer lock.Unlock()
169         }
170
171         defer func() {
172                 if e := fh.Close(); err == nil {
173                         err = e
174                 }
175         }()
176         n, err = io.Copy(fh, &buf)
177         return
178 }