74
74
# RemoteTransport is an adapter from the Transport object model to the
75
75
# SmartClient model, not an encoder.
77
def __init__(self, url, clone_from=None, medium=None, _client=None):
77
# FIXME: the medium parameter should be private, only the tests requires
78
# it. It may be even clearer to define a TestRemoteTransport that handles
79
# the specific cases of providing a _client and/or a _medium, and leave
80
# RemoteTransport as an abstract class.
81
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
84
:param _from_transport: Another RemoteTransport instance that this
85
one is being cloned from. Attributes such as the medium will
83
88
:param medium: The medium to use for this RemoteTransport. This must be
84
supplied if clone_from is None.
89
supplied if _from_transport is None.
85
91
:param _client: Override the _SmartClient used by this transport. This
86
92
should only be used for testing purposes; normally this is
87
93
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
95
super(RemoteTransport, self).__init__(url,
96
_from_transport=_from_transport)
98
# The medium is the connection, except when we need to share it with
99
# other objects (RemoteBzrDir, RemoteRepository etc). In these cases
100
# what we want to share is really the shared connection.
102
if _from_transport is None:
103
# If no _from_transport is specified, we need to intialize the
107
medium, credentials = self._build_medium()
108
self._shared_connection= transport._SharedConnection(medium,
106
111
if _client is None:
107
self._client = client._SmartClient(self._medium)
112
self._client = client._SmartClient(self.get_shared_medium())
109
114
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.
116
def _build_medium(self):
117
"""Create the medium if _from_transport does not provide one.
122
This essentially opens a handle on a different remote directory.
119
The medium is analogous to the connection for ConnectedTransport: it
120
allows connection sharing.
124
if relative_url is None:
125
return RemoteTransport(self.base, self)
127
return RemoteTransport(self.abspath(relative_url), self)
129
125
def is_readonly(self):
130
126
"""Smart server transport can do read/write file operations."""
146
142
raise errors.UnexpectedSmartServerResponse(resp)
148
144
def get_smart_client(self):
145
return self._get_connection()
151
147
def get_smart_medium(self):
154
def _unparse_url(self, path):
155
"""Return URL for a path.
148
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, '', '', ''))
150
def get_shared_medium(self):
151
return self._get_shared_connection()
171
153
def _remote_path(self, relpath):
172
154
"""Returns the Unicode version of the absolute path for relpath."""
449
436
SmartTCPClientMedium).
452
def __init__(self, url):
453
_scheme, _username, _password, _host, _port, _path = \
454
transport.split_url(url)
456
_port = BZR_DEFAULT_PORT
460
except (ValueError, TypeError), e:
461
raise errors.InvalidURL(
462
path=url, extra="invalid port %s" % _port)
463
client_medium = medium.SmartTCPClientMedium(_host, _port)
464
super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
439
def _build_medium(self):
440
assert self.base.startswith('bzr://')
441
if self._port is None:
442
self._port = BZR_DEFAULT_PORT
443
return medium.SmartTCPClientMedium(self._host, self._port), None
467
446
class RemoteSSHTransport(RemoteTransport):
471
450
SmartSSHClientMedium).
474
def __init__(self, url):
475
_scheme, _username, _password, _host, _port, _path = \
476
transport.split_url(url)
478
if _port is not None:
480
except (ValueError, TypeError), e:
481
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)
453
def _build_medium(self):
454
assert self.base.startswith('bzr+ssh://')
455
# ssh will prompt the user for a password if needed and if none is
456
# provided but it will not give it back, so no credentials can be
458
return medium.SmartSSHClientMedium(self._host, self._port,
459
self._user, self._password), None
488
462
class RemoteHTTPTransport(RemoteTransport):
496
470
HTTP path into a local path.
499
def __init__(self, url, http_transport=None):
500
assert url.startswith('bzr+http://')
473
def __init__(self, base, _from_transport=None, http_transport=None):
474
assert base.startswith('bzr+http://')
502
476
if http_transport is None:
503
http_url = url[len('bzr+'):]
477
# FIXME: the password may be lost here because it appears in the
478
# url only for an intial construction (when the url came from the
480
http_url = base[len('bzr+'):]
504
481
self._http_transport = transport.get_transport(http_url)
506
483
self._http_transport = http_transport
507
http_medium = self._http_transport.get_smart_medium()
508
super(RemoteHTTPTransport, self).__init__(url, medium=http_medium)
484
super(RemoteHTTPTransport, self).__init__(
485
base, _from_transport=_from_transport)
487
def _build_medium(self):
488
# We let http_transport take care of the credentials
489
return self._http_transport.get_smart_medium(), None
510
491
def _remote_path(self, relpath):
511
"""After connecting HTTP Transport only deals in relative URLs."""
492
"""After connecting, HTTP Transport only deals in relative URLs."""
512
493
# Adjust the relpath based on which URL this smart transport is
514
base = urlutils.normalize_url(self._http_transport.base)
495
http_base = urlutils.normalize_url(self._http_transport.base)
515
496
url = urlutils.join(self.base[len('bzr+'):], relpath)
516
497
url = urlutils.normalize_url(url)
517
return urlutils.relative_url(base, url)
519
def abspath(self, relpath):
520
"""Return the full url to the given relative path.
522
:param relpath: the relative path or path components
523
:type relpath: str or list
525
return self._unparse_url(self._combine_paths(self._path, relpath))
498
return urlutils.relative_url(http_base, url)
527
500
def clone(self, relative_url):
528
501
"""Make a new RemoteHTTPTransport related to me.