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
   };


Reply via email to