package iconv import ( "io" "runtime" ) const ( defaultReadBufferSize = 8 * 1024 minReadBufferSize = 16 ) type Reader struct { source io.Reader converter *Converter buffer []byte readPos, writePos int eof bool } func NewReader(source io.Reader, fromEncoding, toEncoding string) (*Reader, error) { return NewReaderSized(source, fromEncoding, toEncoding, defaultReadBufferSize) } func NewReaderFromConverter(source io.Reader, converter *Converter) *Reader { return NewReaderFromConverterSized(source, converter, defaultReadBufferSize) } func NewReaderSized(source io.Reader, fromEncoding, toEncoding string, size int) (*Reader, error) { converter, err := NewConverter(fromEncoding, toEncoding) if err != nil { return nil, err } // add a finalizer for the converter we created runtime.SetFinalizer(converter, finalizeConverter) return NewReaderFromConverterSized(source, converter, size), nil } func NewReaderFromConverterSized(source io.Reader, converter *Converter, size int) *Reader { if size < minReadBufferSize { size = minReadBufferSize } return &Reader{ source: source, converter: converter, buffer: make([]byte, size), } } func (r *Reader) Read(p []byte) (int, error) { if len(p) == 0 { return 0, nil } var bytesRead, bytesWritten int var err error // setup for a single read into buffer if possible if !r.eof { if r.readPos > 0 { // slide data to front of buffer r.readPos, r.writePos = 0, copy(r.buffer, r.buffer[r.readPos:r.writePos]) } if r.writePos < len(r.buffer) { // do the single read bytesRead, err = r.source.Read(r.buffer[r.writePos:]) if bytesRead < 0 { panic("iconv: source reader returned negative count from Read") } r.writePos += bytesRead r.eof = err == io.EOF } } if r.readPos < r.writePos || r.eof { // convert any buffered data we have, or do a final reset (for shift based conversions) bytesRead, bytesWritten, err = r.converter.Convert(r.buffer[r.readPos:r.writePos], p) r.readPos += bytesRead // if we experienced an iconv error and didn't make progress, report it. // if we did make progress, it may be informational only (i.e. reporting // an EILSEQ even when using //ignore to skip them) if err != nil && bytesWritten == 0 { return bytesWritten, err } // signal an EOF only if we didn't write anything - accomodates premature // errror checking in user code if bytesWritten == 0 && r.eof { return 0, io.EOF } return bytesWritten, nil } return 0, err } func (r *Reader) Reset(source io.Reader) { r.converter.Reset() *r = Reader{ source: source, converter: r.converter, buffer: r.buffer, } }