diff src/org/tmatesoft/hg/repo/HgRemoteRepository.java @ 177:e10225daface

Use POST for long between queries. Batch between queries (pass multiple pairs to a server) to minimize number thereof
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 02 Apr 2011 23:05:28 +0200
parents a8df7162ec75
children 62665d8f0686
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Sat Apr 02 03:01:14 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgRemoteRepository.java	Sat Apr 02 23:05:28 2011 +0200
@@ -18,8 +18,10 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.StreamTokenizer;
+import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
@@ -27,6 +29,7 @@
 import java.security.cert.X509Certificate;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -39,6 +42,7 @@
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
 
+import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.HgException;
 import org.tmatesoft.hg.core.Nodeid;
 
@@ -106,25 +110,99 @@
 	}
 	
 	public List<Nodeid> between(Nodeid tip, Nodeid base) throws HgException {
+		Range r = new Range(base, tip);
+		// XXX shall handle errors like no range key in the returned map, not sure how.
+		return between(Collections.singletonList(r)).get(r);
+	}
+
+	/**
+	 * @param ranges
+	 * @return map, where keys are input instances, values are corresponding server reply
+	 * @throws HgException 
+	 */
+	public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException {
+		if (ranges.isEmpty()) {
+			return Collections.emptyMap();
+		}
+		// if fact, shall do other way round, this method shall send 
+		LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3);
+		StringBuilder sb = new StringBuilder(20 + ranges.size() * 82);
+		sb.append("pairs=");
+		for (Range r : ranges) {
+			sb.append(r.end.toString());
+			sb.append('-');
+			sb.append(r.start.toString());
+			sb.append('+');
+		}
+		if (sb.charAt(sb.length() - 1) == '+') {
+			// strip last space 
+			sb.setLength(sb.length() - 1);
+		}
 		try {
-			LinkedList<Nodeid> rv = new LinkedList<Nodeid>();
-			URL u = new URL(url, url.getPath() + "?cmd=between&pairs=" + tip.toString() + '-' + base.toString());
-			URLConnection c = setupConnection(u.openConnection());
-			c.connect();
-			System.out.println("Query:" + u.getQuery());
+			boolean usePOST = ranges.size() > 3;
+			URL u = new URL(url, url.getPath() + "?cmd=between" + (usePOST ? "" : '&' + sb.toString()));
+			HttpURLConnection c = setupConnection(u.openConnection());
+			if (usePOST) {
+				c.setRequestMethod("POST");
+				c.setRequestProperty("Content-Length", String.valueOf(sb.length()/*nodeids are ASCII, bytes == characters */));
+				c.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+				c.setDoOutput(true);
+				c.connect();
+				OutputStream os = c.getOutputStream();
+				os.write(sb.toString().getBytes());
+				os.flush();
+				os.close();
+			} else {
+				c.connect();
+			}
+			System.out.printf("%d ranges, method:%s \n", ranges.size(), c.getRequestMethod());
+			System.out.printf("Query (%d bytes):%s\n", u.getQuery().length(), u.getQuery());
 			System.out.println("Response headers:");
 			final Map<String, List<String>> headerFields = c.getHeaderFields();
 			for (String s : headerFields.keySet()) {
 				System.out.printf("%s: %s\n", s, c.getHeaderField(s));
 			}
-			InputStream is = c.getInputStream();
+			InputStreamReader is = new InputStreamReader(c.getInputStream(), "US-ASCII");
 			StreamTokenizer st = new StreamTokenizer(is);
 			st.ordinaryChars('0', '9');
 			st.wordChars('0', '9');
+			st.eolIsSignificant(true);
+			Iterator<Range> rangeItr = ranges.iterator();
+			LinkedList<Nodeid> currRangeList = null;
+			Range currRange = null;
+			boolean possiblyEmptyNextLine = true;
 			while (st.nextToken() != StreamTokenizer.TT_EOF) {
-				System.out.println(st.sval);
-				Nodeid nid = Nodeid.fromAscii(st.sval);
-				rv.addLast(nid);
+				if (st.ttype == StreamTokenizer.TT_EOL) {
+					if (possiblyEmptyNextLine) {
+						// newline follows newline;
+						assert currRange == null;
+						assert currRangeList == null;
+						if (!rangeItr.hasNext()) {
+							throw new HgBadStateException();
+						}
+						rv.put(rangeItr.next(), Collections.<Nodeid>emptyList());
+					} else {
+						if (currRange == null || currRangeList == null) {
+							throw new HgBadStateException();
+						}
+						// indicate next range value is needed
+						currRange = null;
+						currRangeList = null;
+						possiblyEmptyNextLine = true;
+					}
+				} else {
+					possiblyEmptyNextLine = false;
+					if (currRange == null) {
+						if (!rangeItr.hasNext()) {
+							throw new HgBadStateException();
+						}
+						currRange = rangeItr.next();
+						currRangeList = new LinkedList<Nodeid>();
+						rv.put(currRange, currRangeList);
+					}
+					Nodeid nid = Nodeid.fromAscii(st.sval);
+					currRangeList.addLast(nid);
+				}
 			}
 			is.close();
 			return rv;
@@ -135,21 +213,6 @@
 		}
 	}
 
-	/**
-	 * @param ranges
-	 * @return map, where keys are input instances, values are corresponding server reply
-	 * @throws HgException 
-	 */
-	public Map<Range, List<Nodeid>> between(Collection<Range> ranges) throws HgException {
-		// if fact, shall do other way round, this method shall send 
-		LinkedHashMap<Range, List<Nodeid>> rv = new LinkedHashMap<HgRemoteRepository.Range, List<Nodeid>>(ranges.size() * 4 / 3);
-		for (Range r : ranges) {
-			List<Nodeid> between = between(r.end, r.start);
-			rv.put(r, between);
-		}
-		return rv;
-	}
-
 	public List<RemoteBranch> branches(List<Nodeid> nodes) {
 		return Collections.emptyList();
 	}
@@ -159,8 +222,8 @@
 		return new HgLookup().loadBundle(new File("/temp/hg/hg-bundle-000000000000-gz.tmp"));
 	}
 	
-	private URLConnection setupConnection(URLConnection urlConnection) {
-		urlConnection.addRequestProperty("User-Agent", "hg4j/0.5.0");
+	private HttpURLConnection setupConnection(URLConnection urlConnection) {
+		urlConnection.setRequestProperty("User-Agent", "hg4j/0.5.0");
 		urlConnection.addRequestProperty("Accept", "application/mercurial-0.1");
 		if (authInfo != null) {
 			urlConnection.addRequestProperty("Authorization", "Basic " + authInfo);
@@ -168,7 +231,7 @@
 		if (sslContext != null) {
 			((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslContext.getSocketFactory());
 		}
-		return urlConnection;
+		return (HttpURLConnection) urlConnection;
 	}
 
 	public static final class Range {