~bzr-pqm/bzr/bzr.dev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# Copyright (C) 2005, 2006 Canonical Ltd

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import urllib, urllib2

from bzrlib.errors import BzrError
from bzrlib.trace import mutter
from bzrlib.transport.http import HttpTransportBase, extract_auth
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
                           TransportError, ConnectionError)


class HttpTransport(HttpTransportBase):
    """Python urllib transport for http and https.
    """

    # TODO: Implement pipelined versions of all of the *_multi() functions.

    def __init__(self, base):
        """Set the base path where files will be stored."""
        super(HttpTransport, self).__init__(base)

    def _get_url(self, url):
        mutter("get_url %s" % url)
        manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
        url = extract_auth(url, manager)
        auth_handler = urllib2.HTTPBasicAuthHandler(manager)
        opener = urllib2.build_opener(auth_handler)
        url_f = opener.open(url)
        return url_f

    def should_cache(self):
        """Return True if the data pulled across should be cached locally.
        """
        return True

    def has(self, relpath):
        """Does the target location exist?

        TODO: HttpTransport.has() should use a HEAD request,
        not a full GET request.

        TODO: This should be changed so that we don't use
        urllib2 and get an exception, the code path would be
        cleaner if we just do an http HEAD request, and parse
        the return code.
        """
        path = relpath
        try:
            path = self.abspath(relpath)
            f = self._get_url(path)
            # Without the read and then close()
            # we tend to have busy sockets.
            f.read()
            f.close()
            return True
        except urllib2.URLError, e:
            mutter('url error code: %s for has url: %r', e.code, path)
            if e.code == 404:
                return False
            raise
        except IOError, e:
            mutter('io error: %s %s for has url: %r', 
                e.errno, errno.errorcode.get(e.errno), path)
            if e.errno == errno.ENOENT:
                return False
            raise TransportError(orig_error=e)

    def get(self, relpath):
        """Get the file at the given relative path.

        :param relpath: The relative path to the file
        """
        path = relpath
        try:
            path = self.abspath(relpath)
            return self._get_url(path)
        except urllib2.HTTPError, e:
            mutter('url error code: %s for has url: %r', e.code, path)
            if e.code == 404:
                raise NoSuchFile(path, extra=e)
            raise
        except (BzrError, IOError), e:
            if hasattr(e, 'errno'):
                mutter('io error: %s %s for has url: %r', 
                    e.errno, errno.errorcode.get(e.errno), path)
                if e.errno == errno.ENOENT:
                    raise NoSuchFile(path, extra=e)
            raise ConnectionError(msg = "Error retrieving %s: %s" 
                             % (self.abspath(relpath), str(e)),
                             orig_error=e)

    def copy_to(self, relpaths, other, mode=None, pb=None):
        """Copy a set of entries from self into another Transport.

        :param relpaths: A list/generator of entries to be copied.

        TODO: if other is LocalTransport, is it possible to
              do better than put(get())?
        """
        # At this point HttpTransport might be able to check and see if
        # the remote location is the same, and rather than download, and
        # then upload, it could just issue a remote copy_this command.
        if isinstance(other, HttpTransport):
            raise TransportNotPossible('http cannot be the target of copy_to()')
        else:
            return super(HttpTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)

    def move(self, rel_from, rel_to):
        """Move the item at rel_from to the location at rel_to"""
        raise TransportNotPossible('http does not support move()')

    def delete(self, relpath):
        """Delete the item at relpath"""
        raise TransportNotPossible('http does not support delete()')