14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""RemoteTransport client for the smart-server.
19
This module shouldn't be accessed directly. The classes defined here should be
20
imported from bzrlib.smart.
23
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
25
17
from cStringIO import StringIO
29
21
from bzrlib import (
34
from bzrlib.smart import client, medium, protocol
25
from bzrlib.smart.protocol import SmartClientRequestProtocolOne
26
from bzrlib.smart.medium import SmartTCPClientMedium, SmartSSHClientMedium
36
28
# must do this otherwise urllib can't parse the urls properly :(
37
29
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh', 'bzr+http']:
42
34
# Port 4155 is the default port for bzr://, registered with IANA.
43
BZR_DEFAULT_INTERFACE = '0.0.0.0'
44
35
BZR_DEFAULT_PORT = 4155
47
class _SmartStat(object):
38
class SmartStat(object):
49
40
def __init__(self, size, mode):
50
41
self.st_size = size
54
45
class RemoteTransport(transport.Transport):
55
46
"""Connection to a smart server.
57
The connection holds references to the medium that can be used to send
58
requests to the server.
48
The connection holds references to pipes that can be used to send requests
60
51
The connection has a notion of the current directory to which it's
61
52
connected; this is incorporated in filenames passed to the server.
63
54
This supports some higher-level RPC operations and can also be treated
64
55
like a Transport to do file-like operations.
66
The connection can be made over a tcp socket, an ssh pipe or a series of
67
http requests. There are concrete subclasses for each type:
68
RemoteTCPTransport, etc.
57
The connection can be made over a tcp socket, or (in future) an ssh pipe
58
or a series of http requests. There are concrete subclasses for each
59
type: RemoteTCPTransport, etc.
71
62
# IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
74
65
# RemoteTransport is an adapter from the Transport object model to the
75
66
# SmartClient model, not an encoder.
77
def __init__(self, url, clone_from=None, medium=None, _client=None):
68
def __init__(self, url, clone_from=None, medium=None):
80
:param clone_from: Another RemoteTransport instance that this one is
81
being cloned from. Attributes such as credentials and the medium
83
71
:param medium: The medium to use for this RemoteTransport. This must be
84
72
supplied if clone_from is None.
85
:param _client: Override the _SmartClient used by this transport. This
86
should only be used for testing purposes; normally this is
87
determined from the medium.
89
74
### Technically super() here is faulty because Transport's __init__
90
75
### fails to take 2 parameters, and if super were to choose a silly
103
88
# reuse same connection
104
89
self._medium = clone_from._medium
105
90
assert self._medium is not None
107
self._client = client._SmartClient(self._medium)
109
self._client = _client
111
92
def abspath(self, relpath):
112
93
"""Return the full url to the given relative path.
129
110
def is_readonly(self):
130
111
"""Smart server transport can do read/write file operations."""
131
resp = self._call2('Transport.is_readonly')
132
if resp == ('yes', ):
134
elif resp == ('no', ):
136
elif (resp == ('error', "Generic bzr smart protocol error: "
137
"bad request 'Transport.is_readonly'") or
138
resp == ('error', "Generic bzr smart protocol error: "
139
"bad request u'Transport.is_readonly'")):
140
# XXX: nasty hack: servers before 0.16 don't have a
141
# 'Transport.is_readonly' verb, so we do what clients before 0.16
145
self._translate_error(resp)
146
raise errors.UnexpectedSmartServerResponse(resp)
148
114
def get_smart_client(self):
149
115
return self._medium
179
145
def _call2(self, method, *args):
180
146
"""Call a method on the remote server."""
181
return self._client.call(method, *args)
147
protocol = SmartClientRequestProtocolOne(self._medium.get_request())
148
protocol.call(method, *args)
149
return protocol.read_response_tuple()
183
151
def _call_with_body_bytes(self, method, args, body):
184
152
"""Call a method on the remote server with body bytes."""
185
return self._client.call_with_body_bytes(method, args, body)
153
protocol = SmartClientRequestProtocolOne(self._medium.get_request())
154
protocol.call_with_body_bytes((method, ) + args, body)
155
return protocol.read_response_tuple()
187
157
def has(self, relpath):
188
158
"""Indicate whether a remote file of the given name exists or not.
207
177
def get_bytes(self, relpath):
208
178
remote = self._remote_path(relpath)
209
request = self._medium.get_request()
210
smart_protocol = protocol.SmartClientRequestProtocolOne(request)
211
smart_protocol.call('get', remote)
212
resp = smart_protocol.read_response_tuple(True)
179
protocol = SmartClientRequestProtocolOne(self._medium.get_request())
180
protocol.call('get', remote)
181
resp = protocol.read_response_tuple(True)
213
182
if resp != ('ok', ):
214
smart_protocol.cancel_read_body()
183
protocol.cancel_read_body()
215
184
self._translate_error(resp, relpath)
216
return smart_protocol.read_body_bytes()
185
return protocol.read_body_bytes()
218
187
def _serialise_optional_mode(self, mode):
230
199
# FIXME: upload_file is probably not safe for non-ascii characters -
231
200
# should probably just pass all parameters as length-delimited
233
if type(upload_contents) is unicode:
234
# Although not strictly correct, we raise UnicodeEncodeError to be
235
# compatible with other transports.
236
raise UnicodeEncodeError(
237
'undefined', upload_contents, 0, 1,
238
'put_bytes must be given bytes, not unicode.')
239
202
resp = self._call_with_body_bytes('put',
240
203
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
305
268
limit=self._max_readv_combine,
306
269
fudge_factor=self._bytes_to_read_before_seek))
308
request = self._medium.get_request()
309
smart_protocol = protocol.SmartClientRequestProtocolOne(request)
310
smart_protocol.call_with_body_readv_array(
271
protocol = SmartClientRequestProtocolOne(self._medium.get_request())
272
protocol.call_with_body_readv_array(
311
273
('readv', self._remote_path(relpath)),
312
274
[(c.start, c.length) for c in coalesced])
313
resp = smart_protocol.read_response_tuple(True)
275
resp = protocol.read_response_tuple(True)
315
277
if resp[0] != 'readv':
316
278
# This should raise an exception
317
smart_protocol.cancel_read_body()
279
protocol.cancel_read_body()
318
280
self._translate_error(resp)
321
283
# FIXME: this should know how many bytes are needed, for clarity.
322
data = smart_protocol.read_body_bytes()
284
data = protocol.read_body_bytes()
323
285
# Cache the results, but only until they have been fulfilled
325
287
for c_offset in coalesced:
389
351
raise UnicodeEncodeError(encoding, val, start, end, reason)
390
352
elif what == "ReadOnlyError":
391
353
raise errors.TransportNotPossible('readonly transport')
392
elif what == "ReadError":
393
if orig_path is not None:
394
error_path = orig_path
397
raise errors.ReadError(error_path)
399
355
raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
407
363
def stat(self, relpath):
408
364
resp = self._call2('stat', self._remote_path(relpath))
409
365
if resp[0] == 'stat':
410
return _SmartStat(int(resp[1]), int(resp[2], 8))
366
return SmartStat(int(resp[1]), int(resp[2], 8))
412
368
self._translate_error(resp)
460
417
except (ValueError, TypeError), e:
461
418
raise errors.InvalidURL(
462
419
path=url, extra="invalid port %s" % _port)
463
client_medium = medium.SmartTCPClientMedium(_host, _port)
464
super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
420
medium = SmartTCPClientMedium(_host, _port)
421
super(RemoteTCPTransport, self).__init__(url, medium=medium)
467
424
class RemoteSSHTransport(RemoteTransport):
480
437
except (ValueError, TypeError), e:
481
438
raise errors.InvalidURL(path=url, extra="invalid port %s" %
483
client_medium = medium.SmartSSHClientMedium(_host, _port,
484
_username, _password)
485
super(RemoteSSHTransport, self).__init__(url, medium=client_medium)
440
medium = SmartSSHClientMedium(_host, _port, _username, _password)
441
super(RemoteSSHTransport, self).__init__(url, medium=medium)
488
444
class RemoteHTTPTransport(RemoteTransport):
510
466
def _remote_path(self, relpath):
511
467
"""After connecting HTTP Transport only deals in relative URLs."""
512
# Adjust the relpath based on which URL this smart transport is
514
base = urlutils.normalize_url(self._http_transport.base)
515
url = urlutils.join(self.base[len('bzr+'):], relpath)
516
url = urlutils.normalize_url(url)
517
return urlutils.relative_url(base, url)
519
473
def abspath(self, relpath):
520
474
"""Return the full url to the given relative path.
530
484
This is re-implemented rather than using the default
531
485
RemoteTransport.clone() because we must be careful about the underlying
534
Also, the cloned smart transport will POST to the same .bzr/smart
535
location as this transport (although obviously the relative paths in the
536
smart requests may be different). This is so that the server doesn't
537
have to handle .bzr/smart requests at arbitrary places inside .bzr
538
directories, just at the initial URL the user uses.
540
The exception is parent paths (i.e. relative_url of "..").
543
489
abs_url = self.abspath(relative_url)
545
491
abs_url = self.base
546
# We either use the exact same http_transport (for child locations), or
547
# a clone of the underlying http_transport (for parent locations). This
548
# means we share the connection.
549
norm_base = urlutils.normalize_url(self.base)
550
norm_abs_url = urlutils.normalize_url(abs_url)
551
normalized_rel_url = urlutils.relative_url(norm_base, norm_abs_url)
552
if normalized_rel_url == ".." or normalized_rel_url.startswith("../"):
553
http_transport = self._http_transport.clone(normalized_rel_url)
555
http_transport = self._http_transport
556
return RemoteHTTPTransport(abs_url, http_transport=http_transport)
492
# By cloning the underlying http_transport, we are able to share the
494
new_transport = self._http_transport.clone(relative_url)
495
return RemoteHTTPTransport(abs_url, http_transport=new_transport)
559
498
def get_test_permutations():
560
499
"""Return (transport, server) permutations for testing."""
500
from bzrlib.smart import server
561
501
### We may need a little more test framework support to construct an
562
502
### appropriate RemoteTransport in the future.
563
from bzrlib.smart import server
564
503
return [(RemoteTCPTransport, server.SmartTCPServer_for_testing)]