From 482fc9f51d4de6df237baba487755e79a82394a2 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sat, 20 Feb 2016 23:59:07 +0100 Subject: [PATCH] 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. --- src/restic/backend/readseeker.go | 63 ++++++++++++++ src/restic/backend/readseeker_test.go | 114 ++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 src/restic/backend/readseeker.go create mode 100644 src/restic/backend/readseeker_test.go diff --git a/src/restic/backend/readseeker.go b/src/restic/backend/readseeker.go new file mode 100644 index 000000000..ea063e3f3 --- /dev/null +++ b/src/restic/backend/readseeker.go @@ -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 +} diff --git a/src/restic/backend/readseeker_test.go b/src/restic/backend/readseeker_test.go new file mode 100644 index 000000000..013f2528e --- /dev/null +++ b/src/restic/backend/readseeker_test.go @@ -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) + } +}