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