diff --git a/buf_writer.go b/buf_writer.go new file mode 100644 index 0000000..6f6775a --- /dev/null +++ b/buf_writer.go @@ -0,0 +1,92 @@ +package id3v2 + +import ( + "bufio" + "io" +) + +type bufWriter struct { + err error + w *bufio.Writer + written int64 +} + +func newBufWriter(w io.Writer) *bufWriter { + return &bufWriter{w: bufio.NewWriter(w)} +} + +func (bw *bufWriter) EncodeAndWriteText(src string, to Encoding) { + if bw.err != nil { + return + } + bw.err = encodeWriteText(bw, src, to) +} + +func (bw *bufWriter) Flush() error { + if bw.err != nil { + return bw.err + } + return bw.w.Flush() +} + +func (bw *bufWriter) Reset(w io.Writer) { + bw.err = nil + bw.written = 0 + bw.w.Reset(w) +} + +func (bw *bufWriter) WriteByte(c byte) { + if bw.err != nil { + return + } + bw.err = bw.w.WriteByte(c) + if bw.err == nil { + bw.written += 1 + } +} + +func (bw *bufWriter) WriteBytesSize(size uint) { + if bw.err != nil { + return + } + bw.err = writeBytesSize(bw, size) +} + +func (bw *bufWriter) WriteString(s string) { + if bw.err != nil { + return + } + + var n int + n, bw.err = bw.w.WriteString(s) + bw.written += int64(n) +} + +func (bw *bufWriter) Write(p []byte) (n int, err error) { + if bw.err != nil { + return 0, bw.err + } + n, err = bw.w.Write(p) + bw.written += int64(n) + bw.err = err + return n, err +} + +func (bw *bufWriter) Written() int64 { + return bw.written +} + +func useBufWriter(w io.Writer, f func(*bufWriter)) (int64, error) { + var writtenBefore int64 + bw, ok := w.(*bufWriter) + if ok { + writtenBefore = bw.Written() + } else { + bw = getBufWriter(w) + defer putBufWriter(bw) + } + + f(bw) + + return bw.Written() - writtenBefore, bw.Flush() +} diff --git a/comment_frame.go b/comment_frame.go index 9f500ea..579e598 100644 --- a/comment_frame.go +++ b/comment_frame.go @@ -29,38 +29,13 @@ func (cf CommentFrame) WriteTo(w io.Writer) (n int64, err error) { return n, ErrInvalidLanguageLength } - bw, ok := resolveBufioWriter(w) - if !ok { - defer putBufioWriter(bw) - } - - var nn int - - if err = bw.WriteByte(cf.Encoding.Key); err == nil { - n += 1 - } - - nn, _ = bw.WriteString(cf.Language) - n += int64(nn) - - nn, err = encodeWriteText(bw, cf.Description, cf.Encoding) - n += int64(nn) - if err != nil { - bw.Flush() - return - } - - nn, _ = bw.Write(cf.Encoding.TerminationBytes) - n += int64(nn) - - nn, err = encodeWriteText(bw, cf.Text, cf.Encoding) - n += int64(nn) - if err != nil { - bw.Flush() - return - } - - return n, bw.Flush() + return useBufWriter(w, func(bw *bufWriter) { + bw.WriteByte(cf.Encoding.Key) + bw.WriteString(cf.Language) + bw.EncodeAndWriteText(cf.Description, cf.Encoding) + bw.Write(cf.Encoding.TerminationBytes) + bw.EncodeAndWriteText(cf.Text, cf.Encoding) + }) } func parseCommentFrame(rd io.Reader) (Framer, error) { diff --git a/encoding.go b/encoding.go index 81ade70..687ae0c 100644 --- a/encoding.go +++ b/encoding.go @@ -1,8 +1,6 @@ package id3v2 import ( - "bufio" - xencoding "golang.org/x/text/encoding" "golang.org/x/text/encoding/charmap" "golang.org/x/text/encoding/unicode" @@ -91,17 +89,19 @@ func decodeText(src []byte, from Encoding) string { } // encodeWriteText encodes src from UTF-8 to "to" encoding and writes to bw. -func encodeWriteText(bw *bufio.Writer, src string, to Encoding) (n int, err error) { +func encodeWriteText(bw *bufWriter, src string, to Encoding) error { if to.Equals(EncodingUTF8) { - return bw.WriteString(src) + bw.WriteString(src) + return nil } toXEncoding := resolveXEncoding(nil, to) encoded, err := toXEncoding.NewEncoder().String(src) if err != nil { - return 0, err + return err } - return bw.WriteString(encoded) + bw.WriteString(encoded) + return nil } // resolveXEncoding returns golang.org/x/text/encoding encoding diff --git a/encoding_test.go b/encoding_test.go index bd41cc4..77a8817 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -1,7 +1,6 @@ package id3v2 import ( - "bufio" "bytes" "testing" ) @@ -38,22 +37,22 @@ func TestEncodeWriteText(t *testing.T) { } buf := new(bytes.Buffer) - bw := bufio.NewWriter(buf) + bw := newBufWriter(buf) for _, tc := range testCases { buf.Reset() - n, err := encodeWriteText(bw, tc.src, tc.to) - if err != nil { - t.Errorf("Error by encoding and writing text: %v", err) - } + bw.Reset(buf) - bw.Flush() + bw.EncodeAndWriteText(tc.src, tc.to) + if err := bw.Flush(); err != nil { + t.Fatal(err) + } got := buf.Bytes() if !bytes.Equal(got, tc.expected) { t.Errorf("Expected %q from %q encoding, got %q", tc.expected, tc.to, got) } - if n != tc.size { - t.Errorf("Expected %v size, got %v", tc.size, n) + if bw.Written() != int64(tc.size) { + t.Errorf("Expected %v size, got %v", tc.size, bw.Written()) } } } diff --git a/header.go b/header.go index d75f247..284c288 100644 --- a/header.go +++ b/header.go @@ -5,7 +5,6 @@ package id3v2 import ( - "bufio" "bytes" "errors" "io" @@ -57,32 +56,3 @@ func parseHeader(rd io.Reader) (tagHeader, error) { func isID3Tag(data []byte) bool { return len(data) == len(id3Identifier) && bytes.Equal(data, id3Identifier) } - -func writeTagHeader(bw *bufio.Writer, framesSize uint, version byte) error { - // Identifier - if _, err := bw.Write(id3Identifier); err != nil { - return err - } - - // Version - if err := bw.WriteByte(version); err != nil { - return err - } - - // Revision - if err := bw.WriteByte(0); err != nil { - return err - } - - // Flags - if err := bw.WriteByte(0); err != nil { - return err - } - - // Size of frames - if err := writeBytesSize(bw, framesSize); err != nil { - return err - } - - return nil -} diff --git a/header_test.go b/header_test.go index fddc156..9383686 100644 --- a/header_test.go +++ b/header_test.go @@ -5,7 +5,6 @@ package id3v2 import ( - "bufio" "bytes" "testing" ) @@ -36,11 +35,9 @@ func TestWriteTagHeader(t *testing.T) { t.Parallel() buf := new(bytes.Buffer) - bw := bufio.NewWriter(buf) + bw := newBufWriter(buf) - if err := writeTagHeader(bw, 15351, 4); err != nil { - t.Fatal(err) - } + writeTagHeader(bw, 15351, 4) if err := bw.Flush(); err != nil { t.Fatal(err) } diff --git a/parse_test.go b/parse_test.go index 1033af0..fac5265 100644 --- a/parse_test.go +++ b/parse_test.go @@ -5,7 +5,6 @@ package id3v2 import ( - "bufio" "bytes" "errors" "fmt" @@ -320,7 +319,7 @@ func TestParseOptionsParseFramesWithSequenceFrames(t *testing.T) { } else if commentFrame.Language == "ger" { isGerCommentInFrame = true } else { - t.Error("Got unknown comment frame: %v", commentFrame) + t.Errorf("Got unknown comment frame: %v", commentFrame) } } @@ -348,18 +347,18 @@ func TestParseInvalidFrameSize(t *testing.T) { t.Parallel() buf := new(bytes.Buffer) - bw := bufio.NewWriter(buf) + bw := newBufWriter(buf) // Write tag header. - if err := writeTagHeader(bw, tagHeaderSize+16, 4); err != nil { - t.Fatal(err) - } - bw.Flush() + writeTagHeader(bw, tagHeaderSize+16, 4) // Write valid TIT2 frame. - buf.Write([]byte{0x54, 0x49, 0x54, 0x32, 00, 00, 00, 06, 00, 00, 03}) // header and encoding - buf.WriteString("Title") + bw.Write([]byte{0x54, 0x49, 0x54, 0x32, 00, 00, 00, 06, 00, 00, 03}) // header and encoding + bw.WriteString("Title") // Write invalid frame (size byte can't be greater than 127). - buf.Write([]byte{0x54, 0x49, 0x54, 0x32, 255, 255, 255, 255, 00, 00}) + bw.Write([]byte{0x54, 0x49, 0x54, 0x32, 255, 255, 255, 255, 00, 00}) + if err := bw.Flush(); err != nil { + t.Fatal(err) + } tag, err := ParseReader(buf, defaultOpts) if tag == nil || err != nil { diff --git a/picture_frame.go b/picture_frame.go index 4cf9fb5..40822e1 100644 --- a/picture_frame.go +++ b/picture_frame.go @@ -25,42 +25,15 @@ func (pf PictureFrame) Size() int { } func (pf PictureFrame) WriteTo(w io.Writer) (n int64, err error) { - bw, ok := resolveBufioWriter(w) - if !ok { - defer putBufioWriter(bw) - } - - var nn int - - if err = bw.WriteByte(pf.Encoding.Key); err == nil { - n += 1 - } - - nn, _ = bw.WriteString(pf.MimeType) - n += int64(nn) - - if err = bw.WriteByte(0); err == nil { - n += 1 - } - - if err = bw.WriteByte(pf.PictureType); err == nil { - n += 1 - } - - nn, err = encodeWriteText(bw, pf.Description, pf.Encoding) - n += int64(nn) - if err != nil { - bw.Flush() - return - } - - nn, _ = bw.Write(pf.Encoding.TerminationBytes) - n += int64(nn) - - nn, _ = bw.Write(pf.Picture) - n += int64(nn) - - return n, bw.Flush() + return useBufWriter(w, func(bw *bufWriter) { + bw.WriteByte(pf.Encoding.Key) + bw.WriteString(pf.MimeType) + bw.WriteByte(0) + bw.WriteByte(pf.PictureType) + bw.EncodeAndWriteText(pf.Description, pf.Encoding) + bw.Write(pf.Encoding.TerminationBytes) + bw.Write(pf.Picture) + }) } func parsePictureFrame(rd io.Reader) (Framer, error) { diff --git a/pools.go b/pools.go index 4cbb745..ada7b35 100644 --- a/pools.go +++ b/pools.go @@ -5,7 +5,6 @@ package id3v2 import ( - "bufio" "bytes" "io" "sync" @@ -34,20 +33,20 @@ func putByteSlice(b []byte) { bsPool.Put(b) } -// bwPool is a pool of *bufio.Writer. +// bwPool is a pool of *bufWriter. var bwPool = sync.Pool{ - New: func() interface{} { return bufio.NewWriter(nil) }, + New: func() interface{} { return newBufWriter(nil) }, } -// getBufioWriter returns *bufio.Writer with specified w. -func getBufioWriter(w io.Writer) *bufio.Writer { - bw := bwPool.Get().(*bufio.Writer) +// getBufWriter returns *bufWriter with specified w. +func getBufWriter(w io.Writer) *bufWriter { + bw := bwPool.Get().(*bufWriter) bw.Reset(w) return bw } -// putBufioWriter puts bw back to pool. -func putBufioWriter(bw *bufio.Writer) { +// putBufWriter puts bw back to pool. +func putBufWriter(bw *bufWriter) { bwPool.Put(bw) } diff --git a/reader.go b/reader.go index 2cff2b5..f5262d5 100644 --- a/reader.go +++ b/reader.go @@ -7,7 +7,6 @@ package id3v2 import ( "bufio" "bytes" - "errors" "io" ) @@ -86,7 +85,7 @@ func (r *reader) ReadTillDelim(delim byte) ([]byte, error) { // delims. func (r *reader) ReadTillDelims(delims []byte) ([]byte, error) { if len(delims) == 0 { - return nil, errors.New("delims is empty") + return nil, nil } if len(delims) == 1 { return r.ReadTillDelim(delims[0]) diff --git a/tag.go b/tag.go index 1b3003b..7c76f89 100644 --- a/tag.go +++ b/tag.go @@ -5,7 +5,6 @@ package id3v2 import ( - "bufio" "errors" "io" "os" @@ -370,51 +369,40 @@ func (tag *Tag) WriteTo(w io.Writer) (n int64, err error) { } // Write tag header. - bw := getBufioWriter(w) - defer putBufioWriter(bw) - if err := writeTagHeader(bw, uint(framesSize), tag.version); err != nil { - return 0, err - } - n += tagHeaderSize + bw := getBufWriter(w) + defer putBufWriter(bw) + writeTagHeader(bw, uint(framesSize), tag.version) // Write frames. err = tag.iterateOverAllFrames(func(id string, f Framer) error { - nn, err := writeFrame(bw, id, f) - n += nn - return err + return writeFrame(bw, id, f) }) if err != nil { - return n, err + bw.Flush() + return bw.Written(), err } - return n, bw.Flush() + return bw.Written(), bw.Flush() } -func writeFrame(bw *bufio.Writer, id string, frame Framer) (int64, error) { - if err := writeFrameHeader(bw, id, uint(frame.Size())); err != nil { - return 0, err - } - - frameSize, err := frame.WriteTo(bw) - return frameHeaderSize + frameSize, err +func writeTagHeader(bw *bufWriter, framesSize uint, version byte) { + bw.Write(id3Identifier) + bw.WriteByte(version) + bw.WriteByte(0) // Revision + bw.WriteByte(0) // Flags + bw.WriteBytesSize(framesSize) } -func writeFrameHeader(bw *bufio.Writer, id string, frameSize uint) error { - // ID - if _, err := bw.WriteString(id); err != nil { - return err - } - - // Size - if err := writeBytesSize(bw, frameSize); err != nil { - return err - } +func writeFrame(bw *bufWriter, id string, frame Framer) error { + writeFrameHeader(bw, id, uint(frame.Size())) + _, err := frame.WriteTo(bw) + return err +} - // Flags - if _, err := bw.Write([]byte{0, 0}); err != nil { - return err - } - return nil +func writeFrameHeader(bw *bufWriter, id string, frameSize uint) { + bw.WriteString(id) + bw.WriteBytesSize(frameSize) + bw.Write([]byte{0, 0}) // Flags } // Close closes tag's file, if tag was opened with a file. diff --git a/tag_test.go b/tag_test.go index 2c7a70f..3caf33b 100644 --- a/tag_test.go +++ b/tag_test.go @@ -486,7 +486,7 @@ func TestEncodedText(t *testing.T) { t.Fatalf("Error by writing to buf: %v", err) } if n != int64(tag.Size()) { - t.Errorf("Expected WriteTo n %v, got %v", tag.Size(), n) + t.Errorf("Expected WriteTo n==%v, got %v", tag.Size(), n) } tag, err = ParseReader(buf, Options{Parse: true}) @@ -530,7 +530,7 @@ func TestWriteToN(t *testing.T) { t.Fatalf("Error by writing: %v", err) } if n != int64(tag.Size()) { - t.Errorf("Expected WriteTo n %v, got %v", tag.Size(), n) + t.Errorf("Expected WriteTo n==%v, got %v", tag.Size(), n) } if int64(buf.Len()) != n { t.Errorf("buf.Len() and n are not equal: %v != %v ", buf.Len(), n) diff --git a/text_frame.go b/text_frame.go index 16d2f09..a07f84e 100644 --- a/text_frame.go +++ b/text_frame.go @@ -17,26 +17,11 @@ func (tf TextFrame) Size() int { return 1 + encodedSize(tf.Text, tf.Encoding) } -func (tf TextFrame) WriteTo(w io.Writer) (n int64, err error) { - bw, ok := resolveBufioWriter(w) - if !ok { - defer putBufioWriter(bw) - } - - var nn int - - if err = bw.WriteByte(tf.Encoding.Key); err == nil { - n += 1 - } - - nn, err = encodeWriteText(bw, tf.Text, tf.Encoding) - n += int64(nn) - if err != nil { - bw.Flush() - return - } - - return n, bw.Flush() +func (tf TextFrame) WriteTo(w io.Writer) (int64, error) { + return useBufWriter(w, func(bw *bufWriter) { + bw.WriteByte(tf.Encoding.Key) + bw.EncodeAndWriteText(tf.Text, tf.Encoding) + }) } func parseTextFrame(rd io.Reader) (Framer, error) { diff --git a/unsynchronised_lyrics_frame.go b/unsynchronised_lyrics_frame.go index 59720d6..b90089e 100644 --- a/unsynchronised_lyrics_frame.go +++ b/unsynchronised_lyrics_frame.go @@ -29,37 +29,13 @@ func (uslf UnsynchronisedLyricsFrame) WriteTo(w io.Writer) (n int64, err error) return n, ErrInvalidLanguageLength } - bw, ok := resolveBufioWriter(w) - if !ok { - defer putBufioWriter(bw) - } - - var nn int - - if err = bw.WriteByte(uslf.Encoding.Key); err == nil { - n += 1 - } - - nn, _ = bw.WriteString(uslf.Language) - n += int64(nn) - - nn, err = encodeWriteText(bw, uslf.ContentDescriptor, uslf.Encoding) - n += int64(nn) - if err != nil { - return - } - - nn, _ = bw.Write(uslf.Encoding.TerminationBytes) - n += int64(nn) - - nn, err = encodeWriteText(bw, uslf.Lyrics, uslf.Encoding) - n += int64(nn) - if err != nil { - bw.Flush() - return - } - - return n, bw.Flush() + return useBufWriter(w, func(bw *bufWriter) { + bw.WriteByte(uslf.Encoding.Key) + bw.WriteString(uslf.Language) + bw.EncodeAndWriteText(uslf.ContentDescriptor, uslf.Encoding) + bw.Write(uslf.Encoding.TerminationBytes) + bw.EncodeAndWriteText(uslf.Lyrics, uslf.Encoding) + }) } func parseUnsynchronisedLyricsFrame(rd io.Reader) (Framer, error) { diff --git a/util.go b/util.go index 97d0151..7f4a768 100644 --- a/util.go +++ b/util.go @@ -5,7 +5,6 @@ package id3v2 import ( - "bufio" "bytes" "errors" "io" @@ -25,7 +24,7 @@ var ErrSizeOverflow = errors.New("size of tag/frame is greater than allowed in i // writeBytesSize writes size to bw in form of ID3v2 size format (4 * 0b0xxxxxxx). // // If size is greater than allowed (256MB), then it returns ErrSizeOverflow. -func writeBytesSize(bw *bufio.Writer, size uint) error { +func writeBytesSize(bw *bufWriter, size uint) error { if size > maxSize { return ErrSizeOverflow } @@ -95,11 +94,3 @@ func readAll(rd io.Reader) ([]byte, error) { _, err := buf.ReadFrom(rd) return buf.Bytes(), err } - -func resolveBufioWriter(w io.Writer) (bw *bufio.Writer, ok bool) { - bw, ok = w.(*bufio.Writer) - if !ok { - bw = getBufioWriter(w) - } - return -} diff --git a/util_test.go b/util_test.go index 7b878ad..2f8179d 100644 --- a/util_test.go +++ b/util_test.go @@ -5,8 +5,8 @@ package id3v2 import ( - "bufio" "bytes" + "io/ioutil" "testing" ) @@ -19,7 +19,7 @@ func TestWriteBytesSize(t *testing.T) { t.Parallel() buf := new(bytes.Buffer) - bw := bufio.NewWriter(buf) + bw := newBufWriter(buf) if err := writeBytesSize(bw, sizeInt); err != nil { t.Error(err) @@ -43,7 +43,7 @@ func TestParseSize(t *testing.T) { } func BenchmarkWriteBytesSize(b *testing.B) { - bw := bufio.NewWriter(new(bytes.Buffer)) + bw := newBufWriter(ioutil.Discard) b.ResetTimer() for i := 0; i < b.N; i++ {