~bzr-pqm/bzr/bzr.dev

1540.3.3 by Martin Pool
Review updates of pycurl transport
1
# Copyright (C) 2005, 2006 Canonical Ltd
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
2
#
1540.3.3 by Martin Pool
Review updates of pycurl transport
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.
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
7
#
1540.3.3 by Martin Pool
Review updates of pycurl transport
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.
1540.3.18 by Martin Pool
Style review fixes (thanks robertc)
12
#
1540.3.3 by Martin Pool
Review updates of pycurl transport
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
import urllib, urllib2
18
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
19
import bzrlib  # for the version
1540.3.3 by Martin Pool
Review updates of pycurl transport
20
from bzrlib.errors import BzrError
21
from bzrlib.trace import mutter
1636.1.2 by Robert Collins
More review fixen to the relpath at '/' fixes.
22
from bzrlib.transport import register_urlparse_netloc_protocol
1540.3.6 by Martin Pool
[merge] update from bzr.dev
23
from bzrlib.transport.http import HttpTransportBase, extract_auth, HttpServer
24
from bzrlib.errors import (TransportNotPossible, NoSuchFile,
1540.3.3 by Martin Pool
Review updates of pycurl transport
25
                           TransportError, ConnectionError)
26
27
1636.1.2 by Robert Collins
More review fixen to the relpath at '/' fixes.
28
register_urlparse_netloc_protocol('http+urllib')
1636.1.1 by Robert Collins
Fix calling relpath() and abspath() on transports at their root.
29
30
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
31
class Request(urllib2.Request):
32
    """Request object for urllib2 that allows the method to be overridden."""
33
34
    method = None
35
36
    def get_method(self):
37
        if self.method is not None:
38
            return self.method
39
        else:
40
            return urllib2.Request.get_method(self)
41
42
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
43
class HttpTransport_urllib(HttpTransportBase):
1540.3.3 by Martin Pool
Review updates of pycurl transport
44
    """Python urllib transport for http and https.
45
    """
46
47
    # TODO: Implement pipelined versions of all of the *_multi() functions.
48
49
    def __init__(self, base):
50
        """Set the base path where files will be stored."""
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
51
        super(HttpTransport_urllib, self).__init__(base)
52
53
    def _get(self, relpath, ranges):
54
        path = relpath
55
        try:
56
            path = self._real_abspath(relpath)
1540.3.27 by Martin Pool
Integrate http range support for pycurl
57
            response = self._get_url_impl(path, method='GET', ranges=ranges)
58
            return response.code, response
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
59
        except urllib2.HTTPError, e:
60
            mutter('url error code: %s for has url: %r', e.code, path)
61
            if e.code == 404:
62
                raise NoSuchFile(path, extra=e)
63
            raise
64
        except (BzrError, IOError), e:
65
            if hasattr(e, 'errno'):
66
                mutter('io error: %s %s for has url: %r',
67
                    e.errno, errno.errorcode.get(e.errno), path)
68
                if e.errno == errno.ENOENT:
69
                    raise NoSuchFile(path, extra=e)
70
            raise ConnectionError(msg = "Error retrieving %s: %s" 
71
                             % (self.abspath(relpath), str(e)),
72
                             orig_error=e)
73
74
    def _get_url_impl(self, url, method, ranges):
1540.3.27 by Martin Pool
Integrate http range support for pycurl
75
        """Actually pass get request into urllib
76
77
        :returns: urllib Response object
78
        """
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
79
        if ranges:
80
            range_string = ranges
81
        else:
82
            range_string = 'all'
83
        mutter("get_url %s [%s]" % (url, range_string))
1540.3.3 by Martin Pool
Review updates of pycurl transport
84
        manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
85
        url = extract_auth(url, manager)
86
        auth_handler = urllib2.HTTPBasicAuthHandler(manager)
87
        opener = urllib2.build_opener(auth_handler)
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
88
        request = Request(url)
89
        request.method = method
1616.1.11 by Martin Pool
Add Pragma and Cache-control headers to urllib requests
90
        request.add_header('Pragma', 'no-cache')
91
        request.add_header('Cache-control', 'max-age=0')
1540.3.27 by Martin Pool
Integrate http range support for pycurl
92
        request.add_header('User-Agent', 'bzr/%s (urllib)' % bzrlib.__version__)
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
93
        if ranges:
1540.3.27 by Martin Pool
Integrate http range support for pycurl
94
            assert len(ranges) == 1
95
            request.add_header('Range', 'bytes=%d-%d' % ranges[0])
1540.3.15 by Martin Pool
[merge] large merge to sync with bzr.dev
96
        response = opener.open(request)
97
        return response
1540.3.3 by Martin Pool
Review updates of pycurl transport
98
99
    def should_cache(self):
100
        """Return True if the data pulled across should be cached locally.
101
        """
102
        return True
103
104
    def has(self, relpath):
105
        """Does the target location exist?
106
        """
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
107
        abspath = self._real_abspath(relpath)
1540.3.3 by Martin Pool
Review updates of pycurl transport
108
        try:
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
109
            f = self._get_url_impl(abspath, 'HEAD', [])
1540.3.3 by Martin Pool
Review updates of pycurl transport
110
            # Without the read and then close()
111
            # we tend to have busy sockets.
112
            f.read()
113
            f.close()
114
            return True
115
        except urllib2.URLError, e:
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
116
            mutter('url error code: %s for has url: %r', e.code, abspath)
1540.3.3 by Martin Pool
Review updates of pycurl transport
117
            if e.code == 404:
118
                return False
119
            raise
120
        except IOError, e:
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
121
            mutter('io error: %s %s for has url: %r',
122
                e.errno, errno.errorcode.get(e.errno), abspath)
1540.3.3 by Martin Pool
Review updates of pycurl transport
123
            if e.errno == errno.ENOENT:
124
                return False
125
            raise TransportError(orig_error=e)
126
127
    def copy_to(self, relpaths, other, mode=None, pb=None):
128
        """Copy a set of entries from self into another Transport.
129
130
        :param relpaths: A list/generator of entries to be copied.
131
132
        TODO: if other is LocalTransport, is it possible to
133
              do better than put(get())?
134
        """
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
135
        # At this point HttpTransport_urllib might be able to check and see if
1540.3.3 by Martin Pool
Review updates of pycurl transport
136
        # the remote location is the same, and rather than download, and
137
        # then upload, it could just issue a remote copy_this command.
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
138
        if isinstance(other, HttpTransport_urllib):
1540.3.3 by Martin Pool
Review updates of pycurl transport
139
            raise TransportNotPossible('http cannot be the target of copy_to()')
140
        else:
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
141
            return super(HttpTransport_urllib, self).copy_to(relpaths, other, mode=mode, pb=pb)
1540.3.3 by Martin Pool
Review updates of pycurl transport
142
143
    def move(self, rel_from, rel_to):
144
        """Move the item at rel_from to the location at rel_to"""
145
        raise TransportNotPossible('http does not support move()')
146
147
    def delete(self, relpath):
148
        """Delete the item at relpath"""
149
        raise TransportNotPossible('http does not support delete()')
150
1540.3.25 by Martin Pool
New 'http+urllib' scheme
151
152
class HttpServer_urllib(HttpServer):
153
    """Subclass of HttpServer that gives http+urllib urls.
154
155
    This is for use in testing: connections to this server will always go
156
    through urllib where possible.
157
    """
158
159
    # urls returned by this server should require the urllib client impl
160
    _url_protocol = 'http+urllib'
161
162
1540.3.6 by Martin Pool
[merge] update from bzr.dev
163
def get_test_permutations():
164
    """Return the permutations to be used in testing."""
1540.3.26 by Martin Pool
[merge] bzr.dev; pycurl not updated for readv yet
165
    return [(HttpTransport_urllib, HttpServer_urllib),
1540.3.6 by Martin Pool
[merge] update from bzr.dev
166
            ]