~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http.py

  • Committer: John Arbash Meinel
  • Date: 2005-09-15 21:35:53 UTC
  • mfrom: (907.1.57)
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050915213552-a6c83a5ef1e20897
(broken) Transport work is merged in. Tests do not pass yet.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 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
 
"""Implementation of Transport over http.
 
1
#!/usr/bin/env python
 
2
"""\
 
3
An implementation of the Transport object for http access.
17
4
"""
18
5
 
19
 
from bzrlib.transport import Transport, register_transport
20
 
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
21
 
                           NonRelativePath, TransportError)
 
6
from bzrlib.transport import Transport, register_transport, \
 
7
    TransportNotPossible, NoSuchFile, NonRelativePath, \
 
8
    TransportError
22
9
import os, errno
23
10
from cStringIO import StringIO
24
11
import urllib2
25
 
import urlparse
26
12
 
27
13
from bzrlib.errors import BzrError, BzrCheckError
28
 
from bzrlib.branch import Branch
 
14
from bzrlib.branch import Branch, BZR_BRANCH_FORMAT
29
15
from bzrlib.trace import mutter
30
16
 
31
17
# velocitynet.com.au transparently proxies connections and thereby
32
18
# breaks keep-alive -- sucks!
33
19
 
34
20
 
35
 
def get_url(url):
36
 
    import urllib2
37
 
    mutter("get_url %s" % url)
38
 
    url_f = urllib2.urlopen(url)
39
 
    return url_f
40
 
 
 
21
ENABLE_URLGRABBER = False
 
22
 
 
23
 
 
24
if ENABLE_URLGRABBER:
 
25
    import urlgrabber
 
26
    import urlgrabber.keepalive
 
27
    import urlgrabber.grabber
 
28
    urlgrabber.keepalive.DEBUG = 0
 
29
    def get_url(path, compressed=False):
 
30
        try:
 
31
            url = path
 
32
            if compressed:
 
33
                url += '.gz'
 
34
            mutter("grab url %s" % url)
 
35
            url_f = urlgrabber.urlopen(url, keepalive=1, close_connection=0)
 
36
            if not compressed:
 
37
                return url_f
 
38
            else:
 
39
                return gzip.GzipFile(fileobj=StringIO(url_f.read()))
 
40
        except urllib2.URLError, e:
 
41
            raise BzrError("remote fetch failed: %r: %s" % (url, e))
 
42
        except urlgrabber.grabber.URLGrabError, e:
 
43
            raise BzrError("remote fetch failed: %r: %s" % (url, e))
 
44
else:
 
45
    def get_url(url, compressed=False):
 
46
        import urllib2
 
47
        if compressed:
 
48
            url += '.gz'
 
49
        mutter("get_url %s" % url)
 
50
        url_f = urllib2.urlopen(url)
 
51
        if compressed:
 
52
            return gzip.GzipFile(fileobj=StringIO(url_f.read()))
 
53
        else:
 
54
            return url_f
 
55
 
 
56
def _find_remote_root(url):
 
57
    """Return the prefix URL that corresponds to the branch root."""
 
58
    orig_url = url
 
59
    while True:
 
60
        try:
 
61
            ff = get_url(url + '/.bzr/branch-format')
 
62
 
 
63
            fmt = ff.read()
 
64
            ff.close()
 
65
 
 
66
            fmt = fmt.rstrip('\r\n')
 
67
            if fmt != BZR_BRANCH_FORMAT.rstrip('\r\n'):
 
68
                raise BzrError("sorry, branch format %r not supported at url %s"
 
69
                               % (fmt, url))
 
70
            
 
71
            return url
 
72
        except urllib2.URLError:
 
73
            pass
 
74
 
 
75
        try:
 
76
            idx = url.rindex('/')
 
77
        except ValueError:
 
78
            raise BzrError('no branch root found for URL %s' % orig_url)
 
79
 
 
80
        url = url[:idx]        
 
81
        
41
82
class HttpTransportError(TransportError):
42
83
    pass
43
84
 
54
95
        # In the future we might actually connect to the remote host
55
96
        # rather than using get_url
56
97
        # self._connection = None
57
 
        (self._proto, self._host,
58
 
            self._path, self._parameters,
59
 
            self._query, self._fragment) = urlparse.urlparse(self.base)
60
98
 
61
99
    def should_cache(self):
62
100
        """Return True if the data pulled across should be cached locally.
79
117
        """
80
118
        if isinstance(relpath, basestring):
81
119
            relpath = [relpath]
82
 
        basepath = self._path.split('/')
83
 
        if len(basepath) > 0 and basepath[-1] == '':
84
 
            basepath = basepath[:-1]
85
 
 
86
 
        for p in relpath:
87
 
            if p == '..':
88
 
                if len(basepath) < 0:
89
 
                    # In most filesystems, a request for the parent
90
 
                    # of root, just returns root.
91
 
                    continue
92
 
                basepath.pop()
93
 
            elif p == '.':
94
 
                continue # No-op
95
 
            else:
96
 
                basepath.append(p)
97
 
 
98
 
        # Possibly, we could use urlparse.urljoin() here, but
99
 
        # I'm concerned about when it chooses to strip the last
100
 
        # portion of the path, and when it doesn't.
101
 
        path = '/'.join(basepath)
102
 
        return urlparse.urlunparse((self._proto,
103
 
                self._host, path, '', '', ''))
 
120
        baseurl = self.base.rstrip('/')
 
121
        return '/'.join([baseurl] + relpath)
104
122
 
105
123
    def relpath(self, abspath):
106
124
        if not abspath.startswith(self.base):
114
132
 
115
133
        TODO: HttpTransport.has() should use a HEAD request,
116
134
        not a full GET request.
117
 
 
118
 
        TODO: This should be changed so that we don't use
119
 
        urllib2 and get an exception, the code path would be
120
 
        cleaner if we just do an http HEAD request, and parse
121
 
        the return code.
122
135
        """
123
136
        try:
124
137
            f = get_url(self.abspath(relpath))
125
 
            # Without the read and then close()
126
 
            # we tend to have busy sockets.
127
 
            f.read()
128
 
            f.close()
129
138
            return True
130
139
        except BzrError:
131
140
            return False
143
152
        """
144
153
        try:
145
154
            return get_url(self.abspath(relpath))
146
 
        except (BzrError, urllib2.URLError, IOError), e:
 
155
        except BzrError, e:
 
156
            raise NoSuchFile(orig_error=e)
 
157
        except urllib2.URLError, e:
 
158
            raise NoSuchFile(orig_error=e)
 
159
        except IOError, e:
147
160
            raise NoSuchFile(orig_error=e)
148
161
        except Exception,e:
149
162
            raise HttpTransportError(orig_error=e)
150
163
 
151
 
    def get_partial(self, relpath, start, length=None):
152
 
        """Get just part of a file.
153
 
 
154
 
        :param relpath: Path to the file, relative to base
155
 
        :param start: The starting position to read from
156
 
        :param length: The length to read. A length of None indicates
157
 
                       read to the end of the file.
158
 
        :return: A file-like object containing at least the specified bytes.
159
 
                 Some implementations may return objects which can be read
160
 
                 past this length, but this is not guaranteed.
161
 
        """
162
 
        # TODO: You can make specialized http requests for just
163
 
        # a portion of the file. Figure out how to do that.
164
 
        # For now, urllib2 returns files that cannot seek() so
165
 
        # we just read bytes off the beginning, until we
166
 
        # get to the point that we care about.
167
 
        f = self.get(relpath)
168
 
        # TODO: read in smaller chunks, in case things are
169
 
        # buffered internally.
170
 
        f.read(start)
171
 
        return f
172
 
 
173
164
    def put(self, relpath, f):
174
165
        """Copy the file-like or string object into the location.
175
166
 
216
207
        """Delete the item at relpath"""
217
208
        raise TransportNotPossible('http does not support delete()')
218
209
 
219
 
    def listable(self):
220
 
        """See Transport.listable."""
221
 
        return False
 
210
    def async_get(self, relpath):
 
211
        """Make a request for an file at the given location, but
 
212
        don't worry about actually getting it yet.
 
213
 
 
214
        :rtype: AsyncFile
 
215
        """
 
216
        raise NotImplementedError
 
217
 
 
218
    def list_dir(self, relpath):
 
219
        """Return a list of all files at the given location.
 
220
        WARNING: many transports do not support this, so trying avoid using
 
221
        it if at all possible.
 
222
        """
 
223
        raise TransportNotPossible('http does not support list_dir()')
222
224
 
223
225
    def stat(self, relpath):
224
226
        """Return the stat information for a file.
248
250
 
249
251
register_transport('http://', HttpTransport)
250
252
register_transport('https://', HttpTransport)
 
253