forked from TrueCloudLab/restic
Implement streaming functions for local backend
This commit is contained in:
parent
64f7b4e5e9
commit
0a45ec1d3a
2 changed files with 93 additions and 0 deletions
|
@ -3,6 +3,7 @@ package backend
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -223,6 +224,54 @@ func (b *Local) Create(t Type, data []byte) (ID, error) {
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFrom reads content from rd and stores it as type t. Returned is the
|
||||||
|
// storage ID. If the blob is already present, returns ErrAlreadyPresent and
|
||||||
|
// the blob's ID.
|
||||||
|
func (b *Local) CreateFrom(t Type, rd io.Reader) (ID, error) {
|
||||||
|
// TODO: make sure that tempfile is removed upon error
|
||||||
|
|
||||||
|
// check hash while writing
|
||||||
|
hr := NewHashingReader(rd, newHash())
|
||||||
|
|
||||||
|
// create tempfile in backend
|
||||||
|
file, err := b.tempFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data to tempfile
|
||||||
|
_, err = io.Copy(file, hr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get ID
|
||||||
|
id := ID(hr.Sum(nil))
|
||||||
|
|
||||||
|
// check for duplicate ID
|
||||||
|
res, err := b.Test(t, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, arrar.Annotate(err, "test for presence")
|
||||||
|
}
|
||||||
|
|
||||||
|
if res {
|
||||||
|
return id, ErrAlreadyPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename file
|
||||||
|
err = b.renameFile(file, t, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Construct path for given Type and ID.
|
// Construct path for given Type and ID.
|
||||||
func (b *Local) filename(t Type, id ID) string {
|
func (b *Local) filename(t Type, id ID) string {
|
||||||
return filepath.Join(b.dirname(t, id), id.String())
|
return filepath.Join(b.dirname(t, id), id.String())
|
||||||
|
@ -256,6 +305,23 @@ func (b *Local) Get(t Type, id ID) ([]byte, error) {
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetReader returns a reader that yields the content stored under the given
|
||||||
|
// ID. The content is not verified. The reader should be closed after draining
|
||||||
|
// it.
|
||||||
|
func (b *Local) GetReader(t Type, id ID) (io.ReadCloser, error) {
|
||||||
|
if id == nil {
|
||||||
|
return nil, errors.New("unable to load nil ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to open file
|
||||||
|
file, err := os.Open(b.filename(t, id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Test returns true if a blob of the given type and ID exists in the backend.
|
// Test returns true if a blob of the given type and ID exists in the backend.
|
||||||
func (b *Local) Test(t Type, id ID) (bool, error) {
|
func (b *Local) Test(t Type, id ID) (bool, error) {
|
||||||
// try to open file
|
// try to open file
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package backend_test
|
package backend_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -80,8 +81,34 @@ func testBackend(b *backend.Local, t *testing.T) {
|
||||||
|
|
||||||
// compare content
|
// compare content
|
||||||
equals(t, test.data, string(buf))
|
equals(t, test.data, string(buf))
|
||||||
|
|
||||||
|
// compare content again via stream function
|
||||||
|
rd, err := b.GetReader(tpe, id)
|
||||||
|
ok(t, err)
|
||||||
|
buf, err = ioutil.ReadAll(rd)
|
||||||
|
ok(t, err)
|
||||||
|
equals(t, test.data, string(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test stream functions with first file
|
||||||
|
test := TestStrings[0]
|
||||||
|
id, err := backend.ParseID(test.id)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
// create with reader, should return error
|
||||||
|
_, err = b.CreateFrom(tpe, bytes.NewReader([]byte(test.data)))
|
||||||
|
assert(t, err == backend.ErrAlreadyPresent,
|
||||||
|
"expected ErrAlreadyPresent, got %v", err)
|
||||||
|
|
||||||
|
// remove and recreate
|
||||||
|
err = b.Remove(tpe, id)
|
||||||
|
ok(t, err)
|
||||||
|
|
||||||
|
id2, err := b.CreateFrom(tpe, bytes.NewReader([]byte(test.data)))
|
||||||
|
ok(t, err)
|
||||||
|
assert(t, id.Equal(id2),
|
||||||
|
"expected id %v, got %v", id, id2)
|
||||||
|
|
||||||
// list items
|
// list items
|
||||||
IDs := backend.IDs{}
|
IDs := backend.IDs{}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue