comparison src/org/tmatesoft/hg/internal/remote/SshConnector.java @ 687:9859fcea475d

Towards ssh remote repositories: refactor HgRemoteRepository - move http related code to HttpConnector
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 27 Jul 2013 18:34:14 +0200
parents 9897cbfd2790
children 5b5d199e2eb3
comparison
equal deleted inserted replaced
686:f1f095e42555 687:9859fcea475d
19 import java.io.BufferedReader; 19 import java.io.BufferedReader;
20 import java.io.Closeable; 20 import java.io.Closeable;
21 import java.io.EOFException; 21 import java.io.EOFException;
22 import java.io.File; 22 import java.io.File;
23 import java.io.FilterInputStream; 23 import java.io.FilterInputStream;
24 import java.io.FilterOutputStream;
24 import java.io.IOException; 25 import java.io.IOException;
25 import java.io.InputStream; 26 import java.io.InputStream;
26 import java.io.InputStreamReader; 27 import java.io.InputStreamReader;
27 import java.io.OutputStream; 28 import java.io.OutputStream;
28 import java.net.URL; 29 import java.net.URL;
29 import java.util.ArrayList; 30 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection; 31 import java.util.Collection;
32 import java.util.Collections; 32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.List; 33 import java.util.List;
35 import java.util.Set;
36 34
37 import org.tmatesoft.hg.core.HgRemoteConnectionException; 35 import org.tmatesoft.hg.core.HgRemoteConnectionException;
38 import org.tmatesoft.hg.core.Nodeid; 36 import org.tmatesoft.hg.core.Nodeid;
39 import org.tmatesoft.hg.core.SessionContext; 37 import org.tmatesoft.hg.core.SessionContext;
40 import org.tmatesoft.hg.internal.Internals;
41 import org.tmatesoft.hg.repo.HgBundle;
42 import org.tmatesoft.hg.repo.HgRemoteRepository.Range; 38 import org.tmatesoft.hg.repo.HgRemoteRepository.Range;
43 import org.tmatesoft.hg.repo.HgRuntimeException; 39 import org.tmatesoft.hg.repo.HgRuntimeException;
44 import org.tmatesoft.hg.util.LogFacility.Severity; 40 import org.tmatesoft.hg.util.LogFacility.Severity;
45 41
46 import ch.ethz.ssh2.Connection; 42 import ch.ethz.ssh2.Connection;
52 * Remote repository via SSH 48 * Remote repository via SSH
53 * 49 *
54 * @author Artem Tikhomirov 50 * @author Artem Tikhomirov
55 * @author TMate Software Ltd. 51 * @author TMate Software Ltd.
56 */ 52 */
57 public class SshConnector { 53 public class SshConnector implements Connector {
58 private SessionContext sessionCtx; 54 private SessionContext sessionCtx;
59 private URL url; 55 private URL url;
60 private Connection conn; 56 private Connection conn;
61 private Session session; 57 private Session session;
62 private int sessionUse; 58 private int sessionUse;
63 59
64 private StreamGobbler remoteErr, remoteOut; 60 private StreamGobbler remoteErr, remoteOut;
65 private OutputStream remoteIn; 61 private OutputStream remoteIn;
66 62
67 public void connect(URL url, SessionContext sessionContext, Object globalConfig) throws HgRemoteConnectionException { 63 public void init(URL url, SessionContext sessionContext, Object globalConfig) throws HgRuntimeException {
68 sessionCtx = sessionContext; 64 sessionCtx = sessionContext;
69 this.url = url; 65 this.url = url;
66 }
67
68 public void connect() throws HgRemoteConnectionException, HgRuntimeException {
70 try { 69 try {
71 conn = new Connection(url.getHost(), url.getPort() == -1 ? 22 : url.getPort()); 70 conn = new Connection(url.getHost(), url.getPort() == -1 ? 22 : url.getPort());
72 conn.connect(); 71 conn.connect();
73 } catch (IOException ex) { 72 } catch (IOException ex) {
74 throw new HgRemoteConnectionException("Failed to establish connection"); 73 throw new HgRemoteConnectionException("Failed to establish connection");
76 try { 75 try {
77 conn.authenticateWithPublicKey(System.getProperty("user.name"), new File(System.getProperty("user.home"), ".ssh/id_rsa"), null); 76 conn.authenticateWithPublicKey(System.getProperty("user.name"), new File(System.getProperty("user.home"), ".ssh/id_rsa"), null);
78 ConnectionInfo ci = conn.getConnectionInfo(); 77 ConnectionInfo ci = conn.getConnectionInfo();
79 System.out.printf("%s %s %s %d %s %s %s\n", ci.clientToServerCryptoAlgorithm, ci.clientToServerMACAlgorithm, ci.keyExchangeAlgorithm, ci.keyExchangeCounter, ci.serverHostKeyAlgorithm, ci.serverToClientCryptoAlgorithm, ci.serverToClientMACAlgorithm); 78 System.out.printf("%s %s %s %d %s %s %s\n", ci.clientToServerCryptoAlgorithm, ci.clientToServerMACAlgorithm, ci.keyExchangeAlgorithm, ci.keyExchangeCounter, ci.serverHostKeyAlgorithm, ci.serverToClientCryptoAlgorithm, ci.serverToClientMACAlgorithm);
80 } catch (IOException ex) { 79 } catch (IOException ex) {
81 throw new HgRemoteConnectionException("Failed to authenticate", ex).setServerInfo(getLocation()); 80 throw new HgRemoteConnectionException("Failed to authenticate", ex).setServerInfo(getServerLocation());
82 } 81 }
83 } 82 }
84 83
85 public void disconnect() throws HgRemoteConnectionException { 84 public void disconnect() throws HgRemoteConnectionException {
86 if (session != null) { 85 if (session != null) {
117 return; 116 return;
118 } 117 }
119 forceSessionClose(); 118 forceSessionClose();
120 } 119 }
121 120
122 public String getLocation() { 121 public String getServerLocation() {
123 return ""; 122 return ""; // FIXME
123 }
124
125 public String getCapabilities() throws HgRemoteConnectionException {
126 try {
127 consume(remoteOut);
128 consume(remoteErr);
129 remoteIn.write(CMD_HELLO.getBytes());
130 remoteIn.write('\n');
131 remoteIn.write(CMD_CAPABILITIES.getBytes()); // see http connector for details
132 remoteIn.write('\n');
133 remoteIn.write(CMD_HEADS.getBytes());
134 remoteIn.write('\n');
135 checkError();
136 int responseLen = readResponseLength();
137 checkError();
138 FilterStream s = new FilterStream(remoteOut, responseLen);
139 BufferedReader r = new BufferedReader(new InputStreamReader(s));
140 String line;
141 while ((line = r.readLine()) != null) {
142 if (line.startsWith(CMD_CAPABILITIES) && line.length() > (CMD_CAPABILITIES.length()+1)) {
143 line = line.substring(CMD_CAPABILITIES.length());
144 if (line.charAt(0) == ':') {
145 return line.substring(CMD_CAPABILITIES.length() + 1);
146 }
147 }
148 }
149 r.close();
150 consume(remoteOut);
151 checkError();
152 return new String();
153 } catch (IOException ex) {
154 throw new HgRemoteConnectionException("Failed to initiate dialog with server", ex).setRemoteCommand(CMD_HELLO).setServerInfo(getServerLocation());
155 }
124 } 156 }
125 157
126 public InputStream heads() throws HgRemoteConnectionException { 158 public InputStream heads() throws HgRemoteConnectionException {
127 return executeCommand("heads", Collections.<Parameter>emptyList()); 159 return executeCommand("heads", Collections.<Parameter>emptyList());
128 } 160 }
146 public InputStream changegroup(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException { 178 public InputStream changegroup(List<Nodeid> roots) throws HgRemoteConnectionException, HgRuntimeException {
147 String l = join(roots, ' '); 179 String l = join(roots, ' ');
148 return executeCommand("changegroup", Collections.singletonList(new Parameter("roots", l))); 180 return executeCommand("changegroup", Collections.singletonList(new Parameter("roots", l)));
149 } 181 }
150 182
151 public void unbundle(HgBundle bundle, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException { 183 public OutputStream unbundle(long outputLen, List<Nodeid> remoteHeads) throws HgRemoteConnectionException, HgRuntimeException {
152 String l = join(remoteHeads, ' '); 184 String l = join(remoteHeads, ' ');
153 Collections.singletonList(new Parameter("heads", l)); 185 try {
154 throw Internals.notImplemented(); 186 consume(remoteOut);
187 consume(remoteErr);
188 remoteIn.write(CMD_UNBUNDLE.getBytes());
189 remoteIn.write('\n');
190 writeParameters(Collections.singletonList(new Parameter("heads", l)));
191 checkError();
192 return new FilterOutputStream(remoteIn) {
193 @Override
194 public void close() throws IOException {
195 out.flush();
196 @SuppressWarnings("unused")
197 int responseLen = readResponseLength();
198 checkError();
199 // XXX perhaps, need to return responseLen to caller?
200 }
201 };
202 } catch (IOException ex) {
203 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(CMD_UNBUNDLE).setServerInfo(getServerLocation());
204 }
155 } 205 }
156 206
157 public InputStream pushkey(String opName, String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException { 207 public InputStream pushkey(String opName, String namespace, String key, String oldValue, String newValue) throws HgRemoteConnectionException, HgRuntimeException {
158 ArrayList<Parameter> p = new ArrayList<Parameter>(); 208 ArrayList<Parameter> p = new ArrayList<Parameter>();
159 p.add(new Parameter("namespace", namespace)); 209 p.add(new Parameter("namespace", namespace));
165 215
166 public InputStream listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException { 216 public InputStream listkeys(String namespace, String actionName) throws HgRemoteConnectionException, HgRuntimeException {
167 return executeCommand("listkeys", Collections.singletonList(new Parameter("namespace", namespace))); 217 return executeCommand("listkeys", Collections.singletonList(new Parameter("namespace", namespace)));
168 } 218 }
169 219
170
171 public Set<String> initCapabilities() throws HgRemoteConnectionException {
172 try {
173 final String CMD_CAPABILITIES = "capabilities";
174 final String CMD_HEADS = "heads";
175 final String CMD_HELLO = "hello";
176 consume(remoteOut);
177 consume(remoteErr);
178 remoteIn.write(CMD_HELLO.getBytes());
179 remoteIn.write('\n');
180 remoteIn.write(CMD_CAPABILITIES.getBytes()); // see http connector for
181 remoteIn.write('\n');
182 remoteIn.write(CMD_HEADS.getBytes());
183 remoteIn.write('\n');
184 checkError();
185 int responseLen = readResponseLength();
186 checkError();
187 FilterStream s = new FilterStream(remoteOut, responseLen);
188 BufferedReader r = new BufferedReader(new InputStreamReader(s));
189 String line;
190 while ((line = r.readLine()) != null) {
191 if (line.startsWith(CMD_CAPABILITIES) && line.length() > (CMD_CAPABILITIES.length()+1)) {
192 line = line.substring(CMD_CAPABILITIES.length());
193 if (line.charAt(0) == ':') {
194 String[] caps = line.substring(CMD_CAPABILITIES.length() + 1).split("\\s");
195 return new HashSet<String>(Arrays.asList(caps));
196 }
197 }
198 }
199 r.close();
200 consume(remoteOut);
201 checkError();
202 return Collections.emptySet();
203 } catch (IOException ex) {
204 throw new HgRemoteConnectionException("Failed to initiate dialog with server", ex).setRemoteCommand("hello").setServerInfo(getLocation());
205 } catch (HgRemoteConnectionException ex) {
206 ex.setRemoteCommand("hello").setServerInfo(getLocation());
207 throw ex;
208 }
209 }
210
211 private InputStream executeCommand(String cmd, List<Parameter> parameters) throws HgRemoteConnectionException { 220 private InputStream executeCommand(String cmd, List<Parameter> parameters) throws HgRemoteConnectionException {
212 try { 221 try {
213 consume(remoteOut); 222 consume(remoteOut);
214 consume(remoteErr); 223 consume(remoteErr);
215 remoteIn.write(cmd.getBytes()); 224 remoteIn.write(cmd.getBytes());
216 remoteIn.write('\n'); 225 remoteIn.write('\n');
217 for (Parameter p : parameters) { 226 writeParameters(parameters);
218 remoteIn.write(p.name().getBytes());
219 remoteIn.write(' ');
220 remoteIn.write(String.valueOf(p.size()).getBytes());
221 remoteIn.write('\n');
222 remoteIn.write(p.data());
223 remoteIn.write('\n');
224 }
225 checkError(); 227 checkError();
226 int responseLen = readResponseLength(); 228 int responseLen = readResponseLength();
227 checkError(); 229 checkError();
228 return new FilterStream(remoteOut, responseLen); 230 return new FilterStream(remoteOut, responseLen);
229 } catch (IOException ex) { 231 } catch (IOException ex) {
230 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(cmd).setServerInfo(getLocation()); 232 throw new HgRemoteConnectionException("Communication failure", ex).setRemoteCommand(cmd).setServerInfo(getServerLocation());
231 } catch (HgRemoteConnectionException ex) { 233 }
232 ex.setRemoteCommand(cmd).setServerInfo(getLocation()); 234 }
233 throw ex; 235
236 private void writeParameters(List<Parameter> parameters) throws IOException {
237 for (Parameter p : parameters) {
238 remoteIn.write(p.name().getBytes());
239 remoteIn.write(' ');
240 remoteIn.write(String.valueOf(p.size()).getBytes());
241 remoteIn.write('\n');
242 remoteIn.write(p.data());
243 remoteIn.write('\n');
234 } 244 }
235 } 245 }
236 246
237 private void consume(InputStream is) throws IOException { 247 private void consume(InputStream is) throws IOException {
238 while (is.available() > 0) { 248 while (is.available() > 0) {
239 is.read(); 249 is.read();
240 } 250 }
241 } 251 }
242 252
243 private void checkError() throws IOException, HgRemoteConnectionException { 253 private void checkError() throws IOException {
244 if (remoteErr.available() > 0) { 254 if (remoteErr.available() > 0) {
245 StringBuilder sb = new StringBuilder(); 255 StringBuilder sb = new StringBuilder();
246 int c; 256 int c;
247 while ((c = remoteErr.read()) != -1) { 257 while ((c = remoteErr.read()) != -1) {
248 sb.append((char)c); 258 sb.append((char)c);
249 } 259 }
250 throw new HgRemoteConnectionException(sb.toString()); 260 throw new IOException(sb.toString());
251 } 261 }
252 } 262 }
253 263
254 private int readResponseLength() throws IOException { 264 private int readResponseLength() throws IOException {
255 int c; 265 int c;