~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/remote.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-04-07 07:52:50 UTC
  • mfrom: (3340.1.1 208418-1.4)
  • Revision ID: pqm@pqm.ubuntu.com-20080407075250-phs53xnslo8boaeo
Return the correct knit serialisation method in _StreamAccess.
        (Andrew Bennetts, Martin Pool, Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
import urlparse
28
28
 
29
29
from bzrlib import (
 
30
    config,
 
31
    debug,
30
32
    errors,
 
33
    trace,
31
34
    transport,
32
35
    urlutils,
33
36
    )
34
37
from bzrlib.smart import client, medium, protocol
35
 
 
36
 
# must do this otherwise urllib can't parse the urls properly :(
37
 
for scheme in ['ssh', 'bzr', 'bzr+loopback', 'bzr+ssh', 'bzr+http']:
38
 
    transport.register_urlparse_netloc_protocol(scheme)
39
 
del scheme
40
 
 
41
 
 
42
 
# Port 4155 is the default port for bzr://, registered with IANA.
43
 
BZR_DEFAULT_INTERFACE = '0.0.0.0'
44
 
BZR_DEFAULT_PORT = 4155
 
38
from bzrlib.symbol_versioning import (deprecated_method, one_four)
45
39
 
46
40
 
47
41
class _SmartStat(object):
51
45
        self.st_mode = mode
52
46
 
53
47
 
54
 
class RemoteTransport(transport.Transport):
 
48
class RemoteTransport(transport.ConnectedTransport):
55
49
    """Connection to a smart server.
56
50
 
57
51
    The connection holds references to the medium that can be used to send
74
68
    # RemoteTransport is an adapter from the Transport object model to the 
75
69
    # SmartClient model, not an encoder.
76
70
 
77
 
    def __init__(self, url, clone_from=None, medium=None, _client=None):
 
71
    # FIXME: the medium parameter should be private, only the tests requires
 
72
    # it. It may be even clearer to define a TestRemoteTransport that handles
 
73
    # the specific cases of providing a _client and/or a _medium, and leave
 
74
    # RemoteTransport as an abstract class.
 
75
    def __init__(self, url, _from_transport=None, medium=None, _client=None):
78
76
        """Constructor.
79
77
 
80
 
        :param clone_from: Another RemoteTransport instance that this one is
81
 
            being cloned from.  Attributes such as credentials and the medium
82
 
            will be reused.
 
78
        :param _from_transport: Another RemoteTransport instance that this
 
79
            one is being cloned from.  Attributes such as the medium will
 
80
            be reused.
 
81
 
83
82
        :param medium: The medium to use for this RemoteTransport. This must be
84
 
            supplied if clone_from is None.
 
83
            supplied if _from_transport is None.
 
84
 
85
85
        :param _client: Override the _SmartClient used by this transport.  This
86
86
            should only be used for testing purposes; normally this is
87
87
            determined from the medium.
88
88
        """
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('/'):
93
 
            url += '/'
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:
98
 
            self._medium = medium
 
89
        super(RemoteTransport, self).__init__(url,
 
90
                                              _from_transport=_from_transport)
 
91
 
 
92
        # The medium is the connection, except when we need to share it with
 
93
        # other objects (RemoteBzrDir, RemoteRepository etc). In these cases
 
94
        # what we want to share is really the shared connection.
 
95
 
 
96
        if _from_transport is None:
 
97
            # If no _from_transport is specified, we need to intialize the
 
98
            # shared medium.
 
99
            credentials = None
 
100
            if medium is None:
 
101
                medium, credentials = self._build_medium()
 
102
                if 'hpss' in debug.debug_flags:
 
103
                    trace.mutter('hpss: Built a new medium: %s',
 
104
                                 medium.__class__.__name__)
 
105
            self._shared_connection = transport._SharedConnection(medium,
 
106
                                                                  credentials,
 
107
                                                                  self.base)
99
108
        else:
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
 
109
            if medium is None:
 
110
                # No medium was specified, so share the medium from the
 
111
                # _from_transport.
 
112
                medium = self._shared_connection.connection
 
113
 
106
114
        if _client is None:
107
 
            self._client = client._SmartClient(self._medium)
 
115
            self._client = client._SmartClient(medium, self.base)
108
116
        else:
109
117
            self._client = _client
110
118
 
111
 
    def abspath(self, relpath):
112
 
        """Return the full url to the given relative path.
113
 
        
114
 
        @param relpath: the relative path or path components
115
 
        @type relpath: str or list
116
 
        """
117
 
        return self._unparse_url(self._remote_path(relpath))
118
 
    
119
 
    def clone(self, relative_url):
120
 
        """Make a new RemoteTransport related to me, sharing the same connection.
 
119
    def _build_medium(self):
 
120
        """Create the medium if _from_transport does not provide one.
121
121
 
122
 
        This essentially opens a handle on a different remote directory.
 
122
        The medium is analogous to the connection for ConnectedTransport: it
 
123
        allows connection sharing.
123
124
        """
124
 
        if relative_url is None:
125
 
            return RemoteTransport(self.base, self)
126
 
        else:
127
 
            return RemoteTransport(self.abspath(relative_url), self)
 
125
        # No credentials
 
126
        return None, None
128
127
 
129
128
    def is_readonly(self):
130
129
        """Smart server transport can do read/write file operations."""
133
132
            return True
134
133
        elif resp == ('no', ):
135
134
            return False
136
 
        elif resp == ('error', "Generic bzr smart protocol error: "
137
 
                               "bad request 'Transport.is_readonly'"):
 
135
        elif (resp == ('error', "Generic bzr smart protocol error: "
 
136
                                "bad request 'Transport.is_readonly'") or
 
137
              resp == ('error', "Generic bzr smart protocol error: "
 
138
                                "bad request u'Transport.is_readonly'")):
138
139
            # XXX: nasty hack: servers before 0.16 don't have a
139
140
            # 'Transport.is_readonly' verb, so we do what clients before 0.16
140
141
            # did: assume False.
141
142
            return False
142
143
        else:
143
144
            self._translate_error(resp)
144
 
        assert False, 'weird response %r' % (resp,)
 
145
        raise errors.UnexpectedSmartServerResponse(resp)
145
146
 
146
147
    def get_smart_client(self):
147
 
        return self._medium
 
148
        return self._get_connection()
148
149
 
149
150
    def get_smart_medium(self):
150
 
        return self._medium
151
 
                                                   
152
 
    def _unparse_url(self, path):
153
 
        """Return URL for a path.
 
151
        return self._get_connection()
154
152
 
155
 
        :see: SFTPUrlHandling._unparse_url
156
 
        """
157
 
        # TODO: Eventually it should be possible to unify this with
158
 
        # SFTPUrlHandling._unparse_url?
159
 
        if path == '':
160
 
            path = '/'
161
 
        path = urllib.quote(path)
162
 
        netloc = urllib.quote(self._host)
163
 
        if self._username is not None:
164
 
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
165
 
        if self._port is not None:
166
 
            netloc = '%s:%d' % (netloc, self._port)
167
 
        return urlparse.urlunparse((self._scheme, netloc, path, '', '', ''))
 
153
    @deprecated_method(one_four)
 
154
    def get_shared_medium(self):
 
155
        return self._get_shared_connection()
168
156
 
169
157
    def _remote_path(self, relpath):
170
158
        """Returns the Unicode version of the absolute path for relpath."""
204
192
 
205
193
    def get_bytes(self, relpath):
206
194
        remote = self._remote_path(relpath)
207
 
        request = self._medium.get_request()
 
195
        request = self.get_smart_medium().get_request()
208
196
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
209
197
        smart_protocol.call('get', remote)
210
198
        resp = smart_protocol.read_response_tuple(True)
224
212
            self._serialise_optional_mode(mode))
225
213
        self._translate_error(resp)
226
214
 
 
215
    def open_write_stream(self, relpath, mode=None):
 
216
        """See Transport.open_write_stream."""
 
217
        self.put_bytes(relpath, "", mode)
 
218
        result = transport.AppendBasedFileStream(self, relpath)
 
219
        transport._file_streams[self.abspath(relpath)] = result
 
220
        return result
 
221
 
227
222
    def put_bytes(self, relpath, upload_contents, mode=None):
228
223
        # FIXME: upload_file is probably not safe for non-ascii characters -
229
224
        # should probably just pass all parameters as length-delimited
238
233
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
239
234
            upload_contents)
240
235
        self._translate_error(resp)
 
236
        return len(upload_contents)
241
237
 
242
238
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
243
239
                             create_parent_dir=False,
289
285
        resp = self._call2('delete', self._remote_path(relpath))
290
286
        self._translate_error(resp)
291
287
 
292
 
    def readv(self, relpath, offsets):
 
288
    def external_url(self):
 
289
        """See bzrlib.transport.Transport.external_url."""
 
290
        # the external path for RemoteTransports is the base
 
291
        return self.base
 
292
 
 
293
    def recommended_page_size(self):
 
294
        """Return the recommended page size for this transport."""
 
295
        return 64 * 1024
 
296
        
 
297
    def _readv(self, relpath, offsets):
293
298
        if not offsets:
294
299
            return
295
300
 
303
308
                               limit=self._max_readv_combine,
304
309
                               fudge_factor=self._bytes_to_read_before_seek))
305
310
 
306
 
        request = self._medium.get_request()
 
311
        request = self.get_smart_medium().get_request()
307
312
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
308
313
        smart_protocol.call_with_body_readv_array(
309
314
            ('readv', self._remote_path(relpath)),
387
392
                raise UnicodeEncodeError(encoding, val, start, end, reason)
388
393
        elif what == "ReadOnlyError":
389
394
            raise errors.TransportNotPossible('readonly transport')
 
395
        elif what == "ReadError":
 
396
            if orig_path is not None:
 
397
                error_path = orig_path
 
398
            else:
 
399
                error_path = resp[1]
 
400
            raise errors.ReadError(error_path)
 
401
        elif what == "PermissionDenied":
 
402
            if orig_path is not None:
 
403
                error_path = orig_path
 
404
            else:
 
405
                error_path = resp[1]
 
406
            raise errors.PermissionDenied(error_path)
390
407
        else:
391
408
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
392
409
 
393
410
    def disconnect(self):
394
 
        self._medium.disconnect()
 
411
        self.get_smart_medium().disconnect()
395
412
 
396
413
    def delete_tree(self, relpath):
397
414
        raise errors.TransportNotPossible('readonly transport')
441
458
        SmartTCPClientMedium).
442
459
    """
443
460
 
444
 
    def __init__(self, url):
445
 
        _scheme, _username, _password, _host, _port, _path = \
446
 
            transport.split_url(url)
447
 
        if _port is None:
448
 
            _port = BZR_DEFAULT_PORT
449
 
        else:
450
 
            try:
451
 
                _port = int(_port)
452
 
            except (ValueError, TypeError), e:
453
 
                raise errors.InvalidURL(
454
 
                    path=url, extra="invalid port %s" % _port)
455
 
        client_medium = medium.SmartTCPClientMedium(_host, _port)
456
 
        super(RemoteTCPTransport, self).__init__(url, medium=client_medium)
 
461
    def _build_medium(self):
 
462
        assert self.base.startswith('bzr://')
 
463
        return medium.SmartTCPClientMedium(self._host, self._port), None
457
464
 
458
465
 
459
466
class RemoteSSHTransport(RemoteTransport):
463
470
        SmartSSHClientMedium).
464
471
    """
465
472
 
466
 
    def __init__(self, url):
467
 
        _scheme, _username, _password, _host, _port, _path = \
468
 
            transport.split_url(url)
469
 
        try:
470
 
            if _port is not None:
471
 
                _port = int(_port)
472
 
        except (ValueError, TypeError), e:
473
 
            raise errors.InvalidURL(path=url, extra="invalid port %s" % 
474
 
                _port)
475
 
        client_medium = medium.SmartSSHClientMedium(_host, _port,
476
 
                                                    _username, _password)
477
 
        super(RemoteSSHTransport, self).__init__(url, medium=client_medium)
 
473
    def _build_medium(self):
 
474
        assert self.base.startswith('bzr+ssh://')
 
475
        # ssh will prompt the user for a password if needed and if none is
 
476
        # provided but it will not give it back, so no credentials can be
 
477
        # stored.
 
478
        location_config = config.LocationConfig(self.base)
 
479
        bzr_remote_path = location_config.get_bzr_remote_path()
 
480
        return medium.SmartSSHClientMedium(self._host, self._port,
 
481
            self._user, self._password, bzr_remote_path=bzr_remote_path), None
478
482
 
479
483
 
480
484
class RemoteHTTPTransport(RemoteTransport):
488
492
    HTTP path into a local path.
489
493
    """
490
494
 
491
 
    def __init__(self, url, http_transport=None):
492
 
        assert url.startswith('bzr+http://')
 
495
    def __init__(self, base, _from_transport=None, http_transport=None):
 
496
        assert ( base.startswith('bzr+http://') or base.startswith('bzr+https://') )
493
497
 
494
498
        if http_transport is None:
495
 
            http_url = url[len('bzr+'):]
 
499
            # FIXME: the password may be lost here because it appears in the
 
500
            # url only for an intial construction (when the url came from the
 
501
            # command-line).
 
502
            http_url = base[len('bzr+'):]
496
503
            self._http_transport = transport.get_transport(http_url)
497
504
        else:
498
505
            self._http_transport = http_transport
499
 
        http_medium = self._http_transport.get_smart_medium()
500
 
        super(RemoteHTTPTransport, self).__init__(url, medium=http_medium)
 
506
        super(RemoteHTTPTransport, self).__init__(
 
507
            base, _from_transport=_from_transport)
 
508
 
 
509
    def _build_medium(self):
 
510
        # We let http_transport take care of the credentials
 
511
        return self._http_transport.get_smart_medium(), None
501
512
 
502
513
    def _remote_path(self, relpath):
503
 
        """After connecting HTTP Transport only deals in relative URLs."""
 
514
        """After connecting, HTTP Transport only deals in relative URLs."""
504
515
        # Adjust the relpath based on which URL this smart transport is
505
516
        # connected to.
506
 
        base = urlutils.normalize_url(self._http_transport.base)
 
517
        http_base = urlutils.normalize_url(self.get_smart_medium().base)
507
518
        url = urlutils.join(self.base[len('bzr+'):], relpath)
508
519
        url = urlutils.normalize_url(url)
509
 
        return urlutils.relative_url(base, url)
510
 
 
511
 
    def abspath(self, relpath):
512
 
        """Return the full url to the given relative path.
513
 
        
514
 
        :param relpath: the relative path or path components
515
 
        :type relpath: str or list
516
 
        """
517
 
        return self._unparse_url(self._combine_paths(self._path, relpath))
 
520
        return urlutils.relative_url(http_base, url)
518
521
 
519
522
    def clone(self, relative_url):
520
523
        """Make a new RemoteHTTPTransport related to me.
528
531
        smart requests may be different).  This is so that the server doesn't
529
532
        have to handle .bzr/smart requests at arbitrary places inside .bzr
530
533
        directories, just at the initial URL the user uses.
531
 
 
532
 
        The exception is parent paths (i.e. relative_url of "..").
533
534
        """
534
535
        if relative_url:
535
536
            abs_url = self.abspath(relative_url)
536
537
        else:
537
538
            abs_url = self.base
538
 
        # We either use the exact same http_transport (for child locations), or
539
 
        # a clone of the underlying http_transport (for parent locations).  This
540
 
        # means we share the connection.
541
 
        norm_base = urlutils.normalize_url(self.base)
542
 
        norm_abs_url = urlutils.normalize_url(abs_url)
543
 
        normalized_rel_url = urlutils.relative_url(norm_base, norm_abs_url)
544
 
        if normalized_rel_url == ".." or normalized_rel_url.startswith("../"):
545
 
            http_transport = self._http_transport.clone(normalized_rel_url)
546
 
        else:
547
 
            http_transport = self._http_transport
548
 
        return RemoteHTTPTransport(abs_url, http_transport=http_transport)
 
539
        return RemoteHTTPTransport(abs_url,
 
540
                                   _from_transport=self,
 
541
                                   http_transport=self._http_transport)
549
542
 
550
543
 
551
544
def get_test_permutations():