package skate import ( "crypto/sha1" "errors" "fmt" "io" "io/ioutil" "os" "path" "gitlab.com/internetarchive/refcat/skate/atomic" ) var ErrCacheMiss = errors.New("cache miss") type Cache struct { Dir string initialized bool } // Init creates all directories. func (c *Cache) init() error { for i := 0; i < 256; i++ { s := fmt.Sprintf("%02x", i) d := path.Join(c.Dir, s) if _, err := os.Stat(d); os.IsNotExist(err) { if err := os.MkdirAll(d, 0755); err != nil { return err } } } c.initialized = true return nil } func (c *Cache) Has(k string) bool { var ( shard, name = shardedHash(k) spath = path.Join(c.Dir, shard, name) ) if _, err := os.Stat(spath); os.IsNotExist(err) { return false } return true } func (c *Cache) Get(k string) ([]byte, error) { var ( shard, name = shardedHash(k) spath = path.Join(c.Dir, shard, name) ) if !c.initialized { if err := c.init(); err != nil { return nil, err } } if _, err := os.Stat(spath); os.IsNotExist(err) { return nil, ErrCacheMiss } f, err := os.Open(spath) if err != nil { return nil, err } defer f.Close() return ioutil.ReadAll(f) } func (c *Cache) Set(k string, v []byte) error { var ( shard, name = shardedHash(k) spath = path.Join(c.Dir, shard, name) ) if !c.initialized { if err := c.init(); err != nil { return err } } if err := atomic.WriteFile(spath, v, 0755); err != nil { return err } return nil } // shardedHash returns a sha1 shard (2) and name (38) for a given string value // (e.g. a URL). func shardedHash(v string) (shard, name string) { h := sha1.New() _, _ = io.WriteString(h, v) name = fmt.Sprintf("%x", h.Sum(nil)) return name[:2], name[2:] }