~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-11-08 18:36:26 UTC
  • mto: This revision was merged to the branch mainline in revision 1727.
  • Revision ID: john@arbash-meinel.com-20051108183626-71f8414338043265
Updating unified_diff to take a factory, using the new diff algorithm in the code.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
"""\
3
 
An implementation of the Transport object for http access.
 
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.
4
17
"""
5
18
 
6
 
from bzrlib.transport import Transport, register_transport, \
7
 
    TransportNotPossible, NoSuchFile, NonRelativePath, \
8
 
    TransportError
 
19
from bzrlib.transport import Transport, register_transport
 
20
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
 
21
                           NonRelativePath, TransportError)
9
22
import os, errno
10
23
from cStringIO import StringIO
11
24
import urllib2
 
25
import urlparse
12
26
 
13
27
from bzrlib.errors import BzrError, BzrCheckError
14
 
from bzrlib.branch import Branch, BZR_BRANCH_FORMAT
 
28
from bzrlib.branch import Branch
15
29
from bzrlib.trace import mutter
16
30
 
17
 
# velocitynet.com.au transparently proxies connections and thereby
18
 
# breaks keep-alive -- sucks!
19
 
 
20
 
 
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
 
        
 
31
 
 
32
def get_url(url):
 
33
    import urllib2
 
34
    mutter("get_url %s" % url)
 
35
    url_f = urllib2.urlopen(url)
 
36
    return url_f
 
37
 
82
38
class HttpTransportError(TransportError):
83
39
    pass
84
40
 
95
51
        # In the future we might actually connect to the remote host
96
52
        # rather than using get_url
97
53
        # self._connection = None
 
54
        (self._proto, self._host,
 
55
            self._path, self._parameters,
 
56
            self._query, self._fragment) = urlparse.urlparse(self.base)
98
57
 
99
58
    def should_cache(self):
100
59
        """Return True if the data pulled across should be cached locally.
115
74
        """Return the full url to the given relative path.
116
75
        This can be supplied with a string or a list
117
76
        """
 
77
        assert isinstance(relpath, basestring)
118
78
        if isinstance(relpath, basestring):
119
 
            relpath = [relpath]
120
 
        baseurl = self.base.rstrip('/')
121
 
        return '/'.join([baseurl] + relpath)
122
 
 
123
 
    def relpath(self, abspath):
124
 
        if not abspath.startswith(self.base):
125
 
            raise NonRelativePath('path %r is not under base URL %r'
126
 
                           % (abspath, self.base))
127
 
        pl = len(self.base)
128
 
        return abspath[pl:].lstrip('/')
 
79
            relpath_parts = relpath.split('/')
 
80
        else:
 
81
            # TODO: Don't call this with an array - no magic interfaces
 
82
            relpath_parts = relpath[:]
 
83
        if len(relpath_parts) > 1:
 
84
            if relpath_parts[0] == '':
 
85
                raise ValueError("path %r within branch %r seems to be absolute"
 
86
                                 % (relpath, self._path))
 
87
            if relpath_parts[-1] == '':
 
88
                raise ValueError("path %r within branch %r seems to be a directory"
 
89
                                 % (relpath, self._path))
 
90
        basepath = self._path.split('/')
 
91
        if len(basepath) > 0 and basepath[-1] == '':
 
92
            basepath = basepath[:-1]
 
93
        for p in relpath_parts:
 
94
            if p == '..':
 
95
                if len(basepath) == 0:
 
96
                    # In most filesystems, a request for the parent
 
97
                    # of root, just returns root.
 
98
                    continue
 
99
                basepath.pop()
 
100
            elif p == '.' or p == '':
 
101
                continue # No-op
 
102
            else:
 
103
                basepath.append(p)
 
104
        # Possibly, we could use urlparse.urljoin() here, but
 
105
        # I'm concerned about when it chooses to strip the last
 
106
        # portion of the path, and when it doesn't.
 
107
        path = '/'.join(basepath)
 
108
        return urlparse.urlunparse((self._proto,
 
109
                self._host, path, '', '', ''))
129
110
 
130
111
    def has(self, relpath):
131
112
        """Does the target location exist?
132
113
 
133
114
        TODO: HttpTransport.has() should use a HEAD request,
134
115
        not a full GET request.
 
116
 
 
117
        TODO: This should be changed so that we don't use
 
118
        urllib2 and get an exception, the code path would be
 
119
        cleaner if we just do an http HEAD request, and parse
 
120
        the return code.
135
121
        """
136
122
        try:
137
123
            f = get_url(self.abspath(relpath))
 
124
            # Without the read and then close()
 
125
            # we tend to have busy sockets.
 
126
            f.read()
 
127
            f.close()
138
128
            return True
139
 
        except BzrError:
140
 
            return False
141
 
        except urllib2.URLError:
142
 
            return False
 
129
        except urllib2.URLError, e:
 
130
            if e.code == 404:
 
131
                return False
 
132
            raise
143
133
        except IOError, e:
144
134
            if e.errno == errno.ENOENT:
145
135
                return False
152
142
        """
153
143
        try:
154
144
            return get_url(self.abspath(relpath))
155
 
        except BzrError, e:
156
 
            raise NoSuchFile(orig_error=e)
157
145
        except urllib2.URLError, e:
158
 
            raise NoSuchFile(orig_error=e)
159
 
        except IOError, e:
160
 
            raise NoSuchFile(orig_error=e)
161
 
        except Exception,e:
162
 
            raise HttpTransportError(orig_error=e)
 
146
            if e.code == 404:
 
147
                raise NoSuchFile(msg = "Error retrieving %s: %s" 
 
148
                                 % (self.abspath(relpath), str(e)),
 
149
                                 orig_error=e)
 
150
            raise
 
151
        except (BzrError, IOError), e:
 
152
            raise NoSuchFile(msg = "Error retrieving %s: %s" 
 
153
                             % (self.abspath(relpath), str(e)),
 
154
                             orig_error=e)
163
155
 
164
156
    def put(self, relpath, f):
165
157
        """Copy the file-like or string object into the location.
207
199
        """Delete the item at relpath"""
208
200
        raise TransportNotPossible('http does not support delete()')
209
201
 
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()')
 
202
    def listable(self):
 
203
        """See Transport.listable."""
 
204
        return False
224
205
 
225
206
    def stat(self, relpath):
226
207
        """Return the stat information for a file.
247
228
        :return: A lock object, which should be passed to Transport.unlock()
248
229
        """
249
230
        raise TransportNotPossible('http does not support lock_write()')
250
 
 
251
 
register_transport('http://', HttpTransport)
252
 
register_transport('https://', HttpTransport)
253