~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

Merge with prepare-shelf

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Transport is an abstraction layer to handle file access.
 
18
 
 
19
The abstraction is to allow access from the local filesystem, as well
 
20
as remote (such as http or sftp).
 
21
 
 
22
Transports are constructed from a string, being a URL or (as a degenerate
 
23
case) a local filesystem path.  This is typically the top directory of
 
24
a bzrdir, repository, or similar object we are interested in working with.
 
25
The Transport returned has methods to read, write and manipulate files within
 
26
it.
 
27
"""
 
28
 
 
29
from cStringIO import StringIO
 
30
import re
 
31
import sys
 
32
 
 
33
from bzrlib.lazy_import import lazy_import
 
34
lazy_import(globals(), """
 
35
import errno
 
36
from stat import S_ISDIR
 
37
import urllib
 
38
import urlparse
 
39
 
 
40
from bzrlib import (
 
41
    errors,
 
42
    osutils,
 
43
    symbol_versioning,
 
44
    urlutils,
 
45
    )
 
46
""")
 
47
 
 
48
from bzrlib.symbol_versioning import (
 
49
        deprecated_method,
 
50
        deprecated_function,
 
51
        DEPRECATED_PARAMETER,
 
52
        one_four,
 
53
        )
 
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 = {}
 
63
 
 
64
 
 
65
def _get_protocol_handlers():
 
66
    """Return a dictionary of {urlprefix: [factory]}"""
 
67
    return transport_list_registry
 
68
 
 
69
 
 
70
def _set_protocol_handlers(new_handlers):
 
71
    """Replace the current protocol handlers dictionary.
 
72
 
 
73
    WARNING this will remove all build in protocols. Use with care.
 
74
    """
 
75
    global transport_list_registry
 
76
    transport_list_registry = new_handlers
 
77
 
 
78
 
 
79
def _clear_protocol_handlers():
 
80
    global transport_list_registry
 
81
    transport_list_registry = TransportListRegistry()
 
82
 
 
83
 
 
84
def _get_transport_modules():
 
85
    """Return a list of the modules providing transports."""
 
86
    modules = set()
 
87
    for prefix, factory_list in transport_list_registry.iteritems():
 
88
        for factory in factory_list:
 
89
            if hasattr(factory, "_module_name"):
 
90
                modules.add(factory._module_name)
 
91
            else:
 
92
                modules.add(factory._obj.__module__)
 
93
    # Add chroot directly, because there is no handler registered for it.
 
94
    modules.add('bzrlib.transport.chroot')
 
95
    result = list(modules)
 
96
    result.sort()
 
97
    return result
 
98
 
 
99
 
 
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 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
 
 
154
def register_urlparse_netloc_protocol(protocol):
 
155
    """Ensure that protocol is setup to be used with urlparse netloc parsing."""
 
156
    if protocol not in urlparse.uses_netloc:
 
157
        urlparse.uses_netloc.append(protocol)
 
158
 
 
159
 
 
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)
 
180
 
 
181
 
 
182
class _CoalescedOffset(object):
 
183
    """A data container for keeping track of coalesced offsets."""
 
184
 
 
185
    __slots__ = ['start', 'length', 'ranges']
 
186
 
 
187
    def __init__(self, start, length, ranges):
 
188
        self.start = start
 
189
        self.length = length
 
190
        self.ranges = ranges
 
191
 
 
192
    def __cmp__(self, other):
 
193
        return cmp((self.start, self.length, self.ranges),
 
194
                   (other.start, other.length, other.ranges))
 
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
        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
 
 
270
class Transport(object):
 
271
    """This class encapsulates methods for retrieving or putting a file
 
272
    from/to a storage location.
 
273
 
 
274
    Most functions have a _multi variant, which allows you to queue up
 
275
    multiple requests. They generally have a dumb base implementation 
 
276
    which just iterates over the arguments, but smart Transport
 
277
    implementations can do pipelining.
 
278
    In general implementations should support having a generator or a list
 
279
    as an argument (ie always iterate, never index)
 
280
 
 
281
    :ivar base: Base URL for the transport; should always end in a slash.
 
282
    """
 
283
 
 
284
    # implementations can override this if it is more efficient
 
285
    # for them to combine larger read chunks together
 
286
    _max_readv_combine = 50
 
287
    # It is better to read this much more data in order, rather
 
288
    # than doing another seek. Even for the local filesystem,
 
289
    # there is a benefit in just reading.
 
290
    # TODO: jam 20060714 Do some real benchmarking to figure out
 
291
    #       where the biggest benefit between combining reads and
 
292
    #       and seeking is. Consider a runtime auto-tune.
 
293
    _bytes_to_read_before_seek = 0
 
294
 
 
295
    def __init__(self, base):
 
296
        super(Transport, self).__init__()
 
297
        self.base = base
 
298
 
 
299
    def _translate_error(self, e, path, raise_generic=True):
 
300
        """Translate an IOError or OSError into an appropriate bzr error.
 
301
 
 
302
        This handles things like ENOENT, ENOTDIR, EEXIST, and EACCESS
 
303
        """
 
304
        if getattr(e, 'errno', None) is not None:
 
305
            if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
306
                raise errors.NoSuchFile(path, extra=e)
 
307
            # I would rather use errno.EFOO, but there doesn't seem to be
 
308
            # any matching for 267
 
309
            # This is the error when doing a listdir on a file:
 
310
            # WindowsError: [Errno 267] The directory name is invalid
 
311
            if sys.platform == 'win32' and e.errno in (errno.ESRCH, 267):
 
312
                raise errors.NoSuchFile(path, extra=e)
 
313
            if e.errno == errno.EEXIST:
 
314
                raise errors.FileExists(path, extra=e)
 
315
            if e.errno == errno.EACCES:
 
316
                raise errors.PermissionDenied(path, extra=e)
 
317
            if e.errno == errno.ENOTEMPTY:
 
318
                raise errors.DirectoryNotEmpty(path, extra=e)
 
319
            if e.errno == errno.EBUSY:
 
320
                raise errors.ResourceBusy(path, extra=e)
 
321
        if raise_generic:
 
322
            raise errors.TransportError(orig_error=e)
 
323
 
 
324
    def clone(self, offset=None):
 
325
        """Return a new Transport object, cloned from the current location,
 
326
        using a subdirectory or parent directory. This allows connections 
 
327
        to be pooled, rather than a new one needed for each subdir.
 
328
        """
 
329
        raise NotImplementedError(self.clone)
 
330
 
 
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)
 
368
 
 
369
    def _pump(self, from_file, to_file):
 
370
        """Most children will need to copy from one file-like 
 
371
        object or string to another one.
 
372
        This just gives them something easy to call.
 
373
        """
 
374
        return osutils.pumpfile(from_file, to_file)
 
375
 
 
376
    def _get_total(self, multi):
 
377
        """Try to figure out how many entries are in multi,
 
378
        but if not possible, return None.
 
379
        """
 
380
        try:
 
381
            return len(multi)
 
382
        except TypeError: # We can't tell how many, because relpaths is a generator
 
383
            return None
 
384
 
 
385
    def _update_pb(self, pb, msg, count, total):
 
386
        """Update the progress bar based on the current count
 
387
        and total available, total may be None if it was
 
388
        not possible to determine.
 
389
        """
 
390
        if pb is None:
 
391
            return
 
392
        if total is None:
 
393
            pb.update(msg, count, count+1)
 
394
        else:
 
395
            pb.update(msg, count, total)
 
396
 
 
397
    def _iterate_over(self, multi, func, pb, msg, expand=True):
 
398
        """Iterate over all entries in multi, passing them to func,
 
399
        and update the progress bar as you go along.
 
400
 
 
401
        :param expand:  If True, the entries will be passed to the function
 
402
                        by expanding the tuple. If False, it will be passed
 
403
                        as a single parameter.
 
404
        """
 
405
        total = self._get_total(multi)
 
406
        result = []
 
407
        count = 0
 
408
        for entry in multi:
 
409
            self._update_pb(pb, msg, count, total)
 
410
            if expand:
 
411
                result.append(func(*entry))
 
412
            else:
 
413
                result.append(func(entry))
 
414
            count += 1
 
415
        return tuple(result)
 
416
 
 
417
    def abspath(self, relpath):
 
418
        """Return the full url to the given relative path.
 
419
 
 
420
        :param relpath: a string of a relative path
 
421
        """
 
422
 
 
423
        # XXX: Robert Collins 20051016 - is this really needed in the public
 
424
        # interface ?
 
425
        raise NotImplementedError(self.abspath)
 
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
 
 
483
    def relpath(self, abspath):
 
484
        """Return the local path portion from a given absolute path.
 
485
 
 
486
        This default implementation is not suitable for filesystems with
 
487
        aliasing, such as that given by symlinks, where a path may not 
 
488
        start with our base, but still be a relpath once aliasing is 
 
489
        resolved.
 
490
        """
 
491
        # TODO: This might want to use bzrlib.osutils.relpath
 
492
        #       but we have to watch out because of the prefix issues
 
493
        if not (abspath == self.base[:-1] or abspath.startswith(self.base)):
 
494
            raise errors.PathNotChild(abspath, self.base)
 
495
        pl = len(self.base)
 
496
        return abspath[pl:].strip('/')
 
497
 
 
498
    def local_abspath(self, relpath):
 
499
        """Return the absolute path on the local filesystem.
 
500
 
 
501
        This function will only be defined for Transports which have a
 
502
        physical local filesystem representation.
 
503
        """
 
504
        raise errors.NotLocalUrl(self.abspath(relpath))
 
505
 
 
506
 
 
507
    def has(self, relpath):
 
508
        """Does the file relpath exist?
 
509
        
 
510
        Note that some transports MAY allow querying on directories, but this
 
511
        is not part of the protocol.  In other words, the results of 
 
512
        t.has("a_directory_name") are undefined.
 
513
 
 
514
        :rtype: bool
 
515
        """
 
516
        raise NotImplementedError(self.has)
 
517
 
 
518
    def has_multi(self, relpaths, pb=None):
 
519
        """Return True/False for each entry in relpaths"""
 
520
        total = self._get_total(relpaths)
 
521
        count = 0
 
522
        for relpath in relpaths:
 
523
            self._update_pb(pb, 'has', count, total)
 
524
            yield self.has(relpath)
 
525
            count += 1
 
526
 
 
527
    def has_any(self, relpaths):
 
528
        """Return True if any of the paths exist."""
 
529
        for relpath in relpaths:
 
530
            if self.has(relpath):
 
531
                return True
 
532
        return False
 
533
 
 
534
    def iter_files_recursive(self):
 
535
        """Iter the relative paths of files in the transports sub-tree.
 
536
 
 
537
        *NOTE*: This only lists *files*, not subdirectories!
 
538
        
 
539
        As with other listing functions, only some transports implement this,.
 
540
        you may check via listable() to determine if it will.
 
541
        """
 
542
        raise errors.TransportNotPossible("This transport has not "
 
543
                                          "implemented iter_files_recursive "
 
544
                                          "(but must claim to be listable "
 
545
                                          "to trigger this error).")
 
546
 
 
547
    def get(self, relpath):
 
548
        """Get the file at the given relative path.
 
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
 
 
561
        :param relpath: The relative path to the file
 
562
        :rtype: File-like object.
 
563
        """
 
564
        raise NotImplementedError(self.get)
 
565
 
 
566
    def get_bytes(self, relpath):
 
567
        """Get a raw string of the bytes for a file at the given location.
 
568
 
 
569
        :param relpath: The relative path to the file
 
570
        """
 
571
        return self.get(relpath).read()
 
572
 
 
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.
 
645
        :return: A list or generator of (offset, data) tuples
 
646
        """
 
647
        if not offsets:
 
648
            return
 
649
 
 
650
        fp = self.get(relpath)
 
651
        return self._seek_and_read(fp, offsets, relpath)
 
652
 
 
653
    def _seek_and_read(self, fp, offsets, relpath='<unknown>'):
 
654
        """An implementation of readv that uses fp.seek and fp.read.
 
655
 
 
656
        This uses _coalesce_offsets to issue larger reads and fewer seeks.
 
657
 
 
658
        :param fp: A file-like object that supports seek() and read(size)
 
659
        :param offsets: A list of offsets to be read from the given file.
 
660
        :return: yield (pos, data) tuples for each request
 
661
        """
 
662
        # We are going to iterate multiple times, we need a list
 
663
        offsets = list(offsets)
 
664
        sorted_offsets = sorted(offsets)
 
665
 
 
666
        # turn the list of offsets into a stack
 
667
        offset_stack = iter(offsets)
 
668
        cur_offset_and_size = offset_stack.next()
 
669
        coalesced = self._coalesce_offsets(sorted_offsets,
 
670
                               limit=self._max_readv_combine,
 
671
                               fudge_factor=self._bytes_to_read_before_seek)
 
672
 
 
673
        # Cache the results, but only until they have been fulfilled
 
674
        data_map = {}
 
675
        for c_offset in coalesced:
 
676
            # TODO: jam 20060724 it might be faster to not issue seek if 
 
677
            #       we are already at the right location. This should be
 
678
            #       benchmarked.
 
679
            fp.seek(c_offset.start)
 
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))
 
684
            for suboffset, subsize in c_offset.ranges:
 
685
                key = (c_offset.start+suboffset, subsize)
 
686
                data_map[key] = data[suboffset:suboffset+subsize]
 
687
 
 
688
            # Now that we've read some data, see if we can yield anything back
 
689
            while cur_offset_and_size in data_map:
 
690
                this_data = data_map.pop(cur_offset_and_size)
 
691
                yield cur_offset_and_size[0], this_data
 
692
                cur_offset_and_size = offset_stack.next()
 
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
 
 
753
    @staticmethod
 
754
    def _coalesce_offsets(offsets, limit=0, fudge_factor=0, max_size=0):
 
755
        """Yield coalesced offsets.
 
756
 
 
757
        With a long list of neighboring requests, combine them
 
758
        into a single large request, while retaining the original
 
759
        offsets.
 
760
        Turns  [(15, 10), (25, 10)] => [(15, 20, [(0, 10), (10, 10)])]
 
761
        Note that overlapping requests are not permitted. (So [(15, 10), (20,
 
762
        10)] will raise a ValueError.) This is because the data we access never
 
763
        overlaps, and it allows callers to trust that we only need any byte of
 
764
        data for 1 request (so nothing needs to be buffered to fulfill a second
 
765
        request.)
 
766
 
 
767
        :param offsets: A list of (start, length) pairs
 
768
        :param limit: Only combine a maximum of this many pairs Some transports
 
769
                penalize multiple reads more than others, and sometimes it is
 
770
                better to return early.
 
771
                0 means no limit
 
772
        :param fudge_factor: All transports have some level of 'it is
 
773
                better to read some more data and throw it away rather
 
774
                than seek', so collapse if we are 'close enough'
 
775
        :param max_size: Create coalesced offsets no bigger than this size.
 
776
                When a single offset is bigger than 'max_size', it will keep
 
777
                its size and be alone in the coalesced offset.
 
778
                0 means no maximum size.
 
779
        :return: return a list of _CoalescedOffset objects, which have members
 
780
            for where to start, how much to read, and how to split those chunks
 
781
            back up
 
782
        """
 
783
        last_end = None
 
784
        cur = _CoalescedOffset(None, None, [])
 
785
        coalesced_offsets = []
 
786
 
 
787
        for start, size in offsets:
 
788
            end = start + size
 
789
            if (last_end is not None
 
790
                and start <= last_end + fudge_factor
 
791
                and start >= cur.start
 
792
                and (limit <= 0 or len(cur.ranges) < limit)
 
793
                and (max_size <= 0 or end - cur.start <= max_size)):
 
794
                if start < last_end:
 
795
                    raise ValueError('Overlapping range not allowed:'
 
796
                        ' last range ended at %s, new one starts at %s'
 
797
                        % (last_end, start))
 
798
                cur.length = end - cur.start
 
799
                cur.ranges.append((start-cur.start, size))
 
800
            else:
 
801
                if cur.start is not None:
 
802
                    coalesced_offsets.append(cur)
 
803
                cur = _CoalescedOffset(start, size, [(0, size)])
 
804
            last_end = end
 
805
 
 
806
        if cur.start is not None:
 
807
            coalesced_offsets.append(cur)
 
808
        return coalesced_offsets
 
809
 
 
810
    def get_multi(self, relpaths, pb=None):
 
811
        """Get a list of file-like objects, one for each entry in relpaths.
 
812
 
 
813
        :param relpaths: A list of relative paths.
 
814
        :param pb:  An optional ProgressBar for indicating percent done.
 
815
        :return: A list or generator of file-like objects
 
816
        """
 
817
        # TODO: Consider having this actually buffer the requests,
 
818
        # in the default mode, it probably won't give worse performance,
 
819
        # and all children wouldn't have to implement buffering
 
820
        total = self._get_total(relpaths)
 
821
        count = 0
 
822
        for relpath in relpaths:
 
823
            self._update_pb(pb, 'get', count, total)
 
824
            yield self.get(relpath)
 
825
            count += 1
 
826
 
 
827
    def put_bytes(self, relpath, bytes, mode=None):
 
828
        """Atomically put the supplied bytes into the given location.
 
829
 
 
830
        :param relpath: The location to put the contents, relative to the
 
831
            transport base.
 
832
        :param bytes: A bytestring of data.
 
833
        :param mode: Create the file with the given mode.
 
834
        :return: None
 
835
        """
 
836
        if not isinstance(bytes, str):
 
837
            raise AssertionError(
 
838
                'bytes must be a plain string, not %s' % type(bytes))
 
839
        return self.put_file(relpath, StringIO(bytes), mode=mode)
 
840
 
 
841
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
 
842
                             create_parent_dir=False,
 
843
                             dir_mode=None):
 
844
        """Copy the string into the target location.
 
845
 
 
846
        This function is not strictly safe to use. See 
 
847
        Transport.put_bytes_non_atomic for more information.
 
848
 
 
849
        :param relpath: The remote location to put the contents.
 
850
        :param bytes:   A string object containing the raw bytes to write into
 
851
                        the target file.
 
852
        :param mode:    Possible access permissions for new file.
 
853
                        None means do not set remote permissions.
 
854
        :param create_parent_dir: If we cannot create the target file because
 
855
                        the parent directory does not exist, go ahead and
 
856
                        create it, and then try again.
 
857
        :param dir_mode: Possible access permissions for new directories.
 
858
        """
 
859
        if not isinstance(bytes, str):
 
860
            raise AssertionError(
 
861
                'bytes must be a plain string, not %s' % type(bytes))
 
862
        self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,
 
863
                                 create_parent_dir=create_parent_dir,
 
864
                                 dir_mode=dir_mode)
 
865
 
 
866
    def put_file(self, relpath, f, mode=None):
 
867
        """Copy the file-like object into the location.
 
868
 
 
869
        :param relpath: Location to put the contents, relative to base.
 
870
        :param f:       File-like object.
 
871
        :param mode: The mode for the newly created file,
 
872
                     None means just use the default.
 
873
        :return: The length of the file that was written.
 
874
        """
 
875
        # We would like to mark this as NotImplemented, but most likely
 
876
        # transports have defined it in terms of the old api.
 
877
        symbol_versioning.warn('Transport %s should implement put_file,'
 
878
                               ' rather than implementing put() as of'
 
879
                               ' version 0.11.'
 
880
                               % (self.__class__.__name__,),
 
881
                               DeprecationWarning)
 
882
        return self.put(relpath, f, mode=mode)
 
883
        #raise NotImplementedError(self.put_file)
 
884
 
 
885
    def put_file_non_atomic(self, relpath, f, mode=None,
 
886
                            create_parent_dir=False,
 
887
                            dir_mode=None):
 
888
        """Copy the file-like object into the target location.
 
889
 
 
890
        This function is not strictly safe to use. It is only meant to
 
891
        be used when you already know that the target does not exist.
 
892
        It is not safe, because it will open and truncate the remote
 
893
        file. So there may be a time when the file has invalid contents.
 
894
 
 
895
        :param relpath: The remote location to put the contents.
 
896
        :param f:       File-like object.
 
897
        :param mode:    Possible access permissions for new file.
 
898
                        None means do not set remote permissions.
 
899
        :param create_parent_dir: If we cannot create the target file because
 
900
                        the parent directory does not exist, go ahead and
 
901
                        create it, and then try again.
 
902
        :param dir_mode: Possible access permissions for new directories.
 
903
        """
 
904
        # Default implementation just does an atomic put.
 
905
        try:
 
906
            return self.put_file(relpath, f, mode=mode)
 
907
        except errors.NoSuchFile:
 
908
            if not create_parent_dir:
 
909
                raise
 
910
            parent_dir = osutils.dirname(relpath)
 
911
            if parent_dir:
 
912
                self.mkdir(parent_dir, mode=dir_mode)
 
913
                return self.put_file(relpath, f, mode=mode)
 
914
 
 
915
    def mkdir(self, relpath, mode=None):
 
916
        """Create a directory at the given path."""
 
917
        raise NotImplementedError(self.mkdir)
 
918
 
 
919
    def mkdir_multi(self, relpaths, mode=None, pb=None):
 
920
        """Create a group of directories"""
 
921
        def mkdir(path):
 
922
            self.mkdir(path, mode=mode)
 
923
        return len(self._iterate_over(relpaths, mkdir, pb, 'mkdir', expand=False))
 
924
 
 
925
    def open_write_stream(self, relpath, mode=None):
 
926
        """Open a writable file stream at relpath.
 
927
 
 
928
        A file stream is a file like object with a write() method that accepts
 
929
        bytes to write.. Buffering may occur internally until the stream is
 
930
        closed with stream.close().  Calls to readv or the get_* methods will
 
931
        be synchronised with any internal buffering that may be present.
 
932
 
 
933
        :param relpath: The relative path to the file.
 
934
        :param mode: The mode for the newly created file, 
 
935
                     None means just use the default
 
936
        :return: A FileStream. FileStream objects have two methods, write() and
 
937
            close(). There is no guarantee that data is committed to the file
 
938
            if close() has not been called (even if get() is called on the same
 
939
            path).
 
940
        """
 
941
        raise NotImplementedError(self.open_write_stream)
 
942
 
 
943
    def append_file(self, relpath, f, mode=None):
 
944
        """Append bytes from a file-like object to a file at relpath.
 
945
 
 
946
        The file is created if it does not already exist.
 
947
 
 
948
        :param f: a file-like object of the bytes to append.
 
949
        :param mode: Unix mode for newly created files.  This is not used for
 
950
            existing files.
 
951
 
 
952
        :returns: the length of relpath before the content was written to it.
 
953
        """
 
954
        symbol_versioning.warn('Transport %s should implement append_file,'
 
955
                               ' rather than implementing append() as of'
 
956
                               ' version 0.11.'
 
957
                               % (self.__class__.__name__,),
 
958
                               DeprecationWarning)
 
959
        return self.append(relpath, f, mode=mode)
 
960
 
 
961
    def append_bytes(self, relpath, bytes, mode=None):
 
962
        """Append bytes to a file at relpath.
 
963
 
 
964
        The file is created if it does not already exist.
 
965
 
 
966
        :type f: str
 
967
        :param f: a string of the bytes to append.
 
968
        :param mode: Unix mode for newly created files.  This is not used for
 
969
            existing files.
 
970
 
 
971
        :returns: the length of relpath before the content was written to it.
 
972
        """
 
973
        if not isinstance(bytes, str):
 
974
            raise TypeError(
 
975
                'bytes must be a plain string, not %s' % type(bytes))
 
976
        return self.append_file(relpath, StringIO(bytes), mode=mode)
 
977
 
 
978
    def append_multi(self, files, pb=None):
 
979
        """Append the text in each file-like or string object to
 
980
        the supplied location.
 
981
 
 
982
        :param files: A set of (path, f) entries
 
983
        :param pb:  An optional ProgressBar for indicating percent done.
 
984
        """
 
985
        return self._iterate_over(files, self.append_file, pb, 'append', expand=True)
 
986
 
 
987
    def copy(self, rel_from, rel_to):
 
988
        """Copy the item at rel_from to the location at rel_to.
 
989
        
 
990
        Override this for efficiency if a specific transport can do it 
 
991
        faster than this default implementation.
 
992
        """
 
993
        self.put_file(rel_to, self.get(rel_from))
 
994
 
 
995
    def copy_multi(self, relpaths, pb=None):
 
996
        """Copy a bunch of entries.
 
997
        
 
998
        :param relpaths: A list of tuples of the form [(from, to), (from, to),...]
 
999
        """
 
1000
        # This is the non-pipelined implementation, so that
 
1001
        # implementors don't have to implement everything.
 
1002
        return self._iterate_over(relpaths, self.copy, pb, 'copy', expand=True)
 
1003
 
 
1004
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
1005
        """Copy a set of entries from self into another Transport.
 
1006
 
 
1007
        :param relpaths: A list/generator of entries to be copied.
 
1008
        :param mode: This is the target mode for the newly created files
 
1009
        TODO: This interface needs to be updated so that the target location
 
1010
              can be different from the source location.
 
1011
        """
 
1012
        # The dummy implementation just does a simple get + put
 
1013
        def copy_entry(path):
 
1014
            other.put_file(path, self.get(path), mode=mode)
 
1015
 
 
1016
        return len(self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False))
 
1017
 
 
1018
    def copy_tree(self, from_relpath, to_relpath):
 
1019
        """Copy a subtree from one relpath to another.
 
1020
 
 
1021
        If a faster implementation is available, specific transports should 
 
1022
        implement it.
 
1023
        """
 
1024
        source = self.clone(from_relpath)
 
1025
        self.mkdir(to_relpath)
 
1026
        target = self.clone(to_relpath)
 
1027
        files = []
 
1028
        directories = ['.']
 
1029
        while directories:
 
1030
            dir = directories.pop()
 
1031
            if dir != '.':
 
1032
                target.mkdir(dir)
 
1033
            for path in source.list_dir(dir):
 
1034
                path = dir + '/' + path
 
1035
                stat = source.stat(path)
 
1036
                if S_ISDIR(stat.st_mode):
 
1037
                    directories.append(path)
 
1038
                else:
 
1039
                    files.append(path)
 
1040
        source.copy_to(files, target)
 
1041
 
 
1042
    def rename(self, rel_from, rel_to):
 
1043
        """Rename a file or directory.
 
1044
 
 
1045
        This *must* fail if the destination is a nonempty directory - it must
 
1046
        not automatically remove it.  It should raise DirectoryNotEmpty, or
 
1047
        some other PathError if the case can't be specifically detected.
 
1048
 
 
1049
        If the destination is an empty directory or a file this function may
 
1050
        either fail or succeed, depending on the underlying transport.  It
 
1051
        should not attempt to remove the destination if overwriting is not the
 
1052
        native transport behaviour.  If at all possible the transport should
 
1053
        ensure that the rename either completes or not, without leaving the
 
1054
        destination deleted and the new file not moved in place.
 
1055
 
 
1056
        This is intended mainly for use in implementing LockDir.
 
1057
        """
 
1058
        # transports may need to override this
 
1059
        raise NotImplementedError(self.rename)
 
1060
 
 
1061
    def move(self, rel_from, rel_to):
 
1062
        """Move the item at rel_from to the location at rel_to.
 
1063
 
 
1064
        The destination is deleted if possible, even if it's a non-empty
 
1065
        directory tree.
 
1066
        
 
1067
        If a transport can directly implement this it is suggested that
 
1068
        it do so for efficiency.
 
1069
        """
 
1070
        if S_ISDIR(self.stat(rel_from).st_mode):
 
1071
            self.copy_tree(rel_from, rel_to)
 
1072
            self.delete_tree(rel_from)
 
1073
        else:
 
1074
            self.copy(rel_from, rel_to)
 
1075
            self.delete(rel_from)
 
1076
 
 
1077
    def move_multi(self, relpaths, pb=None):
 
1078
        """Move a bunch of entries.
 
1079
        
 
1080
        :param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
 
1081
        """
 
1082
        return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
 
1083
 
 
1084
    def move_multi_to(self, relpaths, rel_to):
 
1085
        """Move a bunch of entries to a single location.
 
1086
        This differs from move_multi in that you give a list of from, and
 
1087
        a single destination, rather than multiple destinations.
 
1088
 
 
1089
        :param relpaths: A list of relative paths [from1, from2, from3, ...]
 
1090
        :param rel_to: A directory where each entry should be placed.
 
1091
        """
 
1092
        # This is not implemented, because you need to do special tricks to
 
1093
        # extract the basename, and add it to rel_to
 
1094
        raise NotImplementedError(self.move_multi_to)
 
1095
 
 
1096
    def delete(self, relpath):
 
1097
        """Delete the item at relpath"""
 
1098
        raise NotImplementedError(self.delete)
 
1099
 
 
1100
    def delete_multi(self, relpaths, pb=None):
 
1101
        """Queue up a bunch of deletes to be done.
 
1102
        """
 
1103
        return self._iterate_over(relpaths, self.delete, pb, 'delete', expand=False)
 
1104
 
 
1105
    def delete_tree(self, relpath):
 
1106
        """Delete an entire tree. This may require a listable transport."""
 
1107
        subtree = self.clone(relpath)
 
1108
        files = []
 
1109
        directories = ['.']
 
1110
        pending_rmdirs = []
 
1111
        while directories:
 
1112
            dir = directories.pop()
 
1113
            if dir != '.':
 
1114
                pending_rmdirs.append(dir)
 
1115
            for path in subtree.list_dir(dir):
 
1116
                path = dir + '/' + path
 
1117
                stat = subtree.stat(path)
 
1118
                if S_ISDIR(stat.st_mode):
 
1119
                    directories.append(path)
 
1120
                else:
 
1121
                    files.append(path)
 
1122
        subtree.delete_multi(files)
 
1123
        pending_rmdirs.reverse()
 
1124
        for dir in pending_rmdirs:
 
1125
            subtree.rmdir(dir)
 
1126
        self.rmdir(relpath)
 
1127
 
 
1128
    def __repr__(self):
 
1129
        return "<%s.%s url=%s>" % (self.__module__, self.__class__.__name__, self.base)
 
1130
 
 
1131
    def stat(self, relpath):
 
1132
        """Return the stat information for a file.
 
1133
        WARNING: This may not be implementable for all protocols, so use
 
1134
        sparingly.
 
1135
        NOTE: This returns an object with fields such as 'st_size'. It MAY
 
1136
        or MAY NOT return the literal result of an os.stat() call, so all
 
1137
        access should be via named fields.
 
1138
        ALSO NOTE: Stats of directories may not be supported on some 
 
1139
        transports.
 
1140
        """
 
1141
        raise NotImplementedError(self.stat)
 
1142
 
 
1143
    def rmdir(self, relpath):
 
1144
        """Remove a directory at the given path."""
 
1145
        raise NotImplementedError
 
1146
 
 
1147
    def stat_multi(self, relpaths, pb=None):
 
1148
        """Stat multiple files and return the information.
 
1149
        """
 
1150
        #TODO:  Is it worth making this a generator instead of a
 
1151
        #       returning a list?
 
1152
        stats = []
 
1153
        def gather(path):
 
1154
            stats.append(self.stat(path))
 
1155
 
 
1156
        count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
 
1157
        return stats
 
1158
 
 
1159
    def listable(self):
 
1160
        """Return True if this store supports listing."""
 
1161
        raise NotImplementedError(self.listable)
 
1162
 
 
1163
    def list_dir(self, relpath):
 
1164
        """Return a list of all files at the given location.
 
1165
        WARNING: many transports do not support this, so trying avoid using
 
1166
        it if at all possible.
 
1167
        """
 
1168
        raise errors.TransportNotPossible("Transport %r has not "
 
1169
                                          "implemented list_dir "
 
1170
                                          "(but must claim to be listable "
 
1171
                                          "to trigger this error)."
 
1172
                                          % (self))
 
1173
 
 
1174
    def lock_read(self, relpath):
 
1175
        """Lock the given file for shared (read) access.
 
1176
 
 
1177
        WARNING: many transports do not support this, so trying avoid using it.
 
1178
        These methods may be removed in the future.
 
1179
 
 
1180
        Transports may raise TransportNotPossible if OS-level locks cannot be
 
1181
        taken over this transport.  
 
1182
 
 
1183
        :return: A lock object, which should contain an unlock() function.
 
1184
        """
 
1185
        raise errors.TransportNotPossible("transport locks not supported on %s" % self)
 
1186
 
 
1187
    def lock_write(self, relpath):
 
1188
        """Lock the given file for exclusive (write) access.
 
1189
 
 
1190
        WARNING: many transports do not support this, so trying avoid using it.
 
1191
        These methods may be removed in the future.
 
1192
 
 
1193
        Transports may raise TransportNotPossible if OS-level locks cannot be
 
1194
        taken over this transport.
 
1195
 
 
1196
        :return: A lock object, which should contain an unlock() function.
 
1197
        """
 
1198
        raise errors.TransportNotPossible("transport locks not supported on %s" % self)
 
1199
 
 
1200
    def is_readonly(self):
 
1201
        """Return true if this connection cannot be written to."""
 
1202
        return False
 
1203
 
 
1204
    def _can_roundtrip_unix_modebits(self):
 
1205
        """Return true if this transport can store and retrieve unix modebits.
 
1206
 
 
1207
        (For example, 0700 to make a directory owner-private.)
 
1208
        
 
1209
        Note: most callers will not want to switch on this, but should rather 
 
1210
        just try and set permissions and let them be either stored or not.
 
1211
        This is intended mainly for the use of the test suite.
 
1212
        
 
1213
        Warning: this is not guaranteed to be accurate as sometimes we can't 
 
1214
        be sure: for example with vfat mounted on unix, or a windows sftp
 
1215
        server."""
 
1216
        # TODO: Perhaps return a e.g. TransportCharacteristics that can answer
 
1217
        # several questions about the transport.
 
1218
        return False
 
1219
 
 
1220
    def _reuse_for(self, other_base):
 
1221
        # This is really needed for ConnectedTransport only, but it's easier to
 
1222
        # have Transport refuses to be reused than testing that the reuse
 
1223
        # should be asked to ConnectedTransport only.
 
1224
        return None
 
1225
 
 
1226
 
 
1227
class _SharedConnection(object):
 
1228
    """A connection shared between several transports."""
 
1229
 
 
1230
    def __init__(self, connection=None, credentials=None, base=None):
 
1231
        """Constructor.
 
1232
 
 
1233
        :param connection: An opaque object specific to each transport.
 
1234
 
 
1235
        :param credentials: An opaque object containing the credentials used to
 
1236
            create the connection.
 
1237
        """
 
1238
        self.connection = connection
 
1239
        self.credentials = credentials
 
1240
        self.base = base
 
1241
 
 
1242
 
 
1243
class ConnectedTransport(Transport):
 
1244
    """A transport connected to a remote server.
 
1245
 
 
1246
    This class provide the basis to implement transports that need to connect
 
1247
    to a remote server.
 
1248
 
 
1249
    Host and credentials are available as private attributes, cloning preserves
 
1250
    them and share the underlying, protocol specific, connection.
 
1251
    """
 
1252
 
 
1253
    def __init__(self, base, _from_transport=None):
 
1254
        """Constructor.
 
1255
 
 
1256
        The caller should ensure that _from_transport points at the same host
 
1257
        as the new base.
 
1258
 
 
1259
        :param base: transport root URL
 
1260
 
 
1261
        :param _from_transport: optional transport to build from. The built
 
1262
            transport will share the connection with this transport.
 
1263
        """
 
1264
        if not base.endswith('/'):
 
1265
            base += '/'
 
1266
        (self._scheme,
 
1267
         self._user, self._password,
 
1268
         self._host, self._port,
 
1269
         self._path) = self._split_url(base)
 
1270
        if _from_transport is not None:
 
1271
            # Copy the password as it does not appear in base and will be lost
 
1272
            # otherwise. It can appear in the _split_url above if the user
 
1273
            # provided it on the command line. Otherwise, daughter classes will
 
1274
            # prompt the user for one when appropriate.
 
1275
            self._password = _from_transport._password
 
1276
 
 
1277
        base = self._unsplit_url(self._scheme,
 
1278
                                 self._user, self._password,
 
1279
                                 self._host, self._port,
 
1280
                                 self._path)
 
1281
 
 
1282
        super(ConnectedTransport, self).__init__(base)
 
1283
        if _from_transport is None:
 
1284
            self._shared_connection = _SharedConnection()
 
1285
        else:
 
1286
            self._shared_connection = _from_transport._shared_connection
 
1287
 
 
1288
    def clone(self, offset=None):
 
1289
        """Return a new transport with root at self.base + offset
 
1290
 
 
1291
        We leave the daughter classes take advantage of the hint
 
1292
        that it's a cloning not a raw creation.
 
1293
        """
 
1294
        if offset is None:
 
1295
            return self.__class__(self.base, _from_transport=self)
 
1296
        else:
 
1297
            return self.__class__(self.abspath(offset), _from_transport=self)
 
1298
 
 
1299
    @staticmethod
 
1300
    def _split_url(url):
 
1301
        """
 
1302
        Extract the server address, the credentials and the path from the url.
 
1303
 
 
1304
        user, password, host and path should be quoted if they contain reserved
 
1305
        chars.
 
1306
 
 
1307
        :param url: an quoted url
 
1308
 
 
1309
        :return: (scheme, user, password, host, port, path) tuple, all fields
 
1310
            are unquoted.
 
1311
        """
 
1312
        if isinstance(url, unicode):
 
1313
            raise errors.InvalidURL('should be ascii:\n%r' % url)
 
1314
        url = url.encode('utf-8')
 
1315
        (scheme, netloc, path, params,
 
1316
         query, fragment) = urlparse.urlparse(url, allow_fragments=False)
 
1317
        user = password = host = port = None
 
1318
        if '@' in netloc:
 
1319
            user, host = netloc.rsplit('@', 1)
 
1320
            if ':' in user:
 
1321
                user, password = user.split(':', 1)
 
1322
                password = urllib.unquote(password)
 
1323
            user = urllib.unquote(user)
 
1324
        else:
 
1325
            host = netloc
 
1326
 
 
1327
        if ':' in host:
 
1328
            host, port = host.rsplit(':', 1)
 
1329
            try:
 
1330
                port = int(port)
 
1331
            except ValueError:
 
1332
                raise errors.InvalidURL('invalid port number %s in url:\n%s' %
 
1333
                                        (port, url))
 
1334
        if host == '':
 
1335
            raise errors.InvalidURL('Host empty in: %s' % url)
 
1336
 
 
1337
        host = urllib.unquote(host)
 
1338
        path = urllib.unquote(path)
 
1339
 
 
1340
        return (scheme, user, password, host, port, path)
 
1341
 
 
1342
    @staticmethod
 
1343
    def _unsplit_url(scheme, user, password, host, port, path):
 
1344
        """
 
1345
        Build the full URL for the given already URL encoded path.
 
1346
 
 
1347
        user, password, host and path will be quoted if they contain reserved
 
1348
        chars.
 
1349
 
 
1350
        :param scheme: protocol
 
1351
 
 
1352
        :param user: login
 
1353
 
 
1354
        :param password: associated password
 
1355
 
 
1356
        :param host: the server address
 
1357
 
 
1358
        :param port: the associated port
 
1359
 
 
1360
        :param path: the absolute path on the server
 
1361
 
 
1362
        :return: The corresponding URL.
 
1363
        """
 
1364
        netloc = urllib.quote(host)
 
1365
        if user is not None:
 
1366
            # Note that we don't put the password back even if we
 
1367
            # have one so that it doesn't get accidentally
 
1368
            # exposed.
 
1369
            netloc = '%s@%s' % (urllib.quote(user), netloc)
 
1370
        if port is not None:
 
1371
            netloc = '%s:%d' % (netloc, port)
 
1372
        path = urllib.quote(path)
 
1373
        return urlparse.urlunparse((scheme, netloc, path, None, None, None))
 
1374
 
 
1375
    def relpath(self, abspath):
 
1376
        """Return the local path portion from a given absolute path"""
 
1377
        scheme, user, password, host, port, path = self._split_url(abspath)
 
1378
        error = []
 
1379
        if (scheme != self._scheme):
 
1380
            error.append('scheme mismatch')
 
1381
        if (user != self._user):
 
1382
            error.append('user name mismatch')
 
1383
        if (host != self._host):
 
1384
            error.append('host mismatch')
 
1385
        if (port != self._port):
 
1386
            error.append('port mismatch')
 
1387
        if not (path == self._path[:-1] or path.startswith(self._path)):
 
1388
            error.append('path mismatch')
 
1389
        if error:
 
1390
            extra = ', '.join(error)
 
1391
            raise errors.PathNotChild(abspath, self.base, extra=extra)
 
1392
        pl = len(self._path)
 
1393
        return path[pl:].strip('/')
 
1394
 
 
1395
    def abspath(self, relpath):
 
1396
        """Return the full url to the given relative path.
 
1397
        
 
1398
        :param relpath: the relative path urlencoded
 
1399
 
 
1400
        :returns: the Unicode version of the absolute path for relpath.
 
1401
        """
 
1402
        relative = urlutils.unescape(relpath).encode('utf-8')
 
1403
        path = self._combine_paths(self._path, relative)
 
1404
        return self._unsplit_url(self._scheme, self._user, self._password,
 
1405
                                 self._host, self._port,
 
1406
                                 path)
 
1407
 
 
1408
    def _remote_path(self, relpath):
 
1409
        """Return the absolute path part of the url to the given relative path.
 
1410
 
 
1411
        This is the path that the remote server expect to receive in the
 
1412
        requests, daughter classes should redefine this method if needed and
 
1413
        use the result to build their requests.
 
1414
 
 
1415
        :param relpath: the path relative to the transport base urlencoded.
 
1416
 
 
1417
        :return: the absolute Unicode path on the server,
 
1418
        """
 
1419
        relative = urlutils.unescape(relpath).encode('utf-8')
 
1420
        remote_path = self._combine_paths(self._path, relative)
 
1421
        return remote_path
 
1422
 
 
1423
    def _get_shared_connection(self):
 
1424
        """Get the object shared amongst cloned transports.
 
1425
 
 
1426
        This should be used only by classes that needs to extend the sharing
 
1427
        with objects other than transports.
 
1428
 
 
1429
        Use _get_connection to get the connection itself.
 
1430
        """
 
1431
        return self._shared_connection
 
1432
 
 
1433
    def _set_connection(self, connection, credentials=None):
 
1434
        """Record a newly created connection with its associated credentials.
 
1435
 
 
1436
        Note: To ensure that connection is still shared after a temporary
 
1437
        failure and a new one needs to be created, daughter classes should
 
1438
        always call this method to set the connection and do so each time a new
 
1439
        connection is created.
 
1440
 
 
1441
        :param connection: An opaque object representing the connection used by
 
1442
            the daughter class.
 
1443
 
 
1444
        :param credentials: An opaque object representing the credentials
 
1445
            needed to create the connection.
 
1446
        """
 
1447
        self._shared_connection.connection = connection
 
1448
        self._shared_connection.credentials = credentials
 
1449
 
 
1450
    def _get_connection(self):
 
1451
        """Returns the transport specific connection object."""
 
1452
        return self._shared_connection.connection
 
1453
 
 
1454
    def _get_credentials(self):
 
1455
        """Returns the credentials used to establish the connection."""
 
1456
        return self._shared_connection.credentials
 
1457
 
 
1458
    def _update_credentials(self, credentials):
 
1459
        """Update the credentials of the current connection.
 
1460
 
 
1461
        Some protocols can renegociate the credentials within a connection,
 
1462
        this method allows daughter classes to share updated credentials.
 
1463
        
 
1464
        :param credentials: the updated credentials.
 
1465
        """
 
1466
        # We don't want to call _set_connection here as we are only updating
 
1467
        # the credentials not creating a new connection.
 
1468
        self._shared_connection.credentials = credentials
 
1469
 
 
1470
    def _reuse_for(self, other_base):
 
1471
        """Returns a transport sharing the same connection if possible.
 
1472
 
 
1473
        Note: we share the connection if the expected credentials are the
 
1474
        same: (host, port, user). Some protocols may disagree and redefine the
 
1475
        criteria in daughter classes.
 
1476
 
 
1477
        Note: we don't compare the passwords here because other_base may have
 
1478
        been obtained from an existing transport.base which do not mention the
 
1479
        password.
 
1480
 
 
1481
        :param other_base: the URL we want to share the connection with.
 
1482
 
 
1483
        :return: A new transport or None if the connection cannot be shared.
 
1484
        """
 
1485
        try:
 
1486
            (scheme, user, password,
 
1487
             host, port, path) = self._split_url(other_base)
 
1488
        except errors.InvalidURL:
 
1489
            # No hope in trying to reuse an existing transport for an invalid
 
1490
            # URL
 
1491
            return None
 
1492
 
 
1493
        transport = None
 
1494
        # Don't compare passwords, they may be absent from other_base or from
 
1495
        # self and they don't carry more information than user anyway.
 
1496
        if (scheme == self._scheme
 
1497
            and user == self._user
 
1498
            and host == self._host
 
1499
            and port == self._port):
 
1500
            if not path.endswith('/'):
 
1501
                # This normally occurs at __init__ time, but it's easier to do
 
1502
                # it now to avoid creating two transports for the same base.
 
1503
                path += '/'
 
1504
            if self._path  == path:
 
1505
                # shortcut, it's really the same transport
 
1506
                return self
 
1507
            # We don't call clone here because the intent is different: we
 
1508
            # build a new transport on a different base (which may be totally
 
1509
            # unrelated) but we share the connection.
 
1510
            transport = self.__class__(other_base, _from_transport=self)
 
1511
        return transport
 
1512
 
 
1513
 
 
1514
# We try to recognize an url lazily (ignoring user, password, etc)
 
1515
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<rest>.*)$')
 
1516
 
 
1517
def get_transport(base, possible_transports=None):
 
1518
    """Open a transport to access a URL or directory.
 
1519
 
 
1520
    :param base: either a URL or a directory name.
 
1521
 
 
1522
    :param transports: optional reusable transports list. If not None, created
 
1523
        transports will be added to the list.
 
1524
 
 
1525
    :return: A new transport optionally sharing its connection with one of
 
1526
        possible_transports.
 
1527
    """
 
1528
    if base is None:
 
1529
        base = '.'
 
1530
    last_err = None
 
1531
    from bzrlib.directory_service import directories
 
1532
    base = directories.dereference(base)
 
1533
 
 
1534
    def convert_path_to_url(base, error_str):
 
1535
        m = _urlRE.match(base)
 
1536
        if m:
 
1537
            # This looks like a URL, but we weren't able to 
 
1538
            # instantiate it as such raise an appropriate error
 
1539
            # FIXME: we have a 'error_str' unused and we use last_err below
 
1540
            raise errors.UnsupportedProtocol(base, last_err)
 
1541
        # This doesn't look like a protocol, consider it a local path
 
1542
        new_base = urlutils.local_path_to_url(base)
 
1543
        # mutter('converting os path %r => url %s', base, new_base)
 
1544
        return new_base
 
1545
 
 
1546
    # Catch any URLs which are passing Unicode rather than ASCII
 
1547
    try:
 
1548
        base = base.encode('ascii')
 
1549
    except UnicodeError:
 
1550
        # Only local paths can be Unicode
 
1551
        base = convert_path_to_url(base,
 
1552
            'URLs must be properly escaped (protocol: %s)')
 
1553
 
 
1554
    transport = None
 
1555
    if possible_transports is not None:
 
1556
        for t in possible_transports:
 
1557
            t_same_connection = t._reuse_for(base)
 
1558
            if t_same_connection is not None:
 
1559
                # Add only new transports
 
1560
                if t_same_connection not in possible_transports:
 
1561
                    possible_transports.append(t_same_connection)
 
1562
                return t_same_connection
 
1563
 
 
1564
    for proto, factory_list in transport_list_registry.iteritems():
 
1565
        if proto is not None and base.startswith(proto):
 
1566
            transport, last_err = _try_transport_factories(base, factory_list)
 
1567
            if transport:
 
1568
                if possible_transports is not None:
 
1569
                    if transport in possible_transports:
 
1570
                        raise AssertionError()
 
1571
                    possible_transports.append(transport)
 
1572
                return transport
 
1573
 
 
1574
    # We tried all the different protocols, now try one last time
 
1575
    # as a local protocol
 
1576
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
 
1577
 
 
1578
    # The default handler is the filesystem handler, stored as protocol None
 
1579
    factory_list = transport_list_registry.get(None)
 
1580
    transport, last_err = _try_transport_factories(base, factory_list)
 
1581
 
 
1582
    return transport
 
1583
 
 
1584
 
 
1585
def _try_transport_factories(base, factory_list):
 
1586
    last_err = None
 
1587
    for factory in factory_list:
 
1588
        try:
 
1589
            return factory.get_obj()(base), None
 
1590
        except errors.DependencyNotPresent, e:
 
1591
            mutter("failed to instantiate transport %r for %r: %r" %
 
1592
                    (factory, base, e))
 
1593
            last_err = e
 
1594
            continue
 
1595
    return None, last_err
 
1596
 
 
1597
 
 
1598
def do_catching_redirections(action, transport, redirected):
 
1599
    """Execute an action with given transport catching redirections.
 
1600
 
 
1601
    This is a facility provided for callers needing to follow redirections
 
1602
    silently. The silence is relative: it is the caller responsability to
 
1603
    inform the user about each redirection or only inform the user of a user
 
1604
    via the exception parameter.
 
1605
 
 
1606
    :param action: A callable, what the caller want to do while catching
 
1607
                  redirections.
 
1608
    :param transport: The initial transport used.
 
1609
    :param redirected: A callable receiving the redirected transport and the 
 
1610
                  RedirectRequested exception.
 
1611
 
 
1612
    :return: Whatever 'action' returns
 
1613
    """
 
1614
    MAX_REDIRECTIONS = 8
 
1615
 
 
1616
    # If a loop occurs, there is little we can do. So we don't try to detect
 
1617
    # them, just getting out if too much redirections occurs. The solution
 
1618
    # is outside: where the loop is defined.
 
1619
    for redirections in range(MAX_REDIRECTIONS):
 
1620
        try:
 
1621
            return action(transport)
 
1622
        except errors.RedirectRequested, e:
 
1623
            redirection_notice = '%s is%s redirected to %s' % (
 
1624
                e.source, e.permanently, e.target)
 
1625
            transport = redirected(transport, e, redirection_notice)
 
1626
    else:
 
1627
        # Loop exited without resolving redirect ? Either the
 
1628
        # user has kept a very very very old reference or a loop
 
1629
        # occurred in the redirections.  Nothing we can cure here:
 
1630
        # tell the user. Note that as the user has been informed
 
1631
        # about each redirection (it is the caller responsibility
 
1632
        # to do that in redirected via the provided
 
1633
        # redirection_notice). The caller may provide more
 
1634
        # information if needed (like what file or directory we
 
1635
        # were trying to act upon when the redirection loop
 
1636
        # occurred).
 
1637
        raise errors.TooManyRedirections
 
1638
 
 
1639
 
 
1640
class Server(object):
 
1641
    """A Transport Server.
 
1642
    
 
1643
    The Server interface provides a server for a given transport. We use
 
1644
    these servers as loopback testing tools. For any given transport the
 
1645
    Servers it provides must either allow writing, or serve the contents
 
1646
    of os.getcwdu() at the time setUp is called.
 
1647
    
 
1648
    Note that these are real servers - they must implement all the things
 
1649
    that we want bzr transports to take advantage of.
 
1650
    """
 
1651
 
 
1652
    def setUp(self):
 
1653
        """Setup the server to service requests."""
 
1654
 
 
1655
    def tearDown(self):
 
1656
        """Remove the server and cleanup any resources it owns."""
 
1657
 
 
1658
    def get_url(self):
 
1659
        """Return a url for this server.
 
1660
        
 
1661
        If the transport does not represent a disk directory (i.e. it is 
 
1662
        a database like svn, or a memory only transport, it should return
 
1663
        a connection to a newly established resource for this Server.
 
1664
        Otherwise it should return a url that will provide access to the path
 
1665
        that was os.getcwdu() when setUp() was called.
 
1666
        
 
1667
        Subsequent calls will return the same resource.
 
1668
        """
 
1669
        raise NotImplementedError
 
1670
 
 
1671
    def get_bogus_url(self):
 
1672
        """Return a url for this protocol, that will fail to connect.
 
1673
        
 
1674
        This may raise NotImplementedError to indicate that this server cannot
 
1675
        provide bogus urls.
 
1676
        """
 
1677
        raise NotImplementedError
 
1678
 
 
1679
 
 
1680
# None is the default transport, for things with no url scheme
 
1681
register_transport_proto('file://',
 
1682
            help="Access using the standard filesystem (default)")
 
1683
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
 
1684
transport_list_registry.set_default_transport("file://")
 
1685
 
 
1686
register_transport_proto('sftp://',
 
1687
            help="Access using SFTP (most SSH servers provide SFTP).",
 
1688
            register_netloc=True)
 
1689
register_lazy_transport('sftp://', 'bzrlib.transport.sftp', 'SFTPTransport')
 
1690
# Decorated http transport
 
1691
register_transport_proto('http+urllib://',
 
1692
#                help="Read-only access of branches exported on the web."
 
1693
                         register_netloc=True)
 
1694
register_lazy_transport('http+urllib://', 'bzrlib.transport.http._urllib',
 
1695
                        'HttpTransport_urllib')
 
1696
register_transport_proto('https+urllib://',
 
1697
#                help="Read-only access of branches exported on the web using SSL."
 
1698
                         register_netloc=True)
 
1699
register_lazy_transport('https+urllib://', 'bzrlib.transport.http._urllib',
 
1700
                        'HttpTransport_urllib')
 
1701
register_transport_proto('http+pycurl://',
 
1702
#                help="Read-only access of branches exported on the web."
 
1703
                         register_netloc=True)
 
1704
register_lazy_transport('http+pycurl://', 'bzrlib.transport.http._pycurl',
 
1705
                        'PyCurlTransport')
 
1706
register_transport_proto('https+pycurl://',
 
1707
#                help="Read-only access of branches exported on the web using SSL."
 
1708
                         register_netloc=True)
 
1709
register_lazy_transport('https+pycurl://', 'bzrlib.transport.http._pycurl',
 
1710
                        'PyCurlTransport')
 
1711
# Default http transports (last declared wins (if it can be imported))
 
1712
register_transport_proto('http://',
 
1713
                 help="Read-only access of branches exported on the web.")
 
1714
register_transport_proto('https://',
 
1715
            help="Read-only access of branches exported on the web using SSL.")
 
1716
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
 
1717
                        'HttpTransport_urllib')
 
1718
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
 
1719
                        'HttpTransport_urllib')
 
1720
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
 
1721
                        'PyCurlTransport')
 
1722
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
 
1723
                        'PyCurlTransport')
 
1724
 
 
1725
register_transport_proto('ftp://', help="Access using passive FTP.")
 
1726
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
 
1727
register_transport_proto('aftp://', help="Access using active FTP.")
 
1728
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
 
1729
 
 
1730
# Default to trying GSSAPI authentication (if the kerberos module is available)
 
1731
register_transport_proto('ftp+gssapi://', register_netloc=True)
 
1732
register_lazy_transport('ftp+gssapi://', 'bzrlib.transport.ftp._gssapi', 
 
1733
                        'GSSAPIFtpTransport')
 
1734
register_transport_proto('aftp+gssapi://', register_netloc=True)
 
1735
register_lazy_transport('aftp+gssapi://', 'bzrlib.transport.ftp._gssapi', 
 
1736
                        'GSSAPIFtpTransport')
 
1737
register_transport_proto('ftp+nogssapi://', register_netloc=True)
 
1738
register_transport_proto('aftp+nogssapi://', register_netloc=True)
 
1739
 
 
1740
register_lazy_transport('ftp://', 'bzrlib.transport.ftp._gssapi', 
 
1741
                        'GSSAPIFtpTransport')
 
1742
register_lazy_transport('aftp://', 'bzrlib.transport.ftp._gssapi', 
 
1743
                        'GSSAPIFtpTransport')
 
1744
register_lazy_transport('ftp+nogssapi://', 'bzrlib.transport.ftp', 
 
1745
                        'FtpTransport')
 
1746
register_lazy_transport('aftp+nogssapi://', 'bzrlib.transport.ftp', 
 
1747
                        'FtpTransport')
 
1748
 
 
1749
register_transport_proto('memory://')
 
1750
register_lazy_transport('memory://', 'bzrlib.transport.memory',
 
1751
                        'MemoryTransport')
 
1752
 
 
1753
# chroots cannot be implicitly accessed, they must be explicitly created:
 
1754
register_transport_proto('chroot+')
 
1755
 
 
1756
register_transport_proto('readonly+',
 
1757
#              help="This modifier converts any transport to be readonly."
 
1758
            )
 
1759
register_lazy_transport('readonly+', 'bzrlib.transport.readonly',
 
1760
                        'ReadonlyTransportDecorator')
 
1761
 
 
1762
register_transport_proto('fakenfs+')
 
1763
register_lazy_transport('fakenfs+', 'bzrlib.transport.fakenfs',
 
1764
                        'FakeNFSTransportDecorator')
 
1765
 
 
1766
register_transport_proto('log+')
 
1767
register_lazy_transport('log+', 'bzrlib.transport.log', 'TransportLogDecorator')
 
1768
 
 
1769
register_transport_proto('trace+')
 
1770
register_lazy_transport('trace+', 'bzrlib.transport.trace',
 
1771
                        'TransportTraceDecorator')
 
1772
 
 
1773
register_transport_proto('unlistable+')
 
1774
register_lazy_transport('unlistable+', 'bzrlib.transport.unlistable',
 
1775
                        'UnlistableTransportDecorator')
 
1776
 
 
1777
register_transport_proto('brokenrename+')
 
1778
register_lazy_transport('brokenrename+', 'bzrlib.transport.brokenrename',
 
1779
                        'BrokenRenameTransportDecorator')
 
1780
 
 
1781
register_transport_proto('vfat+')
 
1782
register_lazy_transport('vfat+',
 
1783
                        'bzrlib.transport.fakevfat',
 
1784
                        'FakeVFATTransportDecorator')
 
1785
 
 
1786
register_transport_proto('nosmart+')
 
1787
register_lazy_transport('nosmart+', 'bzrlib.transport.nosmart',
 
1788
                        'NoSmartTransportDecorator')
 
1789
 
 
1790
# These two schemes were registered, but don't seem to have an actual transport
 
1791
# protocol registered
 
1792
for scheme in ['ssh', 'bzr+loopback']:
 
1793
    register_urlparse_netloc_protocol(scheme)
 
1794
del scheme
 
1795
 
 
1796
register_transport_proto('bzr://',
 
1797
            help="Fast access using the Bazaar smart server.",
 
1798
                         register_netloc=True)
 
1799
 
 
1800
register_lazy_transport('bzr://', 'bzrlib.transport.remote',
 
1801
                        'RemoteTCPTransport')
 
1802
register_transport_proto('bzr-v2://', register_netloc=True)
 
1803
 
 
1804
register_lazy_transport('bzr-v2://', 'bzrlib.transport.remote',
 
1805
                        'RemoteTCPTransportV2Only')
 
1806
register_transport_proto('bzr+http://',
 
1807
#                help="Fast access using the Bazaar smart server over HTTP."
 
1808
                         register_netloc=True)
 
1809
register_lazy_transport('bzr+http://', 'bzrlib.transport.remote',
 
1810
                        'RemoteHTTPTransport')
 
1811
register_transport_proto('bzr+https://',
 
1812
#                help="Fast access using the Bazaar smart server over HTTPS."
 
1813
                         register_netloc=True)
 
1814
register_lazy_transport('bzr+https://',
 
1815
                        'bzrlib.transport.remote',
 
1816
                        'RemoteHTTPTransport')
 
1817
register_transport_proto('bzr+ssh://',
 
1818
            help="Fast access using the Bazaar smart server over SSH.",
 
1819
            register_netloc=True)
 
1820
register_lazy_transport('bzr+ssh://', 'bzrlib.transport.remote',
 
1821
                        'RemoteSSHTransport')