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