]> git.lizzy.rs Git - go-anidb.git/blob - cache.go
anidb: Log UDP traffic
[go-anidb.git] / cache.go
1 package anidb
2
3 import (
4         "bytes"
5         "compress/gzip"
6         "encoding/gob"
7         "errors"
8         "fmt"
9         "io"
10         "os"
11         "path"
12         "reflect"
13         "regexp"
14         "sync"
15         "time"
16 )
17
18 type Cacheable interface {
19         // Updates the last modified time
20         Touch()
21         // Returns true if the Cacheable is nil, or if the last modified time is too old.
22         IsStale() bool
23 }
24
25 func init() {
26         gob.RegisterName("*github.com/Kovensky/go-anidb.invalidKeyCache", &invalidKeyCache{})
27 }
28
29 type invalidKeyCache struct{ time.Time }
30
31 func (c *invalidKeyCache) Touch() {
32         c.Time = time.Now()
33 }
34 func (c *invalidKeyCache) IsStale() bool {
35         return time.Now().Sub(c.Time) > InvalidKeyCacheDuration
36 }
37
38 type cacheDir struct {
39         *sync.RWMutex
40
41         CacheDir string
42 }
43
44 func init() {
45         if err := SetCacheDir(path.Join(os.TempDir(), "anidb", "cache")); err != nil {
46                 panic(err)
47         }
48 }
49
50 var cache cacheDir
51
52 // Sets the cache directory to the given path.
53 //
54 // go-anidb needs a valid cache directory to function, so, during module
55 // initialization, it uses os.TempDir() to set a default cache dir.
56 // go-anidb panics if it's unable to set the default cache dir.
57 func SetCacheDir(path string) (err error) {
58         m := cache.RWMutex
59         if m == nil {
60                 m = &sync.RWMutex{}
61                 cache.RWMutex = m
62         }
63         cache.Lock()
64
65         if err = os.MkdirAll(path, 0755|os.ModeDir); err != nil {
66                 cache.Unlock()
67                 return err
68         }
69
70         cache = cacheDir{
71                 RWMutex:  m,
72                 CacheDir: path,
73         }
74
75         cache.Unlock()
76         RefreshTitles()
77         return nil
78 }
79
80 // Returns the current cache dir.
81 func GetCacheDir() (path string) {
82         cache.RLock()
83         defer cache.RUnlock()
84
85         return cache.CacheDir
86 }
87
88 type cacheKey interface{}
89
90 // All "bad characters" that can't go in Windows paths.
91 // It's a superset of the "bad characters" on other OSes, so this works.
92 var badPath = regexp.MustCompile(`[\\/:\*\?\"<>\|]`)
93
94 func stringify(stuff ...cacheKey) []string {
95         ret := make([]string, len(stuff))
96         for i := range stuff {
97                 s := fmt.Sprint(stuff[i])
98                 ret[i] = badPath.ReplaceAllLiteralString(s, "_")
99         }
100         return ret
101 }
102
103 // Each key but the last is treated as a directory.
104 // The last key is treated as a regular file.
105 //
106 // This also means that cache keys that are file-backed
107 // cannot have subkeys.
108 func cachePath(keys ...cacheKey) string {
109         parts := append([]string{GetCacheDir()}, stringify(keys...)...)
110         p := path.Join(parts...)
111         return p
112 }
113
114 // Opens the file that backs the specified keys.
115 func (c *cacheDir) Open(keys ...cacheKey) (fh *os.File, err error) {
116         subItem := cachePath(keys...)
117         return os.Open(subItem)
118 }
119
120 // Creates a new file to back the specified keys.
121 func (c *cacheDir) Create(keys ...cacheKey) (fh *os.File, err error) {
122         subItem := cachePath(keys...)
123         subDir := path.Dir(subItem)
124
125         if err = os.MkdirAll(subDir, 0755|os.ModeDir); err != nil {
126                 return nil, err
127         }
128         return os.Create(subItem)
129 }
130
131 // Deletes the file that backs the specified keys.
132 func (c *cacheDir) Delete(keys ...cacheKey) (err error) {
133         return os.Remove(cachePath(keys...))
134 }
135
136 // Deletes the specified key and all subkeys.
137 func (c *cacheDir) DeleteAll(keys ...cacheKey) (err error) {
138         return os.RemoveAll(cachePath(keys...))
139 }
140
141 func (c *cacheDir) Get(v Cacheable, keys ...cacheKey) (err error) {
142
143         val := reflect.ValueOf(v)
144         if k := val.Kind(); k == reflect.Ptr || k == reflect.Interface {
145                 val = val.Elem()
146         }
147         if !val.CanSet() {
148                 // panic because this is an internal coding mistake
149                 panic("(*cacheDir).Get(): given Cacheable is not setable")
150         }
151
152         flock := lockFile(cachePath(keys...))
153         if flock != nil {
154                 flock.Lock()
155         }
156         defer func() {
157                 if flock != nil {
158                         flock.Unlock()
159                 }
160         }()
161
162         fh, err := c.Open(keys...)
163         if err != nil {
164                 return err
165         }
166
167         buf := bytes.Buffer{}
168         if _, err = io.Copy(&buf, fh); err != nil {
169                 fh.Close()
170                 return err
171         }
172         if err = fh.Close(); err != nil {
173                 return err
174         }
175
176         if flock != nil {
177                 flock.Unlock()
178                 flock = nil
179         }
180
181         gz, err := gzip.NewReader(&buf)
182         if err != nil {
183                 return err
184         }
185         defer func() {
186                 if e := gz.Close(); err == nil {
187                         err = e
188                 }
189         }()
190
191         switch f := gz.Header.Comment; f {
192         case "encoding/gob":
193                 dec := gob.NewDecoder(gz)
194                 err = dec.Decode(v)
195         default:
196                 return errors.New(fmt.Sprintf("Cached data (format %q) is not in a known format", f))
197         }
198
199         return
200 }
201
202 func (c *cacheDir) Set(v Cacheable, keys ...cacheKey) (n int64, err error) {
203         if v := reflect.ValueOf(v); !v.IsValid() {
204                 panic("reflect.ValueOf() returned invaled value")
205         } else if k := v.Kind(); k == reflect.Ptr || k == reflect.Interface {
206                 if v.IsNil() {
207                         return // no point in saving nil
208                 }
209         }
210
211         // First we encode to memory -- we don't want to create/truncate a file and put bad data in it.
212         buf := bytes.Buffer{}
213         gz, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
214         if err != nil {
215                 return 0, err
216         }
217         gz.Header.Comment = "encoding/gob"
218
219         // it doesn't matter if the caller doesn't see this,
220         // the important part is that the cache does.
221         v.Touch()
222
223         enc := gob.NewEncoder(gz)
224         err = enc.Encode(v)
225
226         if e := gz.Close(); err == nil {
227                 err = e
228         }
229
230         if err != nil {
231                 return 0, err
232         }
233
234         // We have good data, time to actually put it in the cache
235         if flock := lockFile(cachePath(keys...)); flock != nil {
236                 flock.Lock()
237                 defer flock.Unlock()
238         }
239
240         fh, err := c.Create(keys...)
241         if err != nil {
242                 return 0, err
243         }
244         defer func() {
245                 if e := fh.Close(); err == nil {
246                         err = e
247                 }
248         }()
249         n, err = io.Copy(fh, &buf)
250         return
251 }
252
253 // Checks if the given keys are not marked as invalid.
254 //
255 // If the key was marked as invalid but is no longer considered
256 // so, deletes the invalid marker.
257 func (c *cacheDir) CheckValid(keys ...cacheKey) bool {
258         invKeys := append([]cacheKey{"invalid"}, keys...)
259         inv := invalidKeyCache{}
260
261         if cache.Get(&inv, invKeys...) == nil {
262                 if inv.IsStale() {
263                         cache.Delete(invKeys...)
264                 } else {
265                         return false
266                 }
267         }
268         return true
269 }
270
271 // Deletes the given keys and marks them as invalid.
272 //
273 // They are considered invalid for InvalidKeyCacheDuration.
274 func (c *cacheDir) MarkInvalid(keys ...cacheKey) error {
275         invKeys := append([]cacheKey{"invalid"}, keys...)
276
277         cache.Delete(keys...)
278         _, err := cache.Set(&invalidKeyCache{}, invKeys...)
279         return err
280 }