166
166
except ValueError:
167
167
# TODO: Should this be ConnectionError?
168
raise errors.TransportError('%s: invalid port number' % port)
168
raise errors.TransportError(
169
'invalid port number %s in url:\n%s' % (port, url))
169
170
host = urllib.unquote(host)
171
172
path = urllib.unquote(path)
198
199
implementations can do pipelining.
199
200
In general implementations should support having a generator or a list
200
201
as an argument (ie always iterate, never index)
203
:ivar base: Base URL for the transport; should always end in a slash.
203
206
# implementations can override this if it is more efficient
221
224
This handles things like ENOENT, ENOTDIR, EEXIST, and EACCESS
223
if hasattr(e, 'errno'):
226
if getattr(e, 'errno', None) is not None:
224
227
if e.errno in (errno.ENOENT, errno.ENOTDIR):
225
228
raise errors.NoSuchFile(path, extra=e)
226
229
# I would rather use errno.EFOO, but there doesn't seem to be
305
308
def abspath(self, relpath):
306
309
"""Return the full url to the given relative path.
307
This can be supplied with a string or a list
309
XXX: Robert Collins 20051016 - is this really needed in the public
311
:param relpath: a string of a relative path
314
# XXX: Robert Collins 20051016 - is this really needed in the public
312
316
raise NotImplementedError(self.abspath)
318
def _combine_paths(self, base_path, relpath):
319
"""Transform a Transport-relative path to a remote absolute path.
321
This does not handle substitution of ~ but does handle '..' and '.'
326
>>> t = Transport('/')
327
>>> t._combine_paths('/home/sarah', 'project/foo')
328
'/home/sarah/project/foo'
329
>>> t._combine_paths('/home/sarah', '../../etc')
332
:param base_path: urlencoded path for the transport root; typically a
333
URL but need not contain scheme/host/etc.
334
:param relpath: relative url string for relative part of remote path.
335
:return: urlencoded string for final path.
337
# FIXME: share the common code across more transports; variants of
338
# this likely occur in http and sftp too.
340
# TODO: Also need to consider handling of ~, which might vary between
342
if not isinstance(relpath, str):
343
raise errors.InvalidURL("not a valid url: %r" % relpath)
344
if relpath.startswith('/'):
347
base_parts = base_path.split('/')
348
if len(base_parts) > 0 and base_parts[-1] == '':
349
base_parts = base_parts[:-1]
350
for p in relpath.split('/'):
352
if len(base_parts) == 0:
353
# In most filesystems, a request for the parent
354
# of root, just returns root.
361
path = '/'.join(base_parts)
314
364
def relpath(self, abspath):
315
365
"""Return the local path portion from a given absolute path.
342
392
Note that some transports MAY allow querying on directories, but this
343
393
is not part of the protocol. In other words, the results of
344
394
t.has("a_directory_name") are undefined.
346
398
raise NotImplementedError(self.has)
389
442
return self.get(relpath).read()
444
def get_smart_client(self):
445
"""Return a smart client for this transport if possible.
447
:raises NoSmartServer: if no smart server client is available.
449
raise errors.NoSmartServer(self.base)
391
451
def readv(self, relpath, offsets):
392
452
"""Get parts of the file at the given relative path.
400
460
fp = self.get(relpath)
401
return self._seek_and_read(fp, offsets)
461
return self._seek_and_read(fp, offsets, relpath)
403
def _seek_and_read(self, fp, offsets):
463
def _seek_and_read(self, fp, offsets, relpath='<unknown>'):
404
464
"""An implementation of readv that uses fp.seek and fp.read.
406
466
This uses _coalesce_offsets to issue larger reads and fewer seeks.
429
489
fp.seek(c_offset.start)
430
490
data = fp.read(c_offset.length)
491
if len(data) < c_offset.length:
492
raise errors.ShortReadvError(relpath, c_offset.start,
493
c_offset.length, actual=len(data))
431
494
for suboffset, subsize in c_offset.ranges:
432
495
key = (c_offset.start+suboffset, subsize)
433
496
data_map[key] = data[suboffset:suboffset+subsize]
634
697
return self.append_file(relpath, f, mode=mode)
636
699
def append_file(self, relpath, f, mode=None):
637
"""Append the text in the file-like object to the supplied location.
639
returns the length of relpath before the content was written to it.
641
If the file does not exist, it is created with the supplied mode.
700
"""Append bytes from a file-like object to a file at relpath.
702
The file is created if it does not already exist.
704
:param f: a file-like object of the bytes to append.
705
:param mode: Unix mode for newly created files. This is not used for
708
:returns: the length of relpath before the content was written to it.
643
710
symbol_versioning.warn('Transport %s should implement append_file,'
644
711
' rather than implementing append() as of'
648
715
return self.append(relpath, f, mode=mode)
650
717
def append_bytes(self, relpath, bytes, mode=None):
651
"""Append the text in the string object to the supplied location.
653
returns the length of relpath before the content was written to it.
655
If the file does not exist, it is created with the supplied mode.
718
"""Append bytes to a file at relpath.
720
The file is created if it does not already exist.
723
:param f: a string of the bytes to append.
724
:param mode: Unix mode for newly created files. This is not used for
727
:returns: the length of relpath before the content was written to it.
657
729
assert isinstance(bytes, str), \
658
730
'bytes must be a plain string, not %s' % type(bytes)
848
920
WARNING: many transports do not support this, so trying avoid using
849
921
it if at all possible.
851
raise errors.TransportNotPossible("This transport has not "
923
raise errors.TransportNotPossible("Transport %r has not "
852
924
"implemented list_dir "
853
925
"(but must claim to be listable "
854
"to trigger this error).")
926
"to trigger this error)."
856
929
def lock_read(self, relpath):
857
930
"""Lock the given file for shared (read) access.
858
WARNING: many transports do not support this, so trying avoid using it
932
WARNING: many transports do not support this, so trying avoid using it.
933
These methods may be removed in the future.
935
Transports may raise TransportNotPossible if OS-level locks cannot be
936
taken over this transport.
860
938
:return: A lock object, which should contain an unlock() function.
862
raise NotImplementedError(self.lock_read)
940
raise errors.TransportNotPossible("transport locks not supported on %s" % self)
864
942
def lock_write(self, relpath):
865
943
"""Lock the given file for exclusive (write) access.
866
WARNING: many transports do not support this, so trying avoid using it
945
WARNING: many transports do not support this, so trying avoid using it.
946
These methods may be removed in the future.
948
Transports may raise TransportNotPossible if OS-level locks cannot be
949
taken over this transport.
868
951
:return: A lock object, which should contain an unlock() function.
870
raise NotImplementedError(self.lock_write)
953
raise errors.TransportNotPossible("transport locks not supported on %s" % self)
872
955
def is_readonly(self):
873
956
"""Return true if this connection cannot be written to."""
1016
1099
def get_transport_test_permutations(self, module):
1017
1100
"""Get the permutations module wants to have tested."""
1018
if not hasattr(module, 'get_test_permutations'):
1101
if getattr(module, 'get_test_permutations', None) is None:
1019
1102
warning("transport module %s doesn't provide get_test_permutations()"
1020
1103
% module.__name__)
1083
1166
register_lazy_transport('memory://', 'bzrlib.transport.memory', 'MemoryTransport')
1084
1167
register_lazy_transport('readonly+', 'bzrlib.transport.readonly', 'ReadonlyTransportDecorator')
1085
1168
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs', 'FakeNFSTransportDecorator')
1086
register_lazy_transport('vfat+',
1169
register_lazy_transport('vfat+',
1087
1170
'bzrlib.transport.fakevfat',
1088
1171
'FakeVFATTransportDecorator')
1172
register_lazy_transport('bzr://',
1173
'bzrlib.transport.smart',
1174
'SmartTCPTransport')
1175
register_lazy_transport('bzr+ssh://',
1176
'bzrlib.transport.smart',
1177
'SmartSSHTransport')