~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

Merge from integration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
as remote (such as http or sftp).
20
20
"""
21
21
 
 
22
import errno
 
23
from copy import deepcopy
 
24
import sys
 
25
from unittest import TestSuite
 
26
 
22
27
from bzrlib.trace import mutter
23
28
import bzrlib.errors as errors
24
 
import errno
25
29
 
26
30
_protocol_handlers = {
27
31
}
39
43
        _protocol_handlers[prefix] = klass
40
44
 
41
45
 
 
46
def _get_protocol_handlers():
 
47
    """Return a dictionary of prefix:transport-factories."""
 
48
    return _protocol_handlers
 
49
 
 
50
 
 
51
def _set_protocol_handlers(new_handlers):
 
52
    """Replace the current protocol handlers dictionary.
 
53
 
 
54
    WARNING this will remove all build in protocols. Use with care.
 
55
    """
 
56
    global _protocol_handlers
 
57
    _protocol_handlers = new_handlers
 
58
 
 
59
 
 
60
def _get_transport_modules():
 
61
    """Return a list of the modules providing transports."""
 
62
    modules = set()
 
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)
 
68
        else:
 
69
            modules.add(factory.__module__)
 
70
    result = list(modules)
 
71
    result.sort()
 
72
    return result
 
73
 
 
74
 
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:
152
191
        """
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('/')
159
 
 
 
197
        return abspath[pl:].strip('/')
160
198
 
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.
190
228
        """
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).")
192
233
 
193
234
    def get(self, relpath):
194
235
        """Get the file at the given relative path.
214
255
            yield self.get(relpath)
215
256
            count += 1
216
257
 
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.
219
260
 
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
222
265
        """
223
266
        raise NotImplementedError
224
267
 
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.
227
270
 
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.
231
275
        """
232
 
        return self._iterate_over(files, self.put, pb, 'put', expand=True)
 
276
        def put(path, f):
 
277
            self.put(path, f, mode=mode)
 
278
        return self._iterate_over(files, put, pb, 'put', expand=True)
233
279
 
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
237
283
 
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)
 
286
        def mkdir(path):
 
287
            self.mkdir(path, mode=mode)
 
288
        return self._iterate_over(relpaths, mkdir, pb, 'mkdir', expand=False)
241
289
 
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)
256
304
 
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.
 
307
        
 
308
        Override this for efficiency if a specific transport can do it 
 
309
        faster than this default implementation.
 
310
        """
 
311
        self.put(rel_to, self.get(rel_from))
260
312
 
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)
269
321
 
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.
272
324
 
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.
276
329
        """
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)
280
333
 
281
334
        return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
282
335
 
283
336
 
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.
 
339
        
 
340
        If a transport can directly implement this it is suggested that
 
341
        it do so for efficiency.
 
342
        """
 
343
        self.copy(rel_from, rel_to)
 
344
        self.delete(rel_from)
287
345
 
288
346
    def move_multi(self, relpaths, pb=None):
289
347
        """Move a bunch of entries.
347
405
        it if at all possible.
348
406
        """
349
407
        raise errors.TransportNotPossible("This transport has not "
350
 
                                          "implemented list_dir.")
 
408
                                          "implemented list_dir "
 
409
                                          "(but must claim to be listable "
 
410
                                          "to trigger this error).")
351
411
 
352
412
    def lock_read(self, relpath):
353
413
        """Lock the given file for shared (read) access.
365
425
        """
366
426
        raise NotImplementedError
367
427
 
 
428
    def is_readonly(self):
 
429
        """Return true if this connection cannot be written to."""
 
430
        return False
 
431
 
368
432
 
369
433
def get_transport(base):
370
434
    global _protocol_handlers
371
435
    if base is None:
372
436
        base = u'.'
 
437
    else:
 
438
        base = unicode(base)
373
439
    for proto, klass in _protocol_handlers.iteritems():
374
440
        if proto is not None and base.startswith(proto):
375
441
            return klass(base)
388
454
        mod = __import__(module, globals(), locals(), [classname])
389
455
        klass = getattr(mod, classname)
390
456
        return klass(base)
 
457
    _loader.module = module
391
458
    register_transport(scheme, _loader)
392
459
 
393
460
 
398
465
    return urllib.quote(relpath)
399
466
 
400
467
 
 
468
class Server(object):
 
469
    """A Transport Server.
 
470
    
 
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.
 
475
    
 
476
    Note that these are real servers - they must implement all the things
 
477
    that we want bzr transports to take advantage of.
 
478
    """
 
479
 
 
480
    def setUp(self):
 
481
        """Setup the server to service requests."""
 
482
 
 
483
    def tearDown(self):
 
484
        """Remove the server and cleanup any resources it owns."""
 
485
 
 
486
    def get_url(self):
 
487
        """Return a url for this server.
 
488
        
 
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.
 
494
        
 
495
        Subsequent calls will return the same resource.
 
496
        """
 
497
        raise NotImplementedError
 
498
 
 
499
    def get_bogus_url(self):
 
500
        """Return a url for this protocol, that will fail to connect."""
 
501
        raise NotImplementedError
 
502
 
 
503
 
 
504
class TransportTestProviderAdapter(object):
 
505
    """A tool to generate a suite testing all transports for a single test.
 
506
 
 
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.
 
510
    """
 
511
 
 
512
    def adapt(self, test):
 
513
        result = TestSuite()
 
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)
 
523
        return result
 
524
 
 
525
    def get_transport_test_permutations(self, module):
 
526
        """Get the permutations module wants to have tested."""
 
527
        return module.get_test_permutations()
 
528
 
 
529
    def _test_permutations(self):
 
530
        """Return a list of the klass, server_factory pairs to test."""
 
531
        result = []
 
532
        for module in _get_transport_modules():
 
533
            result.extend(self.get_transport_test_permutations(reduce(getattr, 
 
534
                (module).split('.')[1:],
 
535
                 __import__(module))))
 
536
        return result
 
537
        
 
538
 
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')