From d0d47c3781674db571b18df0544f66d93decf342 Mon Sep 17 00:00:00 2001 From: Kane York Date: Sat, 4 Jul 2020 19:58:38 -0700 Subject: memoryobject: make blob reader seekable Replace the bytes.Buffer with a bytes.Reader wrapped in a custom NopCloser, so that the extra reading methods are sill accessible. --- plumbing/memory.go | 17 ++++++++++++++--- plumbing/memory_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/plumbing/memory.go b/plumbing/memory.go index b8e1e1b..21337cc 100644 --- a/plumbing/memory.go +++ b/plumbing/memory.go @@ -3,7 +3,6 @@ package plumbing import ( "bytes" "io" - "io/ioutil" ) // MemoryObject on memory Object implementation @@ -39,9 +38,11 @@ func (o *MemoryObject) Size() int64 { return o.sz } // afterwards func (o *MemoryObject) SetSize(s int64) { o.sz = s } -// Reader returns a ObjectReader used to read the object's content. +// Reader returns an io.ReadCloser used to read the object's content. +// +// For a MemoryObject, this reader is seekable. func (o *MemoryObject) Reader() (io.ReadCloser, error) { - return ioutil.NopCloser(bytes.NewBuffer(o.cont)), nil + return nopCloser{bytes.NewReader(o.cont)}, nil } // Writer returns a ObjectWriter used to write the object's content. @@ -59,3 +60,13 @@ func (o *MemoryObject) Write(p []byte) (n int, err error) { // Close releases any resources consumed by the object when it is acting as a // ObjectWriter. func (o *MemoryObject) Close() error { return nil } + +// nopCloser exposes the extra methods of bytes.Reader while nopping Close(). +// +// This allows clients to attempt seeking in a cached Blob's Reader. +type nopCloser struct { + *bytes.Reader +} + +// Close does nothing. +func (nc nopCloser) Close() error { return nil } diff --git a/plumbing/memory_test.go b/plumbing/memory_test.go index 879ed37..2a141f4 100644 --- a/plumbing/memory_test.go +++ b/plumbing/memory_test.go @@ -1,6 +1,7 @@ package plumbing import ( + "io" "io/ioutil" . "gopkg.in/check.v1" @@ -56,6 +57,33 @@ func (s *MemoryObjectSuite) TestReader(c *C) { c.Assert(b, DeepEquals, []byte("foo")) } +func (s *MemoryObjectSuite) TestSeekableReader(c *C) { + const pageSize = 4096 + const payload = "foo" + content := make([]byte, pageSize+len(payload)) + copy(content[pageSize:], []byte(payload)) + + o := &MemoryObject{cont: content} + + reader, err := o.Reader() + c.Assert(err, IsNil) + defer func() { c.Assert(reader.Close(), IsNil) }() + + rs, ok := reader.(io.ReadSeeker) + c.Assert(ok, Equals, true) + + _, err = rs.Seek(pageSize, io.SeekStart) + c.Assert(err, IsNil) + + b, err := ioutil.ReadAll(rs) + c.Assert(err, IsNil) + c.Assert(b, DeepEquals, []byte(payload)) + + // Check that our Reader isn't also accidentally writable + _, ok = reader.(io.WriteSeeker) + c.Assert(ok, Equals, false) +} + func (s *MemoryObjectSuite) TestWriter(c *C) { o := &MemoryObject{} -- cgit