Package cache contains caching functions.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
5.2 KiB

package cache
import (
"encoding/gob"
"encoding/hex"
"fmt"
"hash"
"os"
"path"
"path/filepath"
"sync"
)
// kv is a key-value pair
type kv struct {
Key, Value interface{}
}
// MemoryKV implements a concurrent safe in-memory key-value store.
type MemoryKV struct {
items map[interface{}]interface{}
mutex sync.RWMutex
}
func NewMemoryKV() *MemoryKV {
return &MemoryKV{
items: make(map[interface{}]interface{}),
}
}
func (kv *MemoryKV) Contains(key interface{}) (bool, error) {
_, contained := kv.items[key]
return contained, nil
}
func (kv *MemoryKV) Delete(key interface{}) (bool, error) {
if _, ok := kv.items[key]; ok {
delete(kv.items, key)
return true, nil
}
return false, nil
}
func (kv *MemoryKV) Get(key interface{}) (interface{}, bool, error) {
value, ok := kv.items[key]
return value, ok, nil
}
func (kv *MemoryKV) Iter(fn func(key, value interface{})) error {
kv.mutex.RLock()
for key, value := range kv.items {
fn(key, value)
}
kv.mutex.RUnlock()
return nil
}
func (kv *MemoryKV) Len() int {
kv.mutex.RLock()
size := len(kv.items)
kv.mutex.RUnlock()
return size
}
func (kv *MemoryKV) Set(key, value interface{}) error {
kv.mutex.Lock()
kv.items[key] = value
kv.mutex.Unlock()
return nil
}
func (kv *MemoryKV) Replace(key, value interface{}) (bool, error) {
kv.mutex.Lock()
var exists bool
if _, exists = kv.items[key]; exists {
kv.items[key] = value
}
kv.mutex.Unlock()
return exists, nil
}
// DiskKV implements a concurrent safe on-disk key-value store.
type DiskKV struct {
Hasher hash.Hash
path string
perm os.FileMode
size int
mutex sync.RWMutex
}
func NewDiskKV(path string, perm os.FileMode) (*DiskKV, error) {
d, err := newDiskKV(path, perm)
if err != nil {
return nil, err
}
if err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
d.size++
}
return nil
}); err != nil {
return nil, err
}
return d, nil
}
func newDiskKV(path string, perm os.FileMode) (*DiskKV, error) {
var err error
if !filepath.IsAbs(path) {
if path, err = filepath.Abs(path); err != nil {
return nil, err
}
}
var info os.FileInfo
if info, err = os.Stat(path); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
if err = os.MkdirAll(path, perm); err != nil {
return nil, err
}
} else if !info.IsDir() {
return nil, fmt.Errorf("cache: %s is not a directory", path)
} else if err = os.Chmod(path, perm); err != nil {
return nil, err
}
return &DiskKV{
path: path,
perm: perm,
Hasher: Hasher(),
}, nil
}
func (d *DiskKV) pathFor(key interface{}) string {
b, err := HashUsing(key, d.Hasher)
if err != nil {
panic(err)
}
h := hex.EncodeToString(b)
return filepath.Join(d.path, h[:2], h[2:4], h)
}
func (d *DiskKV) Contains(key interface{}) (bool, error) {
d.mutex.RLock()
contains, err := d.contains(d.pathFor(key))
d.mutex.RUnlock()
return contains, err
}
func (d *DiskKV) contains(filename string) (bool, error) {
info, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return !info.IsDir(), nil
}
func (d *DiskKV) Len() int {
return d.size
}
func (d *DiskKV) Delete(key interface{}) (bool, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
p := d.pathFor(key)
c, err := d.contains(p)
if !c || err != nil {
return c, err
}
return d.delete(p)
}
func (d *DiskKV) delete(p string) (bool, error) {
if err := os.Remove(p); err != nil {
return false, err
}
// Cleanup cache parent directories if there are no entries.
for _, parent := range []string{path.Dir(p), path.Dir(path.Dir(p))} {
d, err := os.Open(parent)
if err != nil {
return true, err
}
defer d.Close()
items, _ := d.Readdirnames(-1)
if len(items) == 0 {
if err = os.Remove(parent); err != nil {
return true, err
}
}
}
d.size--
return true, nil
}
func (d *DiskKV) Get(key interface{}) (interface{}, bool, error) {
value := new(kv)
if err := d.get(d.pathFor(key), value); err != nil {
return nil, false, err
}
return value.Value, true, nil
}
func (d *DiskKV) get(filename string, value interface{}) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
dec := gob.NewDecoder(f)
return dec.Decode(value)
}
func (d *DiskKV) Iter(fn func(key, value interface{})) error {
return nil
}
func (d *DiskKV) Set(key, value interface{}) error {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.set(d.pathFor(key), key, value)
}
func (d *DiskKV) set(filename string, key, value interface{}) error {
c, err := d.contains(filename)
if err != nil {
return err
}
if err := os.MkdirAll(path.Dir(filename), d.perm); err != nil {
return err
}
var f *os.File
if f, err = os.Create(filename); err != nil {
return err
}
defer f.Close()
enc := gob.NewEncoder(f)
if err := enc.Encode(&kv{key, value}); err != nil {
return err
}
if !c {
d.size++
}
return nil
}
func (d *DiskKV) Replace(key, value interface{}) (bool, error) {
c, err := d.Contains(key)
if err != nil {
return c, err
}
if c {
return true, d.Set(key, value)
}
return false, nil
}
var (
_ Backend = (*MemoryKV)(nil)
_ Backend = (*DiskKV)(nil)
)