~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: Vincent Ladeuil
  • Date: 2011-11-24 15:48:29 UTC
  • mfrom: (6289 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6337.
  • Revision ID: v.ladeuil+lp@free.fr-20111124154829-avowjpsxdl8yp2vz
merge trunk resolving conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
"""
28
28
 
29
29
from cStringIO import StringIO
30
 
import os
31
30
import sys
32
31
 
33
32
from bzrlib.lazy_import import lazy_import
119
118
    def register_transport(self, key, help=None):
120
119
        self.register(key, [], help)
121
120
 
122
 
    def set_default_transport(self, key=None):
123
 
        """Return either 'key' or the default key if key is None"""
124
 
        self._default_key = key
125
 
 
126
121
 
127
122
transport_list_registry = TransportListRegistry()
128
123
 
315
310
    def __init__(self, base):
316
311
        super(Transport, self).__init__()
317
312
        self.base = base
 
313
        (self._raw_base, self._segment_parameters) = (
 
314
            urlutils.split_segment_parameters(base))
318
315
 
319
316
    def _translate_error(self, e, path, raise_generic=True):
320
317
        """Translate an IOError or OSError into an appropriate bzr error.
414
411
        """
415
412
        raise NotImplementedError(self.external_url)
416
413
 
 
414
    def get_segment_parameters(self):
 
415
        """Return the segment parameters for the top segment of the URL.
 
416
        """
 
417
        return self._segment_parameters
 
418
 
 
419
    def set_segment_parameter(self, name, value):
 
420
        """Set a segment parameter.
 
421
 
 
422
        :param name: Segment parameter name (urlencoded string)
 
423
        :param value: Segment parameter value (urlencoded string)
 
424
        """
 
425
        if value is None:
 
426
            try:
 
427
                del self._segment_parameters[name]
 
428
            except KeyError:
 
429
                pass
 
430
        else:
 
431
            self._segment_parameters[name] = value
 
432
        self.base = urlutils.join_segment_parameters(
 
433
            self._raw_base, self._segment_parameters)
 
434
 
417
435
    def _pump(self, from_file, to_file):
418
436
        """Most children will need to copy from one file-like
419
437
        object or string to another one.
484
502
        # interface ?
485
503
        raise NotImplementedError(self.abspath)
486
504
 
487
 
    def _combine_paths(self, base_path, relpath):
488
 
        """Transform a Transport-relative path to a remote absolute path.
489
 
 
490
 
        This does not handle substitution of ~ but does handle '..' and '.'
491
 
        components.
492
 
 
493
 
        Examples::
494
 
 
495
 
            t._combine_paths('/home/sarah', 'project/foo')
496
 
                => '/home/sarah/project/foo'
497
 
            t._combine_paths('/home/sarah', '../../etc')
498
 
                => '/etc'
499
 
            t._combine_paths('/home/sarah', '/etc')
500
 
                => '/etc'
501
 
 
502
 
        :param base_path: urlencoded path for the transport root; typically a
503
 
             URL but need not contain scheme/host/etc.
504
 
        :param relpath: relative url string for relative part of remote path.
505
 
        :return: urlencoded string for final path.
506
 
        """
507
 
        if not isinstance(relpath, str):
508
 
            raise errors.InvalidURL(relpath)
509
 
        if relpath.startswith('/'):
510
 
            base_parts = []
511
 
        else:
512
 
            base_parts = base_path.split('/')
513
 
        if len(base_parts) > 0 and base_parts[-1] == '':
514
 
            base_parts = base_parts[:-1]
515
 
        for p in relpath.split('/'):
516
 
            if p == '..':
517
 
                if len(base_parts) == 0:
518
 
                    # In most filesystems, a request for the parent
519
 
                    # of root, just returns root.
520
 
                    continue
521
 
                base_parts.pop()
522
 
            elif p == '.':
523
 
                continue # No-op
524
 
            elif p != '':
525
 
                base_parts.append(p)
526
 
        path = '/'.join(base_parts)
527
 
        if not path.startswith('/'):
528
 
            path = '/' + path
529
 
        return path
530
 
 
531
505
    def recommended_page_size(self):
532
506
        """Return the recommended page size for this transport.
533
507
 
1368
1342
        """
1369
1343
        if not base.endswith('/'):
1370
1344
            base += '/'
1371
 
        (self._scheme,
1372
 
         self._user, self._password,
1373
 
         self._host, self._port,
1374
 
         self._path) = self._split_url(base)
 
1345
        self._parsed_url = self._split_url(base)
1375
1346
        if _from_transport is not None:
1376
1347
            # Copy the password as it does not appear in base and will be lost
1377
1348
            # otherwise. It can appear in the _split_url above if the user
1378
1349
            # provided it on the command line. Otherwise, daughter classes will
1379
1350
            # prompt the user for one when appropriate.
1380
 
            self._password = _from_transport._password
 
1351
            self._parsed_url.password = _from_transport._parsed_url.password
 
1352
            self._parsed_url.quoted_password = (
 
1353
                _from_transport._parsed_url.quoted_password)
1381
1354
 
1382
 
        base = self._unsplit_url(self._scheme,
1383
 
                                 self._user, self._password,
1384
 
                                 self._host, self._port,
1385
 
                                 self._path)
 
1355
        base = str(self._parsed_url)
1386
1356
 
1387
1357
        super(ConnectedTransport, self).__init__(base)
1388
1358
        if _from_transport is None:
1390
1360
        else:
1391
1361
            self._shared_connection = _from_transport._shared_connection
1392
1362
 
 
1363
    @property
 
1364
    def _user(self):
 
1365
        return self._parsed_url.user
 
1366
 
 
1367
    @property
 
1368
    def _password(self):
 
1369
        return self._parsed_url.password
 
1370
 
 
1371
    @property
 
1372
    def _host(self):
 
1373
        return self._parsed_url.host
 
1374
 
 
1375
    @property
 
1376
    def _port(self):
 
1377
        return self._parsed_url.port
 
1378
 
 
1379
    @property
 
1380
    def _path(self):
 
1381
        return self._parsed_url.path
 
1382
 
 
1383
    @property
 
1384
    def _scheme(self):
 
1385
        return self._parsed_url.scheme
 
1386
 
1393
1387
    def clone(self, offset=None):
1394
1388
        """Return a new transport with root at self.base + offset
1395
1389
 
1403
1397
 
1404
1398
    @staticmethod
1405
1399
    def _split_url(url):
1406
 
        return urlutils.parse_url(url)
 
1400
        return urlutils.URL.from_string(url)
1407
1401
 
1408
1402
    @staticmethod
1409
1403
    def _unsplit_url(scheme, user, password, host, port, path):
1410
 
        """
1411
 
        Build the full URL for the given already URL encoded path.
 
1404
        """Build the full URL for the given already URL encoded path.
1412
1405
 
1413
1406
        user, password, host and path will be quoted if they contain reserved
1414
1407
        chars.
1415
1408
 
1416
1409
        :param scheme: protocol
1417
 
 
1418
1410
        :param user: login
1419
 
 
1420
1411
        :param password: associated password
1421
 
 
1422
1412
        :param host: the server address
1423
 
 
1424
1413
        :param port: the associated port
1425
 
 
1426
1414
        :param path: the absolute path on the server
1427
1415
 
1428
1416
        :return: The corresponding URL.
1440
1428
 
1441
1429
    def relpath(self, abspath):
1442
1430
        """Return the local path portion from a given absolute path"""
1443
 
        scheme, user, password, host, port, path = self._split_url(abspath)
 
1431
        parsed_url = self._split_url(abspath)
1444
1432
        error = []
1445
 
        if (scheme != self._scheme):
 
1433
        if parsed_url.scheme != self._parsed_url.scheme:
1446
1434
            error.append('scheme mismatch')
1447
 
        if (user != self._user):
 
1435
        if parsed_url.user != self._parsed_url.user:
1448
1436
            error.append('user name mismatch')
1449
 
        if (host != self._host):
 
1437
        if parsed_url.host != self._parsed_url.host:
1450
1438
            error.append('host mismatch')
1451
 
        if (port != self._port):
 
1439
        if parsed_url.port != self._parsed_url.port:
1452
1440
            error.append('port mismatch')
1453
 
        if not (path == self._path[:-1] or path.startswith(self._path)):
 
1441
        if (not (parsed_url.path == self._parsed_url.path[:-1] or
 
1442
            parsed_url.path.startswith(self._parsed_url.path))):
1454
1443
            error.append('path mismatch')
1455
1444
        if error:
1456
1445
            extra = ', '.join(error)
1457
1446
            raise errors.PathNotChild(abspath, self.base, extra=extra)
1458
 
        pl = len(self._path)
1459
 
        return path[pl:].strip('/')
 
1447
        pl = len(self._parsed_url.path)
 
1448
        return parsed_url.path[pl:].strip('/')
1460
1449
 
1461
1450
    def abspath(self, relpath):
1462
1451
        """Return the full url to the given relative path.
1465
1454
 
1466
1455
        :returns: the Unicode version of the absolute path for relpath.
1467
1456
        """
1468
 
        relative = urlutils.unescape(relpath).encode('utf-8')
1469
 
        path = self._combine_paths(self._path, relative)
1470
 
        return self._unsplit_url(self._scheme, self._user, self._password,
1471
 
                                 self._host, self._port,
1472
 
                                 path)
 
1457
        return str(self._parsed_url.clone(relpath))
1473
1458
 
1474
1459
    def _remote_path(self, relpath):
1475
1460
        """Return the absolute path part of the url to the given relative path.
1482
1467
 
1483
1468
        :return: the absolute Unicode path on the server,
1484
1469
        """
1485
 
        relative = urlutils.unescape(relpath).encode('utf-8')
1486
 
        remote_path = self._combine_paths(self._path, relative)
1487
 
        return remote_path
 
1470
        return self._parsed_url.clone(relpath).path
1488
1471
 
1489
1472
    def _get_shared_connection(self):
1490
1473
        """Get the object shared amongst cloned transports.
1549
1532
        :return: A new transport or None if the connection cannot be shared.
1550
1533
        """
1551
1534
        try:
1552
 
            (scheme, user, password,
1553
 
             host, port, path) = self._split_url(other_base)
 
1535
            parsed_url = self._split_url(other_base)
1554
1536
        except errors.InvalidURL:
1555
1537
            # No hope in trying to reuse an existing transport for an invalid
1556
1538
            # URL
1559
1541
        transport = None
1560
1542
        # Don't compare passwords, they may be absent from other_base or from
1561
1543
        # self and they don't carry more information than user anyway.
1562
 
        if (scheme == self._scheme
1563
 
            and user == self._user
1564
 
            and host == self._host
1565
 
            and port == self._port):
 
1544
        if (parsed_url.scheme == self._parsed_url.scheme
 
1545
            and parsed_url.user == self._parsed_url.user
 
1546
            and parsed_url.host == self._parsed_url.host
 
1547
            and parsed_url.port == self._parsed_url.port):
 
1548
            path = parsed_url.path
1566
1549
            if not path.endswith('/'):
1567
1550
                # This normally occurs at __init__ time, but it's easier to do
1568
1551
                # it now to avoid creating two transports for the same base.
1569
1552
                path += '/'
1570
 
            if self._path  == path:
 
1553
            if self._parsed_url.path  == path:
1571
1554
                # shortcut, it's really the same transport
1572
1555
                return self
1573
1556
            # We don't call clone here because the intent is different: we
1584
1567
        raise NotImplementedError(self.disconnect)
1585
1568
 
1586
1569
 
1587
 
def get_transport(base, possible_transports=None):
1588
 
    """Open a transport to access a URL or directory.
1589
 
 
1590
 
    :param base: either a URL or a directory name.
1591
 
 
1592
 
    :param transports: optional reusable transports list. If not None, created
1593
 
        transports will be added to the list.
1594
 
 
1595
 
    :return: A new transport optionally sharing its connection with one of
1596
 
        possible_transports.
 
1570
def location_to_url(location):
 
1571
    """Determine a fully qualified URL from a location string.
 
1572
 
 
1573
    This will try to interpret location as both a URL and a directory path. It
 
1574
    will also lookup the location in directories.
 
1575
 
 
1576
    :param location: Unicode or byte string object with a location
 
1577
    :raise InvalidURL: If the location is already a URL, but not valid.
 
1578
    :return: Byte string with resulting URL
1597
1579
    """
1598
 
    if base is None:
1599
 
        base = '.'
1600
 
    last_err = None
 
1580
    if not isinstance(location, basestring):
 
1581
        raise AssertionError("location not a byte or unicode string")
1601
1582
    from bzrlib.directory_service import directories
1602
 
    base = directories.dereference(base)
1603
 
 
1604
 
    def convert_path_to_url(base, error_str):
1605
 
        if urlutils.is_url(base):
1606
 
            # This looks like a URL, but we weren't able to
1607
 
            # instantiate it as such raise an appropriate error
1608
 
            # FIXME: we have a 'error_str' unused and we use last_err below
1609
 
            raise errors.UnsupportedProtocol(base, last_err)
1610
 
        # This doesn't look like a protocol, consider it a local path
1611
 
        new_base = urlutils.local_path_to_url(base)
1612
 
        # mutter('converting os path %r => url %s', base, new_base)
1613
 
        return new_base
 
1583
    location = directories.dereference(location)
1614
1584
 
1615
1585
    # Catch any URLs which are passing Unicode rather than ASCII
1616
1586
    try:
1617
 
        base = base.encode('ascii')
 
1587
        location = location.encode('ascii')
1618
1588
    except UnicodeError:
1619
 
        # Only local paths can be Unicode
1620
 
        base = convert_path_to_url(base,
1621
 
            'URLs must be properly escaped (protocol: %s)')
1622
 
 
 
1589
        if urlutils.is_url(location):
 
1590
            raise errors.InvalidURL(path=location,
 
1591
                extra='URLs must be properly escaped')
 
1592
        location = urlutils.local_path_to_url(location)
 
1593
 
 
1594
    if location.startswith("file:") and not location.startswith("file://"):
 
1595
        return urlutils.join(urlutils.local_path_to_url("."), location[5:])
 
1596
 
 
1597
    if not urlutils.is_url(location):
 
1598
        return urlutils.local_path_to_url(location)
 
1599
 
 
1600
    return location
 
1601
 
 
1602
 
 
1603
def get_transport_from_path(path, possible_transports=None):
 
1604
    """Open a transport for a local path.
 
1605
 
 
1606
    :param path: Local path as byte or unicode string
 
1607
    :return: Transport object for path
 
1608
    """
 
1609
    return get_transport_from_url(urlutils.local_path_to_url(path),
 
1610
        possible_transports)
 
1611
 
 
1612
 
 
1613
def get_transport_from_url(url, possible_transports=None):
 
1614
    """Open a transport to access a URL.
 
1615
    
 
1616
    :param base: a URL
 
1617
    :param transports: optional reusable transports list. If not None, created
 
1618
        transports will be added to the list.
 
1619
 
 
1620
    :return: A new transport optionally sharing its connection with one of
 
1621
        possible_transports.
 
1622
    """
1623
1623
    transport = None
1624
1624
    if possible_transports is not None:
1625
1625
        for t in possible_transports:
1626
 
            t_same_connection = t._reuse_for(base)
 
1626
            t_same_connection = t._reuse_for(url)
1627
1627
            if t_same_connection is not None:
1628
1628
                # Add only new transports
1629
1629
                if t_same_connection not in possible_transports:
1630
1630
                    possible_transports.append(t_same_connection)
1631
1631
                return t_same_connection
1632
1632
 
 
1633
    last_err = None
1633
1634
    for proto, factory_list in transport_list_registry.items():
1634
 
        if proto is not None and base.startswith(proto):
1635
 
            transport, last_err = _try_transport_factories(base, factory_list)
 
1635
        if proto is not None and url.startswith(proto):
 
1636
            transport, last_err = _try_transport_factories(url, factory_list)
1636
1637
            if transport:
1637
1638
                if possible_transports is not None:
1638
1639
                    if transport in possible_transports:
1639
1640
                        raise AssertionError()
1640
1641
                    possible_transports.append(transport)
1641
1642
                return transport
1642
 
 
1643
 
    # We tried all the different protocols, now try one last time
1644
 
    # as a local protocol
1645
 
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
1646
 
 
1647
 
    # The default handler is the filesystem handler, stored as protocol None
1648
 
    factory_list = transport_list_registry.get(None)
1649
 
    transport, last_err = _try_transport_factories(base, factory_list)
1650
 
 
1651
 
    return transport
 
1643
    if not urlutils.is_url(url):
 
1644
        raise errors.InvalidURL(path=url)
 
1645
    raise errors.UnsupportedProtocol(url, last_err)
 
1646
 
 
1647
 
 
1648
def get_transport(base, possible_transports=None):
 
1649
    """Open a transport to access a URL or directory.
 
1650
 
 
1651
    :param base: either a URL or a directory name.
 
1652
 
 
1653
    :param transports: optional reusable transports list. If not None, created
 
1654
        transports will be added to the list.
 
1655
 
 
1656
    :return: A new transport optionally sharing its connection with one of
 
1657
        possible_transports.
 
1658
    """
 
1659
    if base is None:
 
1660
        base = '.'
 
1661
    return get_transport_from_url(location_to_url(base), possible_transports)
1652
1662
 
1653
1663
 
1654
1664
def _try_transport_factories(base, factory_list):
1723
1733
register_transport_proto('file://',
1724
1734
            help="Access using the standard filesystem (default)")
1725
1735
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
1726
 
transport_list_registry.set_default_transport("file://")
1727
1736
 
1728
1737
register_transport_proto('sftp://',
1729
1738
            help="Access using SFTP (most SSH servers provide SFTP).",