Author: stsp
Date: Sun Jan 3 20:02:06 2010
New Revision: 895469
URL: http://svn.apache.org/viewvc?rev=895469&view=rev
Log:
Add support for resetting a stream to a previous 'marked' location.
Implemented for streams backed by APR files and stringbufs, and for
disowned and empty streams.
This makes look-ahead parsing of data read from streams easier,
and will ultimately be used by svn patch to reliably parse unidiff
files with arbitrarily mixed line-endings.
* subversion/include/svn_error_codes.h
(SVN_ERR_STREAM_MARK_NOT_SUPPORTED): New error code.
* subversion/include/svn_io.h
(svn_io_mark_fn_t, svn_stream_set_mark, svn_stream_mark): Declare.
(svn_stream_reset): Document how svn_stream_mark() affects this function.
* subversion/libsvn_subr/stream.c
(svn_stream_t): New field mark_fn.
(svn_stream_create): Initialise mark_fn.
(svn_stream_set_mark, svn_stream_mark, mark_handler_empty,
svn_stream_set_mark, mark_handler_disown, mark_handler_apr,
mark_handler_stringbuf): New.
(svn_stream_disown, svn_stream_from_aprfile2,
svn_stream_from_aprfile_range_readonly, svn_stream_from_stringbuf):
Initialise mark field in baton, set a mark handler on the stream.
(baton_apr, stringbuf_stream_baton): New field mark.
(reset_handler_apr, reset_handler_stringbuf): If a mark has been set,
reset to the mark instead of the stream's origin.
* subversion/tests/libsvn_subr/stream-test.c
(test_stream_mark_file, test_stream_mark_stringbuf): New tests.
(test_funcs): Add new tests.
Modified:
subversion/trunk/subversion/include/svn_error_codes.h
subversion/trunk/subversion/include/svn_io.h
subversion/trunk/subversion/libsvn_subr/stream.c
subversion/trunk/subversion/tests/libsvn_subr/stream-test.c
Modified: subversion/trunk/subversion/include/svn_error_codes.h
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_error_codes.h?rev=895469&r1=895468&r2=895469&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_error_codes.h (original)
+++ subversion/trunk/subversion/include/svn_error_codes.h Sun Jan 3 20:02:06
2010
@@ -293,6 +293,11 @@
SVN_ERR_STREAM_CATEGORY_START + 3,
"Stream doesn't support resetting")
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_STREAM_MARK_NOT_SUPPORTED,
+ SVN_ERR_STREAM_CATEGORY_START + 4,
+ "Stream doesn't support marking")
+
/* node errors */
SVN_ERRDEF(SVN_ERR_NODE_UNKNOWN_KIND,
Modified: subversion/trunk/subversion/include/svn_io.h
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_io.h?rev=895469&r1=895468&r2=895469&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_io.h (original)
+++ subversion/trunk/subversion/include/svn_io.h Sun Jan 3 20:02:06 2010
@@ -702,6 +702,13 @@
*/
typedef svn_error_t *(*svn_io_reset_fn_t)(void *baton);
+/** Mark handler function for a generic stream. @see svn_stream_t and
+ * svn_stream_mark().
+ *
+ * @since New in 1.7.
+ */
+typedef svn_error_t *(*svn_io_mark_fn_t)(void *baton, svn_boolean_t set);
+
/** Line-filtering callback function for a generic stream.
* @a baton is the stream's baton.
* @see svn_stream_t, svn_stream_set_baton() and svn_stream_readline().
@@ -770,6 +777,14 @@
svn_stream_set_reset(svn_stream_t *stream,
svn_io_reset_fn_t reset_fn);
+/** Set @a stream's mark function to @a mark_fn
+ *
+ * @since New in 1.7.
+ */
+void
+svn_stream_set_mark(svn_stream_t *stream,
+ svn_io_mark_fn_t mark_fn);
+
/** Set @a stream's line-filtering callback function to @a line_filter_cb
*
* @since New in 1.7.
@@ -1031,11 +1046,25 @@
* @a SVN_ERR_STREAM_RESET_NOT_SUPPORTED error when the stream doesn't
* implement resetting.
*
+ * If a mark has been set on the stream, the stream is reset to
+ * the mark instead of its origin. @see svn_stream_mark()
+ *
* @since New in 1.7.
*/
svn_error_t *
svn_stream_reset(svn_stream_t *stream);
+/** Set or clear the mark on a generic stream according to @a set.
+ * If @a set is TRUE, the mark is set at the current position in
+ * the stream. If @a set is FALSE, the mark is cleared.
+ * This function returns the #SVN_ERR_STREAM_MARK_NOT_SUPPORTED error
+ * when the stream doesn't implement marking.
+ *
+ * @see svn_stream_reset()
+ * @since New in 1.7.
+ */
+svn_error_t *
+svn_stream_mark(svn_stream_t *stream, svn_boolean_t set);
/** Return a writable stream which, when written to, writes to both of the
* underlying streams. Both of these streams will be closed upon closure of
Modified: subversion/trunk/subversion/libsvn_subr/stream.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/stream.c?rev=895469&r1=895468&r2=895469&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/stream.c (original)
+++ subversion/trunk/subversion/libsvn_subr/stream.c Sun Jan 3 20:02:06 2010
@@ -50,6 +50,7 @@
svn_write_fn_t write_fn;
svn_close_fn_t close_fn;
svn_io_reset_fn_t reset_fn;
+ svn_io_mark_fn_t mark_fn;
svn_io_line_filter_cb_t line_filter_cb;
svn_io_line_transformer_cb_t line_transformer_cb;
};
@@ -68,6 +69,7 @@
stream->read_fn = NULL;
stream->write_fn = NULL;
stream->reset_fn = NULL;
+ stream->mark_fn = NULL;
stream->close_fn = NULL;
stream->line_filter_cb = NULL;
stream->line_transformer_cb = NULL;
@@ -102,6 +104,12 @@
}
void
+svn_stream_set_mark(svn_stream_t *stream, svn_io_mark_fn_t mark_fn)
+{
+ stream->mark_fn = mark_fn;
+}
+
+void
svn_stream_set_close(svn_stream_t *stream, svn_close_fn_t close_fn)
{
stream->close_fn = close_fn;
@@ -147,6 +155,14 @@
return stream->reset_fn(stream->baton);
}
+svn_error_t *
+svn_stream_mark(svn_stream_t *stream, svn_boolean_t set)
+{
+ if (stream->mark_fn == NULL)
+ return svn_error_create(SVN_ERR_STREAM_MARK_NOT_SUPPORTED, NULL, NULL);
+
+ return stream->mark_fn(stream->baton, set);
+}
svn_error_t *
svn_stream_close(svn_stream_t *stream)
@@ -407,6 +423,11 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+mark_handler_empty(void *baton, svn_boolean_t set)
+{
+ return SVN_NO_ERROR;
+}
svn_stream_t *
svn_stream_empty(apr_pool_t *pool)
@@ -417,6 +438,7 @@
svn_stream_set_read(stream, read_handler_empty);
svn_stream_set_write(stream, write_handler_empty);
svn_stream_set_reset(stream, reset_handler_empty);
+ svn_stream_set_mark(stream, mark_handler_empty);
return stream;
}
@@ -499,6 +521,11 @@
return svn_stream_reset(baton);
}
+static svn_error_t *
+mark_handler_disown(void *baton, svn_boolean_t set)
+{
+ return svn_stream_mark(baton, set);
+}
svn_stream_t *
svn_stream_disown(svn_stream_t *stream, apr_pool_t *pool)
@@ -508,6 +535,7 @@
svn_stream_set_read(s, read_handler_disown);
svn_stream_set_write(s, write_handler_disown);
svn_stream_set_reset(s, reset_handler_disown);
+ svn_stream_set_mark(s, mark_handler_disown);
return s;
}
@@ -523,6 +551,8 @@
* When either of these is negative, no range has been specified. */
apr_off_t start;
apr_off_t end;
+
+ apr_off_t mark;
};
@@ -586,14 +616,33 @@
apr_off_t offset;
struct baton_apr *btn = baton;
- /* If we're reading from a range, reset to the start of the range.
- * Otherwise, reset to the start of the file. */
- offset = btn->start >= 0 ? btn->start : 0;
+ if (btn->mark >= 0)
+ offset = btn->mark;
+ else if (btn->start >= 0)
+ offset = btn->start;
+ else
+ offset = 0;
return svn_io_file_seek(btn->file, APR_SET, &offset, btn->pool);
}
static svn_error_t *
+mark_handler_apr(void *baton, svn_boolean_t set)
+{
+ struct baton_apr *btn = baton;
+
+ if (set)
+ {
+ btn->mark = 0;
+ SVN_ERR(svn_io_file_seek(btn->file, APR_CUR, &btn->mark, btn->pool));
+ }
+ else
+ btn->mark = -1;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
close_handler_apr(void *baton)
{
struct baton_apr *btn = baton;
@@ -673,10 +722,12 @@
baton->pool = pool;
baton->start = -1;
baton->end = -1;
+ baton->mark = -1;
stream = svn_stream_create(baton, pool);
svn_stream_set_read(stream, read_handler_apr);
svn_stream_set_write(stream, write_handler_apr);
svn_stream_set_reset(stream, reset_handler_apr);
+ svn_stream_set_mark(stream, mark_handler_apr);
if (! disown)
svn_stream_set_close(stream, close_handler_apr);
@@ -714,9 +765,11 @@
baton->pool = pool;
baton->start = start;
baton->end = end;
+ baton->mark = -1;
stream = svn_stream_create(baton, pool);
svn_stream_set_read(stream, read_handler_apr);
svn_stream_set_reset(stream, reset_handler_apr);
+ svn_stream_set_mark(stream, mark_handler_apr);
if (! disown)
svn_stream_set_close(stream, close_handler_apr);
@@ -1214,6 +1267,7 @@
{
svn_stringbuf_t *str;
apr_size_t amt_read;
+ apr_size_t mark;
};
static svn_error_t *
@@ -1241,7 +1295,21 @@
reset_handler_stringbuf(void *baton)
{
struct stringbuf_stream_baton *btn = baton;
- btn->amt_read = 0;
+ if (btn->mark != (apr_size_t)-1)
+ btn->amt_read = btn->mark;
+ else
+ btn->amt_read = 0;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mark_handler_stringbuf(void *baton, svn_boolean_t set)
+{
+ struct stringbuf_stream_baton *btn = baton;
+ if (set)
+ btn->mark = btn->amt_read;
+ else
+ btn->mark = (apr_size_t)-1;
return SVN_NO_ERROR;
}
@@ -1258,10 +1326,12 @@
baton = apr_palloc(pool, sizeof(*baton));
baton->str = str;
baton->amt_read = 0;
+ baton->mark = (apr_size_t)-1;
stream = svn_stream_create(baton, pool);
svn_stream_set_read(stream, read_handler_stringbuf);
svn_stream_set_write(stream, write_handler_stringbuf);
svn_stream_set_reset(stream, reset_handler_stringbuf);
+ svn_stream_set_mark(stream, mark_handler_stringbuf);
return stream;
}
Modified: subversion/trunk/subversion/tests/libsvn_subr/stream-test.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_subr/stream-test.c?rev=895469&r1=895468&r2=895469&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_subr/stream-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_subr/stream-test.c Sun Jan 3
20:02:06 2010
@@ -464,7 +464,107 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+test_stream_mark_file(apr_pool_t *pool)
+{
+ static const char *file_data[2] = {"One", "Two"};
+ svn_stream_t *stream;
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ apr_file_t *f;
+ static const char *fname = "test_stream_mark.txt";
+ int j;
+ apr_status_t status;
+ static const char *NL = APR_EOL_STR;
+ status = apr_file_open(&f, fname, (APR_READ | APR_WRITE | APR_CREATE |
+ APR_TRUNCATE | APR_DELONCLOSE), APR_OS_DEFAULT, pool);
+ if (status != APR_SUCCESS)
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, "Cannot open '%s'",
+ fname);
+
+ /* Create the file. */
+ for (j = 0; j < 2; j++)
+ {
+ apr_size_t len;
+
+ len = strlen(file_data[j]);
+ status = apr_file_write(f, file_data[j], &len);
+ if (status || len != strlen(file_data[j]))
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
+ "Cannot write to '%s'", fname);
+ len = strlen(NL);
+ status = apr_file_write(f, NL, &len);
+ if (status || len != strlen(NL))
+ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
+ "Cannot write to '%s'", fname);
+ }
+
+ /* Create a stream to read from the file. */
+ stream = svn_stream_from_aprfile2(f, FALSE, pool);
+ SVN_ERR(svn_stream_reset(stream));
+ SVN_ERR(svn_stream_readline(stream, &line, NL, &eof, pool));
+ SVN_ERR_ASSERT(! eof && strcmp(line->data, file_data[0]) == 0);
+ /* Set a mark at the beginning of the second line of the file. */
+ SVN_ERR(svn_stream_mark(stream, TRUE));
+ /* Read the second line and then reset the stream. */
+ SVN_ERR(svn_stream_readline(stream, &line, NL, &eof, pool));
+ SVN_ERR_ASSERT(! eof && strcmp(line->data, file_data[1]) == 0);
+ SVN_ERR(svn_stream_reset(stream));
+ /* The next read should return the second line again. */
+ SVN_ERR(svn_stream_readline(stream, &line, NL, &eof, pool));
+ SVN_ERR_ASSERT(! eof && strcmp(line->data, file_data[1]) == 0);
+ /* Clear the mark. */
+ SVN_ERR(svn_stream_mark(stream, FALSE));
+ /* The next read should return EOF. */
+ SVN_ERR(svn_stream_readline(stream, &line, NL, &eof, pool));
+ SVN_ERR_ASSERT(eof);
+ /* Reset the stream. */
+ SVN_ERR(svn_stream_reset(stream));
+ /* The next read should return the first line again. */
+ SVN_ERR(svn_stream_readline(stream, &line, NL, &eof, pool));
+ SVN_ERR_ASSERT(! eof && strcmp(line->data, file_data[0]) == 0);
+
+ SVN_ERR(svn_stream_close(stream));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_stream_mark_stringbuf(apr_pool_t *pool)
+{
+ svn_stream_t *stream;
+ svn_stringbuf_t *stringbuf;
+ char buf[4];
+ apr_size_t len;
+
+ stringbuf = svn_stringbuf_create("OneTwo", pool);
+ stream = svn_stream_from_stringbuf(stringbuf, pool);
+ len = 3;
+ SVN_ERR(svn_stream_read(stream, buf, &len));
+ buf[3] = '\0';
+ SVN_ERR_ASSERT(strcmp(buf, "One") == 0);
+ SVN_ERR(svn_stream_mark(stream, TRUE));
+ len = 3;
+ SVN_ERR(svn_stream_read(stream, buf, &len));
+ buf[3] = '\0';
+ SVN_ERR_ASSERT(strcmp(buf, "Two") == 0);
+ SVN_ERR(svn_stream_reset(stream));
+ len = 3;
+ SVN_ERR(svn_stream_read(stream, buf, &len));
+ buf[3] = '\0';
+ SVN_ERR_ASSERT(strcmp(buf, "Two") == 0);
+ SVN_ERR(svn_stream_mark(stream, FALSE));
+ SVN_ERR(svn_stream_reset(stream));
+ len = 3;
+ SVN_ERR(svn_stream_read(stream, buf, &len));
+ buf[3] = '\0';
+ SVN_ERR_ASSERT(strcmp(buf, "One") == 0);
+
+ SVN_ERR(svn_stream_close(stream));
+
+ return SVN_NO_ERROR;
+}
/* The test table. */
@@ -486,5 +586,9 @@
"test stream line filtering and transforming"),
SVN_TEST_PASS2(test_stream_tee,
"test 'tee' streams"),
+ SVN_TEST_PASS2(test_stream_mark_file,
+ "test stream marking for files"),
+ SVN_TEST_PASS2(test_stream_mark_stringbuf,
+ "test stream marking for stringbufs"),
SVN_TEST_NULL
};