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

[PATCH] Add Javahl method to stream content

From: Conor MacNeill <conor_at_cenqua.com>
Date: 2005-04-14 08:15:17 CEST

This patch adds the ability to stream file content to a java
OutputStream rather than reading the complete content into a byte array
is is the case with the current fileContent methods. Requiring all of
the content to be read into memory wont scale for larger files. The
buffer size is passed in as part of the interface but this could be set
within the code if desired and eliminated from the interface.

By using an OutputStream approach rather than creating a svn specific
InputStream subClass, the javahl interface can be in control of the
underlying native stream lifetime.

The common code between fileContent and streamFileContent has been
extracted to a new method createReadStream which does the stream creation.

There's also a little bugfix included for the JNIEntry call in the
fileContent method in org_tigris_subversion_javahl_SVNClient.cpp

I've added a simple testcase based on the existing fileContent test
method in BasicTests.

The new interface method takes a pegRevision parameter like the new
version of fileContent but I must admit I'm not sure what a pegRevision
is. Can anyone explain that?

The other issue I came across is that I build subversion using a
checkout of apr. On WinXP, this always fails to get file protection
information in the apr_stat call just after creating a directory. I
disabled this in my working copy to be able to test this patch.

Feel free to make changes, etc and let me know of any issues.

Cheers
Conor

Index: src-trunk/subversion/bindings/java/javahl/native/SVNClient.cpp
===================================================================
--- src-trunk/subversion/bindings/java/javahl/native/SVNClient.cpp (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/native/SVNClient.cpp (working copy)
@@ -2271,18 +2271,129 @@
         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();
+ 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;
+}
+
+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();
+ // get the method id of the method we wish to call
+ 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;
+ }
+
+ 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;
+ svn_error_t *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. Bad hack to avoid going 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 +2402,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)
     {
-
         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 +2447,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,41 +2458,10 @@
         }
         size = buf->len;
     }
- if(read_stream == NULL)
- {
- return NULL;
- }
+ return read_stream;
+}
 
- 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;
-}
-
 /**
  * create a DirEntry java object from svn_dirent_t structure
  */
Index: src-trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp
===================================================================
--- src-trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/native/org_tigris_subversion_javahl_SVNClient.cpp (working copy)
@@ -1349,7 +1349,7 @@
   (JNIEnv *env, jobject jthis, jstring jpath, jobject jrevision,
    jobject jpegRevision)
 {
- JNIEntry(SVNClient, propertyCreate);
+ JNIEntry(SVNClient, fileContent);
     SVNClient *cl = SVNClient::getCppObject(jthis);
     if(cl == NULL)
     {
@@ -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, fileContent);
+ 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
Index: src-trunk/subversion/bindings/java/javahl/native/SVNClient.h
===================================================================
--- src-trunk/subversion/bindings/java/javahl/native/SVNClient.h (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/native/SVNClient.h (working copy)
@@ -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,
@@ -150,6 +153,8 @@
                                    const char *name, svn_string_t *value);
     jobject createJavaDirEntry(const char *path, svn_dirent_t *dirent);
     jobject createJavaInfo(const svn_wc_entry_t *entry);
+ svn_stream_t* createReadStream(apr_pool_t* pool, const char *path,
+ Revision &revision, Revision &pegRevision, size_t& size);
     svn_client_ctx_t * getContext(const char *message);
     Notify *m_notify;
     Notify2 *m_notify2;
Index: src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java
===================================================================
--- src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/tests/BasicTests.java (working copy)
@@ -24,6 +24,7 @@
 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
@@ -1144,6 +1145,31 @@
     }
 
     /**
+ * 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
+ assertTrue("content changed", Arrays.equals(content, testContent));
+ }
+
+ /**
      * test the basic SVNClient.list functionality
      * @throws Throwable
      */
Index: src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java
===================================================================
--- src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientInterface.java (working copy)
@@ -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
@@ -699,6 +701,7 @@
      * @throws ClientException
      */
     byte[] fileContent(String path, Revision revision) throws ClientException;
+
     /**
      * Retrieve the content of a file
      * @param path the path of the file
@@ -711,6 +714,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
      * @param to new url
Index: src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java
===================================================================
--- src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClientSynchronized.java (working copy)
@@ -16,6 +16,9 @@
  * @endcopyright
  */
 package org.tigris.subversion.javahl;
+
+import java.io.OutputStream;
+
 /**
  * This class provides a threadsafe wrapped for SVNClient
  */
@@ -1171,6 +1174,24 @@
     }
 
     /**
+ * Stream file content to the given output stream
+ * @param path the path of the file
+ * @param revision the revision to retrieve
+ * @param bufferSize the size of buffer to use during streaming
+ * @param the stream to which content is written
+ * @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);
+ }
+ }
+
+ /**
      * Rewrite the url's in the working copy
      * @param from old url
      * @param to new url
Index: src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java
===================================================================
--- src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java (revision 14176)
+++ src-trunk/subversion/bindings/java/javahl/src/org/tigris/subversion/javahl/SVNClient.java (working copy)
@@ -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
@@ -922,6 +925,19 @@
             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 the stream to which content is written
+ * @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
      * @param from old url
      * @param to new url

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Thu Apr 14 08:16:10 2005

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