~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: Alexander Belchenko
  • Date: 2006-07-30 16:43:12 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060730164312-b025fd3ff0cee59e
rename  gpl.txt => COPYING.txt

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 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
12
12
#
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
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Transport is an abstraction layer to handle file access.
18
18
 
26
26
it.
27
27
"""
28
28
 
29
 
from cStringIO import StringIO
 
29
import errno
 
30
from collections import deque
 
31
from copy import deepcopy
30
32
import re
 
33
from stat import S_ISDIR
31
34
import sys
32
 
 
33
 
from bzrlib.lazy_import import lazy_import
34
 
lazy_import(globals(), """
35
 
import errno
36
 
from stat import S_ISDIR
 
35
from unittest import TestSuite
37
36
import urllib
38
37
import urlparse
39
 
 
40
 
from bzrlib import (
41
 
    errors,
42
 
    osutils,
43
 
    symbol_versioning,
44
 
    ui,
45
 
    urlutils,
46
 
    )
47
 
""")
48
 
 
49
 
from bzrlib.symbol_versioning import (
 
38
import warnings
 
39
 
 
40
import bzrlib
 
41
import bzrlib.errors as errors
 
42
from bzrlib.errors import DependencyNotPresent
 
43
import bzrlib.osutils as osutils
 
44
from bzrlib.osutils import pumpfile
 
45
from bzrlib.symbol_versioning import (deprecated_passed, deprecated_method, deprecated_function, 
50
46
        DEPRECATED_PARAMETER,
51
 
        )
52
 
from bzrlib.trace import (
53
 
    mutter,
54
 
    )
55
 
from bzrlib import registry
56
 
 
57
 
 
58
 
# a dictionary of open file streams. Keys are absolute paths, values are
59
 
# transport defined.
60
 
_file_streams = {}
 
47
        zero_eight)
 
48
from bzrlib.trace import mutter, warning
 
49
import bzrlib.urlutils as urlutils
 
50
 
 
51
# {prefix: [transport_classes]}
 
52
# Transports are inserted onto the list LIFO and tried in order; as a result
 
53
# transports provided by plugins are tried first, which is usually what we
 
54
# want.
 
55
_protocol_handlers = {
 
56
}
 
57
 
 
58
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
 
59
    """Register a transport that can be used to open URLs
 
60
 
 
61
    Normally you should use register_lazy_transport, which defers loading the
 
62
    implementation until it's actually used, and so avoids pulling in possibly
 
63
    large implementation libraries.
 
64
    """
 
65
    # Note that this code runs very early in library setup -- trace may not be
 
66
    # working, etc.
 
67
    global _protocol_handlers
 
68
    if deprecated_passed(override):
 
69
        warnings.warn("register_transport(override) is deprecated")
 
70
    _protocol_handlers.setdefault(prefix, []).insert(0, klass)
 
71
 
 
72
 
 
73
def register_lazy_transport(scheme, module, classname):
 
74
    """Register lazy-loaded transport class.
 
75
 
 
76
    When opening a URL with the given scheme, load the module and then
 
77
    instantiate the particular class.  
 
78
 
 
79
    If the module raises DependencyNotPresent when it's imported, it is
 
80
    skipped and another implementation of the protocol is tried.  This is
 
81
    intended to be used when the implementation depends on an external
 
82
    implementation that may not be present.  If any other error is raised, it
 
83
    propagates up and the attempt to open the url fails.
 
84
    """
 
85
    # TODO: If no implementation of a protocol is available because of missing
 
86
    # dependencies, we should perhaps show the message about what dependency
 
87
    # was missing.
 
88
    def _loader(base):
 
89
        mod = __import__(module, globals(), locals(), [classname])
 
90
        klass = getattr(mod, classname)
 
91
        return klass(base)
 
92
    _loader.module = module
 
93
    register_transport(scheme, _loader)
61
94
 
62
95
 
63
96
def _get_protocol_handlers():
64
97
    """Return a dictionary of {urlprefix: [factory]}"""
65
 
    return transport_list_registry
 
98
    return _protocol_handlers
66
99
 
67
100
 
68
101
def _set_protocol_handlers(new_handlers):
70
103
 
71
104
    WARNING this will remove all build in protocols. Use with care.
72
105
    """
73
 
    global transport_list_registry
74
 
    transport_list_registry = new_handlers
 
106
    global _protocol_handlers
 
107
    _protocol_handlers = new_handlers
75
108
 
76
109
 
77
110
def _clear_protocol_handlers():
78
 
    global transport_list_registry
79
 
    transport_list_registry = TransportListRegistry()
 
111
    global _protocol_handlers
 
112
    _protocol_handlers = {}
80
113
 
81
114
 
82
115
def _get_transport_modules():
83
116
    """Return a list of the modules providing transports."""
84
117
    modules = set()
85
 
    for prefix, factory_list in transport_list_registry.items():
 
118
    for prefix, factory_list in _protocol_handlers.items():
86
119
        for factory in factory_list:
87
 
            if hasattr(factory, "_module_name"):
88
 
                modules.add(factory._module_name)
 
120
            if factory.__module__ == "bzrlib.transport":
 
121
                # this is a lazy load transport, because no real ones
 
122
                # are directlry in bzrlib.transport
 
123
                modules.add(factory.module)
89
124
            else:
90
 
                modules.add(factory._obj.__module__)
91
 
    # Add chroot and pathfilter directly, because there is no handler
92
 
    # registered for it.
93
 
    modules.add('bzrlib.transport.chroot')
94
 
    modules.add('bzrlib.transport.pathfilter')
 
125
                modules.add(factory.__module__)
95
126
    result = list(modules)
96
127
    result.sort()
97
128
    return result
98
129
 
99
130
 
100
 
class TransportListRegistry(registry.Registry):
101
 
    """A registry which simplifies tracking available Transports.
102
 
 
103
 
    A registration of a new protocol requires two steps:
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 multiple protocols ( 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
 
 
154
131
def register_urlparse_netloc_protocol(protocol):
155
132
    """Ensure that protocol is setup to be used with urlparse netloc parsing."""
156
133
    if protocol not in urlparse.uses_netloc:
157
134
        urlparse.uses_netloc.append(protocol)
158
135
 
159
136
 
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)
 
137
def split_url(url):
 
138
    # TODO: jam 20060606 urls should only be ascii, or they should raise InvalidURL
 
139
    if isinstance(url, unicode):
 
140
        url = url.encode('utf-8')
 
141
    (scheme, netloc, path, params,
 
142
     query, fragment) = urlparse.urlparse(url, allow_fragments=False)
 
143
    username = password = host = port = None
 
144
    if '@' in netloc:
 
145
        username, host = netloc.split('@', 1)
 
146
        if ':' in username:
 
147
            username, password = username.split(':', 1)
 
148
            password = urllib.unquote(password)
 
149
        username = urllib.unquote(username)
 
150
    else:
 
151
        host = netloc
 
152
 
 
153
    if ':' in host:
 
154
        host, port = host.rsplit(':', 1)
 
155
        try:
 
156
            port = int(port)
 
157
        except ValueError:
 
158
            # TODO: Should this be ConnectionError?
 
159
            raise errors.TransportError('%s: invalid port number' % port)
 
160
    host = urllib.unquote(host)
 
161
 
 
162
    path = urllib.unquote(path)
 
163
 
 
164
    return (scheme, username, password, host, port, path)
180
165
 
181
166
 
182
167
class _CoalescedOffset(object):
193
178
        return cmp((self.start, self.length, self.ranges),
194
179
                   (other.start, other.length, other.ranges))
195
180
 
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
 
        osutils.pump_string_file(bytes, self.file_handle)
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
 
 
269
181
 
270
182
class Transport(object):
271
183
    """This class encapsulates methods for retrieving or putting a file
272
184
    from/to a storage location.
273
185
 
274
186
    Most functions have a _multi variant, which allows you to queue up
275
 
    multiple requests. They generally have a dumb base implementation
 
187
    multiple requests. They generally have a dumb base implementation 
276
188
    which just iterates over the arguments, but smart Transport
277
189
    implementations can do pipelining.
278
190
    In general implementations should support having a generator or a list
279
191
    as an argument (ie always iterate, never index)
280
 
 
281
 
    :ivar base: Base URL for the transport; should always end in a slash.
282
192
    """
283
193
 
284
194
    # implementations can override this if it is more efficient
301
211
 
302
212
        This handles things like ENOENT, ENOTDIR, EEXIST, and EACCESS
303
213
        """
304
 
        if getattr(e, 'errno', None) is not None:
305
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
214
        if hasattr(e, 'errno'):
 
215
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
306
216
                raise errors.NoSuchFile(path, extra=e)
307
217
            # I would rather use errno.EFOO, but there doesn't seem to be
308
218
            # any matching for 267
323
233
 
324
234
    def clone(self, offset=None):
325
235
        """Return a new Transport object, cloned from the current location,
326
 
        using a subdirectory or parent directory. This allows connections
 
236
        using a subdirectory or parent directory. This allows connections 
327
237
        to be pooled, rather than a new one needed for each subdir.
328
238
        """
329
239
        raise NotImplementedError(self.clone)
330
240
 
331
 
    def create_prefix(self):
332
 
        """Create all the directories leading down to self.base."""
333
 
        cur_transport = self
334
 
        needed = [cur_transport]
335
 
        # Recurse upwards until we can create a directory successfully
336
 
        while True:
337
 
            new_transport = cur_transport.clone('..')
338
 
            if new_transport.base == cur_transport.base:
339
 
                raise errors.BzrCommandError(
340
 
                    "Failed to create path prefix for %s."
341
 
                    % cur_transport.base)
342
 
            try:
343
 
                new_transport.mkdir('.')
344
 
            except errors.NoSuchFile:
345
 
                needed.append(new_transport)
346
 
                cur_transport = new_transport
347
 
            except errors.FileExists:
348
 
                break
349
 
            else:
350
 
                break
351
 
        # Now we only need to create child directories
352
 
        while needed:
353
 
            cur_transport = needed.pop()
354
 
            cur_transport.ensure_base()
355
 
 
356
 
    def ensure_base(self):
357
 
        """Ensure that the directory this transport references exists.
358
 
 
359
 
        This will create a directory if it doesn't exist.
360
 
        :return: True if the directory was created, False otherwise.
361
 
        """
362
 
        # The default implementation just uses "Easier to ask for forgiveness
363
 
        # than permission". We attempt to create the directory, and just
364
 
        # suppress FileExists and PermissionDenied (for Windows) exceptions.
365
 
        try:
366
 
            self.mkdir('.')
367
 
        except (errors.FileExists, errors.PermissionDenied):
368
 
            return False
369
 
        else:
370
 
            return True
371
 
 
372
 
    def external_url(self):
373
 
        """Return a URL for self that can be given to an external process.
374
 
 
375
 
        There is no guarantee that the URL can be accessed from a different
376
 
        machine - e.g. file:/// urls are only usable on the local machine,
377
 
        sftp:/// urls when the server is only bound to localhost are only
378
 
        usable from localhost etc.
379
 
 
380
 
        NOTE: This method may remove security wrappers (e.g. on chroot
381
 
        transports) and thus should *only* be used when the result will not
382
 
        be used to obtain a new transport within bzrlib. Ideally chroot
383
 
        transports would know enough to cause the external url to be the exact
384
 
        one used that caused the chrooting in the first place, but that is not
385
 
        currently the case.
386
 
 
387
 
        :return: A URL that can be given to another process.
388
 
        :raises InProcessTransport: If the transport is one that cannot be
389
 
            accessed out of the current process (e.g. a MemoryTransport)
390
 
            then InProcessTransport is raised.
391
 
        """
392
 
        raise NotImplementedError(self.external_url)
 
241
    def should_cache(self):
 
242
        """Return True if the data pulled across should be cached locally.
 
243
        """
 
244
        return False
393
245
 
394
246
    def _pump(self, from_file, to_file):
395
 
        """Most children will need to copy from one file-like
 
247
        """Most children will need to copy from one file-like 
396
248
        object or string to another one.
397
249
        This just gives them something easy to call.
398
250
        """
399
 
        return osutils.pumpfile(from_file, to_file)
 
251
        if isinstance(from_file, basestring):
 
252
            to_file.write(from_file)
 
253
        else:
 
254
            pumpfile(from_file, to_file)
400
255
 
401
256
    def _get_total(self, multi):
402
257
        """Try to figure out how many entries are in multi,
407
262
        except TypeError: # We can't tell how many, because relpaths is a generator
408
263
            return None
409
264
 
410
 
    def _report_activity(self, bytes, direction):
411
 
        """Notify that this transport has activity.
412
 
 
413
 
        Implementations should call this from all methods that actually do IO.
414
 
        Be careful that it's not called twice, if one method is implemented on
415
 
        top of another.
416
 
 
417
 
        :param bytes: Number of bytes read or written.
418
 
        :param direction: 'read' or 'write' or None.
419
 
        """
420
 
        ui.ui_factory.report_transport_activity(self, bytes, direction)
421
 
 
422
265
    def _update_pb(self, pb, msg, count, total):
423
266
        """Update the progress bar based on the current count
424
267
        and total available, total may be None if it was
453
296
 
454
297
    def abspath(self, relpath):
455
298
        """Return the full url to the given relative path.
 
299
        This can be supplied with a string or a list
456
300
 
457
 
        :param relpath: a string of a relative path
 
301
        XXX: Robert Collins 20051016 - is this really needed in the public
 
302
             interface ?
458
303
        """
459
 
 
460
 
        # XXX: Robert Collins 20051016 - is this really needed in the public
461
 
        # interface ?
462
304
        raise NotImplementedError(self.abspath)
463
305
 
464
 
    def _combine_paths(self, base_path, relpath):
465
 
        """Transform a Transport-relative path to a remote absolute path.
466
 
 
467
 
        This does not handle substitution of ~ but does handle '..' and '.'
468
 
        components.
469
 
 
470
 
        Examples::
471
 
 
472
 
            t._combine_paths('/home/sarah', 'project/foo')
473
 
                => '/home/sarah/project/foo'
474
 
            t._combine_paths('/home/sarah', '../../etc')
475
 
                => '/etc'
476
 
            t._combine_paths('/home/sarah', '/etc')
477
 
                => '/etc'
478
 
 
479
 
        :param base_path: urlencoded path for the transport root; typically a
480
 
             URL but need not contain scheme/host/etc.
481
 
        :param relpath: relative url string for relative part of remote path.
482
 
        :return: urlencoded string for final path.
483
 
        """
484
 
        if not isinstance(relpath, str):
485
 
            raise errors.InvalidURL(relpath)
486
 
        if relpath.startswith('/'):
487
 
            base_parts = []
488
 
        else:
489
 
            base_parts = base_path.split('/')
490
 
        if len(base_parts) > 0 and base_parts[-1] == '':
491
 
            base_parts = base_parts[:-1]
492
 
        for p in relpath.split('/'):
493
 
            if p == '..':
494
 
                if len(base_parts) == 0:
495
 
                    # In most filesystems, a request for the parent
496
 
                    # of root, just returns root.
497
 
                    continue
498
 
                base_parts.pop()
499
 
            elif p == '.':
500
 
                continue # No-op
501
 
            elif p != '':
502
 
                base_parts.append(p)
503
 
        path = '/'.join(base_parts)
504
 
        if not path.startswith('/'):
505
 
            path = '/' + path
506
 
        return path
507
 
 
508
 
    def recommended_page_size(self):
509
 
        """Return the recommended page size for this transport.
510
 
 
511
 
        This is potentially different for every path in a given namespace.
512
 
        For example, local transports might use an operating system call to
513
 
        get the block size for a given path, which can vary due to mount
514
 
        points.
515
 
 
516
 
        :return: The page size in bytes.
517
 
        """
518
 
        return 4 * 1024
519
 
 
520
306
    def relpath(self, abspath):
521
307
        """Return the local path portion from a given absolute path.
522
308
 
523
309
        This default implementation is not suitable for filesystems with
524
 
        aliasing, such as that given by symlinks, where a path may not
525
 
        start with our base, but still be a relpath once aliasing is
 
310
        aliasing, such as that given by symlinks, where a path may not 
 
311
        start with our base, but still be a relpath once aliasing is 
526
312
        resolved.
527
313
        """
528
314
        # TODO: This might want to use bzrlib.osutils.relpath
537
323
 
538
324
        This function will only be defined for Transports which have a
539
325
        physical local filesystem representation.
540
 
 
541
 
        :raises errors.NotLocalUrl: When no local path representation is
542
 
            available.
543
326
        """
544
 
        raise errors.NotLocalUrl(self.abspath(relpath))
 
327
        # TODO: jam 20060426 Should this raise NotLocalUrl instead?
 
328
        raise errors.TransportNotPossible('This is not a LocalTransport,'
 
329
            ' so there is no local representation for a path')
545
330
 
546
331
    def has(self, relpath):
547
332
        """Does the file relpath exist?
548
 
 
 
333
        
549
334
        Note that some transports MAY allow querying on directories, but this
550
 
        is not part of the protocol.  In other words, the results of
 
335
        is not part of the protocol.  In other words, the results of 
551
336
        t.has("a_directory_name") are undefined.
552
 
 
553
 
        :rtype: bool
554
337
        """
555
338
        raise NotImplementedError(self.has)
556
339
 
574
357
        """Iter the relative paths of files in the transports sub-tree.
575
358
 
576
359
        *NOTE*: This only lists *files*, not subdirectories!
577
 
 
 
360
        
578
361
        As with other listing functions, only some transports implement this,.
579
 
        you may check via listable() to determine if it will.
 
362
        you may check via is_listable to determine if it will.
580
363
        """
581
364
        raise errors.TransportNotPossible("This transport has not "
582
365
                                          "implemented iter_files_recursive "
586
369
    def get(self, relpath):
587
370
        """Get the file at the given relative path.
588
371
 
589
 
        This may fail in a number of ways:
590
 
         - HTTP servers may return content for a directory. (unexpected
591
 
           content failure)
592
 
         - FTP servers may indicate NoSuchFile for a directory.
593
 
         - SFTP servers may give a file handle for a directory that will
594
 
           fail on read().
595
 
 
596
 
        For correct use of the interface, be sure to catch errors.PathError
597
 
        when calling it and catch errors.ReadError when reading from the
598
 
        returned object.
599
 
 
600
372
        :param relpath: The relative path to the file
601
 
        :rtype: File-like object.
602
373
        """
603
374
        raise NotImplementedError(self.get)
604
375
 
605
 
    def get_bytes(self, relpath):
606
 
        """Get a raw string of the bytes for a file at the given location.
607
 
 
608
 
        :param relpath: The relative path to the file
609
 
        """
610
 
        f = self.get(relpath)
611
 
        try:
612
 
            return f.read()
613
 
        finally:
614
 
            f.close()
615
 
 
616
 
    def get_smart_medium(self):
617
 
        """Return a smart client medium for this transport if possible.
618
 
 
619
 
        A smart medium doesn't imply the presence of a smart server: it implies
620
 
        that the smart protocol can be tunnelled via this transport.
621
 
 
622
 
        :raises NoSmartMedium: if no smart server medium is available.
623
 
        """
624
 
        raise errors.NoSmartMedium(self)
625
 
 
626
 
    def readv(self, relpath, offsets, adjust_for_latency=False,
627
 
        upper_limit=None):
628
 
        """Get parts of the file at the given relative path.
629
 
 
630
 
        :param relpath: The path to read data from.
631
 
        :param offsets: A list of (offset, size) tuples.
632
 
        :param adjust_for_latency: Adjust the requested offsets to accomodate
633
 
            transport latency. This may re-order the offsets, expand them to
634
 
            grab adjacent data when there is likely a high cost to requesting
635
 
            data relative to delivering it.
636
 
        :param upper_limit: When adjust_for_latency is True setting upper_limit
637
 
            allows the caller to tell the transport about the length of the
638
 
            file, so that requests are not issued for ranges beyond the end of
639
 
            the file. This matters because some servers and/or transports error
640
 
            in such a case rather than just satisfying the available ranges.
641
 
            upper_limit should always be provided when adjust_for_latency is
642
 
            True, and should be the size of the file in bytes.
643
 
        :return: A list or generator of (offset, data) tuples
644
 
        """
645
 
        if adjust_for_latency:
646
 
            # Design note: We may wish to have different algorithms for the
647
 
            # expansion of the offsets per-transport. E.g. for local disk to
648
 
            # use page-aligned expansion. If that is the case consider the
649
 
            # following structure:
650
 
            #  - a test that transport.readv uses self._offset_expander or some
651
 
            #    similar attribute, to do the expansion
652
 
            #  - a test for each transport that it has some known-good offset
653
 
            #    expander
654
 
            #  - unit tests for each offset expander
655
 
            #  - a set of tests for the offset expander interface, giving
656
 
            #    baseline behaviour (which the current transport
657
 
            #    adjust_for_latency tests could be repurposed to).
658
 
            offsets = self._sort_expand_and_combine(offsets, upper_limit)
659
 
        return self._readv(relpath, offsets)
660
 
 
661
 
    def _readv(self, relpath, offsets):
662
 
        """Get parts of the file at the given relative path.
663
 
 
664
 
        :param relpath: The path to read.
665
 
        :param offsets: A list of (offset, size) tuples.
 
376
    def readv(self, relpath, offsets):
 
377
        """Get parts of the file at the given relative path.
 
378
 
 
379
        :offsets: A list of (offset, size) tuples.
666
380
        :return: A list or generator of (offset, data) tuples
667
381
        """
668
382
        if not offsets:
669
383
            return
670
384
 
671
385
        fp = self.get(relpath)
672
 
        return self._seek_and_read(fp, offsets, relpath)
 
386
        return self._seek_and_read(fp, offsets)
673
387
 
674
 
    def _seek_and_read(self, fp, offsets, relpath='<unknown>'):
 
388
    def _seek_and_read(self, fp, offsets):
675
389
        """An implementation of readv that uses fp.seek and fp.read.
676
390
 
677
391
        This uses _coalesce_offsets to issue larger reads and fewer seeks.
694
408
        # Cache the results, but only until they have been fulfilled
695
409
        data_map = {}
696
410
        for c_offset in coalesced:
697
 
            # TODO: jam 20060724 it might be faster to not issue seek if
 
411
            # TODO: jam 20060724 it might be faster to not issue seek if 
698
412
            #       we are already at the right location. This should be
699
413
            #       benchmarked.
700
414
            fp.seek(c_offset.start)
701
415
            data = fp.read(c_offset.length)
702
 
            if len(data) < c_offset.length:
703
 
                raise errors.ShortReadvError(relpath, c_offset.start,
704
 
                            c_offset.length, actual=len(data))
705
416
            for suboffset, subsize in c_offset.ranges:
706
417
                key = (c_offset.start+suboffset, subsize)
707
418
                data_map[key] = data[suboffset:suboffset+subsize]
709
420
            # Now that we've read some data, see if we can yield anything back
710
421
            while cur_offset_and_size in data_map:
711
422
                this_data = data_map.pop(cur_offset_and_size)
712
 
                this_offset = cur_offset_and_size[0]
713
 
                try:
714
 
                    cur_offset_and_size = offset_stack.next()
715
 
                except StopIteration:
716
 
                    # Close the file handle as there will be no more data
717
 
                    # The handle would normally be cleaned up as this code goes
718
 
                    # out of scope, but as we are a generator, not all code
719
 
                    # will re-enter once we have consumed all the expected
720
 
                    # data. For example:
721
 
                    #   zip(range(len(requests)), readv(foo, requests))
722
 
                    # Will stop because the range is done, and not run the
723
 
                    # cleanup code for the readv().
724
 
                    fp.close()
725
 
                    cur_offset_and_size = None
726
 
                yield this_offset, this_data
727
 
 
728
 
    def _sort_expand_and_combine(self, offsets, upper_limit):
729
 
        """Helper for readv.
730
 
 
731
 
        :param offsets: A readv vector - (offset, length) tuples.
732
 
        :param upper_limit: The highest byte offset that may be requested.
733
 
        :return: A readv vector that will read all the regions requested by
734
 
            offsets, in start-to-end order, with no duplicated regions,
735
 
            expanded by the transports recommended page size.
736
 
        """
737
 
        offsets = sorted(offsets)
738
 
        # short circuit empty requests
739
 
        if len(offsets) == 0:
740
 
            def empty_yielder():
741
 
                # Quick thunk to stop this function becoming a generator
742
 
                # itself, rather we return a generator that has nothing to
743
 
                # yield.
744
 
                if False:
745
 
                    yield None
746
 
            return empty_yielder()
747
 
        # expand by page size at either end
748
 
        maximum_expansion = self.recommended_page_size()
749
 
        new_offsets = []
750
 
        for offset, length in offsets:
751
 
            expansion = maximum_expansion - length
752
 
            if expansion < 0:
753
 
                # we're asking for more than the minimum read anyway.
754
 
                expansion = 0
755
 
            reduction = expansion / 2
756
 
            new_offset = offset - reduction
757
 
            new_length = length + expansion
758
 
            if new_offset < 0:
759
 
                # don't ask for anything < 0
760
 
                new_offset = 0
761
 
            if (upper_limit is not None and
762
 
                new_offset + new_length > upper_limit):
763
 
                new_length = upper_limit - new_offset
764
 
            new_offsets.append((new_offset, new_length))
765
 
        # combine the expanded offsets
766
 
        offsets = []
767
 
        current_offset, current_length = new_offsets[0]
768
 
        current_finish = current_length + current_offset
769
 
        for offset, length in new_offsets[1:]:
770
 
            finish = offset + length
771
 
            if offset > current_finish:
772
 
                # there is a gap, output the current accumulator and start
773
 
                # a new one for the region we're examining.
774
 
                offsets.append((current_offset, current_length))
775
 
                current_offset = offset
776
 
                current_length = length
777
 
                current_finish = finish
778
 
                continue
779
 
            if finish > current_finish:
780
 
                # extend the current accumulator to the end of the region
781
 
                # we're examining.
782
 
                current_finish = finish
783
 
                current_length = finish - current_offset
784
 
        offsets.append((current_offset, current_length))
785
 
        return offsets
 
423
                yield cur_offset_and_size[0], this_data
 
424
                cur_offset_and_size = offset_stack.next()
786
425
 
787
426
    @staticmethod
788
 
    def _coalesce_offsets(offsets, limit=0, fudge_factor=0, max_size=0):
 
427
    def _coalesce_offsets(offsets, limit, fudge_factor):
789
428
        """Yield coalesced offsets.
790
429
 
791
430
        With a long list of neighboring requests, combine them
792
431
        into a single large request, while retaining the original
793
432
        offsets.
794
433
        Turns  [(15, 10), (25, 10)] => [(15, 20, [(0, 10), (10, 10)])]
795
 
        Note that overlapping requests are not permitted. (So [(15, 10), (20,
796
 
        10)] will raise a ValueError.) This is because the data we access never
797
 
        overlaps, and it allows callers to trust that we only need any byte of
798
 
        data for 1 request (so nothing needs to be buffered to fulfill a second
799
 
        request.)
800
434
 
801
435
        :param offsets: A list of (start, length) pairs
802
 
        :param limit: Only combine a maximum of this many pairs Some transports
803
 
                penalize multiple reads more than others, and sometimes it is
804
 
                better to return early.
805
 
                0 means no limit
 
436
        :param limit: Only combine a maximum of this many pairs
 
437
                      Some transports penalize multiple reads more than
 
438
                      others, and sometimes it is better to return early.
 
439
                      0 means no limit
806
440
        :param fudge_factor: All transports have some level of 'it is
807
 
                better to read some more data and throw it away rather
 
441
                better to read some more data and throw it away rather 
808
442
                than seek', so collapse if we are 'close enough'
809
 
        :param max_size: Create coalesced offsets no bigger than this size.
810
 
                When a single offset is bigger than 'max_size', it will keep
811
 
                its size and be alone in the coalesced offset.
812
 
                0 means no maximum size.
813
 
        :return: return a list of _CoalescedOffset objects, which have members
814
 
            for where to start, how much to read, and how to split those chunks
815
 
            back up
 
443
        :return: yield _CoalescedOffset objects, which have members for wher
 
444
                to start, how much to read, and how to split those 
 
445
                chunks back up
816
446
        """
817
447
        last_end = None
818
448
        cur = _CoalescedOffset(None, None, [])
819
 
        coalesced_offsets = []
820
 
 
821
 
        if max_size <= 0:
822
 
            # 'unlimited', but we actually take this to mean 100MB buffer limit
823
 
            max_size = 100*1024*1024
824
449
 
825
450
        for start, size in offsets:
826
451
            end = start + size
827
 
            if (last_end is not None
 
452
            if (last_end is not None 
828
453
                and start <= last_end + fudge_factor
829
454
                and start >= cur.start
830
 
                and (limit <= 0 or len(cur.ranges) < limit)
831
 
                and (max_size <= 0 or end - cur.start <= max_size)):
832
 
                if start < last_end:
833
 
                    raise ValueError('Overlapping range not allowed:'
834
 
                        ' last range ended at %s, new one starts at %s'
835
 
                        % (last_end, start))
 
455
                and (limit <= 0 or len(cur.ranges) < limit)):
836
456
                cur.length = end - cur.start
837
457
                cur.ranges.append((start-cur.start, size))
838
458
            else:
839
459
                if cur.start is not None:
840
 
                    coalesced_offsets.append(cur)
 
460
                    yield cur
841
461
                cur = _CoalescedOffset(start, size, [(0, size)])
842
462
            last_end = end
843
463
 
844
464
        if cur.start is not None:
845
 
            coalesced_offsets.append(cur)
846
 
        return coalesced_offsets
 
465
            yield cur
 
466
 
 
467
        return
847
468
 
848
469
    def get_multi(self, relpaths, pb=None):
849
470
        """Get a list of file-like objects, one for each entry in relpaths.
850
471
 
851
472
        :param relpaths: A list of relative paths.
852
 
        :param pb:  An optional ProgressTask for indicating percent done.
 
473
        :param pb:  An optional ProgressBar for indicating percent done.
853
474
        :return: A list or generator of file-like objects
854
475
        """
855
476
        # TODO: Consider having this actually buffer the requests,
862
483
            yield self.get(relpath)
863
484
            count += 1
864
485
 
865
 
    def put_bytes(self, relpath, bytes, mode=None):
866
 
        """Atomically put the supplied bytes into the given location.
867
 
 
868
 
        :param relpath: The location to put the contents, relative to the
869
 
            transport base.
870
 
        :param bytes: A bytestring of data.
871
 
        :param mode: Create the file with the given mode.
872
 
        :return: None
873
 
        """
874
 
        if not isinstance(bytes, str):
875
 
            raise AssertionError(
876
 
                'bytes must be a plain string, not %s' % type(bytes))
877
 
        return self.put_file(relpath, StringIO(bytes), mode=mode)
878
 
 
879
 
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
880
 
                             create_parent_dir=False,
881
 
                             dir_mode=None):
882
 
        """Copy the string into the target location.
883
 
 
884
 
        This function is not strictly safe to use. See
885
 
        Transport.put_bytes_non_atomic for more information.
886
 
 
887
 
        :param relpath: The remote location to put the contents.
888
 
        :param bytes:   A string object containing the raw bytes to write into
889
 
                        the target file.
890
 
        :param mode:    Possible access permissions for new file.
891
 
                        None means do not set remote permissions.
892
 
        :param create_parent_dir: If we cannot create the target file because
893
 
                        the parent directory does not exist, go ahead and
894
 
                        create it, and then try again.
895
 
        :param dir_mode: Possible access permissions for new directories.
896
 
        """
897
 
        if not isinstance(bytes, str):
898
 
            raise AssertionError(
899
 
                'bytes must be a plain string, not %s' % type(bytes))
900
 
        self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,
901
 
                                 create_parent_dir=create_parent_dir,
902
 
                                 dir_mode=dir_mode)
903
 
 
904
 
    def put_file(self, relpath, f, mode=None):
905
 
        """Copy the file-like object into the location.
 
486
    def put(self, relpath, f, mode=None):
 
487
        """Copy the file-like or string object into the location.
906
488
 
907
489
        :param relpath: Location to put the contents, relative to base.
908
 
        :param f:       File-like object.
909
 
        :param mode: The mode for the newly created file,
910
 
                     None means just use the default.
911
 
        :return: The length of the file that was written.
912
 
        """
913
 
        # We would like to mark this as NotImplemented, but most likely
914
 
        # transports have defined it in terms of the old api.
915
 
        symbol_versioning.warn('Transport %s should implement put_file,'
916
 
                               ' rather than implementing put() as of'
917
 
                               ' version 0.11.'
918
 
                               % (self.__class__.__name__,),
919
 
                               DeprecationWarning)
920
 
        return self.put(relpath, f, mode=mode)
921
 
        #raise NotImplementedError(self.put_file)
922
 
 
923
 
    def put_file_non_atomic(self, relpath, f, mode=None,
924
 
                            create_parent_dir=False,
925
 
                            dir_mode=None):
926
 
        """Copy the file-like object into the target location.
927
 
 
928
 
        This function is not strictly safe to use. It is only meant to
929
 
        be used when you already know that the target does not exist.
930
 
        It is not safe, because it will open and truncate the remote
931
 
        file. So there may be a time when the file has invalid contents.
932
 
 
933
 
        :param relpath: The remote location to put the contents.
934
 
        :param f:       File-like object.
935
 
        :param mode:    Possible access permissions for new file.
936
 
                        None means do not set remote permissions.
937
 
        :param create_parent_dir: If we cannot create the target file because
938
 
                        the parent directory does not exist, go ahead and
939
 
                        create it, and then try again.
940
 
        :param dir_mode: Possible access permissions for new directories.
941
 
        """
942
 
        # Default implementation just does an atomic put.
943
 
        try:
944
 
            return self.put_file(relpath, f, mode=mode)
945
 
        except errors.NoSuchFile:
946
 
            if not create_parent_dir:
947
 
                raise
948
 
            parent_dir = osutils.dirname(relpath)
949
 
            if parent_dir:
950
 
                self.mkdir(parent_dir, mode=dir_mode)
951
 
                return self.put_file(relpath, f, mode=mode)
 
490
        :param f:       File-like or string object.
 
491
        :param mode: The mode for the newly created file, 
 
492
                     None means just use the default
 
493
        """
 
494
        raise NotImplementedError(self.put)
 
495
 
 
496
    def put_multi(self, files, mode=None, pb=None):
 
497
        """Put a set of files into the location.
 
498
 
 
499
        :param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
 
500
        :param pb:  An optional ProgressBar for indicating percent done.
 
501
        :param mode: The mode for the newly created files
 
502
        :return: The number of files copied.
 
503
        """
 
504
        def put(path, f):
 
505
            self.put(path, f, mode=mode)
 
506
        return len(self._iterate_over(files, put, pb, 'put', expand=True))
952
507
 
953
508
    def mkdir(self, relpath, mode=None):
954
509
        """Create a directory at the given path."""
960
515
            self.mkdir(path, mode=mode)
961
516
        return len(self._iterate_over(relpaths, mkdir, pb, 'mkdir', expand=False))
962
517
 
963
 
    def open_write_stream(self, relpath, mode=None):
964
 
        """Open a writable file stream at relpath.
965
 
 
966
 
        A file stream is a file like object with a write() method that accepts
967
 
        bytes to write.. Buffering may occur internally until the stream is
968
 
        closed with stream.close().  Calls to readv or the get_* methods will
969
 
        be synchronised with any internal buffering that may be present.
970
 
 
971
 
        :param relpath: The relative path to the file.
972
 
        :param mode: The mode for the newly created file,
973
 
                     None means just use the default
974
 
        :return: A FileStream. FileStream objects have two methods, write() and
975
 
            close(). There is no guarantee that data is committed to the file
976
 
            if close() has not been called (even if get() is called on the same
977
 
            path).
978
 
        """
979
 
        raise NotImplementedError(self.open_write_stream)
980
 
 
981
 
    def append_file(self, relpath, f, mode=None):
982
 
        """Append bytes from a file-like object to a file at relpath.
983
 
 
984
 
        The file is created if it does not already exist.
985
 
 
986
 
        :param f: a file-like object of the bytes to append.
987
 
        :param mode: Unix mode for newly created files.  This is not used for
988
 
            existing files.
989
 
 
990
 
        :returns: the length of relpath before the content was written to it.
991
 
        """
992
 
        symbol_versioning.warn('Transport %s should implement append_file,'
993
 
                               ' rather than implementing append() as of'
994
 
                               ' version 0.11.'
995
 
                               % (self.__class__.__name__,),
996
 
                               DeprecationWarning)
997
 
        return self.append(relpath, f, mode=mode)
998
 
 
999
 
    def append_bytes(self, relpath, bytes, mode=None):
1000
 
        """Append bytes to a file at relpath.
1001
 
 
1002
 
        The file is created if it does not already exist.
1003
 
 
1004
 
        :type f: str
1005
 
        :param f: a string of the bytes to append.
1006
 
        :param mode: Unix mode for newly created files.  This is not used for
1007
 
            existing files.
1008
 
 
1009
 
        :returns: the length of relpath before the content was written to it.
1010
 
        """
1011
 
        if not isinstance(bytes, str):
1012
 
            raise TypeError(
1013
 
                'bytes must be a plain string, not %s' % type(bytes))
1014
 
        return self.append_file(relpath, StringIO(bytes), mode=mode)
 
518
    def append(self, relpath, f):
 
519
        """Append the text in the file-like or string object to 
 
520
        the supplied location.
 
521
 
 
522
        returns the length of f before the content was written to it.
 
523
        """
 
524
        raise NotImplementedError(self.append)
1015
525
 
1016
526
    def append_multi(self, files, pb=None):
1017
527
        """Append the text in each file-like or string object to
1018
528
        the supplied location.
1019
529
 
1020
530
        :param files: A set of (path, f) entries
1021
 
        :param pb:  An optional ProgressTask for indicating percent done.
 
531
        :param pb:  An optional ProgressBar for indicating percent done.
1022
532
        """
1023
 
        return self._iterate_over(files, self.append_file, pb, 'append', expand=True)
 
533
        return self._iterate_over(files, self.append, pb, 'append', expand=True)
1024
534
 
1025
535
    def copy(self, rel_from, rel_to):
1026
536
        """Copy the item at rel_from to the location at rel_to.
1027
 
 
1028
 
        Override this for efficiency if a specific transport can do it
 
537
        
 
538
        Override this for efficiency if a specific transport can do it 
1029
539
        faster than this default implementation.
1030
540
        """
1031
 
        self.put_file(rel_to, self.get(rel_from))
 
541
        self.put(rel_to, self.get(rel_from))
1032
542
 
1033
543
    def copy_multi(self, relpaths, pb=None):
1034
544
        """Copy a bunch of entries.
1035
 
 
 
545
        
1036
546
        :param relpaths: A list of tuples of the form [(from, to), (from, to),...]
1037
547
        """
1038
548
        # This is the non-pipelined implementation, so that
1049
559
        """
1050
560
        # The dummy implementation just does a simple get + put
1051
561
        def copy_entry(path):
1052
 
            other.put_file(path, self.get(path), mode=mode)
 
562
            other.put(path, self.get(path), mode=mode)
1053
563
 
1054
564
        return len(self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False))
1055
565
 
1056
566
    def copy_tree(self, from_relpath, to_relpath):
1057
567
        """Copy a subtree from one relpath to another.
1058
568
 
1059
 
        If a faster implementation is available, specific transports should
 
569
        If a faster implementation is available, specific transports should 
1060
570
        implement it.
1061
571
        """
1062
572
        source = self.clone(from_relpath)
 
573
        self.mkdir(to_relpath)
1063
574
        target = self.clone(to_relpath)
1064
 
 
1065
 
        # create target directory with the same rwx bits as source.
1066
 
        # use mask to ensure that bits other than rwx are ignored.
1067
 
        stat = self.stat(from_relpath)
1068
 
        target.mkdir('.', stat.st_mode & 0777)
1069
 
        source.copy_tree_to_transport(target)
1070
 
 
1071
 
    def copy_tree_to_transport(self, to_transport):
1072
 
        """Copy a subtree from one transport to another.
1073
 
 
1074
 
        self.base is used as the source tree root, and to_transport.base
1075
 
        is used as the target.  to_transport.base must exist (and be a
1076
 
        directory).
1077
 
        """
1078
575
        files = []
1079
576
        directories = ['.']
1080
577
        while directories:
1081
578
            dir = directories.pop()
1082
579
            if dir != '.':
1083
 
                to_transport.mkdir(dir)
1084
 
            for path in self.list_dir(dir):
 
580
                target.mkdir(dir)
 
581
            for path in source.list_dir(dir):
1085
582
                path = dir + '/' + path
1086
 
                stat = self.stat(path)
 
583
                stat = source.stat(path)
1087
584
                if S_ISDIR(stat.st_mode):
1088
585
                    directories.append(path)
1089
586
                else:
1090
587
                    files.append(path)
1091
 
        self.copy_to(files, to_transport)
 
588
        source.copy_to(files, target)
1092
589
 
1093
590
    def rename(self, rel_from, rel_to):
1094
591
        """Rename a file or directory.
1114
611
 
1115
612
        The destination is deleted if possible, even if it's a non-empty
1116
613
        directory tree.
1117
 
 
 
614
        
1118
615
        If a transport can directly implement this it is suggested that
1119
616
        it do so for efficiency.
1120
617
        """
1127
624
 
1128
625
    def move_multi(self, relpaths, pb=None):
1129
626
        """Move a bunch of entries.
1130
 
 
 
627
        
1131
628
        :param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
1132
629
        """
1133
630
        return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
1186
683
        NOTE: This returns an object with fields such as 'st_size'. It MAY
1187
684
        or MAY NOT return the literal result of an os.stat() call, so all
1188
685
        access should be via named fields.
1189
 
        ALSO NOTE: Stats of directories may not be supported on some
 
686
        ALSO NOTE: Stats of directories may not be supported on some 
1190
687
        transports.
1191
688
        """
1192
689
        raise NotImplementedError(self.stat)
1207
704
        count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
1208
705
        return stats
1209
706
 
1210
 
    def readlink(self, relpath):
1211
 
        """Return a string representing the path to which the symbolic link points."""
1212
 
        raise errors.TransportNotPossible("Dereferencing symlinks is not supported on %s" % self)
1213
 
 
1214
 
    def hardlink(self, source, link_name):
1215
 
        """Create a hardlink pointing to source named link_name."""
1216
 
        raise errors.TransportNotPossible("Hard links are not supported on %s" % self)
1217
 
 
1218
 
    def symlink(self, source, link_name):
1219
 
        """Create a symlink pointing to source named link_name."""
1220
 
        raise errors.TransportNotPossible("Symlinks are not supported on %s" % self)
1221
 
 
1222
707
    def listable(self):
1223
708
        """Return True if this store supports listing."""
1224
709
        raise NotImplementedError(self.listable)
1228
713
        WARNING: many transports do not support this, so trying avoid using
1229
714
        it if at all possible.
1230
715
        """
1231
 
        raise errors.TransportNotPossible("Transport %r has not "
 
716
        raise errors.TransportNotPossible("This transport has not "
1232
717
                                          "implemented list_dir "
1233
718
                                          "(but must claim to be listable "
1234
 
                                          "to trigger this error)."
1235
 
                                          % (self))
 
719
                                          "to trigger this error).")
1236
720
 
1237
721
    def lock_read(self, relpath):
1238
722
        """Lock the given file for shared (read) access.
1239
 
 
1240
 
        WARNING: many transports do not support this, so trying avoid using it.
1241
 
        These methods may be removed in the future.
1242
 
 
1243
 
        Transports may raise TransportNotPossible if OS-level locks cannot be
1244
 
        taken over this transport.
 
723
        WARNING: many transports do not support this, so trying avoid using it
1245
724
 
1246
725
        :return: A lock object, which should contain an unlock() function.
1247
726
        """
1248
 
        raise errors.TransportNotPossible("transport locks not supported on %s" % self)
 
727
        raise NotImplementedError(self.lock_read)
1249
728
 
1250
729
    def lock_write(self, relpath):
1251
730
        """Lock the given file for exclusive (write) access.
1252
 
 
1253
 
        WARNING: many transports do not support this, so trying avoid using it.
1254
 
        These methods may be removed in the future.
1255
 
 
1256
 
        Transports may raise TransportNotPossible if OS-level locks cannot be
1257
 
        taken over this transport.
 
731
        WARNING: many transports do not support this, so trying avoid using it
1258
732
 
1259
733
        :return: A lock object, which should contain an unlock() function.
1260
734
        """
1261
 
        raise errors.TransportNotPossible("transport locks not supported on %s" % self)
 
735
        raise NotImplementedError(self.lock_write)
1262
736
 
1263
737
    def is_readonly(self):
1264
738
        """Return true if this connection cannot be written to."""
1268
742
        """Return true if this transport can store and retrieve unix modebits.
1269
743
 
1270
744
        (For example, 0700 to make a directory owner-private.)
1271
 
 
1272
 
        Note: most callers will not want to switch on this, but should rather
 
745
        
 
746
        Note: most callers will not want to switch on this, but should rather 
1273
747
        just try and set permissions and let them be either stored or not.
1274
748
        This is intended mainly for the use of the test suite.
1275
 
 
1276
 
        Warning: this is not guaranteed to be accurate as sometimes we can't
 
749
        
 
750
        Warning: this is not guaranteed to be accurate as sometimes we can't 
1277
751
        be sure: for example with vfat mounted on unix, or a windows sftp
1278
752
        server."""
1279
753
        # TODO: Perhaps return a e.g. TransportCharacteristics that can answer
1280
754
        # several questions about the transport.
1281
755
        return False
1282
756
 
1283
 
    def _reuse_for(self, other_base):
1284
 
        # This is really needed for ConnectedTransport only, but it's easier to
1285
 
        # have Transport refuses to be reused than testing that the reuse
1286
 
        # should be asked to ConnectedTransport only.
1287
 
        return None
1288
 
 
1289
 
    def disconnect(self):
1290
 
        # This is really needed for ConnectedTransport only, but it's easier to
1291
 
        # have Transport do nothing than testing that the disconnect should be
1292
 
        # asked to ConnectedTransport only.
1293
 
        pass
1294
 
 
1295
 
    def _redirected_to(self, source, target):
1296
 
        """Returns a transport suitable to re-issue a redirected request.
1297
 
 
1298
 
        :param source: The source url as returned by the server.
1299
 
        :param target: The target url as returned by the server.
1300
 
 
1301
 
        The redirection can be handled only if the relpath involved is not
1302
 
        renamed by the redirection.
1303
 
 
1304
 
        :returns: A transport or None.
1305
 
        """
1306
 
        # This returns None by default, meaning the transport can't handle the
1307
 
        # redirection.
1308
 
        return None
1309
 
 
1310
 
 
1311
 
 
1312
 
class _SharedConnection(object):
1313
 
    """A connection shared between several transports."""
1314
 
 
1315
 
    def __init__(self, connection=None, credentials=None, base=None):
1316
 
        """Constructor.
1317
 
 
1318
 
        :param connection: An opaque object specific to each transport.
1319
 
 
1320
 
        :param credentials: An opaque object containing the credentials used to
1321
 
            create the connection.
1322
 
        """
1323
 
        self.connection = connection
1324
 
        self.credentials = credentials
1325
 
        self.base = base
1326
 
 
1327
 
 
1328
 
class ConnectedTransport(Transport):
1329
 
    """A transport connected to a remote server.
1330
 
 
1331
 
    This class provide the basis to implement transports that need to connect
1332
 
    to a remote server.
1333
 
 
1334
 
    Host and credentials are available as private attributes, cloning preserves
1335
 
    them and share the underlying, protocol specific, connection.
1336
 
    """
1337
 
 
1338
 
    def __init__(self, base, _from_transport=None):
1339
 
        """Constructor.
1340
 
 
1341
 
        The caller should ensure that _from_transport points at the same host
1342
 
        as the new base.
1343
 
 
1344
 
        :param base: transport root URL
1345
 
 
1346
 
        :param _from_transport: optional transport to build from. The built
1347
 
            transport will share the connection with this transport.
1348
 
        """
1349
 
        if not base.endswith('/'):
1350
 
            base += '/'
1351
 
        (self._scheme,
1352
 
         self._user, self._password,
1353
 
         self._host, self._port,
1354
 
         self._path) = self._split_url(base)
1355
 
        if _from_transport is not None:
1356
 
            # Copy the password as it does not appear in base and will be lost
1357
 
            # otherwise. It can appear in the _split_url above if the user
1358
 
            # provided it on the command line. Otherwise, daughter classes will
1359
 
            # prompt the user for one when appropriate.
1360
 
            self._password = _from_transport._password
1361
 
 
1362
 
        base = self._unsplit_url(self._scheme,
1363
 
                                 self._user, self._password,
1364
 
                                 self._host, self._port,
1365
 
                                 self._path)
1366
 
 
1367
 
        super(ConnectedTransport, self).__init__(base)
1368
 
        if _from_transport is None:
1369
 
            self._shared_connection = _SharedConnection()
1370
 
        else:
1371
 
            self._shared_connection = _from_transport._shared_connection
1372
 
 
1373
 
    def clone(self, offset=None):
1374
 
        """Return a new transport with root at self.base + offset
1375
 
 
1376
 
        We leave the daughter classes take advantage of the hint
1377
 
        that it's a cloning not a raw creation.
1378
 
        """
1379
 
        if offset is None:
1380
 
            return self.__class__(self.base, _from_transport=self)
1381
 
        else:
1382
 
            return self.__class__(self.abspath(offset), _from_transport=self)
1383
 
 
1384
 
    @staticmethod
1385
 
    def _split_url(url):
1386
 
        return urlutils.parse_url(url)
1387
 
 
1388
 
    @staticmethod
1389
 
    def _unsplit_url(scheme, user, password, host, port, path):
1390
 
        """
1391
 
        Build the full URL for the given already URL encoded path.
1392
 
 
1393
 
        user, password, host and path will be quoted if they contain reserved
1394
 
        chars.
1395
 
 
1396
 
        :param scheme: protocol
1397
 
 
1398
 
        :param user: login
1399
 
 
1400
 
        :param password: associated password
1401
 
 
1402
 
        :param host: the server address
1403
 
 
1404
 
        :param port: the associated port
1405
 
 
1406
 
        :param path: the absolute path on the server
1407
 
 
1408
 
        :return: The corresponding URL.
1409
 
        """
1410
 
        netloc = urllib.quote(host)
1411
 
        if user is not None:
1412
 
            # Note that we don't put the password back even if we
1413
 
            # have one so that it doesn't get accidentally
1414
 
            # exposed.
1415
 
            netloc = '%s@%s' % (urllib.quote(user), netloc)
1416
 
        if port is not None:
1417
 
            netloc = '%s:%d' % (netloc, port)
1418
 
        path = urlutils.escape(path)
1419
 
        return urlparse.urlunparse((scheme, netloc, path, None, None, None))
1420
 
 
1421
 
    def relpath(self, abspath):
1422
 
        """Return the local path portion from a given absolute path"""
1423
 
        scheme, user, password, host, port, path = self._split_url(abspath)
1424
 
        error = []
1425
 
        if (scheme != self._scheme):
1426
 
            error.append('scheme mismatch')
1427
 
        if (user != self._user):
1428
 
            error.append('user name mismatch')
1429
 
        if (host != self._host):
1430
 
            error.append('host mismatch')
1431
 
        if (port != self._port):
1432
 
            error.append('port mismatch')
1433
 
        if not (path == self._path[:-1] or path.startswith(self._path)):
1434
 
            error.append('path mismatch')
1435
 
        if error:
1436
 
            extra = ', '.join(error)
1437
 
            raise errors.PathNotChild(abspath, self.base, extra=extra)
1438
 
        pl = len(self._path)
1439
 
        return path[pl:].strip('/')
1440
 
 
1441
 
    def abspath(self, relpath):
1442
 
        """Return the full url to the given relative path.
1443
 
 
1444
 
        :param relpath: the relative path urlencoded
1445
 
 
1446
 
        :returns: the Unicode version of the absolute path for relpath.
1447
 
        """
1448
 
        relative = urlutils.unescape(relpath).encode('utf-8')
1449
 
        path = self._combine_paths(self._path, relative)
1450
 
        return self._unsplit_url(self._scheme, self._user, self._password,
1451
 
                                 self._host, self._port,
1452
 
                                 path)
1453
 
 
1454
 
    def _remote_path(self, relpath):
1455
 
        """Return the absolute path part of the url to the given relative path.
1456
 
 
1457
 
        This is the path that the remote server expect to receive in the
1458
 
        requests, daughter classes should redefine this method if needed and
1459
 
        use the result to build their requests.
1460
 
 
1461
 
        :param relpath: the path relative to the transport base urlencoded.
1462
 
 
1463
 
        :return: the absolute Unicode path on the server,
1464
 
        """
1465
 
        relative = urlutils.unescape(relpath).encode('utf-8')
1466
 
        remote_path = self._combine_paths(self._path, relative)
1467
 
        return remote_path
1468
 
 
1469
 
    def _get_shared_connection(self):
1470
 
        """Get the object shared amongst cloned transports.
1471
 
 
1472
 
        This should be used only by classes that needs to extend the sharing
1473
 
        with objects other than transports.
1474
 
 
1475
 
        Use _get_connection to get the connection itself.
1476
 
        """
1477
 
        return self._shared_connection
1478
 
 
1479
 
    def _set_connection(self, connection, credentials=None):
1480
 
        """Record a newly created connection with its associated credentials.
1481
 
 
1482
 
        Note: To ensure that connection is still shared after a temporary
1483
 
        failure and a new one needs to be created, daughter classes should
1484
 
        always call this method to set the connection and do so each time a new
1485
 
        connection is created.
1486
 
 
1487
 
        :param connection: An opaque object representing the connection used by
1488
 
            the daughter class.
1489
 
 
1490
 
        :param credentials: An opaque object representing the credentials
1491
 
            needed to create the connection.
1492
 
        """
1493
 
        self._shared_connection.connection = connection
1494
 
        self._shared_connection.credentials = credentials
1495
 
 
1496
 
    def _get_connection(self):
1497
 
        """Returns the transport specific connection object."""
1498
 
        return self._shared_connection.connection
1499
 
 
1500
 
    def _get_credentials(self):
1501
 
        """Returns the credentials used to establish the connection."""
1502
 
        return self._shared_connection.credentials
1503
 
 
1504
 
    def _update_credentials(self, credentials):
1505
 
        """Update the credentials of the current connection.
1506
 
 
1507
 
        Some protocols can renegociate the credentials within a connection,
1508
 
        this method allows daughter classes to share updated credentials.
1509
 
 
1510
 
        :param credentials: the updated credentials.
1511
 
        """
1512
 
        # We don't want to call _set_connection here as we are only updating
1513
 
        # the credentials not creating a new connection.
1514
 
        self._shared_connection.credentials = credentials
1515
 
 
1516
 
    def _reuse_for(self, other_base):
1517
 
        """Returns a transport sharing the same connection if possible.
1518
 
 
1519
 
        Note: we share the connection if the expected credentials are the
1520
 
        same: (host, port, user). Some protocols may disagree and redefine the
1521
 
        criteria in daughter classes.
1522
 
 
1523
 
        Note: we don't compare the passwords here because other_base may have
1524
 
        been obtained from an existing transport.base which do not mention the
1525
 
        password.
1526
 
 
1527
 
        :param other_base: the URL we want to share the connection with.
1528
 
 
1529
 
        :return: A new transport or None if the connection cannot be shared.
1530
 
        """
1531
 
        try:
1532
 
            (scheme, user, password,
1533
 
             host, port, path) = self._split_url(other_base)
1534
 
        except errors.InvalidURL:
1535
 
            # No hope in trying to reuse an existing transport for an invalid
1536
 
            # URL
1537
 
            return None
1538
 
 
1539
 
        transport = None
1540
 
        # Don't compare passwords, they may be absent from other_base or from
1541
 
        # self and they don't carry more information than user anyway.
1542
 
        if (scheme == self._scheme
1543
 
            and user == self._user
1544
 
            and host == self._host
1545
 
            and port == self._port):
1546
 
            if not path.endswith('/'):
1547
 
                # This normally occurs at __init__ time, but it's easier to do
1548
 
                # it now to avoid creating two transports for the same base.
1549
 
                path += '/'
1550
 
            if self._path  == path:
1551
 
                # shortcut, it's really the same transport
1552
 
                return self
1553
 
            # We don't call clone here because the intent is different: we
1554
 
            # build a new transport on a different base (which may be totally
1555
 
            # unrelated) but we share the connection.
1556
 
            transport = self.__class__(other_base, _from_transport=self)
1557
 
        return transport
1558
 
 
1559
 
    def disconnect(self):
1560
 
        """Disconnect the transport.
1561
 
 
1562
 
        If and when required the transport willl reconnect automatically.
1563
 
        """
1564
 
        raise NotImplementedError(self.disconnect)
1565
 
 
1566
 
 
1567
 
def get_transport(base, possible_transports=None):
 
757
 
 
758
# jam 20060426 For compatibility we copy the functions here
 
759
# TODO: The should be marked as deprecated
 
760
urlescape = urlutils.escape
 
761
urlunescape = urlutils.unescape
 
762
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<path>.*)$')
 
763
 
 
764
 
 
765
def get_transport(base):
1568
766
    """Open a transport to access a URL or directory.
1569
767
 
1570
 
    :param base: either a URL or a directory name.
1571
 
 
1572
 
    :param transports: optional reusable transports list. If not None, created
1573
 
        transports will be added to the list.
1574
 
 
1575
 
    :return: A new transport optionally sharing its connection with one of
1576
 
        possible_transports.
 
768
    base is either a URL or a directory name.  
1577
769
    """
 
770
    # TODO: give a better error if base looks like a url but there's no
 
771
    # handler for the scheme?
 
772
    global _protocol_handlers
1578
773
    if base is None:
1579
774
        base = '.'
 
775
 
1580
776
    last_err = None
1581
 
    from bzrlib.directory_service import directories
1582
 
    base = directories.dereference(base)
1583
777
 
1584
778
    def convert_path_to_url(base, error_str):
1585
 
        if urlutils.is_url(base):
1586
 
            # This looks like a URL, but we weren't able to
 
779
        m = _urlRE.match(base)
 
780
        if m:
 
781
            # This looks like a URL, but we weren't able to 
1587
782
            # instantiate it as such raise an appropriate error
1588
 
            # FIXME: we have a 'error_str' unused and we use last_err below
1589
783
            raise errors.UnsupportedProtocol(base, last_err)
1590
784
        # This doesn't look like a protocol, consider it a local path
1591
785
        new_base = urlutils.local_path_to_url(base)
1592
 
        # mutter('converting os path %r => url %s', base, new_base)
 
786
        mutter('converting os path %r => url %s', base, new_base)
1593
787
        return new_base
1594
788
 
1595
789
    # Catch any URLs which are passing Unicode rather than ASCII
1599
793
        # Only local paths can be Unicode
1600
794
        base = convert_path_to_url(base,
1601
795
            'URLs must be properly escaped (protocol: %s)')
1602
 
 
1603
 
    transport = None
1604
 
    if possible_transports is not None:
1605
 
        for t in possible_transports:
1606
 
            t_same_connection = t._reuse_for(base)
1607
 
            if t_same_connection is not None:
1608
 
                # Add only new transports
1609
 
                if t_same_connection not in possible_transports:
1610
 
                    possible_transports.append(t_same_connection)
1611
 
                return t_same_connection
1612
 
 
1613
 
    for proto, factory_list in transport_list_registry.items():
 
796
    
 
797
    for proto, factory_list in _protocol_handlers.iteritems():
1614
798
        if proto is not None and base.startswith(proto):
1615
 
            transport, last_err = _try_transport_factories(base, factory_list)
1616
 
            if transport:
1617
 
                if possible_transports is not None:
1618
 
                    if transport in possible_transports:
1619
 
                        raise AssertionError()
1620
 
                    possible_transports.append(transport)
1621
 
                return transport
 
799
            t, last_err = _try_transport_factories(base, factory_list)
 
800
            if t:
 
801
                return t
1622
802
 
1623
803
    # We tried all the different protocols, now try one last time
1624
804
    # as a local protocol
1625
805
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
1626
806
 
1627
807
    # The default handler is the filesystem handler, stored as protocol None
1628
 
    factory_list = transport_list_registry.get(None)
1629
 
    transport, last_err = _try_transport_factories(base, factory_list)
1630
 
 
1631
 
    return transport
 
808
    return _try_transport_factories(base, _protocol_handlers[None])[0]
1632
809
 
1633
810
 
1634
811
def _try_transport_factories(base, factory_list):
1635
812
    last_err = None
1636
813
    for factory in factory_list:
1637
814
        try:
1638
 
            return factory.get_obj()(base), None
1639
 
        except errors.DependencyNotPresent, e:
 
815
            return factory(base), None
 
816
        except DependencyNotPresent, e:
1640
817
            mutter("failed to instantiate transport %r for %r: %r" %
1641
818
                    (factory, base, e))
1642
819
            last_err = e
1644
821
    return None, last_err
1645
822
 
1646
823
 
1647
 
def do_catching_redirections(action, transport, redirected):
1648
 
    """Execute an action with given transport catching redirections.
1649
 
 
1650
 
    This is a facility provided for callers needing to follow redirections
1651
 
    silently. The silence is relative: it is the caller responsability to
1652
 
    inform the user about each redirection or only inform the user of a user
1653
 
    via the exception parameter.
1654
 
 
1655
 
    :param action: A callable, what the caller want to do while catching
1656
 
                  redirections.
1657
 
    :param transport: The initial transport used.
1658
 
    :param redirected: A callable receiving the redirected transport and the
1659
 
                  RedirectRequested exception.
1660
 
 
1661
 
    :return: Whatever 'action' returns
1662
 
    """
1663
 
    MAX_REDIRECTIONS = 8
1664
 
 
1665
 
    # If a loop occurs, there is little we can do. So we don't try to detect
1666
 
    # them, just getting out if too much redirections occurs. The solution
1667
 
    # is outside: where the loop is defined.
1668
 
    for redirections in range(MAX_REDIRECTIONS):
1669
 
        try:
1670
 
            return action(transport)
1671
 
        except errors.RedirectRequested, e:
1672
 
            redirection_notice = '%s is%s redirected to %s' % (
1673
 
                e.source, e.permanently, e.target)
1674
 
            transport = redirected(transport, e, redirection_notice)
1675
 
    else:
1676
 
        # Loop exited without resolving redirect ? Either the
1677
 
        # user has kept a very very very old reference or a loop
1678
 
        # occurred in the redirections.  Nothing we can cure here:
1679
 
        # tell the user. Note that as the user has been informed
1680
 
        # about each redirection (it is the caller responsibility
1681
 
        # to do that in redirected via the provided
1682
 
        # redirection_notice). The caller may provide more
1683
 
        # information if needed (like what file or directory we
1684
 
        # were trying to act upon when the redirection loop
1685
 
        # occurred).
1686
 
        raise errors.TooManyRedirections
1687
 
 
1688
 
 
1689
824
class Server(object):
1690
825
    """A Transport Server.
1691
 
 
1692
 
    The Server interface provides a server for a given transport type.
 
826
    
 
827
    The Server interface provides a server for a given transport. We use
 
828
    these servers as loopback testing tools. For any given transport the
 
829
    Servers it provides must either allow writing, or serve the contents
 
830
    of os.getcwdu() at the time setUp is called.
 
831
    
 
832
    Note that these are real servers - they must implement all the things
 
833
    that we want bzr transports to take advantage of.
1693
834
    """
1694
835
 
1695
 
    def start_server(self):
 
836
    def setUp(self):
1696
837
        """Setup the server to service requests."""
1697
838
 
1698
 
    def stop_server(self):
 
839
    def tearDown(self):
1699
840
        """Remove the server and cleanup any resources it owns."""
1700
841
 
 
842
    def get_url(self):
 
843
        """Return a url for this server.
 
844
        
 
845
        If the transport does not represent a disk directory (i.e. it is 
 
846
        a database like svn, or a memory only transport, it should return
 
847
        a connection to a newly established resource for this Server.
 
848
        Otherwise it should return a url that will provide access to the path
 
849
        that was os.getcwdu() when setUp() was called.
 
850
        
 
851
        Subsequent calls will return the same resource.
 
852
        """
 
853
        raise NotImplementedError
 
854
 
 
855
    def get_bogus_url(self):
 
856
        """Return a url for this protocol, that will fail to connect."""
 
857
        raise NotImplementedError
 
858
 
 
859
 
 
860
class TransportTestProviderAdapter(object):
 
861
    """A tool to generate a suite testing all transports for a single test.
 
862
 
 
863
    This is done by copying the test once for each transport and injecting
 
864
    the transport_class and transport_server classes into each copy. Each copy
 
865
    is also given a new id() to make it easy to identify.
 
866
    """
 
867
 
 
868
    def adapt(self, test):
 
869
        result = TestSuite()
 
870
        for klass, server_factory in self._test_permutations():
 
871
            new_test = deepcopy(test)
 
872
            new_test.transport_class = klass
 
873
            new_test.transport_server = server_factory
 
874
            def make_new_test_id():
 
875
                new_id = "%s(%s)" % (new_test.id(), server_factory.__name__)
 
876
                return lambda: new_id
 
877
            new_test.id = make_new_test_id()
 
878
            result.addTest(new_test)
 
879
        return result
 
880
 
 
881
    def get_transport_test_permutations(self, module):
 
882
        """Get the permutations module wants to have tested."""
 
883
        if not hasattr(module, 'get_test_permutations'):
 
884
            warning("transport module %s doesn't provide get_test_permutations()"
 
885
                    % module.__name__)
 
886
            return []
 
887
        return module.get_test_permutations()
 
888
 
 
889
    def _test_permutations(self):
 
890
        """Return a list of the klass, server_factory pairs to test."""
 
891
        result = []
 
892
        for module in _get_transport_modules():
 
893
            try:
 
894
                result.extend(self.get_transport_test_permutations(reduce(getattr, 
 
895
                    (module).split('.')[1:],
 
896
                     __import__(module))))
 
897
            except errors.DependencyNotPresent, e:
 
898
                # Continue even if a dependency prevents us 
 
899
                # from running this test
 
900
                pass
 
901
        return result
 
902
 
 
903
 
 
904
class TransportLogger(object):
 
905
    """Adapt a transport to get clear logging data on api calls.
 
906
    
 
907
    Feel free to extend to log whatever calls are of interest.
 
908
    """
 
909
 
 
910
    def __init__(self, adapted):
 
911
        self._adapted = adapted
 
912
        self._calls = []
 
913
 
 
914
    def get(self, name):
 
915
        self._calls.append((name,))
 
916
        return self._adapted.get(name)
 
917
 
 
918
    def __getattr__(self, name):
 
919
        """Thunk all undefined access through to self._adapted."""
 
920
        # raise AttributeError, name 
 
921
        return getattr(self._adapted, name)
 
922
 
 
923
    def readv(self, name, offsets):
 
924
        self._calls.append((name, offsets))
 
925
        return self._adapted.readv(name, offsets)
 
926
        
1701
927
 
1702
928
# None is the default transport, for things with no url scheme
1703
 
register_transport_proto('file://',
1704
 
            help="Access using the standard filesystem (default)")
 
929
register_lazy_transport(None, 'bzrlib.transport.local', 'LocalTransport')
1705
930
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
1706
 
transport_list_registry.set_default_transport("file://")
1707
 
 
1708
 
register_transport_proto('sftp://',
1709
 
            help="Access using SFTP (most SSH servers provide SFTP).",
1710
 
            register_netloc=True)
1711
931
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
1712
 
# Decorated http transport
1713
 
register_transport_proto('http+urllib://',
1714
 
#                help="Read-only access of branches exported on the web."
1715
 
                         register_netloc=True)
1716
932
register_lazy_transport('http+urllib://', 'bzrlib.transport.http._urllib',
1717
933
                        'HttpTransport_urllib')
1718
 
register_transport_proto('https+urllib://',
1719
 
#                help="Read-only access of branches exported on the web using SSL."
1720
 
                         register_netloc=True)
1721
934
register_lazy_transport('https+urllib://', 'bzrlib.transport.http._urllib',
1722
935
                        'HttpTransport_urllib')
1723
 
register_transport_proto('http+pycurl://',
1724
 
#                help="Read-only access of branches exported on the web."
1725
 
                         register_netloc=True)
1726
936
register_lazy_transport('http+pycurl://', 'bzrlib.transport.http._pycurl',
1727
937
                        'PyCurlTransport')
1728
 
register_transport_proto('https+pycurl://',
1729
 
#                help="Read-only access of branches exported on the web using SSL."
1730
 
                         register_netloc=True)
1731
938
register_lazy_transport('https+pycurl://', 'bzrlib.transport.http._pycurl',
1732
939
                        'PyCurlTransport')
1733
 
# Default http transports (last declared wins (if it can be imported))
1734
 
register_transport_proto('http://',
1735
 
                 help="Read-only access of branches exported on the web.")
1736
 
register_transport_proto('https://',
1737
 
            help="Read-only access of branches exported on the web using SSL.")
1738
 
# The default http implementation is urllib, but https is pycurl if available
1739
 
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
1740
 
                        'PyCurlTransport')
1741
940
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1742
941
                        'HttpTransport_urllib')
1743
942
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1744
943
                        'HttpTransport_urllib')
1745
 
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1746
 
                        'PyCurlTransport')
1747
 
 
1748
 
register_transport_proto('ftp://', help="Access using passive FTP.")
 
944
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
 
945
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl', 'PyCurlTransport')
1749
946
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1750
 
register_transport_proto('aftp://', help="Access using active FTP.")
1751
947
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1752
 
register_transport_proto('gio+', help="Access using any GIO supported protocols.")
1753
 
register_lazy_transport('gio+', 'bzrlib.transport.gio_transport', 'GioTransport')
1754
 
 
1755
 
 
1756
 
# Default to trying GSSAPI authentication (if the kerberos module is
1757
 
# available)
1758
 
register_transport_proto('ftp+gssapi://', register_netloc=True)
1759
 
register_transport_proto('aftp+gssapi://', register_netloc=True)
1760
 
register_transport_proto('ftp+nogssapi://', register_netloc=True)
1761
 
register_transport_proto('aftp+nogssapi://', register_netloc=True)
1762
 
register_lazy_transport('ftp+gssapi://', 'bzrlib.transport.ftp._gssapi',
1763
 
                        'GSSAPIFtpTransport')
1764
 
register_lazy_transport('aftp+gssapi://', 'bzrlib.transport.ftp._gssapi',
1765
 
                        'GSSAPIFtpTransport')
1766
 
register_lazy_transport('ftp://', 'bzrlib.transport.ftp._gssapi',
1767
 
                        'GSSAPIFtpTransport')
1768
 
register_lazy_transport('aftp://', 'bzrlib.transport.ftp._gssapi',
1769
 
                        'GSSAPIFtpTransport')
1770
 
register_lazy_transport('ftp+nogssapi://', 'bzrlib.transport.ftp',
1771
 
                        'FtpTransport')
1772
 
register_lazy_transport('aftp+nogssapi://', 'bzrlib.transport.ftp',
1773
 
                        'FtpTransport')
1774
 
 
1775
 
register_transport_proto('memory://')
1776
 
register_lazy_transport('memory://', 'bzrlib.transport.memory',
1777
 
                        'MemoryTransport')
1778
 
 
1779
 
# chroots cannot be implicitly accessed, they must be explicitly created:
1780
 
register_transport_proto('chroot+')
1781
 
 
1782
 
register_transport_proto('readonly+',
1783
 
#              help="This modifier converts any transport to be readonly."
1784
 
            )
1785
 
register_lazy_transport('readonly+', 'bzrlib.transport.readonly',
1786
 
                        'ReadonlyTransportDecorator')
1787
 
 
1788
 
register_transport_proto('fakenfs+')
1789
 
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs',
1790
 
                        'FakeNFSTransportDecorator')
1791
 
 
1792
 
register_transport_proto('log+')
1793
 
register_lazy_transport('log+', 'bzrlib.transport.log', 'TransportLogDecorator')
1794
 
 
1795
 
register_transport_proto('trace+')
1796
 
register_lazy_transport('trace+', 'bzrlib.transport.trace',
1797
 
                        'TransportTraceDecorator')
1798
 
 
1799
 
register_transport_proto('unlistable+')
1800
 
register_lazy_transport('unlistable+', 'bzrlib.transport.unlistable',
1801
 
                        'UnlistableTransportDecorator')
1802
 
 
1803
 
register_transport_proto('brokenrename+')
1804
 
register_lazy_transport('brokenrename+', 'bzrlib.transport.brokenrename',
1805
 
                        'BrokenRenameTransportDecorator')
1806
 
 
1807
 
register_transport_proto('vfat+')
1808
 
register_lazy_transport('vfat+',
 
948
register_lazy_transport('memory://', 'bzrlib.transport.memory', 'MemoryTransport')
 
949
register_lazy_transport('readonly+', 'bzrlib.transport.readonly', 'ReadonlyTransportDecorator')
 
950
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs', 'FakeNFSTransportDecorator')
 
951
register_lazy_transport('vfat+', 
1809
952
                        'bzrlib.transport.fakevfat',
1810
953
                        'FakeVFATTransportDecorator')
1811
 
 
1812
 
register_transport_proto('nosmart+')
1813
 
register_lazy_transport('nosmart+', 'bzrlib.transport.nosmart',
1814
 
                        'NoSmartTransportDecorator')
1815
 
 
1816
 
# These two schemes were registered, but don't seem to have an actual transport
1817
 
# protocol registered
1818
 
for scheme in ['ssh', 'bzr+loopback']:
1819
 
    register_urlparse_netloc_protocol(scheme)
1820
 
del scheme
1821
 
 
1822
 
register_transport_proto('bzr://',
1823
 
            help="Fast access using the Bazaar smart server.",
1824
 
                         register_netloc=True)
1825
 
 
1826
 
register_lazy_transport('bzr://', 'bzrlib.transport.remote',
1827
 
                        'RemoteTCPTransport')
1828
 
register_transport_proto('bzr-v2://', register_netloc=True)
1829
 
 
1830
 
register_lazy_transport('bzr-v2://', 'bzrlib.transport.remote',
1831
 
                        'RemoteTCPTransportV2Only')
1832
 
register_transport_proto('bzr+http://',
1833
 
#                help="Fast access using the Bazaar smart server over HTTP."
1834
 
                         register_netloc=True)
1835
 
register_lazy_transport('bzr+http://', 'bzrlib.transport.remote',
1836
 
                        'RemoteHTTPTransport')
1837
 
register_transport_proto('bzr+https://',
1838
 
#                help="Fast access using the Bazaar smart server over HTTPS."
1839
 
                         register_netloc=True)
1840
 
register_lazy_transport('bzr+https://',
1841
 
                        'bzrlib.transport.remote',
1842
 
                        'RemoteHTTPTransport')
1843
 
register_transport_proto('bzr+ssh://',
1844
 
            help="Fast access using the Bazaar smart server over SSH.",
1845
 
            register_netloc=True)
1846
 
register_lazy_transport('bzr+ssh://', 'bzrlib.transport.remote',
1847
 
                        'RemoteSSHTransport')
1848
 
 
1849
 
register_transport_proto('ssh:')
1850
 
register_lazy_transport('ssh:', 'bzrlib.transport.remote',
1851
 
                        'HintingSSHTransport')
1852
 
 
1853
 
 
1854
 
transport_server_registry = registry.Registry()
1855
 
transport_server_registry.register_lazy('bzr', 'bzrlib.smart.server',
1856
 
    'serve_bzr', help="The Bazaar smart server protocol over TCP. (default port: 4155)")
1857
 
transport_server_registry.default_key = 'bzr'