diff --git a/example_test.go b/example_test.go index c3b2c1a..d3cd2ca 100644 --- a/example_test.go +++ b/example_test.go @@ -45,7 +45,7 @@ func ExampleNewWriteSeekBuffer() { } func ExampleMultiReadSeeker() { - r, _ := io2.MultiReadSeeker( + r, _ := io2.NewMultiReadSeeker( strings.NewReader("Hello !"), strings.NewReader(" World"), ) diff --git a/multireader.go b/multireader.go index 5e082e9..8c0bd60 100644 --- a/multireader.go +++ b/multireader.go @@ -7,50 +7,84 @@ import ( "strings" ) +// MultiReader represents a multiple reader. +type MultiReader interface { + io.Reader + // Current returns a current index of multiple readers. The current starts 0. + Current() int +} + +// MultiReadCloser is the interface that groups the MultiReader and Close methods. +type MultiReadCloser interface { + MultiReader + io.Closer +} + +// MultiReadCloser is the interface that groups the MultiReader, Seek and SeekReader methods. +type MultiReadSeeker interface { + MultiReader + io.Seeker + // SeekReader sets the offset of multiple readers. + SeekReader(current int) (int64, error) +} + +// MultiReadSeekCloser is the interface that groups the MultiReadSeeker and Close methods. +type MultiReadSeekCloser interface { + MultiReadSeeker + io.Closer +} + var errSkip = errors.New("skip") -type reader struct { +type singleReader struct { io.ReadSeekCloser off int64 length int64 } type multiReader struct { - rs []*reader - off int - length int64 + rs []*singleReader + current int + length int64 } var _ io.ReadSeekCloser = (*multiReader)(nil) -// MultiReadCloser returns a ReaderCloser that's the logical concatenation +// NewMultiReader creates a Reader that's the logical concatenation // of the provided input readers. -func MultiReadCloser(rs ...io.ReadCloser) io.ReadCloser { - ds := make([]*reader, len(rs)) +func NewMultiReader(rs ...io.Reader) MultiReader { + ds := make([]*singleReader, len(rs)) for i, r := range rs { - ds[i] = &reader{ - ReadSeekCloser: Delegate(r), - } + ds[i] = &singleReader{ReadSeekCloser: Delegate(r)} } - io.MultiReader() return &multiReader{rs: ds} } -// MultiReadSeeker returns a ReadSeeker that's the logical concatenation +// NewMultiReadCloser create a ReaderCloser that's the logical concatenation // of the provided input readers. -func MultiReadSeeker(rs ...io.ReadSeeker) (io.ReadSeeker, error) { +func NewMultiReadCloser(rs ...io.ReadCloser) MultiReadCloser { + ds := make([]*singleReader, len(rs)) + for i, r := range rs { + ds[i] = &singleReader{ReadSeekCloser: Delegate(r)} + } + return &multiReader{rs: ds} +} + +// NewMultiReadSeeker creates a ReadSeeker that's the logical concatenation +// of the provided input readers. +func NewMultiReadSeeker(rs ...io.ReadSeeker) (MultiReadSeeker, error) { ds := make([]io.ReadSeekCloser, len(rs)) for i, r := range rs { ds[i] = NopReadSeekCloser(r) } - return MultiReadSeekCloser(ds...) + return NewMultiReadSeekCloser(ds...) } -// MultiReadSeekCloser returns a ReadSeekCloser that's the logical concatenation -// of the provided input readers. -func MultiReadSeekCloser(rs ...io.ReadSeekCloser) (io.ReadSeekCloser, error) { +// NewMultiReadSeekCloser creates a ReadSeekCloser that's the logical +// concatenation of the provided input readers. +func NewMultiReadSeekCloser(rs ...io.ReadSeekCloser) (MultiReadSeekCloser, error) { length := int64(0) - ds := make([]*reader, len(rs)) + ds := make([]*singleReader, len(rs)) for i, r := range rs { n, err := r.Seek(0, io.SeekEnd) if err != nil { @@ -59,7 +93,7 @@ func MultiReadSeekCloser(rs ...io.ReadSeekCloser) (io.ReadSeekCloser, error) { if _, err = r.Seek(0, io.SeekStart); err != nil { return nil, err } - ds[i] = &reader{ + ds[i] = &singleReader{ ReadSeekCloser: Delegate(r), length: n, } @@ -68,43 +102,48 @@ func MultiReadSeekCloser(rs ...io.ReadSeekCloser) (io.ReadSeekCloser, error) { return &multiReader{rs: ds, length: length}, nil } -func (mr *multiReader) each(i int, offset int64, fn func(r *reader) error) error { - mr.off = i +// Current returns a current index of multiple readers. +func (mr *multiReader) Current() int { + return mr.current +} + +func (mr *multiReader) each(i int, offset int64, fn func(r *singleReader) error) error { + mr.current = i if offset >= 0 { - for ; mr.off < len(mr.rs); mr.off++ { - if err := fn(mr.rs[mr.off]); err != nil { + for ; mr.current < len(mr.rs); mr.current++ { + if err := fn(mr.rs[mr.current]); err != nil { if err == errSkip { err = nil } return err } } - if mr.off >= len(mr.rs) { - mr.off = len(mr.rs) - 1 + if mr.current >= len(mr.rs) { + mr.current = len(mr.rs) - 1 } return nil } - for ; mr.off >= 0; mr.off-- { - if err := fn(mr.rs[mr.off]); err != nil { + for ; mr.current >= 0; mr.current-- { + if err := fn(mr.rs[mr.current]); err != nil { if err == errSkip { err = nil } return err } } - if mr.off < 0 { - mr.off = 0 + if mr.current < 0 { + mr.current = 0 } return nil } func (mr *multiReader) Read(p []byte) (n int, err error) { - if mr.off >= len(mr.rs) { + if mr.current >= len(mr.rs) { return 0, io.EOF } off := 0 - for ; mr.off < len(mr.rs); mr.off++ { - r := mr.rs[mr.off] + for ; mr.current < len(mr.rs); mr.current++ { + r := mr.rs[mr.current] n, err = r.Read(p[off:]) if err != nil { return 0, err @@ -130,8 +169,20 @@ func (mr *multiReader) Seek(offset int64, whence int) (int64, error) { return 0, errors.New("invalid whence") } +// SeekReader sets the offset of multiple readers. The current starts 0. +func (mr *multiReader) SeekReader(current int) (int64, error) { + var offset int64 + for i, r := range mr.rs { + if i >= current { + break + } + offset += r.length + } + return mr.Seek(offset, io.SeekStart) +} + func (mr *multiReader) resetTails() error { - for i := mr.off + 1; i < len(mr.rs); i++ { + for i := mr.current + 1; i < len(mr.rs); i++ { r := mr.rs[i] if _, err := r.Seek(0, io.SeekStart); err != nil { return err @@ -146,9 +197,9 @@ func (mr *multiReader) seekStart(offset int64, start int) (int64, error) { for i := 0; i < start; i++ { off += mr.rs[i].length } - err := mr.each(start, offset, func(r *reader) error { + err := mr.each(start, offset, func(r *singleReader) error { safeOffset := offset - if mr.off != len(mr.rs)-1 && safeOffset > (r.length-r.off) { + if mr.current != len(mr.rs)-1 && safeOffset > (r.length-r.off) { safeOffset = (r.length - r.off) } n, err := r.Seek(safeOffset, io.SeekStart) @@ -175,18 +226,18 @@ func (mr *multiReader) seekStart(offset int64, start int) (int64, error) { func (mr *multiReader) seekCurrent(offset int64) (int64, error) { off := int64(0) - for i := 0; i < mr.off; i++ { + for i := 0; i < mr.current; i++ { off += mr.rs[i].length } - r := mr.rs[mr.off] + r := mr.rs[mr.current] safeOffset := offset if offset >= 0 { - if mr.off != len(mr.rs)-1 && safeOffset > (r.length-r.off) { + if mr.current != len(mr.rs)-1 && safeOffset > (r.length-r.off) { safeOffset = (r.length - r.off) } } else { - if mr.off != 0 && -safeOffset > r.off { + if mr.current != 0 && -safeOffset > r.off { safeOffset = -r.off } } @@ -197,9 +248,9 @@ func (mr *multiReader) seekCurrent(offset int64) (int64, error) { r.off = n if diffset := offset - safeOffset; diffset != 0 { if offset >= 0 { - return mr.seekStart(diffset, mr.off+1) + return mr.seekStart(diffset, mr.current+1) } - return mr.seekEnd(diffset, mr.off-1) + return mr.seekEnd(diffset, mr.current-1) } if err := mr.resetTails(); err != nil { return 0, err @@ -212,9 +263,9 @@ func (mr *multiReader) seekEnd(offset int64, end int) (int64, error) { for i := end + 1; i < len(mr.rs); i++ { off -= mr.rs[i].length } - err := mr.each(end, offset, func(r *reader) error { + err := mr.each(end, offset, func(r *singleReader) error { safeOffset := offset - if mr.off != 0 && -safeOffset > r.length { + if mr.current != 0 && -safeOffset > r.length { safeOffset = -r.length } n, err := r.Seek(safeOffset, io.SeekEnd) @@ -238,7 +289,7 @@ func (mr *multiReader) seekEnd(offset int64, end int) (int64, error) { func (mr *multiReader) Close() error { var errs []string - mr.each(0, 1, func(r *reader) error { + mr.each(0, 1, func(r *singleReader) error { if err := r.Close(); err != nil { errs = append(errs, err.Error()) } @@ -248,7 +299,7 @@ func (mr *multiReader) Close() error { return fmt.Errorf("failed to close: %s", strings.Join(errs, "; ")) } mr.rs = nil - mr.off = 0 + mr.current = 0 mr.length = 0 return nil } diff --git a/multireader_test.go b/multireader_test.go index b3977b4..70f1b53 100644 --- a/multireader_test.go +++ b/multireader_test.go @@ -8,12 +8,12 @@ import ( "testing" ) -func testMultiReaderStrings(t *testing.T, ss ...string) io.ReadSeekCloser { +func testMultiReaderStrings(t *testing.T, ss ...string) MultiReadSeekCloser { rs := make([]io.ReadSeekCloser, len(ss)) for i, s := range ss { rs[i] = NopReadSeekCloser(strings.NewReader(s)) } - mr, err := MultiReadSeekCloser(rs...) + mr, err := NewMultiReadSeekCloser(rs...) if err != nil { t.Fatal(err) } @@ -22,25 +22,28 @@ func testMultiReaderStrings(t *testing.T, ss ...string) io.ReadSeekCloser { func TestMultiRead(t *testing.T) { tests := []struct { - reader func() io.ReadCloser + reader io.ReadCloser n int errstr string want string }{ { - reader: func() io.ReadCloser { - return testMultiReaderStrings(t, "abc", "def") - }, + reader: NopReadCloser(NewMultiReader( + strings.NewReader("abc"), + strings.NewReader("def"), + )), n: 6, want: "abcdef", }, { - reader: func() io.ReadCloser { - return MultiReadCloser(&Delegator{ - ReadFunc: func(p []byte) (int, error) { - return 0, errors.New("failed to read for coverage") - }, - }) - }, + reader: testMultiReaderStrings(t, "abc", "def"), + n: 6, + want: "abcdef", + }, { + reader: NewMultiReadCloser(&Delegator{ + ReadFunc: func(p []byte) (int, error) { + return 0, errors.New("failed to read for coverage") + }, + }), errstr: "failed to read for coverage", }, } @@ -55,7 +58,7 @@ func TestMultiRead(t *testing.T) { for i, test := range tests { close() - r := test.reader() + r := test.reader deferClose = r.Close p := make([]byte, test.n) @@ -85,7 +88,7 @@ func TestMultiRead(t *testing.T) { func TestMultiSeek(t *testing.T) { newErrSeekReader := func() io.ReadSeekCloser { return &multiReader{ - rs: []*reader{ + rs: []*singleReader{ { ReadSeekCloser: &Delegator{ SeekFunc: func(offset int64, whence int) (int64, error) { @@ -110,25 +113,25 @@ func TestMultiSeek(t *testing.T) { // NOTE: Check single reader. return NopReadSeekCloser(strings.NewReader("abcdef")) }, - offset: int64(2), + offset: 2, whence: io.SeekStart, - n: int64(2), + n: 2, after: "cdef", }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def") }, - offset: int64(2), + offset: 2, whence: io.SeekStart, - n: int64(2), + n: 2, after: "cdef", }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def") }, - offset: int64(4), + offset: 4, whence: io.SeekStart, - n: int64(4), + n: 4, after: "ef", }, { reader: func() io.ReadSeekCloser { @@ -136,16 +139,16 @@ func TestMultiSeek(t *testing.T) { ioutil.ReadAll(r) return r }, - offset: int64(0), + offset: 0, whence: io.SeekStart, - n: int64(0), + n: 0, after: "abcdef", }, { reader: func() io.ReadSeekCloser { r0 := NopReadSeekCloser(strings.NewReader("abc")) r1 := strings.NewReader("def") d1 := Delegate(r1) - r, err := MultiReadSeekCloser(r0, d1) + r, err := NewMultiReadSeekCloser(r0, d1) if err != nil { t.Fatal(err) } @@ -154,7 +157,7 @@ func TestMultiSeek(t *testing.T) { } return r }, - offset: int64(0), + offset: 0, whence: io.SeekStart, errstr: "failed to reset tails", }, { @@ -162,25 +165,25 @@ func TestMultiSeek(t *testing.T) { // NOTE: Check single reader. return NopReadSeekCloser(strings.NewReader("abcdefghi")) }, - offset: int64(-4), + offset: -4, whence: io.SeekEnd, - n: int64(5), + n: 5, after: "fghi", }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def", "ghi") }, - offset: int64(-4), + offset: -4, whence: io.SeekEnd, - n: int64(5), + n: 5, after: "fghi", }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def", "ghi") }, - offset: int64(-9), + offset: -9, whence: io.SeekEnd, - n: int64(0), + n: 0, after: "abcdefghi", }, { reader: func() io.ReadSeekCloser { @@ -191,9 +194,9 @@ func TestMultiSeek(t *testing.T) { } return r }, - offset: int64(3), + offset: 3, whence: io.SeekCurrent, - n: int64(7), + n: 7, after: "hi", }, { reader: func() io.ReadSeekCloser { @@ -203,9 +206,9 @@ func TestMultiSeek(t *testing.T) { } return r }, - offset: int64(3), + offset: 3, whence: io.SeekCurrent, - n: int64(7), + n: 7, after: "hi", }, { reader: func() io.ReadSeekCloser { @@ -215,9 +218,9 @@ func TestMultiSeek(t *testing.T) { } return r }, - offset: int64(-1), + offset: -1, whence: io.SeekCurrent, - n: int64(3), + n: 3, after: "defghi", }, { reader: func() io.ReadSeekCloser { @@ -227,16 +230,16 @@ func TestMultiSeek(t *testing.T) { } return r }, - offset: int64(-4), + offset: -4, whence: io.SeekCurrent, - n: int64(0), + n: 0, after: "abcdefghi", }, { reader: func() io.ReadSeekCloser { r0 := NopReadSeekCloser(strings.NewReader("abc")) r1 := strings.NewReader("def") d1 := Delegate(r1) - r, err := MultiReadSeekCloser(r0, d1) + r, err := NewMultiReadSeekCloser(r0, d1) if err != nil { t.Fatal(err) } @@ -245,7 +248,7 @@ func TestMultiSeek(t *testing.T) { } return r }, - offset: int64(0), + offset: 0, whence: io.SeekCurrent, errstr: "failed to reset tails", }, { @@ -253,14 +256,14 @@ func TestMultiSeek(t *testing.T) { // NOTE: Check single reader. return NopReadSeekCloser(strings.NewReader("abcdef")) }, - offset: int64(-1), + offset: -1, whence: io.SeekStart, errstr: "strings.Reader.Seek: negative position", }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def") }, - offset: int64(-1), + offset: -1, whence: io.SeekStart, errstr: "strings.Reader.Seek: negative position", }, { @@ -268,16 +271,16 @@ func TestMultiSeek(t *testing.T) { // NOTE: Check single reader. return NopReadSeekCloser(strings.NewReader("abcdef")) }, - offset: int64(10), + offset: 10, whence: io.SeekStart, - n: int64(10), + n: 10, }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def") }, - offset: int64(10), + offset: 10, whence: io.SeekStart, - n: int64(10), + n: 10, }, { reader: func() io.ReadSeekCloser { return testMultiReaderStrings(t, "abc", "def") @@ -369,7 +372,7 @@ func TestMultiReadSeeker_Errors(t *testing.T) { }, } for i, test := range tests { - _, err := MultiReadSeeker(test.r) + _, err := NewMultiReadSeeker(test.r) if err == nil { t.Fatalf("tests[%d] no error", i) } @@ -391,12 +394,12 @@ func TestMultiClose(t *testing.T) { errstr string }{ { - r: MultiReadCloser(NopReadCloser(strings.NewReader("no error"))), + r: NewMultiReadCloser(NopReadCloser(strings.NewReader("no error"))), }, { - r: MultiReadCloser(errCloseReader), + r: NewMultiReadCloser(errCloseReader), errstr: "failed to close: close error", }, { - r: MultiReadCloser(errCloseReader, errCloseReader), + r: NewMultiReadCloser(errCloseReader, errCloseReader), errstr: "failed to close: close error; close error", }, } @@ -416,3 +419,66 @@ func TestMultiClose(t *testing.T) { } } } + +func TestMultiSeekReader(t *testing.T) { + tests := []struct { + reader func() MultiReadSeeker + offset int + n int64 + current int + }{ + { + reader: func() MultiReadSeeker { + return testMultiReaderStrings(t, "a", "bc", "def") + }, + offset: 0, + n: 0, + current: 0, + }, { + reader: func() MultiReadSeeker { + return testMultiReaderStrings(t, "a", "bc", "def") + }, + offset: 2, + n: 3, + current: 2, + }, { + reader: func() MultiReadSeeker { + return testMultiReaderStrings(t, "a", "bc", "def") + }, + offset: 10, + n: 6, + current: 2, + }, { + reader: func() MultiReadSeeker { + r := testMultiReaderStrings(t, "a", "bc", "def") + r.Seek(2, io.SeekStart) + return r + }, + offset: 0, + n: 0, + current: 0, + }, { + reader: func() MultiReadSeeker { + r := testMultiReaderStrings(t, "a", "bc", "def") + r.Seek(5, io.SeekStart) + return r + }, + offset: 1, + n: 1, + current: 1, + }, + } + for i, test := range tests { + r := test.reader() + n, err := r.SeekReader(test.offset) + if err != nil { + t.Fatalf("tests[%d] error %v", i, err) + } + if n != test.n { + t.Errorf("tests[%d] n is %d; want %d", i, n, test.n) + } + if current := r.Current(); current != test.current { + t.Errorf("tests[%d] current is %d; want %d", i, current, test.current) + } + } +}