~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-01-03 18:09:01 UTC
  • mfrom: (3159.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20080103180901-w987y1ftqoh02qbm
(vila) Fix #179368 by keeping the current range hint on
        ShortReadvErrors

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
38
 
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
45
 
 
46
39
 
47
40
class _SmartStat(object):
48
41
 
51
44
        self.st_mode = mode
52
45
 
53
46
 
54
 
class RemoteTransport(transport.Transport):
 
47
class RemoteTransport(transport.ConnectedTransport):
55
48
    """Connection to a smart server.
56
49
 
57
50
    The connection holds references to the medium that can be used to send
74
67
    # RemoteTransport is an adapter from the Transport object model to the 
75
68
    # SmartClient model, not an encoder.
76
69
 
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):
78
75
        """Constructor.
79
76
 
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.
 
77
        :param _from_transport: Another RemoteTransport instance that this
 
78
            one is being cloned from.  Attributes such as the medium will
 
79
            be reused.
 
80
 
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.
 
83
 
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.
88
87
        """
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
99
 
        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
 
88
        super(RemoteTransport, self).__init__(url,
 
89
                                              _from_transport=_from_transport)
 
90
 
 
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.
 
94
 
 
95
        if _from_transport is None:
 
96
            # If no _from_transport is specified, we need to intialize the
 
97
            # shared medium.
 
98
            credentials = None
 
99
            if medium is None:
 
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,
 
105
                                                                  credentials)
 
106
 
106
107
        if _client is None:
107
 
            self._client = client._SmartClient(self._medium)
 
108
            self._client = client._SmartClient(self.get_shared_medium())
108
109
        else:
109
110
            self._client = _client
110
111
 
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.
 
112
    def _build_medium(self):
 
113
        """Create the medium if _from_transport does not provide one.
121
114
 
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.
123
117
        """
124
 
        if relative_url is None:
125
 
            return RemoteTransport(self.base, self)
126
 
        else:
127
 
            return RemoteTransport(self.abspath(relative_url), self)
 
118
        # No credentials
 
119
        return None, None
128
120
 
129
121
    def is_readonly(self):
130
122
        """Smart server transport can do read/write file operations."""
143
135
            return False
144
136
        else:
145
137
            self._translate_error(resp)
146
 
        assert False, 'weird response %r' % (resp,)
 
138
        raise errors.UnexpectedSmartServerResponse(resp)
147
139
 
148
140
    def get_smart_client(self):
149
 
        return self._medium
 
141
        return self._get_connection()
150
142
 
151
143
    def get_smart_medium(self):
152
 
        return self._medium
153
 
                                                   
154
 
    def _unparse_url(self, path):
155
 
        """Return URL for a path.
 
144
        return self._get_connection()
156
145
 
157
 
        :see: SFTPUrlHandling._unparse_url
158
 
        """
159
 
        # TODO: Eventually it should be possible to unify this with
160
 
        # SFTPUrlHandling._unparse_url?
161
 
        if path == '':
162
 
            path = '/'
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()
170
148
 
171
149
    def _remote_path(self, relpath):
172
150
        """Returns the Unicode version of the absolute path for relpath."""
206
184
 
207
185
    def get_bytes(self, relpath):
208
186
        remote = self._remote_path(relpath)
209
 
        request = self._medium.get_request()
 
187
        request = self.get_smart_medium().get_request()
210
188
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
211
189
        smart_protocol.call('get', remote)
212
190
        resp = smart_protocol.read_response_tuple(True)
226
204
            self._serialise_optional_mode(mode))
227
205
        self._translate_error(resp)
228
206
 
 
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
 
212
        return result
 
213
 
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
240
225
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
241
226
            upload_contents)
242
227
        self._translate_error(resp)
 
228
        return len(upload_contents)
243
229
 
244
230
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
245
231
                             create_parent_dir=False,
291
277
        resp = self._call2('delete', self._remote_path(relpath))
292
278
        self._translate_error(resp)
293
279
 
294
 
    def readv(self, relpath, offsets):
 
280
    def external_url(self):
 
281
        """See bzrlib.transport.Transport.external_url."""
 
282
        # the external path for RemoteTransports is the base
 
283
        return self.base
 
284
 
 
285
    def _readv(self, relpath, offsets):
295
286
        if not offsets:
296
287
            return
297
288
 
305
296
                               limit=self._max_readv_combine,
306
297
                               fudge_factor=self._bytes_to_read_before_seek))
307
298
 
308
 
        request = self._medium.get_request()
 
299
        request = self.get_smart_medium().get_request()
309
300
        smart_protocol = protocol.SmartClientRequestProtocolOne(request)
310
301
        smart_protocol.call_with_body_readv_array(
311
302
            ('readv', self._remote_path(relpath)),
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
 
386
            else:
 
387
                error_path = resp[1]
 
388
            raise errors.ReadError(error_path)
392
389
        else:
393
390
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
394
391
 
395
392
    def disconnect(self):
396
 
        self._medium.disconnect()
 
393
        self.get_smart_medium().disconnect()
397
394
 
398
395
    def delete_tree(self, relpath):
399
396
        raise errors.TransportNotPossible('readonly transport')
443
440
        SmartTCPClientMedium).
444
441
    """
445
442
 
446
 
    def __init__(self, url):
447
 
        _scheme, _username, _password, _host, _port, _path = \
448
 
            transport.split_url(url)
449
 
        if _port is None:
450
 
            _port = BZR_DEFAULT_PORT
451
 
        else:
452
 
            try:
453
 
                _port = int(_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
459
446
 
460
447
 
461
448
class RemoteSSHTransport(RemoteTransport):
465
452
        SmartSSHClientMedium).
466
453
    """
467
454
 
468
 
    def __init__(self, url):
469
 
        _scheme, _username, _password, _host, _port, _path = \
470
 
            transport.split_url(url)
471
 
        try:
472
 
            if _port is not None:
473
 
                _port = int(_port)
474
 
        except (ValueError, TypeError), e:
475
 
            raise errors.InvalidURL(path=url, extra="invalid port %s" % 
476
 
                _port)
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
 
459
        # stored.
 
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
480
464
 
481
465
 
482
466
class RemoteHTTPTransport(RemoteTransport):
490
474
    HTTP path into a local path.
491
475
    """
492
476
 
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://') )
495
479
 
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
 
483
            # command-line).
 
484
            http_url = base[len('bzr+'):]
498
485
            self._http_transport = transport.get_transport(http_url)
499
486
        else:
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)
 
490
 
 
491
    def _build_medium(self):
 
492
        # We let http_transport take care of the credentials
 
493
        return self._http_transport.get_smart_medium(), None
503
494
 
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
507
498
        # connected to.
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)
512
 
 
513
 
    def abspath(self, relpath):
514
 
        """Return the full url to the given relative path.
515
 
        
516
 
        :param relpath: the relative path or path components
517
 
        :type relpath: str or list
518
 
        """
519
 
        return self._unparse_url(self._combine_paths(self._path, relpath))
 
502
        return urlutils.relative_url(http_base, url)
520
503
 
521
504
    def clone(self, relative_url):
522
505
        """Make a new RemoteHTTPTransport related to me.
547
530
            http_transport = self._http_transport.clone(normalized_rel_url)
548
531
        else:
549
532
            http_transport = self._http_transport
550
 
        return RemoteHTTPTransport(abs_url, http_transport=http_transport)
 
533
        return RemoteHTTPTransport(abs_url,
 
534
                                   _from_transport=self,
 
535
                                   http_transport=http_transport)
551
536
 
552
537
 
553
538
def get_test_permutations():