changeset 22980:fed657e0b156

lib: Add i_stream_nonseekable_try_seek() This can be used by istreams to more easily implement seeking backwards when it has to be done by first seeking back to offset 0 and reading from there.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 05 Jun 2018 13:25:30 +0300
parents ace8424d6f65
children acc052049f0b
files src/lib/istream-private.h src/lib/istream.c
diffstat 2 files changed, 46 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib/istream-private.h	Tue May 29 11:53:15 2018 +0300
+++ b/src/lib/istream-private.h	Tue Jun 05 13:25:30 2018 +0300
@@ -35,6 +35,9 @@
 
 	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;
@@ -71,6 +74,12 @@
 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);
 
 struct istream *i_stream_get_root_io(struct istream *stream);
 void i_stream_set_io(struct istream *stream, struct io *io);
--- a/src/lib/istream.c	Tue May 29 11:53:15 2018 +0300
+++ b/src/lib/istream.c	Tue Jun 05 13:25:30 2018 +0300
@@ -171,7 +171,15 @@
 		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:
@@ -798,6 +806,34 @@
 	}
 }
 
+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)
 {