mirror of
https://github.com/restic/restic.git
synced 2025-03-16 00:00:05 +01:00
Add Cache
This commit is contained in:
parent
17ea5b1be7
commit
cdc379d665
4 changed files with 220 additions and 0 deletions
9
src/restic/cache.go
Normal file
9
src/restic/cache.go
Normal 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
118
src/restic/cache/cache.go
vendored
Normal 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
75
src/restic/cache/cache_test.go
vendored
Normal 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
18
src/restic/cache/testing.go
vendored
Normal 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
|
||||
}
|
Loading…
Add table
Reference in a new issue