407
427
raise NotImplementedError(self.external_url)
429
def get_segment_parameters(self):
430
"""Return the segment parameters for the top segment of the URL.
432
return self._segment_parameters
434
def set_segment_parameter(self, name, value):
435
"""Set a segment parameter.
437
:param name: Segment parameter name (urlencoded string)
438
:param value: Segment parameter value (urlencoded string)
442
del self._segment_parameters[name]
446
self._segment_parameters[name] = value
447
self.base = urlutils.join_segment_parameters(
448
self._raw_base, self._segment_parameters)
409
450
def _pump(self, from_file, to_file):
410
451
"""Most children will need to copy from one file-like
411
452
object or string to another one.
477
518
raise NotImplementedError(self.abspath)
479
def _combine_paths(self, base_path, relpath):
480
"""Transform a Transport-relative path to a remote absolute path.
482
This does not handle substitution of ~ but does handle '..' and '.'
487
t._combine_paths('/home/sarah', 'project/foo')
488
=> '/home/sarah/project/foo'
489
t._combine_paths('/home/sarah', '../../etc')
491
t._combine_paths('/home/sarah', '/etc')
494
:param base_path: urlencoded path for the transport root; typically a
495
URL but need not contain scheme/host/etc.
496
:param relpath: relative url string for relative part of remote path.
497
:return: urlencoded string for final path.
499
if not isinstance(relpath, str):
500
raise errors.InvalidURL(relpath)
501
if relpath.startswith('/'):
504
base_parts = base_path.split('/')
505
if len(base_parts) > 0 and base_parts[-1] == '':
506
base_parts = base_parts[:-1]
507
for p in relpath.split('/'):
509
if len(base_parts) == 0:
510
# In most filesystems, a request for the parent
511
# of root, just returns root.
518
path = '/'.join(base_parts)
519
if not path.startswith('/'):
523
520
def recommended_page_size(self):
524
521
"""Return the recommended page size for this transport.
709
708
# Cache the results, but only until they have been fulfilled
711
for c_offset in coalesced:
712
# TODO: jam 20060724 it might be faster to not issue seek if
713
# we are already at the right location. This should be
715
fp.seek(c_offset.start)
716
data = fp.read(c_offset.length)
717
if len(data) < c_offset.length:
718
raise errors.ShortReadvError(relpath, c_offset.start,
719
c_offset.length, actual=len(data))
720
for suboffset, subsize in c_offset.ranges:
721
key = (c_offset.start+suboffset, subsize)
722
data_map[key] = data[suboffset:suboffset+subsize]
711
for c_offset in coalesced:
712
# TODO: jam 20060724 it might be faster to not issue seek if
713
# we are already at the right location. This should be
715
fp.seek(c_offset.start)
716
data = fp.read(c_offset.length)
717
if len(data) < c_offset.length:
718
raise errors.ShortReadvError(relpath, c_offset.start,
719
c_offset.length, actual=len(data))
720
for suboffset, subsize in c_offset.ranges:
721
key = (c_offset.start+suboffset, subsize)
722
data_map[key] = data[suboffset:suboffset+subsize]
724
# Now that we've read some data, see if we can yield anything back
725
while cur_offset_and_size in data_map:
726
this_data = data_map.pop(cur_offset_and_size)
727
this_offset = cur_offset_and_size[0]
729
cur_offset_and_size = offset_stack.next()
730
except StopIteration:
731
# Close the file handle as there will be no more data
732
# The handle would normally be cleaned up as this code goes
733
# out of scope, but as we are a generator, not all code
734
# will re-enter once we have consumed all the expected
736
# zip(range(len(requests)), readv(foo, requests))
737
# Will stop because the range is done, and not run the
738
# cleanup code for the readv().
740
cur_offset_and_size = None
741
yield this_offset, this_data
724
# Now that we've read some data, see if we can yield anything back
725
while cur_offset_and_size in data_map:
726
this_data = data_map.pop(cur_offset_and_size)
727
this_offset = cur_offset_and_size[0]
729
cur_offset_and_size = offset_stack.next()
730
except StopIteration:
732
cur_offset_and_size = None
733
yield this_offset, this_data
743
737
def _sort_expand_and_combine(self, offsets, upper_limit):
744
738
"""Helper for readv.
1364
1358
if not base.endswith('/'):
1367
self._user, self._password,
1368
self._host, self._port,
1369
self._path) = self._split_url(base)
1360
self._parsed_url = self._split_url(base)
1370
1361
if _from_transport is not None:
1371
1362
# Copy the password as it does not appear in base and will be lost
1372
1363
# otherwise. It can appear in the _split_url above if the user
1373
1364
# provided it on the command line. Otherwise, daughter classes will
1374
1365
# prompt the user for one when appropriate.
1375
self._password = _from_transport._password
1366
self._parsed_url.password = _from_transport._parsed_url.password
1367
self._parsed_url.quoted_password = (
1368
_from_transport._parsed_url.quoted_password)
1377
base = self._unsplit_url(self._scheme,
1378
self._user, self._password,
1379
self._host, self._port,
1370
base = str(self._parsed_url)
1382
1372
super(ConnectedTransport, self).__init__(base)
1383
1373
if _from_transport is None:
1436
1444
def relpath(self, abspath):
1437
1445
"""Return the local path portion from a given absolute path"""
1438
scheme, user, password, host, port, path = self._split_url(abspath)
1446
parsed_url = self._split_url(abspath)
1440
if (scheme != self._scheme):
1448
if parsed_url.scheme != self._parsed_url.scheme:
1441
1449
error.append('scheme mismatch')
1442
if (user != self._user):
1450
if parsed_url.user != self._parsed_url.user:
1443
1451
error.append('user name mismatch')
1444
if (host != self._host):
1452
if parsed_url.host != self._parsed_url.host:
1445
1453
error.append('host mismatch')
1446
if (port != self._port):
1454
if parsed_url.port != self._parsed_url.port:
1447
1455
error.append('port mismatch')
1448
if not (path == self._path[:-1] or path.startswith(self._path)):
1456
if (not (parsed_url.path == self._parsed_url.path[:-1] or
1457
parsed_url.path.startswith(self._parsed_url.path))):
1449
1458
error.append('path mismatch')
1451
1460
extra = ', '.join(error)
1452
1461
raise errors.PathNotChild(abspath, self.base, extra=extra)
1453
pl = len(self._path)
1454
return path[pl:].strip('/')
1462
pl = len(self._parsed_url.path)
1463
return parsed_url.path[pl:].strip('/')
1456
1465
def abspath(self, relpath):
1457
1466
"""Return the full url to the given relative path.
1581
1584
raise NotImplementedError(self.disconnect)
1584
def get_transport(base, possible_transports=None):
1585
"""Open a transport to access a URL or directory.
1587
:param base: either a URL or a directory name.
1589
:param transports: optional reusable transports list. If not None, created
1590
transports will be added to the list.
1592
:return: A new transport optionally sharing its connection with one of
1593
possible_transports.
1587
def location_to_url(location):
1588
"""Determine a fully qualified URL from a location string.
1590
This will try to interpret location as both a URL and a directory path. It
1591
will also lookup the location in directories.
1593
:param location: Unicode or byte string object with a location
1594
:raise InvalidURL: If the location is already a URL, but not valid.
1595
:return: Byte string with resulting URL
1597
if not isinstance(location, basestring):
1598
raise AssertionError("location not a byte or unicode string")
1598
1599
from bzrlib.directory_service import directories
1599
base = directories.dereference(base)
1601
def convert_path_to_url(base, error_str):
1602
if urlutils.is_url(base):
1603
# This looks like a URL, but we weren't able to
1604
# instantiate it as such raise an appropriate error
1605
# FIXME: we have a 'error_str' unused and we use last_err below
1606
raise errors.UnsupportedProtocol(base, last_err)
1607
# This doesn't look like a protocol, consider it a local path
1608
new_base = urlutils.local_path_to_url(base)
1609
# mutter('converting os path %r => url %s', base, new_base)
1600
location = directories.dereference(location)
1612
1602
# Catch any URLs which are passing Unicode rather than ASCII
1614
base = base.encode('ascii')
1604
location = location.encode('ascii')
1615
1605
except UnicodeError:
1616
# Only local paths can be Unicode
1617
base = convert_path_to_url(base,
1618
'URLs must be properly escaped (protocol: %s)')
1606
if urlutils.is_url(location):
1607
raise errors.InvalidURL(path=location,
1608
extra='URLs must be properly escaped')
1609
location = urlutils.local_path_to_url(location)
1611
if location.startswith("file:") and not location.startswith("file://"):
1612
return urlutils.join(urlutils.local_path_to_url("."), location[5:])
1614
if not urlutils.is_url(location):
1615
return urlutils.local_path_to_url(location)
1620
def get_transport_from_path(path, possible_transports=None):
1621
"""Open a transport for a local path.
1623
:param path: Local path as byte or unicode string
1624
:return: Transport object for path
1626
return get_transport_from_url(urlutils.local_path_to_url(path),
1627
possible_transports)
1630
def get_transport_from_url(url, possible_transports=None):
1631
"""Open a transport to access a URL.
1634
:param transports: optional reusable transports list. If not None, created
1635
transports will be added to the list.
1637
:return: A new transport optionally sharing its connection with one of
1638
possible_transports.
1620
1640
transport = None
1621
1641
if possible_transports is not None:
1622
1642
for t in possible_transports:
1623
t_same_connection = t._reuse_for(base)
1643
t_same_connection = t._reuse_for(url)
1624
1644
if t_same_connection is not None:
1625
1645
# Add only new transports
1626
1646
if t_same_connection not in possible_transports:
1627
1647
possible_transports.append(t_same_connection)
1628
1648
return t_same_connection
1630
1651
for proto, factory_list in transport_list_registry.items():
1631
if proto is not None and base.startswith(proto):
1632
transport, last_err = _try_transport_factories(base, factory_list)
1652
if proto is not None and url.startswith(proto):
1653
transport, last_err = _try_transport_factories(url, factory_list)
1634
1655
if possible_transports is not None:
1635
1656
if transport in possible_transports:
1636
1657
raise AssertionError()
1637
1658
possible_transports.append(transport)
1638
1659
return transport
1640
# We tried all the different protocols, now try one last time
1641
# as a local protocol
1642
base = convert_path_to_url(base, 'Unsupported protocol: %s')
1644
# The default handler is the filesystem handler, stored as protocol None
1645
factory_list = transport_list_registry.get(None)
1646
transport, last_err = _try_transport_factories(base, factory_list)
1660
if not urlutils.is_url(url):
1661
raise errors.InvalidURL(path=url)
1662
raise errors.UnsupportedProtocol(url, last_err)
1665
def get_transport(base, possible_transports=None):
1666
"""Open a transport to access a URL or directory.
1668
:param base: either a URL or a directory name.
1670
:param transports: optional reusable transports list. If not None, created
1671
transports will be added to the list.
1673
:return: A new transport optionally sharing its connection with one of
1674
possible_transports.
1678
return get_transport_from_url(location_to_url(base), possible_transports)
1651
1681
def _try_transport_factories(base, factory_list):