[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

r15584: streamy API for retrieval of file contents

From: Daniel Rall <dlr_at_finemaltcoding.com>
Date: 2005-08-04 02:54:04 CEST

Patrick, I've committed Conor's patch which adds a streamy API for
retrieval of versioned file contents. Since the patch refactors the
native implementation of the SVNClient.fileContent() method (rather than
adding a new API without changing any of the current implementation),
you may want to review this change. I both reviewed Conor's patch and
made some incidental modifications to it, and assured it passes 'make
check-javahl', but an extra set of eyes never hurts.

Also, I was wondering if the name "writeFileContent" might be more clear
than "streamFileContent" (though the later certainly declares the
streamy nature of the implementation).

- Dan

attached mail follows:


Author: dlr
Date: Wed Aug 3 19:22:15 2005
New Revision: 15584

Modified:
   trunk/subversion/bindings/java/javahl/native/SVNClient.cpp
   trunk/subversion/bindings/java/javahl/native/SVNClient.h
   trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp
   trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java
   trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java
   trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java
   trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java

Log:
Add a streamy API to JavaHL for retrieving a versioned file's
contents, providing significant scalability benefits over the
pre-existing SVNClient.fileContent() API.

Issue: http://subversion.tigris.org/issues/show_bug.cgi?id=2299
Patch by: Conor MacNeil
          Daniel Rall
Reviewed by: Daniel Rall

[in subversion/bindings/java/javahl/]
* native/SVNClient.cpp
  (fileContent): Modified to leverage new streamFileContent() method.

  (streamFileContent): Writes a versioned file's content to the
   specified OutputStream.

  (createReadStream): A new method factored out of fileContent() which
   is shared by the implementation of both it and the new
   streamFileContent() method.

* native/org_tigris_subversion_javahl_SVNClient.cpp
  (Java_org_tigris_subversion_javahl_SVNClient_streamFileContent):
   Exports the SVNClient.streamFileContent() method into Java-land.

* native/SVNClient.h
  (streamFileContent): New public method.

  (createReadStream): New private method.

* src/org/tigris/subversion/javahl/SVNClientInterface.java
* src/org/tigris/subversion/javahl/SVNClientSynchronized.java
* src/org/tigris/subversion/javahl/SVNClient.java
  (streamFileContent): New public method with default implementations
   delegating to native code.

* src/org/tigris/subversion/javahl/tests/BasicTests.java
  (testBasicCatStream): New test for streamFileContent().

Modified: trunk/subversion/bindings/java/javahl/native/SVNClient.cpp
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/native/SVNClient.cpp?rev=15584&p1=trunk/subversion/bindings/java/javahl/native/SVNClient.cpp&p2=trunk/subversion/bindings/java/javahl/native/SVNClient.cpp&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/native/SVNClient.cpp (original)
+++ trunk/subversion/bindings/java/javahl/native/SVNClient.cpp Wed Aug 3 19:22:15 2005
@@ -2271,18 +2271,135 @@
         return NULL;
     }
 
- svn_stream_t *read_stream = NULL;
     size_t size = 0;
+ svn_stream_t *read_stream = createReadStream(requestPool.pool(),
+ intPath.c_str(), revision,
+ pegRevision, size);
+ if (read_stream == NULL)
+ {
+ return NULL;
+ }
+
+ JNIEnv *env = JNIUtil::getEnv();
+ // size will be set to the number of bytes available.
+ jbyteArray ret = env->NewByteArray(size);
+ if (JNIUtil::isJavaExceptionThrown())
+ {
+ return NULL;
+ }
+ jbyte *retdata = env->GetByteArrayElements(ret, NULL);
+ if (JNIUtil::isJavaExceptionThrown())
+ {
+ return NULL;
+ }
+
+ Err = svn_stream_read(read_stream, (char *)retdata, &size);
+ env->ReleaseByteArrayElements(ret, retdata, 0);
+ if (Err != NULL)
+ {
+ JNIUtil::handleSVNError(Err);
+ return NULL;
+ }
+ if (JNIUtil::isJavaExceptionThrown())
+ {
+ return NULL;
+ }
+
+ return ret;
+}
+
+void SVNClient::streamFileContent(const char *path, Revision &revision,
+ Revision &pegRevision, jobject outputStream,
+ size_t bufSize)
+{
+ Pool requestPool;
+ if (path == NULL)
+ {
+ JNIUtil::throwNullPointerException("path");
+ return;
+ }
+ Path intPath(path);
+ svn_error_t *Err = intPath.error_occured();
+ if (Err != NULL)
+ {
+ JNIUtil::handleSVNError(Err);
+ return;
+ }
+
+ JNIEnv *env = JNIUtil::getEnv();
+ jclass outputStreamClass = env->FindClass("java/io/OutputStream");
+ if (outputStreamClass == NULL)
+ {
+ return;
+ }
+ jmethodID writeMethod = env->GetMethodID(outputStreamClass, "write",
+ "([BII)V");
+ if (writeMethod == NULL)
+ {
+ return;
+ }
+
+ // Create the buffer.
+ jbyteArray buffer = env->NewByteArray(bufSize);
+ if (JNIUtil::isJavaExceptionThrown())
+ {
+ return;
+ }
+ jbyte *bufData = env->GetByteArrayElements(buffer, NULL);
+ if (JNIUtil::isJavaExceptionThrown())
+ {
+ return;
+ }
 
- if(revision.revision()->kind == svn_opt_revision_base)
- // we want the base of the current working copy. Bad hack to avoid going to
- // the server
+ size_t contentSize = 0;
+ svn_stream_t* read_stream = createReadStream(requestPool.pool(), path,
+ revision, pegRevision,
+ contentSize);
+ if (read_stream == NULL)
     {
+ return;
+ }
 
+ while (contentSize > 0)
+ {
+ size_t readSize = bufSize > contentSize ? contentSize : bufSize;
+ Err = svn_stream_read(read_stream, (char *)bufData, &readSize);
+ if (Err != NULL)
+ {
+ env->ReleaseByteArrayElements(buffer, bufData, 0);
+ svn_stream_close(read_stream);
+ JNIUtil::handleSVNError(Err);
+ return;
+ }
+
+ env->ReleaseByteArrayElements(buffer, bufData, JNI_COMMIT);
+ env->CallVoidMethod(outputStream, writeMethod, buffer, 0, readSize);
+ if (JNIUtil::isJavaExceptionThrown())
+ {
+ env->ReleaseByteArrayElements(buffer, bufData, 0);
+ svn_stream_close(read_stream);
+ return;
+ }
+ contentSize -= readSize;
+ }
+
+ env->ReleaseByteArrayElements(buffer, bufData, 0);
+ return;
+}
+
+svn_stream_t* SVNClient::createReadStream(apr_pool_t* pool, const char *path,
+ Revision& revision,
+ Revision &pegRevision, size_t& size)
+{
+ svn_stream_t *read_stream = NULL;
+
+ if (revision.revision()->kind == svn_opt_revision_base)
+ {
+ // We want the base of the current working copy, while
+ // avoiding a round-trip to the server.
         const char *base_path;
- svn_error_t *err = svn_wc_get_pristine_copy_path (intPath.c_str(),
- &base_path,
- requestPool.pool());
+ svn_error_t *err = svn_wc_get_pristine_copy_path(path, &base_path,
+ pool);
         if(err != NULL)
         {
             JNIUtil::handleSVNError(err);
@@ -2291,44 +2408,42 @@
         apr_file_t *file = NULL;
         apr_finfo_t finfo;
         apr_status_t apr_err = apr_stat(&finfo, base_path,
- APR_FINFO_MIN, requestPool.pool());
+ APR_FINFO_MIN, pool);
         if(apr_err)
         {
             JNIUtil::handleAPRError(apr_err, _("open file"));
             return NULL;
         }
- apr_err = apr_file_open(&file, base_path, APR_READ, 0,
- requestPool.pool());
+ apr_err = apr_file_open(&file, base_path, APR_READ, 0, pool);
         if(apr_err)
         {
             JNIUtil::handleAPRError(apr_err, _("open file"));
             return NULL;
         }
- read_stream = svn_stream_from_aprfile(file, requestPool.pool());
+ read_stream = svn_stream_from_aprfile(file, pool);
         size = finfo.size;
     }
- else if(revision.revision()->kind == svn_opt_revision_working)
- // we want the working copy. Going back to the server returns base instead
- // (not good)
+ else if (revision.revision()->kind == svn_opt_revision_working)
     {
-
+ // We want the working copy. Going back to the server returns
+ // base instead (which is not what we want).
         apr_file_t *file = NULL;
         apr_finfo_t finfo;
- apr_status_t apr_err = apr_stat(&finfo, intPath.c_str(),
- APR_FINFO_MIN, requestPool.pool());
+ apr_status_t apr_err = apr_stat(&finfo, path,
+ APR_FINFO_MIN, pool);
         if(apr_err)
         {
             JNIUtil::handleAPRError(apr_err, _("open file"));
             return NULL;
         }
- apr_err = apr_file_open(&file, intPath.c_str(), APR_READ, 0,
- requestPool.pool());
+ apr_err = apr_file_open(&file, path, APR_READ, 0,
+ pool);
         if(apr_err)
         {
             JNIUtil::handleAPRError(apr_err, _("open file"));
             return NULL;
         }
- read_stream = svn_stream_from_aprfile(file, requestPool.pool());
+ read_stream = svn_stream_from_aprfile(file, pool);
         size = finfo.size;
     }
     else
@@ -2338,11 +2453,10 @@
         {
             return NULL;
         }
- svn_stringbuf_t *buf = svn_stringbuf_create("", requestPool.pool());
- read_stream = svn_stream_from_stringbuf(buf, requestPool.pool());
+ svn_stringbuf_t *buf = svn_stringbuf_create("", pool);
+ read_stream = svn_stream_from_stringbuf(buf, pool);
         svn_error_t *err = svn_client_cat2 (read_stream,
- intPath.c_str(), pegRevision.revision(), revision.revision(),
- ctx, requestPool.pool());
+ path, pegRevision.revision(), revision.revision(), ctx, pool);
         if(err != NULL)
         {
             JNIUtil::handleSVNError(err);
@@ -2350,40 +2464,9 @@
         }
         size = buf->len;
     }
- if(read_stream == NULL)
- {
- return NULL;
- }
-
- JNIEnv *env = JNIUtil::getEnv();
- jbyteArray ret = env->NewByteArray(size);
- if(JNIUtil::isJavaExceptionThrown())
- {
- return NULL;
- }
- jbyte *retdata = env->GetByteArrayElements(ret, NULL);
- if(JNIUtil::isJavaExceptionThrown())
- {
- return NULL;
- }
- svn_error_t *err = svn_stream_read (read_stream, (char *)retdata,
- &size);
-
- if(err != NULL)
- {
- env->ReleaseByteArrayElements(ret, retdata, 0);
- JNIUtil::handleSVNError(err);
- return NULL;
- }
- env->ReleaseByteArrayElements(ret, retdata, 0);
- if(JNIUtil::isJavaExceptionThrown())
- {
- return NULL;
- }
-
-
- return ret;
+ return read_stream;
 }
+
 
 /**
  * create a DirEntry java object from svn_dirent_t structure

Modified: trunk/subversion/bindings/java/javahl/native/SVNClient.h
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/native/SVNClient.h?rev=15584&p1=trunk/subversion/bindings/java/javahl/native/SVNClient.h&p2=trunk/subversion/bindings/java/javahl/native/SVNClient.h&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/native/SVNClient.h (original)
+++ trunk/subversion/bindings/java/javahl/native/SVNClient.h Wed Aug 3 19:22:15 2005
@@ -61,6 +61,9 @@
                       bool recurse);
     jbyteArray fileContent(const char *path, Revision &revision,
                            Revision &pegRevision);
+ void streamFileContent(const char *path, Revision &revision,
+ Revision &pegRevision, jobject outputStream,
+ size_t bufSize);
     void propertyCreate(const char *path, const char *name,
                             JNIByteArray &value, bool recurse, bool force);
     void propertyCreate(const char *path, const char *name,
@@ -151,6 +154,9 @@
     jobject createJavaDirEntry(const char *path, svn_dirent_t *dirent);
     jobject createJavaInfo(const svn_wc_entry_t *entry);
     svn_client_ctx_t * getContext(const char *message);
+ svn_stream_t * createReadStream(apr_pool_t* pool, const char *path,
+ Revision &revision, Revision &pegRevision,
+ size_t& size);
     Notify *m_notify;
     Notify2 *m_notify2;
     Prompter *m_prompter;

Modified: trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp?rev=15584&p1=trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp&p2=trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp (original)
+++ trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp Wed Aug 3 19:22:15 2005
@@ -1373,6 +1373,36 @@
     }
     return cl->fileContent(path, revision, pegRevision);
 }
+
+JNIEXPORT void JNICALL Java_org_tigris_subversion_javahl_SVNClient_streamFileContent
+ (JNIEnv *env, jobject jthis, jstring jpath, jobject jrevision,
+ jobject jpegRevision, jint bufSize, jobject jstream)
+{
+ JNIEntry(SVNClient, streamFileContent);
+ SVNClient *cl = SVNClient::getCppObject(jthis);
+ if (cl == NULL)
+ {
+ JNIUtil::throwError(_("bad c++ this"));
+ return;
+ }
+ JNIStringHolder path(jpath);
+ if (JNIUtil::isExceptionThrown())
+ {
+ return;
+ }
+ Revision revision(jrevision);
+ if (JNIUtil::isExceptionThrown())
+ {
+ return;
+ }
+ Revision pegRevision(jpegRevision);
+ if (JNIUtil::isExceptionThrown())
+ {
+ return;
+ }
+ cl->streamFileContent(path, revision, pegRevision, jstream, bufSize);
+}
+
 /*
  * Class: org_tigris_subversion_javahl_SVNClient
  * Method: getVersionInfo

Modified: trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java?rev=15584&p1=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java&p2=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java (original)
+++ trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java Wed Aug 3 19:22:15 2005
@@ -17,6 +17,9 @@
  * ====================================================================
  * @endcopyright
  */
+
+import java.io.OutputStream;
+
 /**
  * This is the main interface class. All subversion commandline client svn &
  * svnversion operation are implemented in this class. This class is not
@@ -934,6 +937,20 @@
     public native byte[] fileContent(String path, Revision revision,
                                      Revision pegRevision)
             throws ClientException;
+
+ /**
+ * Write the file's content to the specified output stream.
+ *
+ * @param path the path of the file
+ * @param revision the revision to retrieve
+ * @param pegRevision the revision at which to interpret the path
+ * @param the stream to write the file's content to
+ * @throws ClientException
+ */
+ public native void streamFileContent(String path, Revision revision,
+ Revision pegRevision, int bufferSize,
+ OutputStream stream)
+ throws ClientException;
 
     /**
      * Rewrite the url's in the working copy

Modified: trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java?rev=15584&p1=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java&p2=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java (original)
+++ trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java Wed Aug 3 19:22:15 2005
@@ -1,5 +1,4 @@
 package org.tigris.subversion.javahl;
-
 /**
  * @copyright
  * ====================================================================
@@ -17,6 +16,9 @@
  * ====================================================================
  * @endcopyright
  */
+
+import java.io.OutputStream;
+
 /**
  * This interface is the commom interface for all subversion
  * operations. It is implemented by SVNClient and SVNClientSynchronized
@@ -710,6 +712,19 @@
      */
     byte[] fileContent(String path, Revision revision, Revision pegRevision)
             throws ClientException;
+ /**
+ * Stream file content to the given output stream
+ * @param path the path of the file
+ * @param revision the revision to retrieve
+ * @param pegRevision the revision to interpret path
+ * @param bufferSize the size of buffer to use during streaming
+ * @param the stream to which content is written
+ * @throws ClientException
+ */
+ void streamFileContent(String path, Revision revision, Revision pegRevision,
+ int bufferSize, OutputStream stream)
+ throws ClientException;
+
     /**
      * Rewrite the url's in the working copy
      * @param from old url

Modified: trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java?rev=15584&p1=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java&p2=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java (original)
+++ trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java Wed Aug 3 19:22:15 2005
@@ -16,6 +16,9 @@
  * @endcopyright
  */
 package org.tigris.subversion.javahl;
+
+import java.io.OutputStream;
+
 /**
  * This class provides a threadsafe wrapped for SVNClient
  */
@@ -1167,6 +1170,27 @@
         synchronized(clazz)
         {
             return worker.fileContent(path, revision, pegRevision);
+ }
+ }
+
+ /**
+ * Write the file's content to the specified output stream.
+ *
+ * @param path the path of the file
+ * @param revision the revision to retrieve
+ * @param pegRevision the revision at which to interpret the path
+ * @param the stream to write the file's content to
+ * @throws ClientException
+ */
+ public void streamFileContent(String path, Revision revision,
+ Revision pegRevision, int bufferSize,
+ OutputStream stream)
+ throws ClientException
+ {
+ synchronized(clazz)
+ {
+ worker.streamFileContent(path, revision, pegRevision, bufferSize,
+ stream);
         }
     }
 

Modified: trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java
Url: http://svn.collab.net/viewcvs/svn/trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java?rev=15584&p1=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java&p2=trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java&r1=15583&r2=15584
==============================================================================
--- trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java (original)
+++ trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java Wed Aug 3 19:22:15 2005
@@ -24,10 +24,12 @@
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
+import java.io.ByteArrayOutputStream;
 import java.util.Arrays;
+
 /**
- * this class tests the basic functionality of javahl binding. It was inspired
- * by the tests in subversion/tests/clients/cmdline/basic_tests.py
+ * Tests the basic functionality of javahl binding (inspired by the
+ * tests in subversion/tests/clients/cmdline/basic_tests.py).
  */
 public class BasicTests extends SVNTests
 {
@@ -1137,6 +1139,32 @@
         pw.close();
         // get the content from the repository
         byte[] content = client.fileContent(thisTest.getWCPath()+"/A/mu", null);
+ byte[] testContent = thisTest.getWc().getItemContent("A/mu").getBytes();
+
+ // the content should be the same
+ assertTrue("content changed", Arrays.equals(content, testContent));
+ }
+
+ /**
+ * test the basic SVNClient.fileContent functionality
+ * @throws Throwable
+ */
+ public void testBasicCatStream() throws Throwable
+ {
+ // create the working copy
+ OneTest thisTest = new OneTest();
+
+ // modify A/mu
+ File mu = new File(thisTest.getWorkingCopy(), "A/mu");
+ PrintWriter pw = new PrintWriter(new FileOutputStream(mu, true));
+ pw.print("some text");
+ pw.close();
+ // get the content from the repository
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ client.streamFileContent(thisTest.getWCPath() + "/A/mu", null, null,
+ 100, baos);
+
+ byte[] content = baos.toByteArray();
         byte[] testContent = thisTest.getWc().getItemContent("A/mu").getBytes();
 
         // the content should be the same

---------------------------------------------------------------------
To unsubscribe, e-mail: svn-unsubscribe@subversion.tigris.org
For additional commands, e-mail: svn-help@subversion.tigris.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Thu Aug 4 02:54:47 2005

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.