~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/__init__.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2012, 2016 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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 __future__ import absolute_import
30
 
 
31
29
from cStringIO import StringIO
 
30
import re
32
31
import sys
33
32
 
34
33
from bzrlib.lazy_import import lazy_import
35
34
lazy_import(globals(), """
36
35
import errno
37
36
from stat import S_ISDIR
 
37
import urllib
38
38
import urlparse
39
39
 
40
40
from bzrlib import (
41
41
    errors,
42
42
    osutils,
43
43
    symbol_versioning,
44
 
    ui,
45
44
    urlutils,
46
45
    )
47
46
""")
48
47
 
49
48
from bzrlib.symbol_versioning import (
50
 
    DEPRECATED_PARAMETER,
51
 
    )
 
49
        deprecated_method,
 
50
        deprecated_function,
 
51
        DEPRECATED_PARAMETER,
 
52
        one_four,
 
53
        )
52
54
from bzrlib.trace import (
53
55
    mutter,
54
56
    )
55
 
from bzrlib import (
56
 
    hooks,
57
 
    registry,
58
 
    )
 
57
from bzrlib import registry
59
58
 
60
59
 
61
60
# a dictionary of open file streams. Keys are absolute paths, values are
85
84
def _get_transport_modules():
86
85
    """Return a list of the modules providing transports."""
87
86
    modules = set()
88
 
    for prefix, factory_list in transport_list_registry.items():
 
87
    for prefix, factory_list in transport_list_registry.iteritems():
89
88
        for factory in factory_list:
90
 
            modules.add(factory.get_module())
91
 
    # Add chroot and pathfilter directly, because there is no handler
92
 
    # registered for it.
 
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.
93
94
    modules.add('bzrlib.transport.chroot')
94
 
    modules.add('bzrlib.transport.pathfilter')
95
95
    result = list(modules)
96
96
    result.sort()
97
97
    return result
100
100
class TransportListRegistry(registry.Registry):
101
101
    """A registry which simplifies tracking available Transports.
102
102
 
103
 
    A registration of a new protocol requires two steps:
 
103
    A registration of a new protocol requires two step:
104
104
    1) register the prefix with the function register_transport( )
105
105
    2) register the protocol provider with the function
106
106
    register_transport_provider( ) ( and the "lazy" variant )
107
107
 
108
108
    This is needed because:
109
 
    a) a single provider can support multiple protocols ( like the ftp
 
109
    a) a single provider can support multple protcol ( like the ftp
110
110
    provider which supports both the ftp:// and the aftp:// protocols )
111
111
    b) a single protocol can have multiple providers ( like the http://
112
112
    protocol which is supported by both the urllib and pycurl provider )
122
122
    def register_transport(self, key, help=None):
123
123
        self.register(key, [], help)
124
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
 
125
129
 
126
130
transport_list_registry = TransportListRegistry()
127
131
 
138
142
def register_lazy_transport(prefix, module, classname):
139
143
    if not prefix in transport_list_registry:
140
144
        register_transport_proto(prefix)
141
 
    transport_list_registry.register_lazy_transport_provider(
142
 
        prefix, module, classname)
143
 
 
144
 
 
145
 
def register_transport(prefix, klass):
 
145
    transport_list_registry.register_lazy_transport_provider(prefix, module, classname)
 
146
 
 
147
 
 
148
def register_transport(prefix, klass, override=DEPRECATED_PARAMETER):
146
149
    if not prefix in transport_list_registry:
147
150
        register_transport_proto(prefix)
148
151
    transport_list_registry.register_transport_provider(prefix, klass)
232
235
    def _close(self):
233
236
        """A hook point for subclasses that need to take action on close."""
234
237
 
235
 
    def close(self, want_fdatasync=False):
236
 
        if want_fdatasync:
237
 
            try:
238
 
                self.fdatasync()
239
 
            except errors.TransportNotPossible:
240
 
                pass
 
238
    def close(self):
241
239
        self._close()
242
240
        del _file_streams[self.transport.abspath(self.relpath)]
243
241
 
244
 
    def fdatasync(self):
245
 
        """Force data out to physical disk if possible.
246
 
 
247
 
        :raises TransportNotPossible: If this transport has no way to 
248
 
            flush to disk.
249
 
        """
250
 
        raise errors.TransportNotPossible(
251
 
            "%s cannot fdatasync" % (self.transport,))
252
 
 
253
242
 
254
243
class FileFileStream(FileStream):
255
244
    """A file stream object returned by open_write_stream.
256
 
 
 
245
    
257
246
    This version uses a file like object to perform writes.
258
247
    """
259
248
 
264
253
    def _close(self):
265
254
        self.file_handle.close()
266
255
 
267
 
    def fdatasync(self):
268
 
        """Force data out to physical disk if possible."""
269
 
        self.file_handle.flush()
270
 
        try:
271
 
            fileno = self.file_handle.fileno()
272
 
        except AttributeError:
273
 
            raise errors.TransportNotPossible()
274
 
        osutils.fdatasync(fileno)
275
 
 
276
256
    def write(self, bytes):
277
257
        osutils.pump_string_file(bytes, self.file_handle)
278
258
 
279
259
 
280
260
class AppendBasedFileStream(FileStream):
281
261
    """A file stream object returned by open_write_stream.
282
 
 
 
262
    
283
263
    This version uses append on a transport to perform writes.
284
264
    """
285
265
 
287
267
        self.transport.append_bytes(self.relpath, bytes)
288
268
 
289
269
 
290
 
class TransportHooks(hooks.Hooks):
291
 
    """Mapping of hook names to registered callbacks for transport hooks"""
292
 
    def __init__(self):
293
 
        super(TransportHooks, self).__init__()
294
 
        self.add_hook("post_connect",
295
 
            "Called after a new connection is established or a reconnect "
296
 
            "occurs. The sole argument passed is either the connected "
297
 
            "transport or smart medium instance.", (2, 5))
298
 
 
299
 
 
300
270
class Transport(object):
301
271
    """This class encapsulates methods for retrieving or putting a file
302
272
    from/to a storage location.
303
273
 
304
274
    Most functions have a _multi variant, which allows you to queue up
305
 
    multiple requests. They generally have a dumb base implementation
 
275
    multiple requests. They generally have a dumb base implementation 
306
276
    which just iterates over the arguments, but smart Transport
307
277
    implementations can do pipelining.
308
278
    In general implementations should support having a generator or a list
321
291
    #       where the biggest benefit between combining reads and
322
292
    #       and seeking is. Consider a runtime auto-tune.
323
293
    _bytes_to_read_before_seek = 0
324
 
    
325
 
    hooks = TransportHooks()
326
294
 
327
295
    def __init__(self, base):
328
 
        super(Transport, self).__init__()
 
296
        super(Transport, self).__init__(base=base)
329
297
        self.base = base
330
 
        (self._raw_base, self._segment_parameters) = (
331
 
            urlutils.split_segment_parameters(base))
332
298
 
333
299
    def _translate_error(self, e, path, raise_generic=True):
334
300
        """Translate an IOError or OSError into an appropriate bzr error.
336
302
        This handles things like ENOENT, ENOTDIR, EEXIST, and EACCESS
337
303
        """
338
304
        if getattr(e, 'errno', None) is not None:
339
 
            if e.errno in (errno.ENOENT, errno.ENOTDIR):
340
 
                raise errors.NoSuchFile(path, extra=e)
341
 
            elif e.errno == errno.EINVAL:
342
 
                mutter("EINVAL returned on path %s: %r" % (path, e))
 
305
            if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
343
306
                raise errors.NoSuchFile(path, extra=e)
344
307
            # I would rather use errno.EFOO, but there doesn't seem to be
345
308
            # any matching for 267
360
323
 
361
324
    def clone(self, offset=None):
362
325
        """Return a new Transport object, cloned from the current location,
363
 
        using a subdirectory or parent directory. This allows connections
 
326
        using a subdirectory or parent directory. This allows connections 
364
327
        to be pooled, rather than a new one needed for each subdir.
365
328
        """
366
329
        raise NotImplementedError(self.clone)
367
330
 
368
 
    def create_prefix(self, mode=None):
369
 
        """Create all the directories leading down to self.base."""
370
 
        cur_transport = self
371
 
        needed = [cur_transport]
372
 
        # Recurse upwards until we can create a directory successfully
373
 
        while True:
374
 
            new_transport = cur_transport.clone('..')
375
 
            if new_transport.base == cur_transport.base:
376
 
                raise errors.BzrCommandError(
377
 
                    "Failed to create path prefix for %s."
378
 
                    % cur_transport.base)
379
 
            try:
380
 
                new_transport.mkdir('.', mode=mode)
381
 
            except errors.NoSuchFile:
382
 
                needed.append(new_transport)
383
 
                cur_transport = new_transport
384
 
            except errors.FileExists:
385
 
                break
386
 
            else:
387
 
                break
388
 
        # Now we only need to create child directories
389
 
        while needed:
390
 
            cur_transport = needed.pop()
391
 
            cur_transport.ensure_base(mode=mode)
392
 
 
393
 
    def ensure_base(self, mode=None):
 
331
    def ensure_base(self):
394
332
        """Ensure that the directory this transport references exists.
395
333
 
396
334
        This will create a directory if it doesn't exist.
400
338
        # than permission". We attempt to create the directory, and just
401
339
        # suppress FileExists and PermissionDenied (for Windows) exceptions.
402
340
        try:
403
 
            self.mkdir('.', mode=mode)
 
341
            self.mkdir('.')
404
342
        except (errors.FileExists, errors.PermissionDenied):
405
343
            return False
406
344
        else:
428
366
        """
429
367
        raise NotImplementedError(self.external_url)
430
368
 
431
 
    def get_segment_parameters(self):
432
 
        """Return the segment parameters for the top segment of the URL.
433
 
        """
434
 
        return self._segment_parameters
435
 
 
436
 
    def set_segment_parameter(self, name, value):
437
 
        """Set a segment parameter.
438
 
 
439
 
        :param name: Segment parameter name (urlencoded string)
440
 
        :param value: Segment parameter value (urlencoded string)
441
 
        """
442
 
        if value is None:
443
 
            try:
444
 
                del self._segment_parameters[name]
445
 
            except KeyError:
446
 
                pass
447
 
        else:
448
 
            self._segment_parameters[name] = value
449
 
        self.base = urlutils.join_segment_parameters(
450
 
            self._raw_base, self._segment_parameters)
451
 
 
452
369
    def _pump(self, from_file, to_file):
453
 
        """Most children will need to copy from one file-like
 
370
        """Most children will need to copy from one file-like 
454
371
        object or string to another one.
455
372
        This just gives them something easy to call.
456
373
        """
465
382
        except TypeError: # We can't tell how many, because relpaths is a generator
466
383
            return None
467
384
 
468
 
    def _report_activity(self, bytes, direction):
469
 
        """Notify that this transport has activity.
470
 
 
471
 
        Implementations should call this from all methods that actually do IO.
472
 
        Be careful that it's not called twice, if one method is implemented on
473
 
        top of another.
474
 
 
475
 
        :param bytes: Number of bytes read or written.
476
 
        :param direction: 'read' or 'write' or None.
477
 
        """
478
 
        ui.ui_factory.report_transport_activity(self, bytes, direction)
479
 
 
480
385
    def _update_pb(self, pb, msg, count, total):
481
386
        """Update the progress bar based on the current count
482
387
        and total available, total may be None if it was
519
424
        # interface ?
520
425
        raise NotImplementedError(self.abspath)
521
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
 
522
471
    def recommended_page_size(self):
523
472
        """Return the recommended page size for this transport.
524
473
 
525
474
        This is potentially different for every path in a given namespace.
526
 
        For example, local transports might use an operating system call to
 
475
        For example, local transports might use an operating system call to 
527
476
        get the block size for a given path, which can vary due to mount
528
477
        points.
529
478
 
535
484
        """Return the local path portion from a given absolute path.
536
485
 
537
486
        This default implementation is not suitable for filesystems with
538
 
        aliasing, such as that given by symlinks, where a path may not
539
 
        start with our base, but still be a relpath once aliasing is
 
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 
540
489
        resolved.
541
490
        """
542
491
        # TODO: This might want to use bzrlib.osutils.relpath
551
500
 
552
501
        This function will only be defined for Transports which have a
553
502
        physical local filesystem representation.
554
 
 
555
 
        :raises errors.NotLocalUrl: When no local path representation is
556
 
            available.
557
503
        """
558
504
        raise errors.NotLocalUrl(self.abspath(relpath))
559
505
 
 
506
 
560
507
    def has(self, relpath):
561
508
        """Does the file relpath exist?
562
 
 
 
509
        
563
510
        Note that some transports MAY allow querying on directories, but this
564
 
        is not part of the protocol.  In other words, the results of
 
511
        is not part of the protocol.  In other words, the results of 
565
512
        t.has("a_directory_name") are undefined.
566
513
 
567
514
        :rtype: bool
588
535
        """Iter the relative paths of files in the transports sub-tree.
589
536
 
590
537
        *NOTE*: This only lists *files*, not subdirectories!
591
 
 
 
538
        
592
539
        As with other listing functions, only some transports implement this,.
593
540
        you may check via listable() to determine if it will.
594
541
        """
621
568
 
622
569
        :param relpath: The relative path to the file
623
570
        """
624
 
        f = self.get(relpath)
625
 
        try:
626
 
            return f.read()
627
 
        finally:
628
 
            f.close()
 
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)
629
583
 
630
584
    def get_smart_medium(self):
631
585
        """Return a smart client medium for this transport if possible.
637
591
        """
638
592
        raise errors.NoSmartMedium(self)
639
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
 
640
605
    def readv(self, relpath, offsets, adjust_for_latency=False,
641
606
        upper_limit=None):
642
607
        """Get parts of the file at the given relative path.
690
655
 
691
656
        This uses _coalesce_offsets to issue larger reads and fewer seeks.
692
657
 
693
 
        :param fp: A file-like object that supports seek() and read(size).
694
 
            Note that implementations are allowed to call .close() on this file
695
 
            handle, so don't trust that you can use it for other work.
 
658
        :param fp: A file-like object that supports seek() and read(size)
696
659
        :param offsets: A list of offsets to be read from the given file.
697
660
        :return: yield (pos, data) tuples for each request
698
661
        """
709
672
 
710
673
        # Cache the results, but only until they have been fulfilled
711
674
        data_map = {}
712
 
        try:
713
 
            for c_offset in coalesced:
714
 
                # TODO: jam 20060724 it might be faster to not issue seek if
715
 
                #       we are already at the right location. This should be
716
 
                #       benchmarked.
717
 
                fp.seek(c_offset.start)
718
 
                data = fp.read(c_offset.length)
719
 
                if len(data) < c_offset.length:
720
 
                    raise errors.ShortReadvError(relpath, c_offset.start,
721
 
                                c_offset.length, actual=len(data))
722
 
                for suboffset, subsize in c_offset.ranges:
723
 
                    key = (c_offset.start+suboffset, subsize)
724
 
                    data_map[key] = data[suboffset:suboffset+subsize]
 
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]
725
687
 
726
 
                # Now that we've read some data, see if we can yield anything back
727
 
                while cur_offset_and_size in data_map:
728
 
                    this_data = data_map.pop(cur_offset_and_size)
729
 
                    this_offset = cur_offset_and_size[0]
730
 
                    try:
731
 
                        cur_offset_and_size = offset_stack.next()
732
 
                    except StopIteration:
733
 
                        fp.close()
734
 
                        cur_offset_and_size = None
735
 
                    yield this_offset, this_data
736
 
        finally:
737
 
            fp.close()
 
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()
738
693
 
739
694
    def _sort_expand_and_combine(self, offsets, upper_limit):
740
695
        """Helper for readv.
803
758
        into a single large request, while retaining the original
804
759
        offsets.
805
760
        Turns  [(15, 10), (25, 10)] => [(15, 20, [(0, 10), (10, 10)])]
806
 
        Note that overlapping requests are not permitted. (So [(15, 10), (20,
807
 
        10)] will raise a ValueError.) This is because the data we access never
808
 
        overlaps, and it allows callers to trust that we only need any byte of
809
 
        data for 1 request (so nothing needs to be buffered to fulfill a second
810
 
        request.)
811
761
 
812
762
        :param offsets: A list of (start, length) pairs
 
763
 
813
764
        :param limit: Only combine a maximum of this many pairs Some transports
814
765
                penalize multiple reads more than others, and sometimes it is
815
766
                better to return early.
816
767
                0 means no limit
 
768
 
817
769
        :param fudge_factor: All transports have some level of 'it is
818
 
                better to read some more data and throw it away rather
 
770
                better to read some more data and throw it away rather 
819
771
                than seek', so collapse if we are 'close enough'
 
772
 
820
773
        :param max_size: Create coalesced offsets no bigger than this size.
821
774
                When a single offset is bigger than 'max_size', it will keep
822
775
                its size and be alone in the coalesced offset.
823
776
                0 means no maximum size.
824
 
        :return: return a list of _CoalescedOffset objects, which have members
825
 
            for where to start, how much to read, and how to split those chunks
826
 
            back up
 
777
 
 
778
        :return: yield _CoalescedOffset objects, which have members for where
 
779
                to start, how much to read, and how to split those 
 
780
                chunks back up
827
781
        """
828
782
        last_end = None
829
783
        cur = _CoalescedOffset(None, None, [])
830
 
        coalesced_offsets = []
831
 
 
832
 
        if max_size <= 0:
833
 
            # 'unlimited', but we actually take this to mean 100MB buffer limit
834
 
            max_size = 100*1024*1024
835
784
 
836
785
        for start, size in offsets:
837
786
            end = start + size
840
789
                and start >= cur.start
841
790
                and (limit <= 0 or len(cur.ranges) < limit)
842
791
                and (max_size <= 0 or end - cur.start <= max_size)):
843
 
                if start < last_end:
844
 
                    raise ValueError('Overlapping range not allowed:'
845
 
                        ' last range ended at %s, new one starts at %s'
846
 
                        % (last_end, start))
847
792
                cur.length = end - cur.start
848
793
                cur.ranges.append((start-cur.start, size))
849
794
            else:
850
795
                if cur.start is not None:
851
 
                    coalesced_offsets.append(cur)
 
796
                    yield cur
852
797
                cur = _CoalescedOffset(start, size, [(0, size)])
853
798
            last_end = end
854
799
 
855
800
        if cur.start is not None:
856
 
            coalesced_offsets.append(cur)
857
 
        return coalesced_offsets
 
801
            yield cur
 
802
 
 
803
        return
858
804
 
859
805
    def get_multi(self, relpaths, pb=None):
860
806
        """Get a list of file-like objects, one for each entry in relpaths.
861
807
 
862
808
        :param relpaths: A list of relative paths.
863
 
        :param pb:  An optional ProgressTask for indicating percent done.
 
809
        :param pb:  An optional ProgressBar for indicating percent done.
864
810
        :return: A list or generator of file-like objects
865
811
        """
866
812
        # TODO: Consider having this actually buffer the requests,
873
819
            yield self.get(relpath)
874
820
            count += 1
875
821
 
876
 
    def put_bytes(self, relpath, raw_bytes, mode=None):
 
822
    def put_bytes(self, relpath, bytes, mode=None):
877
823
        """Atomically put the supplied bytes into the given location.
878
824
 
879
825
        :param relpath: The location to put the contents, relative to the
880
826
            transport base.
881
 
        :param raw_bytes: A bytestring of data.
 
827
        :param bytes: A bytestring of data.
882
828
        :param mode: Create the file with the given mode.
883
829
        :return: None
884
830
        """
885
 
        if not isinstance(raw_bytes, str):
886
 
            raise TypeError(
887
 
                'raw_bytes must be a plain string, not %s' % type(raw_bytes))
888
 
        return self.put_file(relpath, StringIO(raw_bytes), mode=mode)
 
831
        if not isinstance(bytes, str):
 
832
            raise AssertionError(
 
833
                'bytes must be a plain string, not %s' % type(bytes))
 
834
        return self.put_file(relpath, StringIO(bytes), mode=mode)
889
835
 
890
 
    def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
 
836
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
891
837
                             create_parent_dir=False,
892
838
                             dir_mode=None):
893
839
        """Copy the string into the target location.
894
840
 
895
 
        This function is not strictly safe to use. See
 
841
        This function is not strictly safe to use. See 
896
842
        Transport.put_bytes_non_atomic for more information.
897
843
 
898
844
        :param relpath: The remote location to put the contents.
899
 
        :param raw_bytes:   A string object containing the raw bytes to write
900
 
                        into the target file.
 
845
        :param bytes:   A string object containing the raw bytes to write into
 
846
                        the target file.
901
847
        :param mode:    Possible access permissions for new file.
902
848
                        None means do not set remote permissions.
903
849
        :param create_parent_dir: If we cannot create the target file because
905
851
                        create it, and then try again.
906
852
        :param dir_mode: Possible access permissions for new directories.
907
853
        """
908
 
        if not isinstance(raw_bytes, str):
909
 
            raise TypeError(
910
 
                'raw_bytes must be a plain string, not %s' % type(raw_bytes))
911
 
        self.put_file_non_atomic(relpath, StringIO(raw_bytes), mode=mode,
 
854
        if not isinstance(bytes, str):
 
855
            raise AssertionError(
 
856
                'bytes must be a plain string, not %s' % type(bytes))
 
857
        self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,
912
858
                                 create_parent_dir=create_parent_dir,
913
859
                                 dir_mode=dir_mode)
914
860
 
980
926
        be synchronised with any internal buffering that may be present.
981
927
 
982
928
        :param relpath: The relative path to the file.
983
 
        :param mode: The mode for the newly created file,
 
929
        :param mode: The mode for the newly created file, 
984
930
                     None means just use the default
985
931
        :return: A FileStream. FileStream objects have two methods, write() and
986
932
            close(). There is no guarantee that data is committed to the file
1029
975
        the supplied location.
1030
976
 
1031
977
        :param files: A set of (path, f) entries
1032
 
        :param pb:  An optional ProgressTask for indicating percent done.
 
978
        :param pb:  An optional ProgressBar for indicating percent done.
1033
979
        """
1034
980
        return self._iterate_over(files, self.append_file, pb, 'append', expand=True)
1035
981
 
1036
982
    def copy(self, rel_from, rel_to):
1037
983
        """Copy the item at rel_from to the location at rel_to.
1038
 
 
1039
 
        Override this for efficiency if a specific transport can do it
 
984
        
 
985
        Override this for efficiency if a specific transport can do it 
1040
986
        faster than this default implementation.
1041
987
        """
1042
988
        self.put_file(rel_to, self.get(rel_from))
1043
989
 
1044
990
    def copy_multi(self, relpaths, pb=None):
1045
991
        """Copy a bunch of entries.
1046
 
 
 
992
        
1047
993
        :param relpaths: A list of tuples of the form [(from, to), (from, to),...]
1048
994
        """
1049
995
        # This is the non-pipelined implementation, so that
1067
1013
    def copy_tree(self, from_relpath, to_relpath):
1068
1014
        """Copy a subtree from one relpath to another.
1069
1015
 
1070
 
        If a faster implementation is available, specific transports should
 
1016
        If a faster implementation is available, specific transports should 
1071
1017
        implement it.
1072
1018
        """
1073
1019
        source = self.clone(from_relpath)
 
1020
        self.mkdir(to_relpath)
1074
1021
        target = self.clone(to_relpath)
1075
 
 
1076
 
        # create target directory with the same rwx bits as source.
1077
 
        # use mask to ensure that bits other than rwx are ignored.
1078
 
        stat = self.stat(from_relpath)
1079
 
        target.mkdir('.', stat.st_mode & 0777)
1080
 
        source.copy_tree_to_transport(target)
1081
 
 
1082
 
    def copy_tree_to_transport(self, to_transport):
1083
 
        """Copy a subtree from one transport to another.
1084
 
 
1085
 
        self.base is used as the source tree root, and to_transport.base
1086
 
        is used as the target.  to_transport.base must exist (and be a
1087
 
        directory).
1088
 
        """
1089
1022
        files = []
1090
1023
        directories = ['.']
1091
1024
        while directories:
1092
1025
            dir = directories.pop()
1093
1026
            if dir != '.':
1094
 
                to_transport.mkdir(dir)
1095
 
            for path in self.list_dir(dir):
 
1027
                target.mkdir(dir)
 
1028
            for path in source.list_dir(dir):
1096
1029
                path = dir + '/' + path
1097
 
                stat = self.stat(path)
 
1030
                stat = source.stat(path)
1098
1031
                if S_ISDIR(stat.st_mode):
1099
1032
                    directories.append(path)
1100
1033
                else:
1101
1034
                    files.append(path)
1102
 
        self.copy_to(files, to_transport)
 
1035
        source.copy_to(files, target)
1103
1036
 
1104
1037
    def rename(self, rel_from, rel_to):
1105
1038
        """Rename a file or directory.
1125
1058
 
1126
1059
        The destination is deleted if possible, even if it's a non-empty
1127
1060
        directory tree.
1128
 
 
 
1061
        
1129
1062
        If a transport can directly implement this it is suggested that
1130
1063
        it do so for efficiency.
1131
1064
        """
1138
1071
 
1139
1072
    def move_multi(self, relpaths, pb=None):
1140
1073
        """Move a bunch of entries.
1141
 
 
 
1074
        
1142
1075
        :param relpaths: A list of tuples of the form [(from1, to1), (from2, to2),...]
1143
1076
        """
1144
1077
        return self._iterate_over(relpaths, self.move, pb, 'move', expand=True)
1197
1130
        NOTE: This returns an object with fields such as 'st_size'. It MAY
1198
1131
        or MAY NOT return the literal result of an os.stat() call, so all
1199
1132
        access should be via named fields.
1200
 
        ALSO NOTE: Stats of directories may not be supported on some
 
1133
        ALSO NOTE: Stats of directories may not be supported on some 
1201
1134
        transports.
1202
1135
        """
1203
1136
        raise NotImplementedError(self.stat)
1218
1151
        count = self._iterate_over(relpaths, gather, pb, 'stat', expand=False)
1219
1152
        return stats
1220
1153
 
1221
 
    def readlink(self, relpath):
1222
 
        """Return a string representing the path to which the symbolic link points."""
1223
 
        raise errors.TransportNotPossible("Dereferencing symlinks is not supported on %s" % self)
1224
 
 
1225
 
    def hardlink(self, source, link_name):
1226
 
        """Create a hardlink pointing to source named link_name."""
1227
 
        raise errors.TransportNotPossible("Hard links are not supported on %s" % self)
1228
 
 
1229
 
    def symlink(self, source, link_name):
1230
 
        """Create a symlink pointing to source named link_name."""
1231
 
        raise errors.TransportNotPossible("Symlinks are not supported on %s" % self)
1232
 
 
1233
1154
    def listable(self):
1234
1155
        """Return True if this store supports listing."""
1235
1156
        raise NotImplementedError(self.listable)
1252
1173
        These methods may be removed in the future.
1253
1174
 
1254
1175
        Transports may raise TransportNotPossible if OS-level locks cannot be
1255
 
        taken over this transport.
 
1176
        taken over this transport.  
1256
1177
 
1257
1178
        :return: A lock object, which should contain an unlock() function.
1258
1179
        """
1279
1200
        """Return true if this transport can store and retrieve unix modebits.
1280
1201
 
1281
1202
        (For example, 0700 to make a directory owner-private.)
1282
 
 
1283
 
        Note: most callers will not want to switch on this, but should rather
 
1203
        
 
1204
        Note: most callers will not want to switch on this, but should rather 
1284
1205
        just try and set permissions and let them be either stored or not.
1285
1206
        This is intended mainly for the use of the test suite.
1286
 
 
1287
 
        Warning: this is not guaranteed to be accurate as sometimes we can't
 
1207
        
 
1208
        Warning: this is not guaranteed to be accurate as sometimes we can't 
1288
1209
        be sure: for example with vfat mounted on unix, or a windows sftp
1289
1210
        server."""
1290
1211
        # TODO: Perhaps return a e.g. TransportCharacteristics that can answer
1297
1218
        # should be asked to ConnectedTransport only.
1298
1219
        return None
1299
1220
 
1300
 
    def disconnect(self):
1301
 
        # This is really needed for ConnectedTransport only, but it's easier to
1302
 
        # have Transport do nothing than testing that the disconnect should be
1303
 
        # asked to ConnectedTransport only.
1304
 
        pass
1305
 
 
1306
 
    def _redirected_to(self, source, target):
1307
 
        """Returns a transport suitable to re-issue a redirected request.
1308
 
 
1309
 
        :param source: The source url as returned by the server.
1310
 
        :param target: The target url as returned by the server.
1311
 
 
1312
 
        The redirection can be handled only if the relpath involved is not
1313
 
        renamed by the redirection.
1314
 
 
1315
 
        :returns: A transport or None.
1316
 
        """
1317
 
        # This returns None by default, meaning the transport can't handle the
1318
 
        # redirection.
1319
 
        return None
1320
 
 
1321
 
 
1322
1221
 
1323
1222
class _SharedConnection(object):
1324
1223
    """A connection shared between several transports."""
1359
1258
        """
1360
1259
        if not base.endswith('/'):
1361
1260
            base += '/'
1362
 
        self._parsed_url = self._split_url(base)
 
1261
        (self._scheme,
 
1262
         self._user, self._password,
 
1263
         self._host, self._port,
 
1264
         self._path) = self._split_url(base)
1363
1265
        if _from_transport is not None:
1364
1266
            # Copy the password as it does not appear in base and will be lost
1365
1267
            # otherwise. It can appear in the _split_url above if the user
1366
1268
            # provided it on the command line. Otherwise, daughter classes will
1367
1269
            # prompt the user for one when appropriate.
1368
 
            self._parsed_url.password = _from_transport._parsed_url.password
1369
 
            self._parsed_url.quoted_password = (
1370
 
                _from_transport._parsed_url.quoted_password)
 
1270
            self._password = _from_transport._password
1371
1271
 
1372
 
        base = str(self._parsed_url)
 
1272
        base = self._unsplit_url(self._scheme,
 
1273
                                 self._user, self._password,
 
1274
                                 self._host, self._port,
 
1275
                                 self._path)
1373
1276
 
1374
1277
        super(ConnectedTransport, self).__init__(base)
1375
1278
        if _from_transport is None:
1377
1280
        else:
1378
1281
            self._shared_connection = _from_transport._shared_connection
1379
1282
 
1380
 
    @property
1381
 
    def _user(self):
1382
 
        return self._parsed_url.user
1383
 
 
1384
 
    @property
1385
 
    def _password(self):
1386
 
        return self._parsed_url.password
1387
 
 
1388
 
    @property
1389
 
    def _host(self):
1390
 
        return self._parsed_url.host
1391
 
 
1392
 
    @property
1393
 
    def _port(self):
1394
 
        return self._parsed_url.port
1395
 
 
1396
 
    @property
1397
 
    def _path(self):
1398
 
        return self._parsed_url.path
1399
 
 
1400
 
    @property
1401
 
    def _scheme(self):
1402
 
        return self._parsed_url.scheme
1403
 
 
1404
1283
    def clone(self, offset=None):
1405
1284
        """Return a new transport with root at self.base + offset
1406
1285
 
1414
1293
 
1415
1294
    @staticmethod
1416
1295
    def _split_url(url):
1417
 
        return urlutils.URL.from_string(url)
 
1296
        """
 
1297
        Extract the server address, the credentials and the path from the url.
 
1298
 
 
1299
        user, password, host and path should be quoted if they contain reserved
 
1300
        chars.
 
1301
 
 
1302
        :param url: an quoted url
 
1303
 
 
1304
        :return: (scheme, user, password, host, port, path) tuple, all fields
 
1305
            are unquoted.
 
1306
        """
 
1307
        if isinstance(url, unicode):
 
1308
            raise errors.InvalidURL('should be ascii:\n%r' % url)
 
1309
        url = url.encode('utf-8')
 
1310
        (scheme, netloc, path, params,
 
1311
         query, fragment) = urlparse.urlparse(url, allow_fragments=False)
 
1312
        user = password = host = port = None
 
1313
        if '@' in netloc:
 
1314
            user, host = netloc.rsplit('@', 1)
 
1315
            if ':' in user:
 
1316
                user, password = user.split(':', 1)
 
1317
                password = urllib.unquote(password)
 
1318
            user = urllib.unquote(user)
 
1319
        else:
 
1320
            host = netloc
 
1321
 
 
1322
        if ':' in host:
 
1323
            host, port = host.rsplit(':', 1)
 
1324
            try:
 
1325
                port = int(port)
 
1326
            except ValueError:
 
1327
                raise errors.InvalidURL('invalid port number %s in url:\n%s' %
 
1328
                                        (port, url))
 
1329
        if host == '':
 
1330
            raise errors.InvalidURL('Host empty in: %s' % url)
 
1331
 
 
1332
        host = urllib.unquote(host)
 
1333
        path = urllib.unquote(path)
 
1334
 
 
1335
        return (scheme, user, password, host, port, path)
1418
1336
 
1419
1337
    @staticmethod
1420
1338
    def _unsplit_url(scheme, user, password, host, port, path):
1421
 
        """Build the full URL for the given already URL encoded path.
 
1339
        """
 
1340
        Build the full URL for the given already URL encoded path.
1422
1341
 
1423
1342
        user, password, host and path will be quoted if they contain reserved
1424
1343
        chars.
1425
1344
 
1426
1345
        :param scheme: protocol
 
1346
 
1427
1347
        :param user: login
 
1348
 
1428
1349
        :param password: associated password
 
1350
 
1429
1351
        :param host: the server address
 
1352
 
1430
1353
        :param port: the associated port
 
1354
 
1431
1355
        :param path: the absolute path on the server
1432
1356
 
1433
1357
        :return: The corresponding URL.
1434
1358
        """
1435
 
        netloc = urlutils.quote(host)
 
1359
        netloc = urllib.quote(host)
1436
1360
        if user is not None:
1437
1361
            # Note that we don't put the password back even if we
1438
1362
            # have one so that it doesn't get accidentally
1439
1363
            # exposed.
1440
 
            netloc = '%s@%s' % (urlutils.quote(user), netloc)
 
1364
            netloc = '%s@%s' % (urllib.quote(user), netloc)
1441
1365
        if port is not None:
1442
1366
            netloc = '%s:%d' % (netloc, port)
1443
 
        path = urlutils.escape(path)
 
1367
        path = urllib.quote(path)
1444
1368
        return urlparse.urlunparse((scheme, netloc, path, None, None, None))
1445
1369
 
1446
1370
    def relpath(self, abspath):
1447
1371
        """Return the local path portion from a given absolute path"""
1448
 
        parsed_url = self._split_url(abspath)
 
1372
        scheme, user, password, host, port, path = self._split_url(abspath)
1449
1373
        error = []
1450
 
        if parsed_url.scheme != self._parsed_url.scheme:
 
1374
        if (scheme != self._scheme):
1451
1375
            error.append('scheme mismatch')
1452
 
        if parsed_url.user != self._parsed_url.user:
 
1376
        if (user != self._user):
1453
1377
            error.append('user name mismatch')
1454
 
        if parsed_url.host != self._parsed_url.host:
 
1378
        if (host != self._host):
1455
1379
            error.append('host mismatch')
1456
 
        if parsed_url.port != self._parsed_url.port:
 
1380
        if (port != self._port):
1457
1381
            error.append('port mismatch')
1458
 
        if (not (parsed_url.path == self._parsed_url.path[:-1] or
1459
 
            parsed_url.path.startswith(self._parsed_url.path))):
 
1382
        if not (path == self._path[:-1] or path.startswith(self._path)):
1460
1383
            error.append('path mismatch')
1461
1384
        if error:
1462
1385
            extra = ', '.join(error)
1463
1386
            raise errors.PathNotChild(abspath, self.base, extra=extra)
1464
 
        pl = len(self._parsed_url.path)
1465
 
        return parsed_url.path[pl:].strip('/')
 
1387
        pl = len(self._path)
 
1388
        return path[pl:].strip('/')
1466
1389
 
1467
1390
    def abspath(self, relpath):
1468
1391
        """Return the full url to the given relative path.
1469
 
 
 
1392
        
1470
1393
        :param relpath: the relative path urlencoded
1471
1394
 
1472
1395
        :returns: the Unicode version of the absolute path for relpath.
1473
1396
        """
1474
 
        return str(self._parsed_url.clone(relpath))
 
1397
        relative = urlutils.unescape(relpath).encode('utf-8')
 
1398
        path = self._combine_paths(self._path, relative)
 
1399
        return self._unsplit_url(self._scheme, self._user, self._password,
 
1400
                                 self._host, self._port,
 
1401
                                 path)
1475
1402
 
1476
1403
    def _remote_path(self, relpath):
1477
1404
        """Return the absolute path part of the url to the given relative path.
1484
1411
 
1485
1412
        :return: the absolute Unicode path on the server,
1486
1413
        """
1487
 
        return self._parsed_url.clone(relpath).path
 
1414
        relative = urlutils.unescape(relpath).encode('utf-8')
 
1415
        remote_path = self._combine_paths(self._path, relative)
 
1416
        return remote_path
1488
1417
 
1489
1418
    def _get_shared_connection(self):
1490
1419
        """Get the object shared amongst cloned transports.
1512
1441
        """
1513
1442
        self._shared_connection.connection = connection
1514
1443
        self._shared_connection.credentials = credentials
1515
 
        for hook in self.hooks["post_connect"]:
1516
 
            hook(self)
1517
1444
 
1518
1445
    def _get_connection(self):
1519
1446
        """Returns the transport specific connection object."""
1528
1455
 
1529
1456
        Some protocols can renegociate the credentials within a connection,
1530
1457
        this method allows daughter classes to share updated credentials.
1531
 
 
 
1458
        
1532
1459
        :param credentials: the updated credentials.
1533
1460
        """
1534
1461
        # We don't want to call _set_connection here as we are only updating
1551
1478
        :return: A new transport or None if the connection cannot be shared.
1552
1479
        """
1553
1480
        try:
1554
 
            parsed_url = self._split_url(other_base)
 
1481
            (scheme, user, password,
 
1482
             host, port, path) = self._split_url(other_base)
1555
1483
        except errors.InvalidURL:
1556
1484
            # No hope in trying to reuse an existing transport for an invalid
1557
1485
            # URL
1560
1488
        transport = None
1561
1489
        # Don't compare passwords, they may be absent from other_base or from
1562
1490
        # self and they don't carry more information than user anyway.
1563
 
        if (parsed_url.scheme == self._parsed_url.scheme
1564
 
            and parsed_url.user == self._parsed_url.user
1565
 
            and parsed_url.host == self._parsed_url.host
1566
 
            and parsed_url.port == self._parsed_url.port):
1567
 
            path = parsed_url.path
 
1491
        if (scheme == self._scheme
 
1492
            and user == self._user
 
1493
            and host == self._host
 
1494
            and port == self._port):
1568
1495
            if not path.endswith('/'):
1569
1496
                # This normally occurs at __init__ time, but it's easier to do
1570
1497
                # it now to avoid creating two transports for the same base.
1571
1498
                path += '/'
1572
 
            if self._parsed_url.path  == path:
 
1499
            if self._path  == path:
1573
1500
                # shortcut, it's really the same transport
1574
1501
                return self
1575
1502
            # We don't call clone here because the intent is different: we
1578
1505
            transport = self.__class__(other_base, _from_transport=self)
1579
1506
        return transport
1580
1507
 
1581
 
    def disconnect(self):
1582
 
        """Disconnect the transport.
1583
 
 
1584
 
        If and when required the transport willl reconnect automatically.
1585
 
        """
1586
 
        raise NotImplementedError(self.disconnect)
1587
 
 
1588
 
 
1589
 
def location_to_url(location):
1590
 
    """Determine a fully qualified URL from a location string.
1591
 
 
1592
 
    This will try to interpret location as both a URL and a directory path. It
1593
 
    will also lookup the location in directories.
1594
 
 
1595
 
    :param location: Unicode or byte string object with a location
1596
 
    :raise InvalidURL: If the location is already a URL, but not valid.
1597
 
    :return: Byte string with resulting URL
 
1508
 
 
1509
# We try to recognize an url lazily (ignoring user, password, etc)
 
1510
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<rest>.*)$')
 
1511
 
 
1512
def get_transport(base, possible_transports=None):
 
1513
    """Open a transport to access a URL or directory.
 
1514
 
 
1515
    :param base: either a URL or a directory name.
 
1516
 
 
1517
    :param transports: optional reusable transports list. If not None, created
 
1518
        transports will be added to the list.
 
1519
 
 
1520
    :return: A new transport optionally sharing its connection with one of
 
1521
        possible_transports.
1598
1522
    """
1599
 
    if not isinstance(location, basestring):
1600
 
        raise AssertionError("location not a byte or unicode string")
 
1523
    if base is None:
 
1524
        base = '.'
 
1525
    last_err = None
1601
1526
    from bzrlib.directory_service import directories
1602
 
    location = directories.dereference(location)
 
1527
    base = directories.dereference(base)
 
1528
 
 
1529
    def convert_path_to_url(base, error_str):
 
1530
        m = _urlRE.match(base)
 
1531
        if m:
 
1532
            # This looks like a URL, but we weren't able to 
 
1533
            # instantiate it as such raise an appropriate error
 
1534
            # FIXME: we have a 'error_str' unused and we use last_err below
 
1535
            raise errors.UnsupportedProtocol(base, last_err)
 
1536
        # This doesn't look like a protocol, consider it a local path
 
1537
        new_base = urlutils.local_path_to_url(base)
 
1538
        # mutter('converting os path %r => url %s', base, new_base)
 
1539
        return new_base
1603
1540
 
1604
1541
    # Catch any URLs which are passing Unicode rather than ASCII
1605
1542
    try:
1606
 
        location = location.encode('ascii')
 
1543
        base = base.encode('ascii')
1607
1544
    except UnicodeError:
1608
 
        if urlutils.is_url(location):
1609
 
            raise errors.InvalidURL(path=location,
1610
 
                extra='URLs must be properly escaped')
1611
 
        location = urlutils.local_path_to_url(location)
1612
 
 
1613
 
    if location.startswith("file:") and not location.startswith("file://"):
1614
 
        return urlutils.join(urlutils.local_path_to_url("."), location[5:])
1615
 
 
1616
 
    if not urlutils.is_url(location):
1617
 
        return urlutils.local_path_to_url(location)
1618
 
 
1619
 
    return location
1620
 
 
1621
 
 
1622
 
def get_transport_from_path(path, possible_transports=None):
1623
 
    """Open a transport for a local path.
1624
 
 
1625
 
    :param path: Local path as byte or unicode string
1626
 
    :return: Transport object for path
1627
 
    """
1628
 
    return get_transport_from_url(urlutils.local_path_to_url(path),
1629
 
        possible_transports)
1630
 
 
1631
 
 
1632
 
def get_transport_from_url(url, possible_transports=None):
1633
 
    """Open a transport to access a URL.
1634
 
    
1635
 
    :param base: a URL
1636
 
    :param transports: optional reusable transports list. If not None, created
1637
 
        transports will be added to the list.
1638
 
 
1639
 
    :return: A new transport optionally sharing its connection with one of
1640
 
        possible_transports.
1641
 
    """
 
1545
        # Only local paths can be Unicode
 
1546
        base = convert_path_to_url(base,
 
1547
            'URLs must be properly escaped (protocol: %s)')
 
1548
 
1642
1549
    transport = None
1643
1550
    if possible_transports is not None:
1644
1551
        for t in possible_transports:
1645
 
            t_same_connection = t._reuse_for(url)
 
1552
            t_same_connection = t._reuse_for(base)
1646
1553
            if t_same_connection is not None:
1647
1554
                # Add only new transports
1648
1555
                if t_same_connection not in possible_transports:
1649
1556
                    possible_transports.append(t_same_connection)
1650
1557
                return t_same_connection
1651
1558
 
1652
 
    last_err = None
1653
 
    for proto, factory_list in transport_list_registry.items():
1654
 
        if proto is not None and url.startswith(proto):
1655
 
            transport, last_err = _try_transport_factories(url, factory_list)
 
1559
    for proto, factory_list in transport_list_registry.iteritems():
 
1560
        if proto is not None and base.startswith(proto):
 
1561
            transport, last_err = _try_transport_factories(base, factory_list)
1656
1562
            if transport:
1657
1563
                if possible_transports is not None:
1658
1564
                    if transport in possible_transports:
1659
1565
                        raise AssertionError()
1660
1566
                    possible_transports.append(transport)
1661
1567
                return transport
1662
 
    if not urlutils.is_url(url):
1663
 
        raise errors.InvalidURL(path=url)
1664
 
    raise errors.UnsupportedProtocol(url, last_err)
1665
 
 
1666
 
 
1667
 
def get_transport(base, possible_transports=None):
1668
 
    """Open a transport to access a URL or directory.
1669
 
 
1670
 
    :param base: either a URL or a directory name.
1671
 
 
1672
 
    :param transports: optional reusable transports list. If not None, created
1673
 
        transports will be added to the list.
1674
 
 
1675
 
    :return: A new transport optionally sharing its connection with one of
1676
 
        possible_transports.
1677
 
    """
1678
 
    if base is None:
1679
 
        base = '.'
1680
 
    return get_transport_from_url(location_to_url(base), possible_transports)
 
1568
 
 
1569
    # We tried all the different protocols, now try one last time
 
1570
    # as a local protocol
 
1571
    base = convert_path_to_url(base, 'Unsupported protocol: %s')
 
1572
 
 
1573
    # The default handler is the filesystem handler, stored as protocol None
 
1574
    factory_list = transport_list_registry.get(None)
 
1575
    transport, last_err = _try_transport_factories(base, factory_list)
 
1576
 
 
1577
    return transport
1681
1578
 
1682
1579
 
1683
1580
def _try_transport_factories(base, factory_list):
1704
1601
    :param action: A callable, what the caller want to do while catching
1705
1602
                  redirections.
1706
1603
    :param transport: The initial transport used.
1707
 
    :param redirected: A callable receiving the redirected transport and the
 
1604
    :param redirected: A callable receiving the redirected transport and the 
1708
1605
                  RedirectRequested exception.
1709
1606
 
1710
1607
    :return: Whatever 'action' returns
1737
1634
 
1738
1635
class Server(object):
1739
1636
    """A Transport Server.
1740
 
 
1741
 
    The Server interface provides a server for a given transport type.
 
1637
    
 
1638
    The Server interface provides a server for a given transport. We use
 
1639
    these servers as loopback testing tools. For any given transport the
 
1640
    Servers it provides must either allow writing, or serve the contents
 
1641
    of os.getcwdu() at the time setUp is called.
 
1642
    
 
1643
    Note that these are real servers - they must implement all the things
 
1644
    that we want bzr transports to take advantage of.
1742
1645
    """
1743
1646
 
1744
 
    def start_server(self):
 
1647
    def setUp(self):
1745
1648
        """Setup the server to service requests."""
1746
1649
 
1747
 
    def stop_server(self):
 
1650
    def tearDown(self):
1748
1651
        """Remove the server and cleanup any resources it owns."""
1749
1652
 
 
1653
    def get_url(self):
 
1654
        """Return a url for this server.
 
1655
        
 
1656
        If the transport does not represent a disk directory (i.e. it is 
 
1657
        a database like svn, or a memory only transport, it should return
 
1658
        a connection to a newly established resource for this Server.
 
1659
        Otherwise it should return a url that will provide access to the path
 
1660
        that was os.getcwdu() when setUp() was called.
 
1661
        
 
1662
        Subsequent calls will return the same resource.
 
1663
        """
 
1664
        raise NotImplementedError
 
1665
 
 
1666
    def get_bogus_url(self):
 
1667
        """Return a url for this protocol, that will fail to connect.
 
1668
        
 
1669
        This may raise NotImplementedError to indicate that this server cannot
 
1670
        provide bogus urls.
 
1671
        """
 
1672
        raise NotImplementedError
 
1673
 
1750
1674
 
1751
1675
# None is the default transport, for things with no url scheme
1752
1676
register_transport_proto('file://',
1753
1677
            help="Access using the standard filesystem (default)")
1754
1678
register_lazy_transport('file://', 'bzrlib.transport.local', 'LocalTransport')
 
1679
transport_list_registry.set_default_transport("file://")
1755
1680
 
1756
1681
register_transport_proto('sftp://',
1757
1682
            help="Access using SFTP (most SSH servers provide SFTP).",
1783
1708
                 help="Read-only access of branches exported on the web.")
1784
1709
register_transport_proto('https://',
1785
1710
            help="Read-only access of branches exported on the web using SSL.")
1786
 
# The default http implementation is urllib
 
1711
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
 
1712
                        'HttpTransport_urllib')
 
1713
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
 
1714
                        'HttpTransport_urllib')
1787
1715
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
1788
1716
                        'PyCurlTransport')
1789
 
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1790
 
                        'HttpTransport_urllib')
1791
1717
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1792
1718
                        'PyCurlTransport')
1793
 
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1794
 
                        'HttpTransport_urllib')
1795
1719
 
1796
1720
register_transport_proto('ftp://', help="Access using passive FTP.")
1797
1721
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1798
1722
register_transport_proto('aftp://', help="Access using active FTP.")
1799
1723
register_lazy_transport('aftp://', 'bzrlib.transport.ftp', 'FtpTransport')
1800
 
register_transport_proto('gio+', help="Access using any GIO supported protocols.")
1801
 
register_lazy_transport('gio+', 'bzrlib.transport.gio_transport', 'GioTransport')
1802
 
 
1803
 
 
1804
 
# Default to trying GSSAPI authentication (if the kerberos module is
1805
 
# available)
 
1724
 
 
1725
# Default to trying GSSAPI authentication (if the kerberos module is available)
1806
1726
register_transport_proto('ftp+gssapi://', register_netloc=True)
 
1727
register_lazy_transport('ftp+gssapi://', 'bzrlib.transport.ftp._gssapi', 
 
1728
                        'GSSAPIFtpTransport')
1807
1729
register_transport_proto('aftp+gssapi://', register_netloc=True)
 
1730
register_lazy_transport('aftp+gssapi://', 'bzrlib.transport.ftp._gssapi', 
 
1731
                        'GSSAPIFtpTransport')
1808
1732
register_transport_proto('ftp+nogssapi://', register_netloc=True)
1809
1733
register_transport_proto('aftp+nogssapi://', register_netloc=True)
1810
 
register_lazy_transport('ftp+gssapi://', 'bzrlib.transport.ftp._gssapi',
1811
 
                        'GSSAPIFtpTransport')
1812
 
register_lazy_transport('aftp+gssapi://', 'bzrlib.transport.ftp._gssapi',
1813
 
                        'GSSAPIFtpTransport')
1814
 
register_lazy_transport('ftp://', 'bzrlib.transport.ftp._gssapi',
1815
 
                        'GSSAPIFtpTransport')
1816
 
register_lazy_transport('aftp://', 'bzrlib.transport.ftp._gssapi',
1817
 
                        'GSSAPIFtpTransport')
1818
 
register_lazy_transport('ftp+nogssapi://', 'bzrlib.transport.ftp',
 
1734
 
 
1735
register_lazy_transport('ftp://', 'bzrlib.transport.ftp._gssapi', 
 
1736
                        'GSSAPIFtpTransport')
 
1737
register_lazy_transport('aftp://', 'bzrlib.transport.ftp._gssapi', 
 
1738
                        'GSSAPIFtpTransport')
 
1739
register_lazy_transport('ftp+nogssapi://', 'bzrlib.transport.ftp', 
1819
1740
                        'FtpTransport')
1820
 
register_lazy_transport('aftp+nogssapi://', 'bzrlib.transport.ftp',
 
1741
register_lazy_transport('aftp+nogssapi://', 'bzrlib.transport.ftp', 
1821
1742
                        'FtpTransport')
1822
1743
 
1823
1744
register_transport_proto('memory://')
1824
1745
register_lazy_transport('memory://', 'bzrlib.transport.memory',
1825
1746
                        'MemoryTransport')
1826
1747
 
 
1748
# chroots cannot be implicitly accessed, they must be explicitly created:
 
1749
register_transport_proto('chroot+')
 
1750
 
1827
1751
register_transport_proto('readonly+',
1828
1752
#              help="This modifier converts any transport to be readonly."
1829
1753
            )
1858
1782
register_lazy_transport('nosmart+', 'bzrlib.transport.nosmart',
1859
1783
                        'NoSmartTransportDecorator')
1860
1784
 
 
1785
# These two schemes were registered, but don't seem to have an actual transport
 
1786
# protocol registered
 
1787
for scheme in ['ssh', 'bzr+loopback']:
 
1788
    register_urlparse_netloc_protocol(scheme)
 
1789
del scheme
 
1790
 
1861
1791
register_transport_proto('bzr://',
1862
1792
            help="Fast access using the Bazaar smart server.",
1863
1793
                         register_netloc=True)
1884
1814
            register_netloc=True)
1885
1815
register_lazy_transport('bzr+ssh://', 'bzrlib.transport.remote',
1886
1816
                        'RemoteSSHTransport')
1887
 
 
1888
 
register_transport_proto('ssh:')
1889
 
register_lazy_transport('ssh:', 'bzrlib.transport.remote',
1890
 
                        'HintingSSHTransport')
1891
 
 
1892
 
 
1893
 
transport_server_registry = registry.Registry()
1894
 
transport_server_registry.register_lazy('bzr', 'bzrlib.smart.server',
1895
 
    'serve_bzr', help="The Bazaar smart server protocol over TCP. (default port: 4155)")
1896
 
transport_server_registry.default_key = 'bzr'