diff --git a/src/lib/istream-private.h b/src/lib/istream-private.h index a88653133d..02b35f3e8d 100644 --- a/src/lib/istream-private.h +++ b/src/lib/istream-private.h @@ -39,6 +39,9 @@ struct istream_private { size_t buffer_size, max_buffer_size, init_buffer_size; size_t skip, pos, try_alloc_limit; + /* If seeking backwards within the buffer, the next read() will + return again pos..high_pos */ + size_t high_pos; struct istream *parent; /* for filter streams */ uoff_t parent_start_offset; @@ -98,6 +101,12 @@ void i_stream_free_buffer(struct istream_private *stream); ssize_t i_stream_read_copy_from_parent(struct istream *istream); void i_stream_default_seek_nonseekable(struct istream_private *stream, uoff_t v_offset, bool mark); +/* Returns FALSE if seeking must be done by starting from the beginning. + The caller is then expected to reset the stream and call this function + again, which should work then. If TRUE is returned, the seek was either + successfully done or stream_errno is set. */ +bool i_stream_nonseekable_try_seek(struct istream_private *stream, + uoff_t v_offset); /* Default snapshot handling: use memarea if it exists, otherwise snapshot parent stream. */ diff --git a/src/lib/istream.c b/src/lib/istream.c index 2424e54c1f..aeb4942312 100644 --- a/src/lib/istream.c +++ b/src/lib/istream.c @@ -303,7 +303,15 @@ ssize_t i_stream_read_memarea(struct istream *stream) i_stream_seek(_stream->parent, _stream->parent_expected_offset); old_size = _stream->pos - _stream->skip; - ret = _stream->read(_stream); + if (_stream->pos < _stream->high_pos) { + /* we're here because we seeked back within the read buffer. */ + ret = _stream->high_pos - _stream->pos; + _stream->pos = _stream->high_pos; + _stream->high_pos = 0; + } else { + _stream->high_pos = 0; + ret = _stream->read(_stream); + } i_assert(old_size <= _stream->pos - _stream->skip); switch (ret) { case -2: @@ -1030,6 +1038,34 @@ void i_stream_default_seek_nonseekable(struct istream_private *stream, } } +bool i_stream_nonseekable_try_seek(struct istream_private *stream, + uoff_t v_offset) +{ + uoff_t start_offset = stream->istream.v_offset - stream->skip; + + if (v_offset < start_offset) { + /* have to seek backwards */ + i_stream_seek(stream->parent, stream->parent_start_offset); + stream->parent_expected_offset = stream->parent_start_offset; + stream->skip = stream->pos = 0; + stream->istream.v_offset = 0; + stream->high_pos = 0; + return FALSE; + } + + if (v_offset <= start_offset + stream->pos) { + /* seeking backwards within what's already cached */ + stream->skip = v_offset - start_offset; + stream->istream.v_offset = v_offset; + stream->high_pos = stream->pos; + stream->pos = stream->skip; + } else { + /* read forward */ + i_stream_default_seek_nonseekable(stream, v_offset, FALSE); + } + return TRUE; +} + static int i_stream_default_stat(struct istream_private *stream, bool exact) {