39
43
_protocol_handlers[prefix] = klass
46
def _get_protocol_handlers():
47
"""Return a dictionary of prefix:transport-factories."""
48
return _protocol_handlers
51
def _set_protocol_handlers(new_handlers):
52
"""Replace the current protocol handlers dictionary.
54
WARNING this will remove all build in protocols. Use with care.
56
global _protocol_handlers
57
_protocol_handlers = new_handlers
60
def _get_transport_modules():
61
"""Return a list of the modules providing transports."""
63
for prefix, factory in _protocol_handlers.items():
64
if factory.__module__ == "bzrlib.transport":
65
# this is a lazy load transport, because no real ones
66
# are directlry in bzrlib.transport
67
modules.add(factory.module)
69
modules.add(factory.__module__)
70
result = list(modules)
42
75
class Transport(object):
43
76
"""This class encapsulates methods for retrieving or putting a file
44
77
from/to a storage location.
63
96
if hasattr(e, 'errno'):
64
97
if e.errno in (errno.ENOENT, errno.ENOTDIR):
65
98
raise errors.NoSuchFile(path, extra=e)
99
# I would rather use errno.EFOO, but there doesn't seem to be
100
# any matching for 267
101
# This is the error when doing a listdir on a file:
102
# WindowsError: [Errno 267] The directory name is invalid
103
if sys.platform == 'win32' and e.errno in (errno.ESRCH, 267):
104
raise errors.NoSuchFile(path, extra=e)
66
105
if e.errno == errno.EEXIST:
67
106
raise errors.FileExists(path, extra=e)
68
107
if e.errno == errno.EACCES:
153
192
# TODO: This might want to use bzrlib.osutils.relpath
154
193
# but we have to watch out because of the prefix issues
155
if not abspath.startswith(self.base):
194
if not (abspath == self.base[:-1] or abspath.startswith(self.base)):
156
195
raise errors.PathNotChild(abspath, self.base)
157
196
pl = len(self.base)
158
return abspath[pl:].lstrip('/')
197
return abspath[pl:].strip('/')
161
199
def has(self, relpath):
162
200
"""Does the file relpath exist?
188
226
As with other listing functions, only some transports implement this,.
189
227
you may check via is_listable to determine if it will.
191
raise NotImplementedError
229
raise errors.TransportNotPossible("This transport has not "
230
"implemented iter_files_recursive "
231
"(but must claim to be listable "
232
"to trigger this error).")
193
234
def get(self, relpath):
194
235
"""Get the file at the given relative path.
214
255
yield self.get(relpath)
217
def put(self, relpath, f):
258
def put(self, relpath, f, mode=None):
218
259
"""Copy the file-like or string object into the location.
220
261
:param relpath: Location to put the contents, relative to base.
221
262
:param f: File-like or string object.
263
:param mode: The mode for the newly created file,
264
None means just use the default
223
266
raise NotImplementedError
225
def put_multi(self, files, pb=None):
226
"""Put a set of files or strings into the location.
268
def put_multi(self, files, mode=None, pb=None):
269
"""Put a set of files into the location.
228
271
:param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
229
272
:param pb: An optional ProgressBar for indicating percent done.
273
:param mode: The mode for the newly created files
230
274
:return: The number of files copied.
232
return self._iterate_over(files, self.put, pb, 'put', expand=True)
277
self.put(path, f, mode=mode)
278
return self._iterate_over(files, put, pb, 'put', expand=True)
234
def mkdir(self, relpath):
280
def mkdir(self, relpath, mode=None):
235
281
"""Create a directory at the given path."""
236
282
raise NotImplementedError
238
def mkdir_multi(self, relpaths, pb=None):
284
def mkdir_multi(self, relpaths, mode=None, pb=None):
239
285
"""Create a group of directories"""
240
return self._iterate_over(relpaths, self.mkdir, pb, 'mkdir', expand=False)
287
self.mkdir(path, mode=mode)
288
return self._iterate_over(relpaths, mkdir, pb, 'mkdir', expand=False)
242
290
def append(self, relpath, f):
243
291
"""Append the text in the file-like or string object to
255
303
return self._iterate_over(files, self.append, pb, 'append', expand=True)
257
305
def copy(self, rel_from, rel_to):
258
"""Copy the item at rel_from to the location at rel_to"""
259
raise NotImplementedError
306
"""Copy the item at rel_from to the location at rel_to.
308
Override this for efficiency if a specific transport can do it
309
faster than this default implementation.
311
self.put(rel_to, self.get(rel_from))
261
313
def copy_multi(self, relpaths, pb=None):
262
314
"""Copy a bunch of entries.
267
319
# implementors don't have to implement everything.
268
320
return self._iterate_over(relpaths, self.copy, pb, 'copy', expand=True)
270
def copy_to(self, relpaths, other, pb=None):
322
def copy_to(self, relpaths, other, mode=None, pb=None):
271
323
"""Copy a set of entries from self into another Transport.
273
325
:param relpaths: A list/generator of entries to be copied.
326
:param mode: This is the target mode for the newly created files
274
327
TODO: This interface needs to be updated so that the target location
275
328
can be different from the source location.
277
330
# The dummy implementation just does a simple get + put
278
331
def copy_entry(path):
279
other.put(path, self.get(path))
332
other.put(path, self.get(path), mode=mode)
281
334
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
284
337
def move(self, rel_from, rel_to):
285
"""Move the item at rel_from to the location at rel_to"""
286
raise NotImplementedError
338
"""Move the item at rel_from to the location at rel_to.
340
If a transport can directly implement this it is suggested that
341
it do so for efficiency.
343
self.copy(rel_from, rel_to)
344
self.delete(rel_from)
288
346
def move_multi(self, relpaths, pb=None):
289
347
"""Move a bunch of entries.
398
465
return urllib.quote(relpath)
468
class Server(object):
469
"""A Transport Server.
471
The Server interface provides a server for a given transport. We use
472
these servers as loopback testing tools. For any given transport the
473
Servers it provides must either allow writing, or serve the contents
474
of os.getcwdu() at the time setUp is called.
476
Note that these are real servers - they must implement all the things
477
that we want bzr transports to take advantage of.
481
"""Setup the server to service requests."""
484
"""Remove the server and cleanup any resources it owns."""
487
"""Return a url for this server.
489
If the transport does not represent a disk directory (i.e. it is
490
a database like svn, or a memory only transport, it should return
491
a connection to a newly established resource for this Server.
492
Otherwise it should return a url that will provide access to the path
493
that was os.getcwdu() when setUp() was called.
495
Subsequent calls will return the same resource.
497
raise NotImplementedError
499
def get_bogus_url(self):
500
"""Return a url for this protocol, that will fail to connect."""
501
raise NotImplementedError
504
class TransportTestProviderAdapter(object):
505
"""A tool to generate a suite testing all transports for a single test.
507
This is done by copying the test once for each transport and injecting
508
the transport_class and transport_server classes into each copy. Each copy
509
is also given a new id() to make it easy to identify.
512
def adapt(self, test):
514
for klass, server_factory in self._test_permutations():
515
new_test = deepcopy(test)
516
new_test.transport_class = klass
517
new_test.transport_server = server_factory
518
def make_new_test_id():
519
new_id = "%s(%s)" % (new_test.id(), server_factory.__name__)
520
return lambda: new_id
521
new_test.id = make_new_test_id()
522
result.addTest(new_test)
525
def get_transport_test_permutations(self, module):
526
"""Get the permutations module wants to have tested."""
527
return module.get_test_permutations()
529
def _test_permutations(self):
530
"""Return a list of the klass, server_factory pairs to test."""
532
for module in _get_transport_modules():
533
result.extend(self.get_transport_test_permutations(reduce(getattr,
534
(module).split('.')[1:],
535
__import__(module))))
401
539
# None is the default transport, for things with no url scheme
402
540
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
403
541
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
406
544
register_lazy_transport('https://', 'bzrlib.transport.http', 'HttpTransport')
407
545
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
408
546
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
547
register_lazy_transport('memory://', 'bzrlib.transport.memory', 'MemoryTransport')