Skip to content

Commit 4109984

Browse files
authored
Make flac.NewSeek() use a buffered reader too (#72)
* Make flac.NewSeek() use a buffered reader Which is similar to how flac.New() works. * Test ReadSeeker * Rewrite ReadSeeker tests * Fix error message inconsistencies
1 parent e2b51c0 commit 4109984

File tree

3 files changed

+422
-2
lines changed

3 files changed

+422
-2
lines changed

flac.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"os"
3636

3737
"github.com/mewkiz/flac/frame"
38+
"github.com/mewkiz/flac/internal/bufseekio"
3839
"github.com/mewkiz/flac/meta"
3940
)
4041

@@ -96,7 +97,8 @@ func New(r io.Reader) (stream *Stream, err error) {
9697
// will not be buffered, which might result in performance issues. Using an
9798
// in-memory buffer like *bytes.Reader should work well.
9899
func NewSeek(rs io.ReadSeeker) (stream *Stream, err error) {
99-
stream = &Stream{r: rs, seekTableSize: defaultSeekTableSize}
100+
br := bufseekio.NewReadSeeker(rs)
101+
stream = &Stream{r: br, seekTableSize: defaultSeekTableSize}
100102

101103
// Verify FLAC signature and parse the StreamInfo metadata block.
102104
block, err := stream.parseStreamInfo()
@@ -121,7 +123,7 @@ func NewSeek(rs io.ReadSeeker) (stream *Stream, err error) {
121123
}
122124

123125
// Record file offset of the first frame header.
124-
stream.dataStart, err = rs.Seek(0, io.SeekCurrent)
126+
stream.dataStart, err = br.Seek(0, io.SeekCurrent)
125127
return stream, err
126128
}
127129

internal/bufseekio/readseeker.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package bufseekio
2+
3+
import (
4+
"errors"
5+
"io"
6+
)
7+
8+
const (
9+
defaultBufSize = 4096
10+
)
11+
12+
// ReadSeeker implements buffering for an io.ReadSeeker object.
13+
// ReadSeeker is based on bufio.Reader with Seek functionality added
14+
// and unneeded functionality removed.
15+
type ReadSeeker struct {
16+
buf []byte
17+
pos int64 // absolute start position of buf
18+
rd io.ReadSeeker // read-seeker provided by the client
19+
r, w int // buf read and write positions within buf
20+
err error
21+
}
22+
23+
const minReadBufferSize = 16
24+
25+
// NewReadSeekerSize returns a new ReadSeeker whose buffer has at least the specified
26+
// size. If the argument io.ReadSeeker is already a ReadSeeker with large enough
27+
// size, it returns the underlying ReadSeeker.
28+
func NewReadSeekerSize(rd io.ReadSeeker, size int) *ReadSeeker {
29+
// Is it already a Reader?
30+
b, ok := rd.(*ReadSeeker)
31+
if ok && len(b.buf) >= size {
32+
return b
33+
}
34+
if size < minReadBufferSize {
35+
size = minReadBufferSize
36+
}
37+
r := new(ReadSeeker)
38+
r.reset(make([]byte, size), rd)
39+
return r
40+
}
41+
42+
// NewReadSeeker returns a new ReadSeeker whose buffer has the default size.
43+
func NewReadSeeker(rd io.ReadSeeker) *ReadSeeker {
44+
return NewReadSeekerSize(rd, defaultBufSize)
45+
}
46+
47+
var errNegativeRead = errors.New("bufseekio: reader returned negative count from Read")
48+
49+
func (b *ReadSeeker) reset(buf []byte, r io.ReadSeeker) {
50+
*b = ReadSeeker{
51+
buf: buf,
52+
rd: r,
53+
}
54+
}
55+
56+
func (b *ReadSeeker) readErr() error {
57+
err := b.err
58+
b.err = nil
59+
return err
60+
}
61+
62+
// Read reads data into p.
63+
// It returns the number of bytes read into p.
64+
// The bytes are taken from at most one Read on the underlying Reader,
65+
// hence n may be less than len(p).
66+
// To read exactly len(p) bytes, use io.ReadFull(b, p).
67+
// If the underlying Reader can return a non-zero count with io.EOF,
68+
// then this Read method can do so as well; see the [io.Reader] docs.
69+
func (b *ReadSeeker) Read(p []byte) (n int, err error) {
70+
n = len(p)
71+
if n == 0 {
72+
if b.buffered() > 0 {
73+
return 0, nil
74+
}
75+
return 0, b.readErr()
76+
}
77+
if b.r == b.w {
78+
if b.err != nil {
79+
return 0, b.readErr()
80+
}
81+
if len(p) >= len(b.buf) {
82+
// Large read, empty buffer.
83+
// Read directly into p to avoid copy.
84+
n, b.err = b.rd.Read(p)
85+
if n < 0 {
86+
panic(errNegativeRead)
87+
}
88+
b.pos += int64(n)
89+
return n, b.readErr()
90+
}
91+
// One read.
92+
b.pos += int64(b.r)
93+
b.r = 0
94+
b.w = 0
95+
n, b.err = b.rd.Read(b.buf)
96+
if n < 0 {
97+
panic(errNegativeRead)
98+
}
99+
if n == 0 {
100+
return 0, b.readErr()
101+
}
102+
b.w += n
103+
}
104+
105+
// copy as much as we can
106+
// Note: if the slice panics here, it is probably because
107+
// the underlying reader returned a bad count. See issue 49795.
108+
n = copy(p, b.buf[b.r:b.w])
109+
b.r += n
110+
return n, nil
111+
}
112+
113+
// buffered returns the number of bytes that can be read from the current buffer.
114+
func (b *ReadSeeker) buffered() int { return b.w - b.r }
115+
116+
func (b *ReadSeeker) Seek(offset int64, whence int) (int64, error) {
117+
// The stream.Seek() implementation makes heavy use of seeking with offset 0
118+
// to obtain the current position; let's optimize for it.
119+
if offset == 0 && whence == io.SeekCurrent {
120+
return b.position(), nil
121+
}
122+
// When seeking from the end, the absolute position isn't known by ReadSeeker
123+
// so the current buffer cannot be used. Seeking cannot be avoided.
124+
if whence == io.SeekEnd {
125+
return b.seek(offset, whence)
126+
}
127+
// Calculate the absolute offset.
128+
abs := offset
129+
if whence == io.SeekCurrent {
130+
abs += b.position()
131+
}
132+
// Check if the offset is within buf.
133+
if abs >= b.pos && abs < b.pos+int64(b.w) {
134+
b.r = int(abs - b.pos)
135+
return abs, nil
136+
}
137+
138+
return b.seek(abs, io.SeekStart)
139+
}
140+
141+
func (b *ReadSeeker) seek(offset int64, whence int) (int64, error) {
142+
b.r = 0
143+
b.w = 0
144+
var err error
145+
b.pos, err = b.rd.Seek(offset, whence)
146+
return b.pos, err
147+
}
148+
149+
// position returns the absolute read offset.
150+
func (b *ReadSeeker) position() int64 {
151+
return b.pos + int64(b.r)
152+
}

0 commit comments

Comments
 (0)