21 type Cacheable interface {
22 // Updates the last modified time
24 // Returns true if the Cacheable is nil, or if the last modified time is too old.
29 gob.RegisterName("*github.com/Kovensky/go-anidb.invalidKeyCache", &invalidKeyCache{})
32 type invalidKeyCache struct{ time.Time }
34 func (c *invalidKeyCache) Touch() {
37 func (c *invalidKeyCache) IsStale() bool {
38 return time.Now().Sub(c.Time) > InvalidKeyCacheDuration
41 type cacheDir struct {
48 if err := SetCacheDir(path.Join(os.TempDir(), "anidb", "cache")); err != nil {
55 // Sets the cache directory to the given path.
57 // go-anidb needs a valid cache directory to function, so, during module
58 // initialization, it uses os.TempDir() to set a default cache dir.
59 // go-anidb panics if it's unable to set the default cache dir.
60 func SetCacheDir(path string) (err error) {
68 if err = os.MkdirAll(path, 0755|os.ModeDir); err != nil {
83 // Returns the current cache dir.
84 func GetCacheDir() (path string) {
91 type cacheKey interface{}
93 // All "bad characters" that can't go in Windows paths.
94 // It's a superset of the "bad characters" on other OSes, so this works.
95 var badPath = regexp.MustCompile(`[\\/:\*\?\"<>\|]`)
97 func stringify(stuff ...cacheKey) []string {
98 ret := make([]string, len(stuff))
99 for i := range stuff {
100 s := fmt.Sprint(stuff[i])
101 ret[i] = badPath.ReplaceAllLiteralString(s, "_")
106 // Each key but the last is treated as a directory.
107 // The last key is treated as a regular file.
109 // This also means that cache keys that are file-backed
110 // cannot have subkeys.
111 func cachePath(keys ...cacheKey) string {
112 parts := append([]string{GetCacheDir()}, stringify(keys...)...)
113 p := path.Join(parts...)
117 // Opens the file that backs the specified keys.
118 func (c *cacheDir) Open(keys ...cacheKey) (fh *os.File, err error) {
119 subItem := cachePath(keys...)
120 return os.Open(subItem)
123 // Creates a new file to back the specified keys.
124 func (c *cacheDir) Create(keys ...cacheKey) (fh *os.File, err error) {
125 subItem := cachePath(keys...)
126 subDir := path.Dir(subItem)
128 if err = os.MkdirAll(subDir, 0755|os.ModeDir); err != nil {
131 return os.Create(subItem)
134 // Deletes the file that backs the specified keys.
135 func (c *cacheDir) Delete(keys ...cacheKey) (err error) {
136 return os.Remove(cachePath(keys...))
139 // Deletes the specified key and all subkeys.
140 func (c *cacheDir) DeleteAll(keys ...cacheKey) (err error) {
141 return os.RemoveAll(cachePath(keys...))
144 func (c *cacheDir) Get(v Cacheable, keys ...cacheKey) (err error) {
146 log.Println("Got entry", keys, "(error", err, ")")
149 val := reflect.ValueOf(v)
150 if k := val.Kind(); k == reflect.Ptr || k == reflect.Interface {
154 // panic because this is an internal coding mistake
155 panic("(*cacheDir).Get(): given Cacheable is not setable")
158 flock := lockFile(cachePath(keys...))
168 fh, err := c.Open(keys...)
173 buf := bytes.Buffer{}
174 if _, err = io.Copy(&buf, fh); err != nil {
178 if err = fh.Close(); err != nil {
187 gz, err := gzip.NewReader(&buf)
192 if e := gz.Close(); err == nil {
198 // if err == io.EOF {
203 switch f := gz.Header.Comment; f {
205 dec := gob.NewDecoder(gz)
208 return errors.New(fmt.Sprintf("Cached data (format %q) is not in a known format", f))
214 func (c *cacheDir) Set(v Cacheable, keys ...cacheKey) (n int64, err error) {
215 if v := reflect.ValueOf(v); !v.IsValid() {
216 panic("reflect.ValueOf() returned invaled value")
217 } else if k := v.Kind(); k == reflect.Ptr || k == reflect.Interface {
219 return // no point in saving nil
223 log.Println("Set entry", keys, "(error", err, ")")
226 // First we encode to memory -- we don't want to create/truncate a file and put bad data in it.
227 buf := bytes.Buffer{}
228 gz, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
232 gz.Header.Comment = "encoding/gob"
234 // it doesn't matter if the caller doesn't see this,
235 // the important part is that the cache does.
238 enc := gob.NewEncoder(gz)
241 if e := gz.Close(); err == nil {
249 // We have good data, time to actually put it in the cache
250 if flock := lockFile(cachePath(keys...)); flock != nil {
255 fh, err := c.Create(keys...)
260 if e := fh.Close(); err == nil {
264 n, err = io.Copy(fh, &buf)
268 // Checks if the given keys are not marked as invalid.
270 // If the key was marked as invalid but is no longer considered
271 // so, deletes the invalid marker.
272 func (c *cacheDir) CheckValid(keys ...cacheKey) bool {
273 invKeys := append([]cacheKey{"invalid"}, keys...)
274 inv := invalidKeyCache{}
276 if cache.Get(&inv, invKeys...) == nil {
278 cache.Delete(invKeys...)
286 // Deletes the given keys and marks them as invalid.
288 // They are considered invalid for InvalidKeyCacheDuration.
289 func (c *cacheDir) MarkInvalid(keys ...cacheKey) error {
290 invKeys := append([]cacheKey{"invalid"}, keys...)
292 cache.Delete(keys...)
293 _, err := cache.Set(&invalidKeyCache{}, invKeys...)