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