/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

First attempt to merge .dev and resolve the conflicts (but tests are 
failing)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
26
26
it.
27
27
"""
28
28
 
29
 
import errno
30
 
from collections import deque
31
 
from copy import deepcopy
32
29
from cStringIO import StringIO
33
30
import re
 
31
import sys
 
32
 
 
33
from bzrlib.lazy_import import lazy_import
 
34
lazy_import(globals(), """
 
35
import errno
34
36
from stat import S_ISDIR
35
 
import sys
36
 
from unittest import TestSuite
37
37
import urllib
38
38
import urlparse
39
 
import warnings
40
39
 
41
 
import bzrlib
42
40
from bzrlib import (
43
41
    errors,
44
42
    osutils,
45
43
    symbol_versioning,
46
44
    urlutils,
47
45
    )
48
 
from bzrlib.errors import DependencyNotPresent
49
 
from bzrlib.osutils import pumpfile
 
46
""")
 
47
 
50
48
from bzrlib.symbol_versioning import (
51
 
        deprecated_passed,
52
49
        deprecated_method,
53
50
        deprecated_function,
54
51
        DEPRECATED_PARAMETER,
55
 
        zero_eight,
56
 
        zero_eleven,
 
52
        one_four,
57
53
        )
58
 
from bzrlib.trace import mutter, warning
59
 
 
60
 
# {prefix: [transport_classes]}
61
 
# Transports are inserted onto the list LIFO and tried in order; as a result
62
 
# transports provided by plugins are tried first, which is usually what we
63
 
# want.
64
 
_protocol_handlers = {
65
 
}
66
 
 
67
 
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
68
 
    """Register a transport that can be used to open URLs
69
 
 
70
 
    Normally you should use register_lazy_transport, which defers loading the
71
 
    implementation until it's actually used, and so avoids pulling in possibly
72
 
    large implementation libraries.
73
 
    """
74
 
    # Note that this code runs very early in library setup -- trace may not be
75
 
    # working, etc.
76
 
    global _protocol_handlers
77
 
    if deprecated_passed(override):
78
 
        warnings.warn("register_transport(override) is deprecated")
79
 
    _protocol_handlers.setdefault(prefix, []).insert(0, klass)
80
 
 
81
 
 
82
 
def register_lazy_transport(scheme, module, classname):
83
 
    """Register lazy-loaded transport class.
84
 
 
85
 
    When opening a URL with the given scheme, load the module and then
86
 
    instantiate the particular class.  
87
 
 
88
 
    If the module raises DependencyNotPresent when it's imported, it is
89
 
    skipped and another implementation of the protocol is tried.  This is
90
 
    intended to be used when the implementation depends on an external
91
 
    implementation that may not be present.  If any other error is raised, it
92
 
    propagates up and the attempt to open the url fails.
93
 
    """
94
 
    # TODO: If no implementation of a protocol is available because of missing
95
 
    # dependencies, we should perhaps show the message about what dependency
96
 
    # was missing.
97
 
    def _loader(base):
98
 
        mod = __import__(module, globals(), locals(), [classname])
99
 
        klass = getattr(mod, classname)
100
 
        return klass(base)
101
 
    _loader.module = module
102
 
    register_transport(scheme, _loader)
 
54
from bzrlib.trace import (
 
55
    mutter,
 
56
    )
 
57
from bzrlib import registry
 
58
 
 
59
 
 
60
# a dictionary of open file streams. Keys are absolute paths, values are
 
61
# transport defined.
 
62
_file_streams = {}
103
63
 
104
64
 
105
65
def _get_protocol_handlers():
106
66
    """Return a dictionary of {urlprefix: [factory]}"""
107
 
    return _protocol_handlers
 
67
    return transport_list_registry
108
68
 
109
69
 
110
70
def _set_protocol_handlers(new_handlers):
112
72
 
113
73
    WARNING this will remove all build in protocols. Use with care.
114
74
    """
115
 
    global _protocol_handlers
116
 
    _protocol_handlers = new_handlers
 
75
    global transport_list_registry
 
76
    transport_list_registry = new_handlers
117
77
 
118
78
 
119
79
def _clear_protocol_handlers():
120
 
    global _protocol_handlers
121
 
    _protocol_handlers = {}
 
80
    global transport_list_registry
 
81
    transport_list_registry = TransportListRegistry()
122
82
 
123
83
 
124
84
def _get_transport_modules():
125
85
    """Return a list of the modules providing transports."""
126
86
    modules = set()
127
 
    for prefix, factory_list in _protocol_handlers.items():
 
87
    for prefix, factory_list in transport_list_registry.iteritems():
128
88
        for factory in factory_list:
129
 
            if factory.__module__ == "bzrlib.transport":
130
 
                # this is a lazy load transport, because no real ones
131
 
                # are directlry in bzrlib.transport
132
 
                modules.add(factory.module)
 
89
            if hasattr(factory, "_module_name"):
 
90
                modules.add(factory._module_name)
133
91
            else:
134
 
                modules.add(factory.__module__)
 
92
                modules.add(factory._obj.__module__)
 
93
    # Add chroot directly, because there is no handler registered for it.
 
94
    modules.add('bzrlib.transport.chroot')
135
95
    result = list(modules)
136
96
    result.sort()
137
97
    return result
138
98
 
139
99
 
 
100
class TransportListRegistry(registry.Registry):
 
101
    """A registry which simplifies tracking available Transports.
 
102
 
 
103
    A registration of a new protocol requires two step:
 
104
    1) register the prefix with the function register_transport( )
 
105
    2) register the protocol provider with the function
 
106
    register_transport_provider( ) ( and the "lazy" variant )
 
107
 
 
108
    This is needed because:
 
109
    a) a single provider can support multple protcol ( like the ftp
 
110
    provider which supports both the ftp:// and the aftp:// protocols )
 
111
    b) a single protocol can have multiple providers ( like the http://
 
112
    protocol which is supported by both the urllib and pycurl provider )
 
113
    """
 
114
 
 
115
    def register_transport_provider(self, key, obj):
 
116
        self.get(key).insert(0, registry._ObjectGetter(obj))
 
117
 
 
118
    def register_lazy_transport_provider(self, key, module_name, member_name):
 
119
        self.get(key).insert(0,
 
120
                registry._LazyObjectGetter(module_name, member_name))
 
121
 
 
122
    def register_transport(self, key, help=None):
 
123
        self.register(key, [], help)
 
124
 
 
125
    def set_default_transport(self, key=None):
 
126
        """Return either 'key' or the default key if key is None"""
 
127
        self._default_key = key
 
128
 
 
129
 
 
130
transport_list_registry = TransportListRegistry()
 
131
 
 
132
 
 
133
def register_transport_proto(prefix, help=None, info=None,
 
134
                             register_netloc=False):
 
135
    transport_list_registry.register_transport(prefix, help)
 
136
    if register_netloc:
 
137
        if not prefix.endswith('://'):
 
138
            raise ValueError(prefix)
 
139
        register_urlparse_netloc_protocol(prefix[:-3])
 
140
 
 
141
 
 
142
def register_lazy_transport(prefix, module, classname):
 
143
    if not prefix in transport_list_registry:
 
144
        register_transport_proto(prefix)
 
145
    transport_list_registry.register_lazy_transport_provider(prefix, module, classname)
 
146
 
 
147
 
 
148
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
 
149
    if not prefix in transport_list_registry:
 
150
        register_transport_proto(prefix)
 
151
    transport_list_registry.register_transport_provider(prefix, klass)
 
152
 
 
153
 
140
154
def register_urlparse_netloc_protocol(protocol):
141
155
    """Ensure that protocol is setup to be used with urlparse netloc parsing."""
142
156
    if protocol not in urlparse.uses_netloc:
143
157
        urlparse.uses_netloc.append(protocol)
144
158
 
145
159
 
146
 
def split_url(url):
147
 
    # TODO: jam 20060606 urls should only be ascii, or they should raise InvalidURL
148
 
    if isinstance(url, unicode):
149
 
        url = url.encode('utf-8')
150
 
    (scheme, netloc, path, params,
151
 
     query, fragment) = urlparse.urlparse(url, allow_fragments=False)
152
 
    username = password = host = port = None
153
 
    if '@' in netloc:
154
 
        username, host = netloc.split('@', 1)
155
 
        if ':' in username:
156
 
            username, password = username.split(':', 1)
157
 
            password = urllib.unquote(password)
158
 
        username = urllib.unquote(username)
159
 
    else:
160
 
        host = netloc
161
 
 
162
 
    if ':' in host:
163
 
        host, port = host.rsplit(':', 1)
164
 
        try:
165
 
            port = int(port)
166
 
        except ValueError:
167
 
            # TODO: Should this be ConnectionError?
168
 
            raise errors.TransportError('%s: invalid port number' % port)
169
 
    host = urllib.unquote(host)
170
 
 
171
 
    path = urllib.unquote(path)
172
 
 
173
 
    return (scheme, username, password, host, port, path)
 
160
def _unregister_urlparse_netloc_protocol(protocol):
 
161
    """Remove protocol from urlparse netloc parsing.
 
162
 
 
163
    Except for tests, you should never use that function. Using it with 'http',
 
164
    for example, will break all http transports.
 
165
    """
 
166
    if protocol in urlparse.uses_netloc:
 
167
        urlparse.uses_netloc.remove(protocol)
 
168
 
 
169
 
 
170
def unregister_transport(scheme, factory):
 
171
    """Unregister a transport."""
 
172
    l = transport_list_registry.get(scheme)
 
173
    for i in l:
 
174
        o = i.get_obj( )
 
175
        if o == factory:
 
176
            transport_list_registry.get(scheme).remove(i)
 
177
            break
 
178
    if len(l) == 0:
 
179
        transport_list_registry.remove(scheme)
174
180
 
175
181
 
176
182
class _CoalescedOffset(object):
187
193
        return cmp((self.start, self.length, self.ranges),
188
194
                   (other.start, other.length, other.ranges))
189
195
 
 
196
    def __repr__(self):
 
197
        return '%s(%r, %r, %r)' % (self.__class__.__name__,
 
198
            self.start, self.length, self.ranges)
 
199
 
 
200
 
 
201
class LateReadError(object):
 
202
    """A helper for transports which pretends to be a readable file.
 
203
 
 
204
    When read() is called, errors.ReadError is raised.
 
205
    """
 
206
 
 
207
    def __init__(self, path):
 
208
        self._path = path
 
209
 
 
210
    def close(self):
 
211
        """a no-op - do nothing."""
 
212
 
 
213
    def _fail(self):
 
214
        """Raise ReadError."""
 
215
        raise errors.ReadError(self._path)
 
216
 
 
217
    def __iter__(self):
 
218
        self._fail()
 
219
 
 
220
    def read(self, count=-1):
 
221
        self._fail()
 
222
 
 
223
    def readlines(self):
 
224
        self._fail()
 
225
 
 
226
 
 
227
class FileStream(object):
 
228
    """Base class for FileStreams."""
 
229
 
 
230
    def __init__(self, transport, relpath):
 
231
        """Create a FileStream for relpath on transport."""
 
232
        self.transport = transport
 
233
        self.relpath = relpath
 
234
 
 
235
    def _close(self):
 
236
        """A hook point for subclasses that need to take action on close."""
 
237
 
 
238
    def close(self):
 
239
        self._close()
 
240
        del _file_streams[self.transport.abspath(self.relpath)]
 
241
 
 
242
 
 
243
class FileFileStream(FileStream):
 
244
    """A file stream object returned by open_write_stream.
 
245
    
 
246
    This version uses a file like object to perform writes.
 
247
    """
 
248
 
 
249
    def __init__(self, transport, relpath, file_handle):
 
250
        FileStream.__init__(self, transport, relpath)
 
251
        self.file_handle = file_handle
 
252
 
 
253
    def _close(self):
 
254
        self.file_handle.close()
 
255
 
 
256
    def write(self, bytes):
 
257
        self.file_handle.write(bytes)
 
258
 
 
259
 
 
260
class AppendBasedFileStream(FileStream):
 
261
    """A file stream object returned by open_write_stream.
 
262
    
 
263
    This version uses append on a transport to perform writes.
 
264
    """
 
265
 
 
266
    def write(self, bytes):
 
267
        self.transport.append_bytes(self.relpath, bytes)
 
268
 
190
269
 
191
270
class Transport(object):
192
271
    """This class encapsulates methods for retrieving or putting a file
214
293
    _bytes_to_read_before_seek = 0
215
294
 
216
295
    def __init__(self, base):
217
 
        super(Transport, self).__init__()
 
296
        super(Transport, self).__init__(base=base)
218
297
        self.base = base
219
298
 
220
299
    def _translate_error(self, e, path, raise_generic=True):
223
302
        This handles things like ENOENT, ENOTDIR, EEXIST, and EACCESS
224
303
        """
225
304
        if getattr(e, 'errno', None) is not None:
226
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
 
305
            if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
227
306
                raise errors.NoSuchFile(path, extra=e)
228
307
            # I would rather use errno.EFOO, but there doesn't seem to be
229
308
            # any matching for 267
249
328
        """
250
329
        raise NotImplementedError(self.clone)
251
330
 
252
 
    def should_cache(self):
253
 
        """Return True if the data pulled across should be cached locally.
254
 
        """
255
 
        return False
 
331
    def ensure_base(self):
 
332
        """Ensure that the directory this transport references exists.
 
333
 
 
334
        This will create a directory if it doesn't exist.
 
335
        :return: True if the directory was created, False otherwise.
 
336
        """
 
337
        # The default implementation just uses "Easier to ask for forgiveness
 
338
        # than permission". We attempt to create the directory, and just
 
339
        # suppress FileExists and PermissionDenied (for Windows) exceptions.
 
340
        try:
 
341
            self.mkdir('.')
 
342
        except (errors.FileExists, errors.PermissionDenied):
 
343
            return False
 
344
        else:
 
345
            return True
 
346
 
 
347
    def external_url(self):
 
348
        """Return a URL for self that can be given to an external process.
 
349
 
 
350
        There is no guarantee that the URL can be accessed from a different
 
351
        machine - e.g. file:/// urls are only usable on the local machine,
 
352
        sftp:/// urls when the server is only bound to localhost are only
 
353
        usable from localhost etc.
 
354
 
 
355
        NOTE: This method may remove security wrappers (e.g. on chroot
 
356
        transports) and thus should *only* be used when the result will not
 
357
        be used to obtain a new transport within bzrlib. Ideally chroot
 
358
        transports would know enough to cause the external url to be the exact
 
359
        one used that caused the chrooting in the first place, but that is not
 
360
        currently the case.
 
361
 
 
362
        :return: A URL that can be given to another process.
 
363
        :raises InProcessTransport: If the transport is one that cannot be
 
364
            accessed out of the current process (e.g. a MemoryTransport)
 
365
            then InProcessTransport is raised.
 
366
        """
 
367
        raise NotImplementedError(self.external_url)
256
368
 
257
369
    def _pump(self, from_file, to_file):
258
370
        """Most children will need to copy from one file-like 
259
371
        object or string to another one.
260
372
        This just gives them something easy to call.
261
373
        """
262
 
        assert not isinstance(from_file, basestring), \
263
 
            '_pump should only be called on files not %s' % (type(from_file,))
264
 
        pumpfile(from_file, to_file)
 
374
        return osutils.pumpfile(from_file, to_file)
265
375
 
266
376
    def _get_total(self, multi):
267
377
        """Try to figure out how many entries are in multi,
306
416
 
307
417
    def abspath(self, relpath):
308
418
        """Return the full url to the given relative path.
309
 
        This can be supplied with a string or a list
310
419
 
311
 
        XXX: Robert Collins 20051016 - is this really needed in the public
312
 
             interface ?
 
420
        :param relpath: a string of a relative path
313
421
        """
 
422
 
 
423
        # XXX: Robert Collins 20051016 - is this really needed in the public
 
424
        # interface ?
314
425
        raise NotImplementedError(self.abspath)
315
426
 
 
427
    def _combine_paths(self, base_path, relpath):
 
428
        """Transform a Transport-relative path to a remote absolute path.
 
429
 
 
430
        This does not handle substitution of ~ but does handle '..' and '.'
 
431
        components.
 
432
 
 
433
        Examples::
 
434
 
 
435
            t._combine_paths('/home/sarah', 'project/foo')
 
436
                => '/home/sarah/project/foo'
 
437
            t._combine_paths('/home/sarah', '../../etc')
 
438
                => '/etc'
 
439
            t._combine_paths('/home/sarah', '/etc')
 
440
                => '/etc'
 
441
 
 
442
        :param base_path: urlencoded path for the transport root; typically a 
 
443
             URL but need not contain scheme/host/etc.
 
444
        :param relpath: relative url string for relative part of remote path.
 
445
        :return: urlencoded string for final path.
 
446
        """
 
447
        if not isinstance(relpath, str):
 
448
            raise errors.InvalidURL(relpath)
 
449
        if relpath.startswith('/'):
 
450
            base_parts = []
 
451
        else:
 
452
            base_parts = base_path.split('/')
 
453
        if len(base_parts) > 0 and base_parts[-1] == '':
 
454
            base_parts = base_parts[:-1]
 
455
        for p in relpath.split('/'):
 
456
            if p == '..':
 
457
                if len(base_parts) == 0:
 
458
                    # In most filesystems, a request for the parent
 
459
                    # of root, just returns root.
 
460
                    continue
 
461
                base_parts.pop()
 
462
            elif p == '.':
 
463
                continue # No-op
 
464
            elif p != '':
 
465
                base_parts.append(p)
 
466
        path = '/'.join(base_parts)
 
467
        if not path.startswith('/'):
 
468
            path = '/' + path
 
469
        return path
 
470
 
 
471
    def recommended_page_size(self):
 
472
        """Return the recommended page size for this transport.
 
473
 
 
474
        This is potentially different for every path in a given namespace.
 
475
        For example, local transports might use an operating system call to 
 
476
        get the block size for a given path, which can vary due to mount
 
477
        points.
 
478
 
 
479
        :return: The page size in bytes.
 
480
        """
 
481
        return 4 * 1024
 
482
 
316
483
    def relpath(self, abspath):
317
484
        """Return the local path portion from a given absolute path.
318
485
 
334
501
        This function will only be defined for Transports which have a
335
502
        physical local filesystem representation.
336
503
        """
337
 
        # TODO: jam 20060426 Should this raise NotLocalUrl instead?
338
 
        raise errors.TransportNotPossible('This is not a LocalTransport,'
339
 
            ' so there is no local representation for a path')
 
504
        raise errors.NotLocalUrl(self.abspath(relpath))
 
505
 
340
506
 
341
507
    def has(self, relpath):
342
508
        """Does the file relpath exist?
371
537
        *NOTE*: This only lists *files*, not subdirectories!
372
538
        
373
539
        As with other listing functions, only some transports implement this,.
374
 
        you may check via is_listable to determine if it will.
 
540
        you may check via listable() to determine if it will.
375
541
        """
376
542
        raise errors.TransportNotPossible("This transport has not "
377
543
                                          "implemented iter_files_recursive "
381
547
    def get(self, relpath):
382
548
        """Get the file at the given relative path.
383
549
 
 
550
        This may fail in a number of ways:
 
551
         - HTTP servers may return content for a directory. (unexpected
 
552
           content failure)
 
553
         - FTP servers may indicate NoSuchFile for a directory.
 
554
         - SFTP servers may give a file handle for a directory that will
 
555
           fail on read().
 
556
 
 
557
        For correct use of the interface, be sure to catch errors.PathError
 
558
        when calling it and catch errors.ReadError when reading from the
 
559
        returned object.
 
560
 
384
561
        :param relpath: The relative path to the file
 
562
        :rtype: File-like object.
385
563
        """
386
564
        raise NotImplementedError(self.get)
387
565
 
392
570
        """
393
571
        return self.get(relpath).read()
394
572
 
395
 
    def readv(self, relpath, offsets):
396
 
        """Get parts of the file at the given relative path.
397
 
 
398
 
        :offsets: A list of (offset, size) tuples.
 
573
    @deprecated_method(one_four)
 
574
    def get_smart_client(self):
 
575
        """Return a smart client for this transport if possible.
 
576
 
 
577
        A smart client doesn't imply the presence of a smart server: it implies
 
578
        that the smart protocol can be tunnelled via this transport.
 
579
 
 
580
        :raises NoSmartServer: if no smart server client is available.
 
581
        """
 
582
        raise errors.NoSmartServer(self.base)
 
583
 
 
584
    def get_smart_medium(self):
 
585
        """Return a smart client medium for this transport if possible.
 
586
 
 
587
        A smart medium doesn't imply the presence of a smart server: it implies
 
588
        that the smart protocol can be tunnelled via this transport.
 
589
 
 
590
        :raises NoSmartMedium: if no smart server medium is available.
 
591
        """
 
592
        raise errors.NoSmartMedium(self)
 
593
 
 
594
    @deprecated_method(one_four)
 
595
    def get_shared_medium(self):
 
596
        """Return a smart client shared medium for this transport if possible.
 
597
 
 
598
        A smart medium doesn't imply the presence of a smart server: it implies
 
599
        that the smart protocol can be tunnelled via this transport.
 
600
 
 
601
        :raises NoSmartMedium: if no smart server medium is available.
 
602
        """
 
603
        raise errors.NoSmartMedium(self)
 
604
 
 
605
    def readv(self, relpath, offsets, adjust_for_latency=False,
 
606
        upper_limit=None):
 
607
        """Get parts of the file at the given relative path.
 
608
 
 
609
        :param relpath: The path to read data from.
 
610
        :param offsets: A list of (offset, size) tuples.
 
611
        :param adjust_for_latency: Adjust the requested offsets to accomodate
 
612
            transport latency. This may re-order the offsets, expand them to
 
613
            grab adjacent data when there is likely a high cost to requesting
 
614
            data relative to delivering it.
 
615
        :param upper_limit: When adjust_for_latency is True setting upper_limit
 
616
            allows the caller to tell the transport about the length of the
 
617
            file, so that requests are not issued for ranges beyond the end of
 
618
            the file. This matters because some servers and/or transports error
 
619
            in such a case rather than just satisfying the available ranges.
 
620
            upper_limit should always be provided when adjust_for_latency is
 
621
            True, and should be the size of the file in bytes.
 
622
        :return: A list or generator of (offset, data) tuples
 
623
        """
 
624
        if adjust_for_latency:
 
625
            # Design note: We may wish to have different algorithms for the
 
626
            # expansion of the offsets per-transport. E.g. for local disk to
 
627
            # use page-aligned expansion. If that is the case consider the
 
628
            # following structure:
 
629
            #  - a test that transport.readv uses self._offset_expander or some
 
630
            #    similar attribute, to do the expansion
 
631
            #  - a test for each transport that it has some known-good offset
 
632
            #    expander
 
633
            #  - unit tests for each offset expander
 
634
            #  - a set of tests for the offset expander interface, giving
 
635
            #    baseline behaviour (which the current transport
 
636
            #    adjust_for_latency tests could be repurposed to).
 
637
            offsets = self._sort_expand_and_combine(offsets, upper_limit)
 
638
        return self._readv(relpath, offsets)
 
639
 
 
640
    def _readv(self, relpath, offsets):
 
641
        """Get parts of the file at the given relative path.
 
642
 
 
643
        :param relpath: The path to read.
 
644
        :param offsets: A list of (offset, size) tuples.
399
645
        :return: A list or generator of (offset, data) tuples
400
646
        """
401
647
        if not offsets:
402
648
            return
403
649
 
404
650
        fp = self.get(relpath)
405
 
        return self._seek_and_read(fp, offsets)
 
651
        return self._seek_and_read(fp, offsets, relpath)
406
652
 
407
 
    def _seek_and_read(self, fp, offsets):
 
653
    def _seek_and_read(self, fp, offsets, relpath='<unknown>'):
408
654
        """An implementation of readv that uses fp.seek and fp.read.
409
655
 
410
656
        This uses _coalesce_offsets to issue larger reads and fewer seeks.
432
678
            #       benchmarked.
433
679
            fp.seek(c_offset.start)
434
680
            data = fp.read(c_offset.length)
 
681
            if len(data) < c_offset.length:
 
682
                raise errors.ShortReadvError(relpath, c_offset.start,
 
683
                            c_offset.length, actual=len(data))
435
684
            for suboffset, subsize in c_offset.ranges:
436
685
                key = (c_offset.start+suboffset, subsize)
437
686
                data_map[key] = data[suboffset:suboffset+subsize]
442
691
                yield cur_offset_and_size[0], this_data
443
692
                cur_offset_and_size = offset_stack.next()
444
693
 
 
694
    def _sort_expand_and_combine(self, offsets, upper_limit):
 
695
        """Helper for readv.
 
696
 
 
697
        :param offsets: A readv vector - (offset, length) tuples.
 
698
        :param upper_limit: The highest byte offset that may be requested.
 
699
        :return: A readv vector that will read all the regions requested by
 
700
            offsets, in start-to-end order, with no duplicated regions,
 
701
            expanded by the transports recommended page size.
 
702
        """
 
703
        offsets = sorted(offsets)
 
704
        # short circuit empty requests
 
705
        if len(offsets) == 0:
 
706
            def empty_yielder():
 
707
                # Quick thunk to stop this function becoming a generator
 
708
                # itself, rather we return a generator that has nothing to
 
709
                # yield.
 
710
                if False:
 
711
                    yield None
 
712
            return empty_yielder()
 
713
        # expand by page size at either end
 
714
        maximum_expansion = self.recommended_page_size()
 
715
        new_offsets = []
 
716
        for offset, length in offsets:
 
717
            expansion = maximum_expansion - length
 
718
            if expansion < 0:
 
719
                # we're asking for more than the minimum read anyway.
 
720
                expansion = 0
 
721
            reduction = expansion / 2
 
722
            new_offset = offset - reduction
 
723
            new_length = length + expansion
 
724
            if new_offset < 0:
 
725
                # don't ask for anything < 0
 
726
                new_offset = 0
 
727
            if (upper_limit is not None and
 
728
                new_offset + new_length > upper_limit):
 
729
                new_length = upper_limit - new_offset
 
730
            new_offsets.append((new_offset, new_length))
 
731
        # combine the expanded offsets
 
732
        offsets = []
 
733
        current_offset, current_length = new_offsets[0]
 
734
        current_finish = current_length + current_offset
 
735
        for offset, length in new_offsets[1:]:
 
736
            finish = offset + length
 
737
            if offset > current_finish:
 
738
                # there is a gap, output the current accumulator and start
 
739
                # a new one for the region we're examining.
 
740
                offsets.append((current_offset, current_length))
 
741
                current_offset = offset
 
742
                current_length = length
 
743
                current_finish = finish
 
744
                continue
 
745
            if finish > current_finish:
 
746
                # extend the current accumulator to the end of the region
 
747
                # we're examining.
 
748
                current_finish = finish
 
749
                current_length = finish - current_offset
 
750
        offsets.append((current_offset, current_length))
 
751
        return offsets
 
752
 
445
753
    @staticmethod
446
 
    def _coalesce_offsets(offsets, limit, fudge_factor):
 
754
    def _coalesce_offsets(offsets, limit=0, fudge_factor=0, max_size=0):
447
755
        """Yield coalesced offsets.
448
756
 
449
757
        With a long list of neighboring requests, combine them
452
760
        Turns  [(15, 10), (25, 10)] => [(15, 20, [(0, 10), (10, 10)])]
453
761
 
454
762
        :param offsets: A list of (start, length) pairs
455
 
        :param limit: Only combine a maximum of this many pairs
456
 
                      Some transports penalize multiple reads more than
457
 
                      others, and sometimes it is better to return early.
458
 
                      0 means no limit
 
763
 
 
764
        :param limit: Only combine a maximum of this many pairs Some transports
 
765
                penalize multiple reads more than others, and sometimes it is
 
766
                better to return early.
 
767
                0 means no limit
 
768
 
459
769
        :param fudge_factor: All transports have some level of 'it is
460
770
                better to read some more data and throw it away rather 
461
771
                than seek', so collapse if we are 'close enough'
462
 
        :return: yield _CoalescedOffset objects, which have members for wher
 
772
 
 
773
        :param max_size: Create coalesced offsets no bigger than this size.
 
774
                When a single offset is bigger than 'max_size', it will keep
 
775
                its size and be alone in the coalesced offset.
 
776
                0 means no maximum size.
 
777
 
 
778
        :return: yield _CoalescedOffset objects, which have members for where
463
779
                to start, how much to read, and how to split those 
464
780
                chunks back up
465
781
        """
468
784
 
469
785
        for start, size in offsets:
470
786
            end = start + size
471
 
            if (last_end is not None 
 
787
            if (last_end is not None
472
788
                and start <= last_end + fudge_factor
473
789
                and start >= cur.start
474
 
                and (limit <= 0 or len(cur.ranges) < limit)):
 
790
                and (limit <= 0 or len(cur.ranges) < limit)
 
791
                and (max_size <= 0 or end - cur.start <= max_size)):
475
792
                cur.length = end - cur.start
476
793
                cur.ranges.append((start-cur.start, size))
477
794
            else:
502
819
            yield self.get(relpath)
503
820
            count += 1
504
821
 
505
 
    @deprecated_method(zero_eleven)
506
 
    def put(self, relpath, f, mode=None):
507
 
        """Copy the file-like object into the location.
508
 
 
509
 
        :param relpath: Location to put the contents, relative to base.
510
 
        :param f:       File-like object.
511
 
        :param mode: The mode for the newly created file, 
512
 
                     None means just use the default
513
 
        """
514
 
        if isinstance(f, str):
515
 
            return self.put_bytes(relpath, f, mode=mode)
516
 
        else:
517
 
            return self.put_file(relpath, f, mode=mode)
518
 
 
519
822
    def put_bytes(self, relpath, bytes, mode=None):
520
823
        """Atomically put the supplied bytes into the given location.
521
824
 
525
828
        :param mode: Create the file with the given mode.
526
829
        :return: None
527
830
        """
528
 
        assert isinstance(bytes, str), \
529
 
            'bytes must be a plain string, not %s' % type(bytes)
 
831
        if not isinstance(bytes, str):
 
832
            raise AssertionError(
 
833
                'bytes must be a plain string, not %s' % type(bytes))
530
834
        return self.put_file(relpath, StringIO(bytes), mode=mode)
531
835
 
532
836
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
547
851
                        create it, and then try again.
548
852
        :param dir_mode: Possible access permissions for new directories.
549
853
        """
550
 
        assert isinstance(bytes, str), \
551
 
            'bytes must be a plain string, not %s' % type(bytes)
 
854
        if not isinstance(bytes, str):
 
855
            raise AssertionError(
 
856
                'bytes must be a plain string, not %s' % type(bytes))
552
857
        self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,
553
858
                                 create_parent_dir=create_parent_dir,
554
859
                                 dir_mode=dir_mode)
560
865
        :param f:       File-like object.
561
866
        :param mode: The mode for the newly created file,
562
867
                     None means just use the default.
 
868
        :return: The length of the file that was written.
563
869
        """
564
870
        # We would like to mark this as NotImplemented, but most likely
565
871
        # transports have defined it in terms of the old api.
601
907
                self.mkdir(parent_dir, mode=dir_mode)
602
908
                return self.put_file(relpath, f, mode=mode)
603
909
 
604
 
    @deprecated_method(zero_eleven)
605
 
    def put_multi(self, files, mode=None, pb=None):
606
 
        """Put a set of files into the location.
607
 
 
608
 
        :param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
609
 
        :param pb:  An optional ProgressBar for indicating percent done.
610
 
        :param mode: The mode for the newly created files
611
 
        :return: The number of files copied.
612
 
        """
613
 
        def _put(path, f):
614
 
            if isinstance(f, str):
615
 
                self.put_bytes(path, f, mode=mode)
616
 
            else:
617
 
                self.put_file(path, f, mode=mode)
618
 
        return len(self._iterate_over(files, _put, pb, 'put', expand=True))
619
 
 
620
910
    def mkdir(self, relpath, mode=None):
621
911
        """Create a directory at the given path."""
622
912
        raise NotImplementedError(self.mkdir)
627
917
            self.mkdir(path, mode=mode)
628
918
        return len(self._iterate_over(relpaths, mkdir, pb, 'mkdir', expand=False))
629
919
 
630
 
    @deprecated_method(zero_eleven)
631
 
    def append(self, relpath, f, mode=None):
632
 
        """Append the text in the file-like object to the supplied location.
633
 
 
634
 
        returns the length of relpath before the content was written to it.
635
 
        
636
 
        If the file does not exist, it is created with the supplied mode.
 
920
    def open_write_stream(self, relpath, mode=None):
 
921
        """Open a writable file stream at relpath.
 
922
 
 
923
        A file stream is a file like object with a write() method that accepts
 
924
        bytes to write.. Buffering may occur internally until the stream is
 
925
        closed with stream.close().  Calls to readv or the get_* methods will
 
926
        be synchronised with any internal buffering that may be present.
 
927
 
 
928
        :param relpath: The relative path to the file.
 
929
        :param mode: The mode for the newly created file, 
 
930
                     None means just use the default
 
931
        :return: A FileStream. FileStream objects have two methods, write() and
 
932
            close(). There is no guarantee that data is committed to the file
 
933
            if close() has not been called (even if get() is called on the same
 
934
            path).
637
935
        """
638
 
        return self.append_file(relpath, f, mode=mode)
 
936
        raise NotImplementedError(self.open_write_stream)
639
937
 
640
938
    def append_file(self, relpath, f, mode=None):
641
939
        """Append bytes from a file-like object to a file at relpath.
667
965
 
668
966
        :returns: the length of relpath before the content was written to it.
669
967
        """
670
 
        assert isinstance(bytes, str), \
671
 
            'bytes must be a plain string, not %s' % type(bytes)
 
968
        if not isinstance(bytes, str):
 
969
            raise TypeError(
 
970
                'bytes must be a plain string, not %s' % type(bytes))
672
971
        return self.append_file(relpath, StringIO(bytes), mode=mode)
673
972
 
674
973
    def append_multi(self, files, pb=None):
861
1160
        WARNING: many transports do not support this, so trying avoid using
862
1161
        it if at all possible.
863
1162
        """
864
 
        raise errors.TransportNotPossible("This transport has not "
 
1163
        raise errors.TransportNotPossible("Transport %r has not "
865
1164
                                          "implemented list_dir "
866
1165
                                          "(but must claim to be listable "
867
 
                                          "to trigger this error).")
 
1166
                                          "to trigger this error)."
 
1167
                                          % (self))
868
1168
 
869
1169
    def lock_read(self, relpath):
870
1170
        """Lock the given file for shared (read) access.
871
 
        WARNING: many transports do not support this, so trying avoid using it
 
1171
 
 
1172
        WARNING: many transports do not support this, so trying avoid using it.
 
1173
        These methods may be removed in the future.
 
1174
 
 
1175
        Transports may raise TransportNotPossible if OS-level locks cannot be
 
1176
        taken over this transport.  
872
1177
 
873
1178
        :return: A lock object, which should contain an unlock() function.
874
1179
        """
875
 
        raise NotImplementedError(self.lock_read)
 
1180
        raise errors.TransportNotPossible("transport locks not supported on %s" % self)
876
1181
 
877
1182
    def lock_write(self, relpath):
878
1183
        """Lock the given file for exclusive (write) access.
879
 
        WARNING: many transports do not support this, so trying avoid using it
 
1184
 
 
1185
        WARNING: many transports do not support this, so trying avoid using it.
 
1186
        These methods may be removed in the future.
 
1187
 
 
1188
        Transports may raise TransportNotPossible if OS-level locks cannot be
 
1189
        taken over this transport.
880
1190
 
881
1191
        :return: A lock object, which should contain an unlock() function.
882
1192
        """
883
 
        raise NotImplementedError(self.lock_write)
 
1193
        raise errors.TransportNotPossible("transport locks not supported on %s" % self)
884
1194
 
885
1195
    def is_readonly(self):
886
1196
        """Return true if this connection cannot be written to."""
902
1212
        # several questions about the transport.
903
1213
        return False
904
1214
 
905
 
 
906
 
# jam 20060426 For compatibility we copy the functions here
907
 
# TODO: The should be marked as deprecated
908
 
urlescape = urlutils.escape
909
 
urlunescape = urlutils.unescape
910
 
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<path>.*)$')
911
 
 
912
 
 
913
 
def get_transport(base):
 
1215
    def _reuse_for(self, other_base):
 
1216
        # This is really needed for ConnectedTransport only, but it's easier to
 
1217
        # have Transport refuses to be reused than testing that the reuse
 
1218
        # should be asked to ConnectedTransport only.
 
1219
        return None
 
1220
 
 
1221
 
 
1222
class _SharedConnection(object):
 
1223
    """A connection shared between several transports."""
 
1224
 
 
1225
    def __init__(self, connection=None, credentials=None, base=None):
 
1226
        """Constructor.
 
1227
 
 
1228
        :param connection: An opaque object specific to each transport.
 
1229
 
 
1230
        :param credentials: An opaque object containing the credentials used to
 
1231
            create the connection.
 
1232
        """
 
1233
        self.connection = connection
 
1234
        self.credentials = credentials
 
1235
        self.base = base
 
1236
 
 
1237
 
 
1238
class ConnectedTransport(Transport):
 
1239
    """A transport connected to a remote server.
 
1240
 
 
1241
    This class provide the basis to implement transports that need to connect
 
1242
    to a remote server.
 
1243
 
 
1244
    Host and credentials are available as private attributes, cloning preserves
 
1245
    them and share the underlying, protocol specific, connection.
 
1246
    """
 
1247
 
 
1248
    def __init__(self, base, _from_transport=None):
 
1249
        """Constructor.
 
1250
 
 
1251
        The caller should ensure that _from_transport points at the same host
 
1252
        as the new base.
 
1253
 
 
1254
        :param base: transport root URL
 
1255
 
 
1256
        :param _from_transport: optional transport to build from. The built
 
1257
            transport will share the connection with this transport.
 
1258
        """
 
1259
        if not base.endswith('/'):
 
1260
            base += '/'
 
1261
        (self._scheme,
 
1262
         self._user, self._password,
 
1263
         self._host, self._port,
 
1264
         self._path) = self._split_url(base)
 
1265
        if _from_transport is not None:
 
1266
            # Copy the password as it does not appear in base and will be lost
 
1267
            # otherwise. It can appear in the _split_url above if the user
 
1268
            # provided it on the command line. Otherwise, daughter classes will
 
1269
            # prompt the user for one when appropriate.
 
1270
            self._password = _from_transport._password
 
1271
 
 
1272
        base = self._unsplit_url(self._scheme,
 
1273
                                 self._user, self._password,
 
1274
                                 self._host, self._port,
 
1275
                                 self._path)
 
1276
 
 
1277
        super(ConnectedTransport, self).__init__(base)
 
1278
        if _from_transport is None:
 
1279
            self._shared_connection = _SharedConnection()
 
1280
        else:
 
1281
            self._shared_connection = _from_transport._shared_connection
 
1282
 
 
1283
    def clone(self, offset=None):
 
1284
        """Return a new transport with root at self.base + offset
 
1285
 
 
1286
        We leave the daughter classes take advantage of the hint
 
1287
        that it's a cloning not a raw creation.
 
1288
        """
 
1289
        if offset is None:
 
1290
            return self.__class__(self.base, _from_transport=self)
 
1291
        else:
 
1292
            return self.__class__(self.abspath(offset), _from_transport=self)
 
1293
 
 
1294
    @staticmethod
 
1295
    def _split_url(url):
 
1296
        """
 
1297
        Extract the server address, the credentials and the path from the url.
 
1298
 
 
1299
        user, password, host and path should be quoted if they contain reserved
 
1300
        chars.
 
1301
 
 
1302
        :param url: an quoted url
 
1303
 
 
1304
        :return: (scheme, user, password, host, port, path) tuple, all fields
 
1305
            are unquoted.
 
1306
        """
 
1307
        if isinstance(url, unicode):
 
1308
            raise errors.InvalidURL('should be ascii:\n%r' % url)
 
1309
        url = url.encode('utf-8')
 
1310
        (scheme, netloc, path, params,
 
1311
         query, fragment) = urlparse.urlparse(url, allow_fragments=False)
 
1312
        user = password = host = port = None
 
1313
        if '@' in netloc:
 
1314
            user, host = netloc.rsplit('@', 1)
 
1315
            if ':' in user:
 
1316
                user, password = user.split(':', 1)
 
1317
                password = urllib.unquote(password)
 
1318
            user = urllib.unquote(user)
 
1319
        else:
 
1320
            host = netloc
 
1321
 
 
1322
        if ':' in host:
 
1323
            host, port = host.rsplit(':', 1)
 
1324
            try:
 
1325
                port = int(port)
 
1326
            except ValueError:
 
1327
                raise errors.InvalidURL('invalid port number %s in url:\n%s' %
 
1328
                                        (port, url))
 
1329
        if host == '':
 
1330
            raise errors.InvalidURL('Host empty in: %s' % url)
 
1331
 
 
1332
        host = urllib.unquote(host)
 
1333
        path = urllib.unquote(path)
 
1334
 
 
1335
        return (scheme, user, password, host, port, path)
 
1336
 
 
1337
    @staticmethod
 
1338
    def _unsplit_url(scheme, user, password, host, port, path):
 
1339
        """
 
1340
        Build the full URL for the given already URL encoded path.
 
1341
 
 
1342
        user, password, host and path will be quoted if they contain reserved
 
1343
        chars.
 
1344
 
 
1345
        :param scheme: protocol
 
1346
 
 
1347
        :param user: login
 
1348
 
 
1349
        :param password: associated password
 
1350
 
 
1351
        :param host: the server address
 
1352
 
 
1353
        :param port: the associated port
 
1354
 
 
1355
        :param path: the absolute path on the server
 
1356
 
 
1357
        :return: The corresponding URL.
 
1358
        """
 
1359
        netloc = urllib.quote(host)
 
1360
        if user is not None:
 
1361
            # Note that we don't put the password back even if we
 
1362
            # have one so that it doesn't get accidentally
 
1363
            # exposed.
 
1364
            netloc = '%s@%s' % (urllib.quote(user), netloc)
 
1365
        if port is not None:
 
1366
            netloc = '%s:%d' % (netloc, port)
 
1367
        path = urllib.quote(path)
 
1368
        return urlparse.urlunparse((scheme, netloc, path, None, None, None))
 
1369
 
 
1370
    def relpath(self, abspath):
 
1371
        """Return the local path portion from a given absolute path"""
 
1372
        scheme, user, password, host, port, path = self._split_url(abspath)
 
1373
        error = []
 
1374
        if (scheme != self._scheme):
 
1375
            error.append('scheme mismatch')
 
1376
        if (user != self._user):
 
1377
            error.append('user name mismatch')
 
1378
        if (host != self._host):
 
1379
            error.append('host mismatch')
 
1380
        if (port != self._port):
 
1381
            error.append('port mismatch')
 
1382
        if not (path == self._path[:-1] or path.startswith(self._path)):
 
1383
            error.append('path mismatch')
 
1384
        if error:
 
1385
            extra = ', '.join(error)
 
1386
            raise errors.PathNotChild(abspath, self.base, extra=extra)
 
1387
        pl = len(self._path)
 
1388
        return path[pl:].strip('/')
 
1389
 
 
1390
    def abspath(self, relpath):
 
1391
        """Return the full url to the given relative path.
 
1392
        
 
1393
        :param relpath: the relative path urlencoded
 
1394
 
 
1395
        :returns: the Unicode version of the absolute path for relpath.
 
1396
        """
 
1397
        relative = urlutils.unescape(relpath).encode('utf-8')
 
1398
        path = self._combine_paths(self._path, relative)
 
1399
        return self._unsplit_url(self._scheme, self._user, self._password,
 
1400
                                 self._host, self._port,
 
1401
                                 path)
 
1402
 
 
1403
    def _remote_path(self, relpath):
 
1404
        """Return the absolute path part of the url to the given relative path.
 
1405
 
 
1406
        This is the path that the remote server expect to receive in the
 
1407
        requests, daughter classes should redefine this method if needed and
 
1408
        use the result to build their requests.
 
1409
 
 
1410
        :param relpath: the path relative to the transport base urlencoded.
 
1411
 
 
1412
        :return: the absolute Unicode path on the server,
 
1413
        """
 
1414
        relative = urlutils.unescape(relpath).encode('utf-8')
 
1415
        remote_path = self._combine_paths(self._path, relative)
 
1416
        return remote_path
 
1417
 
 
1418
    def _get_shared_connection(self):
 
1419
        """Get the object shared amongst cloned transports.
 
1420
 
 
1421
        This should be used only by classes that needs to extend the sharing
 
1422
        with objects other than transports.
 
1423
 
 
1424
        Use _get_connection to get the connection itself.
 
1425
        """
 
1426
        return self._shared_connection
 
1427
 
 
1428
    def _set_connection(self, connection, credentials=None):
 
1429
        """Record a newly created connection with its associated credentials.
 
1430
 
 
1431
        Note: To ensure that connection is still shared after a temporary
 
1432
        failure and a new one needs to be created, daughter classes should
 
1433
        always call this method to set the connection and do so each time a new
 
1434
        connection is created.
 
1435
 
 
1436
        :param connection: An opaque object representing the connection used by
 
1437
            the daughter class.
 
1438
 
 
1439
        :param credentials: An opaque object representing the credentials
 
1440
            needed to create the connection.
 
1441
        """
 
1442
        self._shared_connection.connection = connection
 
1443
        self._shared_connection.credentials = credentials
 
1444
 
 
1445
    def _get_connection(self):
 
1446
        """Returns the transport specific connection object."""
 
1447
        return self._shared_connection.connection
 
1448
 
 
1449
    def _get_credentials(self):
 
1450
        """Returns the credentials used to establish the connection."""
 
1451
        return self._shared_connection.credentials
 
1452
 
 
1453
    def _update_credentials(self, credentials):
 
1454
        """Update the credentials of the current connection.
 
1455
 
 
1456
        Some protocols can renegociate the credentials within a connection,
 
1457
        this method allows daughter classes to share updated credentials.
 
1458
        
 
1459
        :param credentials: the updated credentials.
 
1460
        """
 
1461
        # We don't want to call _set_connection here as we are only updating
 
1462
        # the credentials not creating a new connection.
 
1463
        self._shared_connection.credentials = credentials
 
1464
 
 
1465
    def _reuse_for(self, other_base):
 
1466
        """Returns a transport sharing the same connection if possible.
 
1467
 
 
1468
        Note: we share the connection if the expected credentials are the
 
1469
        same: (host, port, user). Some protocols may disagree and redefine the
 
1470
        criteria in daughter classes.
 
1471
 
 
1472
        Note: we don't compare the passwords here because other_base may have
 
1473
        been obtained from an existing transport.base which do not mention the
 
1474
        password.
 
1475
 
 
1476
        :param other_base: the URL we want to share the connection with.
 
1477
 
 
1478
        :return: A new transport or None if the connection cannot be shared.
 
1479
        """
 
1480
        try:
 
1481
            (scheme, user, password,
 
1482
             host, port, path) = self._split_url(other_base)
 
1483
        except errors.InvalidURL:
 
1484
            # No hope in trying to reuse an existing transport for an invalid
 
1485
            # URL
 
1486
            return None
 
1487
 
 
1488
        transport = None
 
1489
        # Don't compare passwords, they may be absent from other_base or from
 
1490
        # self and they don't carry more information than user anyway.
 
1491
        if (scheme == self._scheme
 
1492
            and user == self._user
 
1493
            and host == self._host
 
1494
            and port == self._port):
 
1495
            if not path.endswith('/'):
 
1496
                # This normally occurs at __init__ time, but it's easier to do
 
1497
                # it now to avoid creating two transports for the same base.
 
1498
                path += '/'
 
1499
            if self._path  == path:
 
1500
                # shortcut, it's really the same transport
 
1501
                return self
 
1502
            # We don't call clone here because the intent is different: we
 
1503
            # build a new transport on a different base (which may be totally
 
1504
            # unrelated) but we share the connection.
 
1505
            transport = self.__class__(other_base, _from_transport=self)
 
1506
        return transport
 
1507
 
 
1508
 
 
1509
# We try to recognize an url lazily (ignoring user, password, etc)
 
1510
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<rest>.*)$')
 
1511
 
 
1512
def get_transport(base, possible_transports=None):
914
1513
    """Open a transport to access a URL or directory.
915
1514
 
916
 
    base is either a URL or a directory name.  
 
1515
    :param base: either a URL or a directory name.
 
1516
 
 
1517
    :param transports: optional reusable transports list. If not None, created
 
1518
        transports will be added to the list.
 
1519
 
 
1520
    :return: A new transport optionally sharing its connection with one of
 
1521
        possible_transports.
917
1522
    """
918
 
    # TODO: give a better error if base looks like a url but there's no
919
 
    # handler for the scheme?
920
 
    global _protocol_handlers
921
1523
    if base is None:
922
1524
        base = '.'
923
 
 
924
1525
    last_err = None
 
1526
    from bzrlib.directory_service import directories
 
1527
    base = directories.dereference(base)
925
1528
 
926
1529
    def convert_path_to_url(base, error_str):
927
1530
        m = _urlRE.match(base)
928
1531
        if m:
929
1532
            # This looks like a URL, but we weren't able to 
930
1533
            # instantiate it as such raise an appropriate error
 
1534
            # FIXME: we have a 'error_str' unused and we use last_err below
931
1535
            raise errors.UnsupportedProtocol(base, last_err)
932
1536
        # This doesn't look like a protocol, consider it a local path
933
1537
        new_base = urlutils.local_path_to_url(base)
941
1545
        # Only local paths can be Unicode
942
1546
        base = convert_path_to_url(base,
943
1547
            'URLs must be properly escaped (protocol: %s)')
944
 
    
945
 
    for proto, factory_list in _protocol_handlers.iteritems():
 
1548
 
 
1549
    transport = None
 
1550
    if possible_transports is not None:
 
1551
        for t in possible_transports:
 
1552
            t_same_connection = t._reuse_for(base)
 
1553
            if t_same_connection is not None:
 
1554
                # Add only new transports
 
1555
                if t_same_connection not in possible_transports:
 
1556
                    possible_transports.append(t_same_connection)
 
1557
                return t_same_connection
 
1558
 
 
1559
    for proto, factory_list in transport_list_registry.iteritems():
946
1560
        if proto is not None and base.startswith(proto):
947
 
            t, last_err = _try_transport_factories(base, factory_list)
948
 
            if t:
949
 
                return t
 
1561
            transport, last_err = _try_transport_factories(base, factory_list)
 
1562
            if transport:
 
1563
                if possible_transports is not None:
 
1564
                    if transport in possible_transports:
 
1565
                        raise AssertionError()
 
1566
                    possible_transports.append(transport)
 
1567
                return transport
950
1568
 
951
1569
    # We tried all the different protocols, now try one last time
952
1570
    # as a local protocol
953
1571
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
954
1572
 
955
1573
    # The default handler is the filesystem handler, stored as protocol None
956
 
    return _try_transport_factories(base, _protocol_handlers[None])[0]
 
1574
    factory_list = transport_list_registry.get(None)
 
1575
    transport, last_err = _try_transport_factories(base, factory_list)
 
1576
 
 
1577
    return transport
957
1578
 
958
1579
 
959
1580
def _try_transport_factories(base, factory_list):
960
1581
    last_err = None
961
1582
    for factory in factory_list:
962
1583
        try:
963
 
            return factory(base), None
964
 
        except DependencyNotPresent, e:
 
1584
            return factory.get_obj()(base), None
 
1585
        except errors.DependencyNotPresent, e:
965
1586
            mutter("failed to instantiate transport %r for %r: %r" %
966
1587
                    (factory, base, e))
967
1588
            last_err = e
969
1590
    return None, last_err
970
1591
 
971
1592
 
 
1593
def do_catching_redirections(action, transport, redirected):
 
1594
    """Execute an action with given transport catching redirections.
 
1595
 
 
1596
    This is a facility provided for callers needing to follow redirections
 
1597
    silently. The silence is relative: it is the caller responsability to
 
1598
    inform the user about each redirection or only inform the user of a user
 
1599
    via the exception parameter.
 
1600
 
 
1601
    :param action: A callable, what the caller want to do while catching
 
1602
                  redirections.
 
1603
    :param transport: The initial transport used.
 
1604
    :param redirected: A callable receiving the redirected transport and the 
 
1605
                  RedirectRequested exception.
 
1606
 
 
1607
    :return: Whatever 'action' returns
 
1608
    """
 
1609
    MAX_REDIRECTIONS = 8
 
1610
 
 
1611
    # If a loop occurs, there is little we can do. So we don't try to detect
 
1612
    # them, just getting out if too much redirections occurs. The solution
 
1613
    # is outside: where the loop is defined.
 
1614
    for redirections in range(MAX_REDIRECTIONS):
 
1615
        try:
 
1616
            return action(transport)
 
1617
        except errors.RedirectRequested, e:
 
1618
            redirection_notice = '%s is%s redirected to %s' % (
 
1619
                e.source, e.permanently, e.target)
 
1620
            transport = redirected(transport, e, redirection_notice)
 
1621
    else:
 
1622
        # Loop exited without resolving redirect ? Either the
 
1623
        # user has kept a very very very old reference or a loop
 
1624
        # occurred in the redirections.  Nothing we can cure here:
 
1625
        # tell the user. Note that as the user has been informed
 
1626
        # about each redirection (it is the caller responsibility
 
1627
        # to do that in redirected via the provided
 
1628
        # redirection_notice). The caller may provide more
 
1629
        # information if needed (like what file or directory we
 
1630
        # were trying to act upon when the redirection loop
 
1631
        # occurred).
 
1632
        raise errors.TooManyRedirections
 
1633
 
 
1634
 
972
1635
class Server(object):
973
1636
    """A Transport Server.
974
1637
    
1001
1664
        raise NotImplementedError
1002
1665
 
1003
1666
    def get_bogus_url(self):
1004
 
        """Return a url for this protocol, that will fail to connect."""
 
1667
        """Return a url for this protocol, that will fail to connect.
 
1668
        
 
1669
        This may raise NotImplementedError to indicate that this server cannot
 
1670
        provide bogus urls.
 
1671
        """
1005
1672
        raise NotImplementedError
1006
1673
 
1007
1674
 
1008
 
class TransportTestProviderAdapter(object):
1009
 
    """A tool to generate a suite testing all transports for a single test.
1010
 
 
1011
 
    This is done by copying the test once for each transport and injecting
1012
 
    the transport_class and transport_server classes into each copy. Each copy
1013
 
    is also given a new id() to make it easy to identify.
1014
 
    """
1015
 
 
1016
 
    def adapt(self, test):
1017
 
        result = TestSuite()
1018
 
        for klass, server_factory in self._test_permutations():
1019
 
            new_test = deepcopy(test)
1020
 
            new_test.transport_class = klass
1021
 
            new_test.transport_server = server_factory
1022
 
            def make_new_test_id():
1023
 
                new_id = "%s(%s)" % (new_test.id(), server_factory.__name__)
1024
 
                return lambda: new_id
1025
 
            new_test.id = make_new_test_id()
1026
 
            result.addTest(new_test)
1027
 
        return result
1028
 
 
1029
 
    def get_transport_test_permutations(self, module):
1030
 
        """Get the permutations module wants to have tested."""
1031
 
        if getattr(module, 'get_test_permutations', None) is None:
1032
 
            warning("transport module %s doesn't provide get_test_permutations()"
1033
 
                    % module.__name__)
1034
 
            return []
1035
 
        return module.get_test_permutations()
1036
 
 
1037
 
    def _test_permutations(self):
1038
 
        """Return a list of the klass, server_factory pairs to test."""
1039
 
        result = []
1040
 
        for module in _get_transport_modules():
1041
 
            try:
1042
 
                result.extend(self.get_transport_test_permutations(reduce(getattr, 
1043
 
                    (module).split('.')[1:],
1044
 
                     __import__(module))))
1045
 
            except errors.DependencyNotPresent, e:
1046
 
                # Continue even if a dependency prevents us 
1047
 
                # from running this test
1048
 
                pass
1049
 
        return result
1050
 
 
1051
 
 
1052
 
class TransportLogger(object):
1053
 
    """Adapt a transport to get clear logging data on api calls.
1054
 
    
1055
 
    Feel free to extend to log whatever calls are of interest.
1056
 
    """
1057
 
 
1058
 
    def __init__(self, adapted):
1059
 
        self._adapted = adapted
1060
 
        self._calls = []
1061
 
 
1062
 
    def get(self, name):
1063
 
        self._calls.append((name,))
1064
 
        return self._adapted.get(name)
1065
 
 
1066
 
    def __getattr__(self, name):
1067
 
        """Thunk all undefined access through to self._adapted."""
1068
 
        # raise AttributeError, name 
1069
 
        return getattr(self._adapted, name)
1070
 
 
1071
 
    def readv(self, name, offsets):
1072
 
        self._calls.append((name, offsets))
1073
 
        return self._adapted.readv(name, offsets)
1074
 
        
1075
 
 
1076
1675
# None is the default transport, for things with no url scheme
1077
 
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
 
1676
register_transport_proto('file://',
 
1677
            help="Access using the standard filesystem (default)")
1078
1678
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
 
1679
transport_list_registry.set_default_transport("file://")
 
1680
 
 
1681
register_transport_proto('sftp://',
 
1682
            help="Access using SFTP (most SSH servers provide SFTP).",
 
1683
            register_netloc=True)
1079
1684
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
 
1685
# Decorated http transport
 
1686
register_transport_proto('http+urllib://',
 
1687
#                help="Read-only access of branches exported on the web."
 
1688
                         register_netloc=True)
1080
1689
register_lazy_transport('http+urllib://', 'bzrlib.transport.http._urllib',
1081
1690
                        'HttpTransport_urllib')
 
1691
register_transport_proto('https+urllib://',
 
1692
#                help="Read-only access of branches exported on the web using SSL."
 
1693
                         register_netloc=True)
1082
1694
register_lazy_transport('https+urllib://', 'bzrlib.transport.http._urllib',
1083
1695
                        'HttpTransport_urllib')
 
1696
register_transport_proto('http+pycurl://',
 
1697
#                help="Read-only access of branches exported on the web."
 
1698
                         register_netloc=True)
1084
1699
register_lazy_transport('http+pycurl://', 'bzrlib.transport.http._pycurl',
1085
1700
                        'PyCurlTransport')
 
1701
register_transport_proto('https+pycurl://',
 
1702
#                help="Read-only access of branches exported on the web using SSL."
 
1703
                         register_netloc=True)
1086
1704
register_lazy_transport('https+pycurl://', 'bzrlib.transport.http._pycurl',
1087
1705
                        'PyCurlTransport')
 
1706
# Default http transports (last declared wins (if it can be imported))
 
1707
register_transport_proto('http://',
 
1708
                 help="Read-only access of branches exported on the web.")
 
1709
register_transport_proto('https://',
 
1710
            help="Read-only access of branches exported on the web using SSL.")
1088
1711
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1089
1712
                        'HttpTransport_urllib')
1090
1713
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1091
1714
                        'HttpTransport_urllib')
1092
 
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
1093
 
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
 
1715
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
 
1716
                        'PyCurlTransport')
 
1717
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
 
1718
                        'PyCurlTransport')
 
1719
 
 
1720
register_transport_proto('ftp://', help="Access using passive FTP.")
1094
1721
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
 
1722
register_transport_proto('aftp://', help="Access using active FTP.")
1095
1723
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1096
 
register_lazy_transport('memory://', 'bzrlib.transport.memory', 'MemoryTransport')
1097
 
register_lazy_transport('readonly+', 'bzrlib.transport.readonly', 'ReadonlyTransportDecorator')
1098
 
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs', 'FakeNFSTransportDecorator')
1099
 
register_lazy_transport('vfat+', 
 
1724
 
 
1725
register_transport_proto('memory://')
 
1726
register_lazy_transport('memory://', 'bzrlib.transport.memory',
 
1727
                        'MemoryTransport')
 
1728
 
 
1729
# chroots cannot be implicitly accessed, they must be explicitly created:
 
1730
register_transport_proto('chroot+')
 
1731
 
 
1732
register_transport_proto('readonly+',
 
1733
#              help="This modifier converts any transport to be readonly."
 
1734
            )
 
1735
register_lazy_transport('readonly+', 'bzrlib.transport.readonly',
 
1736
                        'ReadonlyTransportDecorator')
 
1737
 
 
1738
register_transport_proto('fakenfs+')
 
1739
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs',
 
1740
                        'FakeNFSTransportDecorator')
 
1741
 
 
1742
register_transport_proto('trace+')
 
1743
register_lazy_transport('trace+', 'bzrlib.transport.trace',
 
1744
                        'TransportTraceDecorator')
 
1745
 
 
1746
register_transport_proto('unlistable+')
 
1747
register_lazy_transport('unlistable+', 'bzrlib.transport.unlistable',
 
1748
                        'UnlistableTransportDecorator')
 
1749
 
 
1750
register_transport_proto('brokenrename+')
 
1751
register_lazy_transport('brokenrename+', 'bzrlib.transport.brokenrename',
 
1752
                        'BrokenRenameTransportDecorator')
 
1753
 
 
1754
register_transport_proto('vfat+')
 
1755
register_lazy_transport('vfat+',
1100
1756
                        'bzrlib.transport.fakevfat',
1101
1757
                        'FakeVFATTransportDecorator')
 
1758
 
 
1759
register_transport_proto('nosmart+')
 
1760
register_lazy_transport('nosmart+', 'bzrlib.transport.nosmart',
 
1761
                        'NoSmartTransportDecorator')
 
1762
 
 
1763
# These two schemes were registered, but don't seem to have an actual transport
 
1764
# protocol registered
 
1765
for scheme in ['ssh', 'bzr+loopback']:
 
1766
    register_urlparse_netloc_protocol(scheme)
 
1767
del scheme
 
1768
 
 
1769
register_transport_proto('bzr://',
 
1770
            help="Fast access using the Bazaar smart server.",
 
1771
                         register_netloc=True)
 
1772
 
 
1773
register_lazy_transport('bzr://', 'bzrlib.transport.remote',
 
1774
                        'RemoteTCPTransport')
 
1775
register_transport_proto('bzr-v2://', register_netloc=True)
 
1776
 
 
1777
register_lazy_transport('bzr-v2://', 'bzrlib.transport.remote',
 
1778
                        'RemoteTCPTransportV2Only')
 
1779
register_transport_proto('bzr+http://',
 
1780
#                help="Fast access using the Bazaar smart server over HTTP."
 
1781
                         register_netloc=True)
 
1782
register_lazy_transport('bzr+http://', 'bzrlib.transport.remote',
 
1783
                        'RemoteHTTPTransport')
 
1784
register_transport_proto('bzr+https://',
 
1785
#                help="Fast access using the Bazaar smart server over HTTPS."
 
1786
                         register_netloc=True)
 
1787
register_lazy_transport('bzr+https://',
 
1788
                        'bzrlib.transport.remote',
 
1789
                        'RemoteHTTPTransport')
 
1790
register_transport_proto('bzr+ssh://',
 
1791
            help="Fast access using the Bazaar smart server over SSH.",
 
1792
            register_netloc=True)
 
1793
register_lazy_transport('bzr+ssh://', 'bzrlib.transport.remote',
 
1794
                        'RemoteSSHTransport')