1
0
Fork 0
mirror of https://github.com/restic/restic.git synced 2025-03-16 00:00:05 +01:00

Add Cache

This commit is contained in:
Alexander Neumann 2016-09-04 17:47:50 +02:00
parent 17ea5b1be7
commit cdc379d665
4 changed files with 220 additions and 0 deletions

9
src/restic/cache.go Normal file
View file

@ -0,0 +1,9 @@
package restic
// Cache stores blobs locally.
type Cache interface {
GetBlob(BlobHandle, []byte) (bool, error)
PutBlob(BlobHandle, []byte) error
DeleteBlob(BlobHandle) error
HasBlob(BlobHandle) bool
}

118
src/restic/cache/cache.go vendored Normal file
View file

@ -0,0 +1,118 @@
// Package cache implements a local cache for data.
package cache
import (
"io"
"io/ioutil"
"os"
"path/filepath"
"restic"
"restic/errors"
)
// Cache is a local cache implementation.
type Cache struct {
dir string
}
// make sure that Cache implement restic.Cache
var _ restic.Cache = &Cache{}
// NewCache creates a new cache in the given directory. If it is the empty
// string, the cache directory for the current user is used instead.
func NewCache(dir string) restic.Cache {
return &Cache{dir: dir}
}
func fn(dir string, h restic.BlobHandle) string {
return filepath.Join(dir, string(h.Type), h.ID.String())
}
// GetBlob returns a blob from the cache. If the blob is not in the cache, ok
// is set to false.
func (c *Cache) GetBlob(h restic.BlobHandle, buf []byte) (ok bool, err error) {
filename := fn(c.dir, h)
fi, err := os.Stat(filename)
if os.IsNotExist(errors.Cause(err)) {
return false, nil
}
if fi.Size() != int64(len(buf)) {
return false, errors.Errorf("wrong bufsize: %d != %d", fi.Size(), len(buf))
}
if err != nil {
return false, errors.Wrap(err, "Stat")
}
var f *os.File
f, err = os.Open(filename)
if err != nil {
return false, errors.Wrap(err, "Open")
}
defer func() {
e := f.Close()
if err == nil {
err = e
}
}()
_, err = io.ReadFull(f, buf)
if err != nil {
return false, err
}
return true, nil
}
func createDirs(filename string) error {
dir := filepath.Dir(filename)
fi, err := os.Stat(dir)
if err != nil && os.IsNotExist(errors.Cause(err)) {
err = os.MkdirAll(dir, 0700)
return errors.Wrap(err, "mkdir cache dir")
}
if err != nil {
return err
}
if !fi.IsDir() {
return errors.Errorf("is not a directory: %v", dir)
}
return nil
}
// PutBlob saves a blob in the cache.
func (c *Cache) PutBlob(h restic.BlobHandle, buf []byte) error {
filename := fn(c.dir, h)
if err := createDirs(filename); err != nil {
return err
}
return ioutil.WriteFile(filename, buf, 0700)
}
// DeleteBlob removes a blob from the cache. If it isn't included in the cache,
// a nil error is returned.
func (c *Cache) DeleteBlob(h restic.BlobHandle) error {
err := os.Remove(fn(c.dir, h))
if err != nil && os.IsNotExist(errors.Cause(err)) {
err = nil
}
return err
}
// HasBlob check whether the cache has a particular blob.
func (c *Cache) HasBlob(h restic.BlobHandle) bool {
_, err := os.Stat(fn(c.dir, h))
if err != nil {
return false
}
return true
}

75
src/restic/cache/cache_test.go vendored Normal file
View file

@ -0,0 +1,75 @@
package cache
import (
"restic"
"restic/test"
"testing"
)
func TestCache(t *testing.T) {
c, cleanup := TestNewCache(t)
defer cleanup()
buf := test.Random(23, 2*1024*1024)
id := restic.Hash(buf)
h := restic.BlobHandle{ID: id, Type: restic.DataBlob}
if c.HasBlob(h) {
t.Errorf("cache has blob before storing it")
}
test.OK(t, c.PutBlob(h, buf))
if !c.HasBlob(h) {
t.Errorf("cache does not have blob after store")
}
treeHandle := restic.BlobHandle{ID: id, Type: restic.TreeBlob}
if c.HasBlob(treeHandle) {
t.Errorf("cache has tree blob although only a data blob was stored")
}
buf2 := make([]byte, len(buf))
ok, err := c.GetBlob(h, buf2)
test.OK(t, err)
if !ok {
t.Errorf("could not get blob from cache")
}
ok, err = c.GetBlob(treeHandle, buf2)
test.OK(t, err)
test.Assert(t, !ok, "got blob for tree that was never stored")
err = c.DeleteBlob(treeHandle)
test.OK(t, c.DeleteBlob(h))
if c.HasBlob(h) {
t.Errorf("cache still has blob after delete")
}
}
func TestCacheBufsize(t *testing.T) {
c, cleanup := TestNewCache(t)
defer cleanup()
h := restic.BlobHandle{ID: restic.NewRandomID(), Type: restic.TreeBlob}
buf := test.Random(5, 1000)
test.OK(t, c.PutBlob(h, buf))
for i := len(buf) - 1; i <= len(buf)+1; i++ {
buf2 := make([]byte, i)
ok, err := c.GetBlob(h, buf2)
if i == len(buf) {
test.OK(t, err)
test.Assert(t, ok, "unable to get blob for correct buf size")
test.Equals(t, buf, buf2)
continue
}
test.Assert(t, !ok, "ok is true for wrong buffer size %v", i)
test.Assert(t, err != nil, "error is nil, although buffer size is wrong")
}
}

18
src/restic/cache/testing.go vendored Normal file
View file

@ -0,0 +1,18 @@
package cache
import (
"path/filepath"
"restic"
"restic/test"
"testing"
)
// TestNewCache creates a cache usable for testing.
func TestNewCache(t testing.TB) (restic.Cache, func()) {
tempdir, cleanup := test.TempDir(t)
cachedir := filepath.Join(tempdir, "cache")
c := NewCache(cachedir)
return c, cleanup
}