392
429
raise NotImplementedError(self.external_url)
431
def get_segment_parameters(self):
432
"""Return the segment parameters for the top segment of the URL.
434
return self._segment_parameters
436
def set_segment_parameter(self, name, value):
437
"""Set a segment parameter.
439
:param name: Segment parameter name (urlencoded string)
440
:param value: Segment parameter value (urlencoded string)
444
del self._segment_parameters[name]
448
self._segment_parameters[name] = value
449
self.base = urlutils.join_segment_parameters(
450
self._raw_base, self._segment_parameters)
394
452
def _pump(self, from_file, to_file):
395
453
"""Most children will need to copy from one file-like
396
454
object or string to another one.
462
520
raise NotImplementedError(self.abspath)
464
def _combine_paths(self, base_path, relpath):
465
"""Transform a Transport-relative path to a remote absolute path.
467
This does not handle substitution of ~ but does handle '..' and '.'
472
t._combine_paths('/home/sarah', 'project/foo')
473
=> '/home/sarah/project/foo'
474
t._combine_paths('/home/sarah', '../../etc')
476
t._combine_paths('/home/sarah', '/etc')
479
:param base_path: urlencoded path for the transport root; typically a
480
URL but need not contain scheme/host/etc.
481
:param relpath: relative url string for relative part of remote path.
482
:return: urlencoded string for final path.
484
if not isinstance(relpath, str):
485
raise errors.InvalidURL(relpath)
486
if relpath.startswith('/'):
489
base_parts = base_path.split('/')
490
if len(base_parts) > 0 and base_parts[-1] == '':
491
base_parts = base_parts[:-1]
492
for p in relpath.split('/'):
494
if len(base_parts) == 0:
495
# In most filesystems, a request for the parent
496
# of root, just returns root.
503
path = '/'.join(base_parts)
504
if not path.startswith('/'):
508
522
def recommended_page_size(self):
509
523
"""Return the recommended page size for this transport.
694
710
# Cache the results, but only until they have been fulfilled
696
for c_offset in coalesced:
697
# TODO: jam 20060724 it might be faster to not issue seek if
698
# we are already at the right location. This should be
700
fp.seek(c_offset.start)
701
data = fp.read(c_offset.length)
702
if len(data) < c_offset.length:
703
raise errors.ShortReadvError(relpath, c_offset.start,
704
c_offset.length, actual=len(data))
705
for suboffset, subsize in c_offset.ranges:
706
key = (c_offset.start+suboffset, subsize)
707
data_map[key] = data[suboffset:suboffset+subsize]
713
for c_offset in coalesced:
714
# TODO: jam 20060724 it might be faster to not issue seek if
715
# we are already at the right location. This should be
717
fp.seek(c_offset.start)
718
data = fp.read(c_offset.length)
719
if len(data) < c_offset.length:
720
raise errors.ShortReadvError(relpath, c_offset.start,
721
c_offset.length, actual=len(data))
722
for suboffset, subsize in c_offset.ranges:
723
key = (c_offset.start+suboffset, subsize)
724
data_map[key] = data[suboffset:suboffset+subsize]
709
# Now that we've read some data, see if we can yield anything back
710
while cur_offset_and_size in data_map:
711
this_data = data_map.pop(cur_offset_and_size)
712
this_offset = cur_offset_and_size[0]
714
cur_offset_and_size = offset_stack.next()
715
except StopIteration:
716
# Close the file handle as there will be no more data
717
# The handle would normally be cleaned up as this code goes
718
# out of scope, but as we are a generator, not all code
719
# will re-enter once we have consumed all the expected
721
# zip(range(len(requests)), readv(foo, requests))
722
# Will stop because the range is done, and not run the
723
# cleanup code for the readv().
725
cur_offset_and_size = None
726
yield this_offset, this_data
726
# Now that we've read some data, see if we can yield anything back
727
while cur_offset_and_size in data_map:
728
this_data = data_map.pop(cur_offset_and_size)
729
this_offset = cur_offset_and_size[0]
731
cur_offset_and_size = offset_stack.next()
732
except StopIteration:
734
cur_offset_and_size = None
735
yield this_offset, this_data
728
739
def _sort_expand_and_combine(self, offsets, upper_limit):
729
740
"""Helper for readv.
862
873
yield self.get(relpath)
865
def put_bytes(self, relpath, bytes, mode=None):
876
def put_bytes(self, relpath, raw_bytes, mode=None):
866
877
"""Atomically put the supplied bytes into the given location.
868
879
:param relpath: The location to put the contents, relative to the
870
:param bytes: A bytestring of data.
881
:param raw_bytes: A bytestring of data.
871
882
:param mode: Create the file with the given mode.
874
if not isinstance(bytes, str):
875
raise AssertionError(
876
'bytes must be a plain string, not %s' % type(bytes))
877
return self.put_file(relpath, StringIO(bytes), mode=mode)
885
if not isinstance(raw_bytes, str):
887
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
888
return self.put_file(relpath, StringIO(raw_bytes), mode=mode)
879
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
890
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
880
891
create_parent_dir=False,
882
893
"""Copy the string into the target location.
1349
1360
if not base.endswith('/'):
1352
self._user, self._password,
1353
self._host, self._port,
1354
self._path) = self._split_url(base)
1362
self._parsed_url = self._split_url(base)
1355
1363
if _from_transport is not None:
1356
1364
# Copy the password as it does not appear in base and will be lost
1357
1365
# otherwise. It can appear in the _split_url above if the user
1358
1366
# provided it on the command line. Otherwise, daughter classes will
1359
1367
# prompt the user for one when appropriate.
1360
self._password = _from_transport._password
1368
self._parsed_url.password = _from_transport._parsed_url.password
1369
self._parsed_url.quoted_password = (
1370
_from_transport._parsed_url.quoted_password)
1362
base = self._unsplit_url(self._scheme,
1363
self._user, self._password,
1364
self._host, self._port,
1372
base = str(self._parsed_url)
1367
1374
super(ConnectedTransport, self).__init__(base)
1368
1375
if _from_transport is None:
1385
1416
def _split_url(url):
1386
return urlutils.parse_url(url)
1417
return urlutils.URL.from_string(url)
1389
1420
def _unsplit_url(scheme, user, password, host, port, path):
1391
Build the full URL for the given already URL encoded path.
1421
"""Build the full URL for the given already URL encoded path.
1393
1423
user, password, host and path will be quoted if they contain reserved
1396
1426
:param scheme: protocol
1398
1427
:param user: login
1400
1428
:param password: associated password
1402
1429
:param host: the server address
1404
1430
:param port: the associated port
1406
1431
:param path: the absolute path on the server
1408
1433
:return: The corresponding URL.
1410
netloc = urllib.quote(host)
1435
netloc = urlutils.quote(host)
1411
1436
if user is not None:
1412
1437
# Note that we don't put the password back even if we
1413
1438
# have one so that it doesn't get accidentally
1415
netloc = '%s@%s' % (urllib.quote(user), netloc)
1440
netloc = '%s@%s' % (urlutils.quote(user), netloc)
1416
1441
if port is not None:
1417
1442
netloc = '%s:%d' % (netloc, port)
1418
1443
path = urlutils.escape(path)
1421
1446
def relpath(self, abspath):
1422
1447
"""Return the local path portion from a given absolute path"""
1423
scheme, user, password, host, port, path = self._split_url(abspath)
1448
parsed_url = self._split_url(abspath)
1425
if (scheme != self._scheme):
1450
if parsed_url.scheme != self._parsed_url.scheme:
1426
1451
error.append('scheme mismatch')
1427
if (user != self._user):
1452
if parsed_url.user != self._parsed_url.user:
1428
1453
error.append('user name mismatch')
1429
if (host != self._host):
1454
if parsed_url.host != self._parsed_url.host:
1430
1455
error.append('host mismatch')
1431
if (port != self._port):
1456
if parsed_url.port != self._parsed_url.port:
1432
1457
error.append('port mismatch')
1433
if not (path == self._path[:-1] or path.startswith(self._path)):
1458
if (not (parsed_url.path == self._parsed_url.path[:-1] or
1459
parsed_url.path.startswith(self._parsed_url.path))):
1434
1460
error.append('path mismatch')
1436
1462
extra = ', '.join(error)
1437
1463
raise errors.PathNotChild(abspath, self.base, extra=extra)
1438
pl = len(self._path)
1439
return path[pl:].strip('/')
1464
pl = len(self._parsed_url.path)
1465
return parsed_url.path[pl:].strip('/')
1441
1467
def abspath(self, relpath):
1442
1468
"""Return the full url to the given relative path.
1564
1586
raise NotImplementedError(self.disconnect)
1567
def get_transport(base, possible_transports=None):
1568
"""Open a transport to access a URL or directory.
1570
:param base: either a URL or a directory name.
1572
:param transports: optional reusable transports list. If not None, created
1573
transports will be added to the list.
1575
:return: A new transport optionally sharing its connection with one of
1576
possible_transports.
1589
def location_to_url(location):
1590
"""Determine a fully qualified URL from a location string.
1592
This will try to interpret location as both a URL and a directory path. It
1593
will also lookup the location in directories.
1595
:param location: Unicode or byte string object with a location
1596
:raise InvalidURL: If the location is already a URL, but not valid.
1597
:return: Byte string with resulting URL
1599
if not isinstance(location, basestring):
1600
raise AssertionError("location not a byte or unicode string")
1581
1601
from bzrlib.directory_service import directories
1582
base = directories.dereference(base)
1584
def convert_path_to_url(base, error_str):
1585
if urlutils.is_url(base):
1586
# This looks like a URL, but we weren't able to
1587
# instantiate it as such raise an appropriate error
1588
# FIXME: we have a 'error_str' unused and we use last_err below
1589
raise errors.UnsupportedProtocol(base, last_err)
1590
# This doesn't look like a protocol, consider it a local path
1591
new_base = urlutils.local_path_to_url(base)
1592
# mutter('converting os path %r => url %s', base, new_base)
1602
location = directories.dereference(location)
1595
1604
# Catch any URLs which are passing Unicode rather than ASCII
1597
base = base.encode('ascii')
1606
location = location.encode('ascii')
1598
1607
except UnicodeError:
1599
# Only local paths can be Unicode
1600
base = convert_path_to_url(base,
1601
'URLs must be properly escaped (protocol: %s)')
1608
if urlutils.is_url(location):
1609
raise errors.InvalidURL(path=location,
1610
extra='URLs must be properly escaped')
1611
location = urlutils.local_path_to_url(location)
1613
if location.startswith("file:") and not location.startswith("file://"):
1614
return urlutils.join(urlutils.local_path_to_url("."), location[5:])
1616
if not urlutils.is_url(location):
1617
return urlutils.local_path_to_url(location)
1622
def get_transport_from_path(path, possible_transports=None):
1623
"""Open a transport for a local path.
1625
:param path: Local path as byte or unicode string
1626
:return: Transport object for path
1628
return get_transport_from_url(urlutils.local_path_to_url(path),
1629
possible_transports)
1632
def get_transport_from_url(url, possible_transports=None):
1633
"""Open a transport to access a URL.
1636
:param transports: optional reusable transports list. If not None, created
1637
transports will be added to the list.
1639
:return: A new transport optionally sharing its connection with one of
1640
possible_transports.
1603
1642
transport = None
1604
1643
if possible_transports is not None:
1605
1644
for t in possible_transports:
1606
t_same_connection = t._reuse_for(base)
1645
t_same_connection = t._reuse_for(url)
1607
1646
if t_same_connection is not None:
1608
1647
# Add only new transports
1609
1648
if t_same_connection not in possible_transports:
1610
1649
possible_transports.append(t_same_connection)
1611
1650
return t_same_connection
1613
1653
for proto, factory_list in transport_list_registry.items():
1614
if proto is not None and base.startswith(proto):
1615
transport, last_err = _try_transport_factories(base, factory_list)
1654
if proto is not None and url.startswith(proto):
1655
transport, last_err = _try_transport_factories(url, factory_list)
1617
1657
if possible_transports is not None:
1618
1658
if transport in possible_transports:
1619
1659
raise AssertionError()
1620
1660
possible_transports.append(transport)
1621
1661
return transport
1623
# We tried all the different protocols, now try one last time
1624
# as a local protocol
1625
base = convert_path_to_url(base, 'Unsupported protocol: %s')
1627
# The default handler is the filesystem handler, stored as protocol None
1628
factory_list = transport_list_registry.get(None)
1629
transport, last_err = _try_transport_factories(base, factory_list)
1662
if not urlutils.is_url(url):
1663
raise errors.InvalidURL(path=url)
1664
raise errors.UnsupportedProtocol(url, last_err)
1667
def get_transport(base, possible_transports=None):
1668
"""Open a transport to access a URL or directory.
1670
:param base: either a URL or a directory name.
1672
:param transports: optional reusable transports list. If not None, created
1673
transports will be added to the list.
1675
:return: A new transport optionally sharing its connection with one of
1676
possible_transports.
1680
return get_transport_from_url(location_to_url(base), possible_transports)
1634
1683
def _try_transport_factories(base, factory_list):
1735
1783
help="Read-only access of branches exported on the web.")
1736
1784
register_transport_proto('https://',
1737
1785
help="Read-only access of branches exported on the web using SSL.")
1738
# The default http implementation is urllib, but https is pycurl if available
1786
# The default http implementation is urllib
1739
1787
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
1740
1788
'PyCurlTransport')
1741
1789
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1742
1790
'HttpTransport_urllib')
1791
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1743
1793
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1744
1794
'HttpTransport_urllib')
1745
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1748
1796
register_transport_proto('ftp://', help="Access using passive FTP.")
1749
1797
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')