74
67
# RemoteTransport is an adapter from the Transport object model to the
75
68
# SmartClient model, not an encoder.
77
def __init__(self, url, clone_from=None, medium=None, _client=None):
70
# FIXME: the medium parameter should be private, only the tests requires
71
# it. It may be even clearer to define a TestRemoteTransport that handles
72
# the specific cases of providing a _client and/or a _medium, and leave
73
# RemoteTransport as an abstract class.
74
def __init__(self, url, _from_transport=None, medium=None, _client=None):
80
:param clone_from: Another RemoteTransport instance that this one is
81
being cloned from. Attributes such as credentials and the medium
77
:param _from_transport: Another RemoteTransport instance that this
78
one is being cloned from. Attributes such as the medium will
83
81
:param medium: The medium to use for this RemoteTransport. This must be
84
supplied if clone_from is None.
82
supplied if _from_transport is None.
85
84
:param _client: Override the _SmartClient used by this transport. This
86
85
should only be used for testing purposes; normally this is
87
86
determined from the medium.
89
### Technically super() here is faulty because Transport's __init__
90
### fails to take 2 parameters, and if super were to choose a silly
91
### initialisation order things would blow up.
92
if not url.endswith('/'):
94
super(RemoteTransport, self).__init__(url)
95
self._scheme, self._username, self._password, self._host, self._port, self._path = \
96
transport.split_url(url)
97
if clone_from is None:
100
# credentials may be stripped from the base in some circumstances
101
# as yet to be clearly defined or documented, so copy them.
102
self._username = clone_from._username
103
# reuse same connection
104
self._medium = clone_from._medium
105
assert self._medium is not None
88
super(RemoteTransport, self).__init__(url,
89
_from_transport=_from_transport)
91
# The medium is the connection, except when we need to share it with
92
# other objects (RemoteBzrDir, RemoteRepository etc). In these cases
93
# what we want to share is really the shared connection.
95
if _from_transport is None:
96
# If no _from_transport is specified, we need to intialize the
100
medium, credentials = self._build_medium()
101
if 'hpss' in debug.debug_flags:
102
trace.mutter('hpss: Built a new medium: %s',
103
medium.__class__.__name__)
104
self._shared_connection = transport._SharedConnection(medium,
106
107
if _client is None:
107
self._client = client._SmartClient(self._medium)
108
self._client = client._SmartClient(self.get_shared_medium())
109
110
self._client = _client
111
def abspath(self, relpath):
112
"""Return the full url to the given relative path.
114
@param relpath: the relative path or path components
115
@type relpath: str or list
117
return self._unparse_url(self._remote_path(relpath))
119
def clone(self, relative_url):
120
"""Make a new RemoteTransport related to me, sharing the same connection.
112
def _build_medium(self):
113
"""Create the medium if _from_transport does not provide one.
122
This essentially opens a handle on a different remote directory.
115
The medium is analogous to the connection for ConnectedTransport: it
116
allows connection sharing.
124
if relative_url is None:
125
return RemoteTransport(self.base, self)
127
return RemoteTransport(self.abspath(relative_url), self)
129
121
def is_readonly(self):
130
122
"""Smart server transport can do read/write file operations."""
145
137
self._translate_error(resp)
146
assert False, 'weird response %r' % (resp,)
138
raise errors.UnexpectedSmartServerResponse(resp)
148
140
def get_smart_client(self):
141
return self._get_connection()
151
143
def get_smart_medium(self):
154
def _unparse_url(self, path):
155
"""Return URL for a path.
144
return self._get_connection()
157
:see: SFTPUrlHandling._unparse_url
159
# TODO: Eventually it should be possible to unify this with
160
# SFTPUrlHandling._unparse_url?
163
path = urllib.quote(path)
164
netloc = urllib.quote(self._host)
165
if self._username is not None:
166
netloc = '%s@%s' % (urllib.quote(self._username), netloc)
167
if self._port is not None:
168
netloc = '%s:%d' % (netloc, self._port)
169
return urlparse.urlunparse((self._scheme, netloc, path, '', '', ''))
146
def get_shared_medium(self):
147
return self._get_shared_connection()
171
149
def _remote_path(self, relpath):
172
150
"""Returns the Unicode version of the absolute path for relpath."""
226
204
self._serialise_optional_mode(mode))
227
205
self._translate_error(resp)
207
def open_write_stream(self, relpath, mode=None):
208
"""See Transport.open_write_stream."""
209
self.put_bytes(relpath, "", mode)
210
result = transport.AppendBasedFileStream(self, relpath)
211
transport._file_streams[self.abspath(relpath)] = result
229
214
def put_bytes(self, relpath, upload_contents, mode=None):
230
215
# FIXME: upload_file is probably not safe for non-ascii characters -
231
216
# should probably just pass all parameters as length-delimited
389
380
raise UnicodeEncodeError(encoding, val, start, end, reason)
390
381
elif what == "ReadOnlyError":
391
382
raise errors.TransportNotPossible('readonly transport')
383
elif what == "ReadError":
384
if orig_path is not None:
385
error_path = orig_path
388
raise errors.ReadError(error_path)
393
390
raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
395
392
def disconnect(self):
396
self._medium.disconnect()
393
self.get_smart_medium().disconnect()
398
395
def delete_tree(self, relpath):
399
396
raise errors.TransportNotPossible('readonly transport')
443
440
SmartTCPClientMedium).
446
def __init__(self, url):
447
_scheme, _username, _password, _host, _port, _path = \
448
transport.split_url(url)
450
_port = BZR_DEFAULT_PORT
454
except (ValueError, TypeError), e:
455
raise errors.InvalidURL(
456
path=url, extra="invalid port %s" % _port)
457
client_medium = medium.SmartTCPClientMedium(_host, _port)
458
super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
443
def _build_medium(self):
444
assert self.base.startswith('bzr://')
445
return medium.SmartTCPClientMedium(self._host, self._port), None
461
448
class RemoteSSHTransport(RemoteTransport):
465
452
SmartSSHClientMedium).
468
def __init__(self, url):
469
_scheme, _username, _password, _host, _port, _path = \
470
transport.split_url(url)
472
if _port is not None:
474
except (ValueError, TypeError), e:
475
raise errors.InvalidURL(path=url, extra="invalid port %s" %
477
client_medium = medium.SmartSSHClientMedium(_host, _port,
478
_username, _password)
479
super(RemoteSSHTransport, self).__init__(url, medium=client_medium)
455
def _build_medium(self):
456
assert self.base.startswith('bzr+ssh://')
457
# ssh will prompt the user for a password if needed and if none is
458
# provided but it will not give it back, so no credentials can be
460
location_config = config.LocationConfig(self.base)
461
bzr_remote_path = location_config.get_bzr_remote_path()
462
return medium.SmartSSHClientMedium(self._host, self._port,
463
self._user, self._password, bzr_remote_path=bzr_remote_path), None
482
466
class RemoteHTTPTransport(RemoteTransport):
490
474
HTTP path into a local path.
493
def __init__(self, url, http_transport=None):
494
assert url.startswith('bzr+http://')
477
def __init__(self, base, _from_transport=None, http_transport=None):
478
assert ( base.startswith('bzr+http://') or base.startswith('bzr+https://') )
496
480
if http_transport is None:
497
http_url = url[len('bzr+'):]
481
# FIXME: the password may be lost here because it appears in the
482
# url only for an intial construction (when the url came from the
484
http_url = base[len('bzr+'):]
498
485
self._http_transport = transport.get_transport(http_url)
500
487
self._http_transport = http_transport
501
http_medium = self._http_transport.get_smart_medium()
502
super(RemoteHTTPTransport, self).__init__(url, medium=http_medium)
488
super(RemoteHTTPTransport, self).__init__(
489
base, _from_transport=_from_transport)
491
def _build_medium(self):
492
# We let http_transport take care of the credentials
493
return self._http_transport.get_smart_medium(), None
504
495
def _remote_path(self, relpath):
505
"""After connecting HTTP Transport only deals in relative URLs."""
496
"""After connecting, HTTP Transport only deals in relative URLs."""
506
497
# Adjust the relpath based on which URL this smart transport is
508
base = urlutils.normalize_url(self._http_transport.base)
499
http_base = urlutils.normalize_url(self._http_transport.base)
509
500
url = urlutils.join(self.base[len('bzr+'):], relpath)
510
501
url = urlutils.normalize_url(url)
511
return urlutils.relative_url(base, url)
513
def abspath(self, relpath):
514
"""Return the full url to the given relative path.
516
:param relpath: the relative path or path components
517
:type relpath: str or list
519
return self._unparse_url(self._combine_paths(self._path, relpath))
502
return urlutils.relative_url(http_base, url)
521
504
def clone(self, relative_url):
522
505
"""Make a new RemoteHTTPTransport related to me.