~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: abentley
  • Date: 2006-04-20 23:47:53 UTC
  • mfrom: (1681 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1683.
  • Revision ID: abentley@lappy-20060420234753-6a6874b76f09f86d
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
16
17
"""Transport is an abstraction layer to handle file access.
17
18
 
18
19
The abstraction is to allow access from the local filesystem, as well
19
20
as remote (such as http or sftp).
 
21
 
 
22
Transports are constructed from a string, being a URL or (as a degenerate
 
23
case) a local filesystem path.  This is typically the top directory of
 
24
a bzrdir, repository, or similar object we are interested in working with.
 
25
The Transport returned has methods to read, write and manipulate files within
 
26
it.
20
27
"""
21
28
 
22
29
import errno
25
32
from stat import *
26
33
import sys
27
34
from unittest import TestSuite
 
35
import urllib
 
36
import urlparse
28
37
 
29
 
from bzrlib.trace import mutter
 
38
from bzrlib.trace import mutter, warning
30
39
import bzrlib.errors as errors
 
40
from bzrlib.errors import DependencyNotPresent
 
41
from bzrlib.symbol_versioning import *
31
42
 
 
43
# {prefix: [transport_classes]}
 
44
# Transports are inserted onto the list LIFO and tried in order; as a result
 
45
# transports provided by plugins are tried first, which is usually what we
 
46
# want.
32
47
_protocol_handlers = {
33
48
}
34
49
 
35
 
def register_transport(prefix, klass, override=True):
 
50
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
 
51
    """Register a transport that can be used to open URLs
 
52
 
 
53
    Normally you should use register_lazy_transport, which defers loading the
 
54
    implementation until it's actually used, and so avoids pulling in possibly
 
55
    large implementation libraries.
 
56
    """
 
57
    # Note that this code runs very early in library setup -- trace may not be
 
58
    # working, etc.
36
59
    global _protocol_handlers
37
 
    # trace messages commented out because they're typically 
38
 
    # run during import before trace is set up
39
 
    if _protocol_handlers.has_key(prefix):
40
 
        if override:
41
 
            ## mutter('overriding transport: %s => %s' % (prefix, klass.__name__))
42
 
            _protocol_handlers[prefix] = klass
43
 
    else:
44
 
        ## mutter('registering transport: %s => %s' % (prefix, klass.__name__))
45
 
        _protocol_handlers[prefix] = klass
 
60
    if deprecated_passed(override):
 
61
        warn("register_transport(override) is deprecated")
 
62
    _protocol_handlers.setdefault(prefix, []).insert(0, klass)
 
63
 
 
64
 
 
65
def register_lazy_transport(scheme, module, classname):
 
66
    """Register lazy-loaded transport class.
 
67
 
 
68
    When opening a URL with the given scheme, load the module and then
 
69
    instantiate the particular class.  
 
70
 
 
71
    If the module raises DependencyNotPresent when it's imported, it is
 
72
    skipped and another implementation of the protocol is tried.  This is
 
73
    intended to be used when the implementation depends on an external
 
74
    implementation that may not be present.  If any other error is raised, it
 
75
    propagates up and the attempt to open the url fails.
 
76
    """
 
77
    # TODO: If no implementation of a protocol is available because of missing
 
78
    # dependencies, we should perhaps show the message about what dependency
 
79
    # was missing.
 
80
    def _loader(base):
 
81
        mod = __import__(module, globals(), locals(), [classname])
 
82
        klass = getattr(mod, classname)
 
83
        return klass(base)
 
84
    _loader.module = module
 
85
    register_transport(scheme, _loader)
46
86
 
47
87
 
48
88
def _get_protocol_handlers():
49
 
    """Return a dictionary of prefix:transport-factories."""
 
89
    """Return a dictionary of {urlprefix: [factory]}"""
50
90
    return _protocol_handlers
51
91
 
52
92
 
59
99
    _protocol_handlers = new_handlers
60
100
 
61
101
 
 
102
def _clear_protocol_handlers():
 
103
    global _protocol_handlers
 
104
    _protocol_handlers = {}
 
105
 
 
106
 
62
107
def _get_transport_modules():
63
108
    """Return a list of the modules providing transports."""
64
109
    modules = set()
65
 
    for prefix, factory in _protocol_handlers.items():
66
 
        if factory.__module__ == "bzrlib.transport":
67
 
            # this is a lazy load transport, because no real ones
68
 
            # are directlry in bzrlib.transport
69
 
            modules.add(factory.module)
70
 
        else:
71
 
            modules.add(factory.__module__)
 
110
    for prefix, factory_list in _protocol_handlers.items():
 
111
        for factory in factory_list:
 
112
            if factory.__module__ == "bzrlib.transport":
 
113
                # this is a lazy load transport, because no real ones
 
114
                # are directlry in bzrlib.transport
 
115
                modules.add(factory.module)
 
116
            else:
 
117
                modules.add(factory.__module__)
72
118
    result = list(modules)
73
119
    result.sort()
74
120
    return result
75
121
 
76
122
 
 
123
def register_urlparse_netloc_protocol(protocol):
 
124
    """Ensure that protocol is setup to be used with urlparse netloc parsing."""
 
125
    if protocol not in urlparse.uses_netloc:
 
126
        urlparse.uses_netloc.append(protocol)
 
127
 
 
128
 
77
129
class Transport(object):
78
130
    """This class encapsulates methods for retrieving or putting a file
79
131
    from/to a storage location.
110
162
                raise errors.PermissionDenied(path, extra=e)
111
163
            if e.errno == errno.ENOTEMPTY:
112
164
                raise errors.DirectoryNotEmpty(path, extra=e)
 
165
            if e.errno == errno.EBUSY:
 
166
                raise errors.ResourceBusy(path, extra=e)
113
167
        if raise_generic:
114
168
            raise errors.TransportError(orig_error=e)
115
169
 
118
172
        using a subdirectory or parent directory. This allows connections 
119
173
        to be pooled, rather than a new one needed for each subdir.
120
174
        """
121
 
        raise NotImplementedError
 
175
        raise NotImplementedError(self.clone)
122
176
 
123
177
    def should_cache(self):
124
178
        """Return True if the data pulled across should be cached locally.
184
238
        XXX: Robert Collins 20051016 - is this really needed in the public
185
239
             interface ?
186
240
        """
187
 
        raise NotImplementedError
 
241
        raise NotImplementedError(self.abspath)
188
242
 
189
243
    def relpath(self, abspath):
190
244
        """Return the local path portion from a given absolute path.
208
262
        is not part of the protocol.  In other words, the results of 
209
263
        t.has("a_directory_name") are undefined."
210
264
        """
211
 
        raise NotImplementedError
 
265
        raise NotImplementedError(self.has)
212
266
 
213
267
    def has_multi(self, relpaths, pb=None):
214
268
        """Return True/False for each entry in relpaths"""
244
298
 
245
299
        :param relpath: The relative path to the file
246
300
        """
247
 
        raise NotImplementedError
 
301
        raise NotImplementedError(self.get)
248
302
 
249
303
    def readv(self, relpath, offsets):
250
304
        """Get parts of the file at the given relative path.
315
369
        :param mode: The mode for the newly created file, 
316
370
                     None means just use the default
317
371
        """
318
 
        raise NotImplementedError
 
372
        raise NotImplementedError(self.put)
319
373
 
320
374
    def put_multi(self, files, mode=None, pb=None):
321
375
        """Put a set of files into the location.
331
385
 
332
386
    def mkdir(self, relpath, mode=None):
333
387
        """Create a directory at the given path."""
334
 
        raise NotImplementedError
 
388
        raise NotImplementedError(self.mkdir)
335
389
 
336
390
    def mkdir_multi(self, relpaths, mode=None, pb=None):
337
391
        """Create a group of directories"""
345
399
 
346
400
        returns the length of f before the content was written to it.
347
401
        """
348
 
        raise NotImplementedError
 
402
        raise NotImplementedError(self.append)
349
403
 
350
404
    def append_multi(self, files, pb=None):
351
405
        """Append the text in each file-like or string object to
463
517
        """
464
518
        # This is not implemented, because you need to do special tricks to
465
519
        # extract the basename, and add it to rel_to
466
 
        raise NotImplementedError
 
520
        raise NotImplementedError(self.move_multi_to)
467
521
 
468
522
    def delete(self, relpath):
469
523
        """Delete the item at relpath"""
470
 
        raise NotImplementedError
 
524
        raise NotImplementedError(self.delete)
471
525
 
472
526
    def delete_multi(self, relpaths, pb=None):
473
527
        """Queue up a bunch of deletes to be done.
510
564
        ALSO NOTE: Stats of directories may not be supported on some 
511
565
        transports.
512
566
        """
513
 
        raise NotImplementedError
 
567
        raise NotImplementedError(self.stat)
514
568
 
515
569
    def rmdir(self, relpath):
516
570
        """Remove a directory at the given path."""
530
584
 
531
585
    def listable(self):
532
586
        """Return True if this store supports listing."""
533
 
        raise NotImplementedError
 
587
        raise NotImplementedError(self.listable)
534
588
 
535
589
    def list_dir(self, relpath):
536
590
        """Return a list of all files at the given location.
548
602
 
549
603
        :return: A lock object, which should contain an unlock() function.
550
604
        """
551
 
        raise NotImplementedError
 
605
        raise NotImplementedError(self.lock_read)
552
606
 
553
607
    def lock_write(self, relpath):
554
608
        """Lock the given file for exclusive (write) access.
556
610
 
557
611
        :return: A lock object, which should contain an unlock() function.
558
612
        """
559
 
        raise NotImplementedError
 
613
        raise NotImplementedError(self.lock_write)
560
614
 
561
615
    def is_readonly(self):
562
616
        """Return true if this connection cannot be written to."""
563
617
        return False
564
618
 
 
619
    def _can_roundtrip_unix_modebits(self):
 
620
        """Return true if this transport can store and retrieve unix modebits.
 
621
 
 
622
        (For example, 0700 to make a directory owner-private.)
 
623
        
 
624
        Note: most callers will not want to switch on this, but should rather 
 
625
        just try and set permissions and let them be either stored or not.
 
626
        This is intended mainly for the use of the test suite.
 
627
        
 
628
        Warning: this is not guaranteed to be accurate as sometimes we can't 
 
629
        be sure: for example with vfat mounted on unix, or a windows sftp
 
630
        server."""
 
631
        # TODO: Perhaps return a e.g. TransportCharacteristics that can answer
 
632
        # several questions about the transport.
 
633
        return False
 
634
 
565
635
 
566
636
def get_transport(base):
567
637
    """Open a transport to access a URL or directory.
568
638
 
569
639
    base is either a URL or a directory name.  
570
640
    """
 
641
    # TODO: give a better error if base looks like a url but there's no
 
642
    # handler for the scheme?
571
643
    global _protocol_handlers
572
644
    if base is None:
573
645
        base = u'.'
574
646
    else:
575
647
        base = unicode(base)
576
 
    for proto, klass in _protocol_handlers.iteritems():
 
648
    for proto, factory_list in _protocol_handlers.iteritems():
577
649
        if proto is not None and base.startswith(proto):
578
 
            return klass(base)
579
 
    # The default handler is the filesystem handler
580
 
    # which has a lookup of None
581
 
    return _protocol_handlers[None](base)
582
 
 
583
 
 
584
 
def register_lazy_transport(scheme, module, classname):
585
 
    """Register lazy-loaded transport class.
586
 
 
587
 
    When opening a URL with the given scheme, load the module and then
588
 
    instantiate the particular class.  
589
 
    """
590
 
    def _loader(base):
591
 
        mod = __import__(module, globals(), locals(), [classname])
592
 
        klass = getattr(mod, classname)
593
 
        return klass(base)
594
 
    _loader.module = module
595
 
    register_transport(scheme, _loader)
 
650
            t = _try_transport_factories(base, factory_list)
 
651
            if t:
 
652
                return t
 
653
    # The default handler is the filesystem handler, stored as protocol None
 
654
    return _try_transport_factories(base, _protocol_handlers[None])
 
655
 
 
656
 
 
657
def _try_transport_factories(base, factory_list):
 
658
    for factory in factory_list:
 
659
        try:
 
660
            return factory(base)
 
661
        except DependencyNotPresent, e:
 
662
            mutter("failed to instantiate transport %r for %r: %r" %
 
663
                    (factory, base, e))
 
664
            continue
 
665
    return None
596
666
 
597
667
 
598
668
def urlescape(relpath):
599
669
    """Escape relpath to be a valid url."""
600
 
    # TODO utf8 it first. utf8relpath = relpath.encode('utf8')
601
 
    import urllib
 
670
    if isinstance(relpath, unicode):
 
671
        relpath = relpath.encode('utf-8')
602
672
    return urllib.quote(relpath)
603
673
 
604
674
 
605
675
def urlunescape(relpath):
606
676
    """Unescape relpath from url format."""
607
 
    import urllib
608
677
    return urllib.unquote(relpath)
609
678
    # TODO de-utf8 it last. relpath = utf8relpath.decode('utf8')
610
679
 
668
737
 
669
738
    def get_transport_test_permutations(self, module):
670
739
        """Get the permutations module wants to have tested."""
 
740
        if not hasattr(module, 'get_test_permutations'):
 
741
            warning("transport module %s doesn't provide get_test_permutations()"
 
742
                    % module.__name__)
 
743
            return []
671
744
        return module.get_test_permutations()
672
745
 
673
746
    def _test_permutations(self):
713
786
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
714
787
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
715
788
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
716
 
register_lazy_transport('http://', 'bzrlib.transport.http', 'HttpTransport')
717
 
register_lazy_transport('https://', 'bzrlib.transport.http', 'HttpTransport')
 
789
register_lazy_transport('http+urllib://', 'bzrlib.transport.http._urllib',
 
790
                        'HttpTransport_urllib')
 
791
register_lazy_transport('https+urllib://', 'bzrlib.transport.http._urllib',
 
792
                        'HttpTransport_urllib')
 
793
register_lazy_transport('http+pycurl://', 'bzrlib.transport.http._pycurl',
 
794
                        'PyCurlTransport')
 
795
register_lazy_transport('https+pycurl://', 'bzrlib.transport.http._pycurl',
 
796
                        'PyCurlTransport')
 
797
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
 
798
                        'HttpTransport_urllib')
 
799
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
 
800
                        'HttpTransport_urllib')
 
801
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
 
802
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
718
803
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
719
804
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
720
805
register_lazy_transport('memory:/', 'bzrlib.transport.memory', 'MemoryTransport')
721
806
register_lazy_transport('readonly+', 'bzrlib.transport.readonly', 'ReadonlyTransportDecorator')
 
807
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs', 'FakeNFSTransportDecorator')
 
808
register_lazy_transport('vfat+', 
 
809
                        'bzrlib.transport.fakevfat',
 
810
                        'FakeVFATTransportDecorator')