forked from TrueCloudLab/restic
Add backend.readSeeker
This struct implements an io.ReadSeeker on top of a backend. This is the easiest way to make the packer compatible to the new backend without loading a complete pack into a bytes.Buffer.
This commit is contained in:
parent
b114ab7108
commit
482fc9f51d
2 changed files with 177 additions and 0 deletions
63
src/restic/backend/readseeker.go
Normal file
63
src/restic/backend/readseeker.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package backend
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type readSeeker struct {
|
||||
be Backend
|
||||
h Handle
|
||||
t Type
|
||||
name string
|
||||
offset int64
|
||||
size int64
|
||||
}
|
||||
|
||||
// NewReadSeeker returns an io.ReadSeeker for the given object in the backend.
|
||||
func NewReadSeeker(be Backend, h Handle) io.ReadSeeker {
|
||||
return &readSeeker{be: be, h: h}
|
||||
}
|
||||
|
||||
func (rd *readSeeker) Read(p []byte) (int, error) {
|
||||
n, err := rd.be.Load(rd.h, p, rd.offset)
|
||||
rd.offset += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (rd *readSeeker) Seek(offset int64, whence int) (n int64, err error) {
|
||||
switch whence {
|
||||
case 0:
|
||||
rd.offset = offset
|
||||
case 1:
|
||||
rd.offset += offset
|
||||
case 2:
|
||||
if rd.size == 0 {
|
||||
rd.size, err = rd.getSize()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
pos := rd.size + offset
|
||||
if pos < 0 {
|
||||
return 0, errors.New("invalid offset, before start of blob")
|
||||
}
|
||||
|
||||
rd.offset = pos
|
||||
return rd.offset, nil
|
||||
default:
|
||||
return 0, errors.New("invalid value for parameter whence")
|
||||
}
|
||||
|
||||
return rd.offset, nil
|
||||
}
|
||||
|
||||
func (rd *readSeeker) getSize() (int64, error) {
|
||||
stat, err := rd.be.Stat(rd.h)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return stat.Size, nil
|
||||
}
|
114
src/restic/backend/readseeker_test.go
Normal file
114
src/restic/backend/readseeker_test.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package backend_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand"
|
||||
"restic/backend"
|
||||
"restic/backend/mem"
|
||||
"testing"
|
||||
|
||||
. "restic/test"
|
||||
)
|
||||
|
||||
func abs(a int) int {
|
||||
if a < 0 {
|
||||
return -a
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func loadAndCompare(t testing.TB, rd io.ReadSeeker, size int, offset int64, expected []byte) {
|
||||
var (
|
||||
pos int64
|
||||
err error
|
||||
)
|
||||
|
||||
if offset >= 0 {
|
||||
pos, err = rd.Seek(offset, 0)
|
||||
} else {
|
||||
pos, err = rd.Seek(offset, 2)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Seek(%d, 0) returned error: %v", offset, err)
|
||||
return
|
||||
}
|
||||
|
||||
if offset >= 0 && pos != offset {
|
||||
t.Errorf("pos after seek is wrong, want %d, got %d", offset, pos)
|
||||
} else if offset < 0 && pos != int64(size)+offset {
|
||||
t.Errorf("pos after relative seek is wrong, want %d, got %d", int64(size)+offset, pos)
|
||||
}
|
||||
|
||||
buf := make([]byte, len(expected))
|
||||
n, err := rd.Read(buf)
|
||||
|
||||
// if we requested data beyond the end of the file, ignore
|
||||
// ErrUnexpectedEOF error
|
||||
if offset > 0 && len(buf) > size && err == io.ErrUnexpectedEOF {
|
||||
err = nil
|
||||
buf = buf[:size]
|
||||
}
|
||||
|
||||
if offset < 0 && len(buf) > abs(int(offset)) && err == io.ErrUnexpectedEOF {
|
||||
err = nil
|
||||
buf = buf[:abs(int(offset))]
|
||||
}
|
||||
|
||||
if n != len(buf) {
|
||||
t.Errorf("Load(%d, %d): wrong length returned, want %d, got %d",
|
||||
len(buf), offset, len(buf), n)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Load(%d, %d): unexpected error: %v", len(buf), offset, err)
|
||||
return
|
||||
}
|
||||
|
||||
buf = buf[:n]
|
||||
if !bytes.Equal(buf, expected) {
|
||||
t.Errorf("Load(%d, %d) returned wrong bytes", len(buf), offset)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadSeeker(t *testing.T) {
|
||||
b := mem.New()
|
||||
|
||||
length := rand.Intn(1<<24) + 2000
|
||||
|
||||
data := Random(23, length)
|
||||
id := backend.Hash(data)
|
||||
|
||||
handle := backend.Handle{Type: backend.Data, Name: id.String()}
|
||||
err := b.Save(handle, data)
|
||||
if err != nil {
|
||||
t.Fatalf("Save() error: %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < 50; i++ {
|
||||
l := rand.Intn(length + 2000)
|
||||
o := rand.Intn(length + 2000)
|
||||
|
||||
if rand.Float32() > 0.5 {
|
||||
o = -o
|
||||
}
|
||||
|
||||
d := data
|
||||
if o > 0 && o < len(d) {
|
||||
d = d[o:]
|
||||
} else {
|
||||
o = len(d)
|
||||
d = d[:0]
|
||||
}
|
||||
|
||||
if l > 0 && l < len(d) {
|
||||
d = d[:l]
|
||||
}
|
||||
|
||||
rd := backend.NewReadSeeker(b, handle)
|
||||
loadAndCompare(t, rd, len(data), int64(o), d)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue