~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http.py

merge merge tweaks from aaron, which includes latest .dev

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.
4
 
"""
5
 
 
6
 
from bzrlib.transport import Transport, register_transport, \
7
 
    TransportNotPossible, NoSuchFile, NonRelativePath, \
8
 
    TransportError
9
 
import os, errno
10
 
from cStringIO import StringIO
11
 
import urllib2
12
 
 
13
 
from bzrlib.errors import BzrError, BzrCheckError
14
 
from bzrlib.branch import Branch, BZR_BRANCH_FORMAT
15
 
from bzrlib.trace import mutter
16
 
 
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
 
        
82
 
class HttpTransportError(TransportError):
83
 
    pass
84
 
 
85
 
class HttpTransport(Transport):
86
 
    """This is the transport agent for http:// access.
87
 
    
88
 
    TODO: Implement pipelined versions of all of the *_multi() functions.
89
 
    """
90
 
 
91
 
    def __init__(self, base):
92
 
        """Set the base path where files will be stored."""
93
 
        assert base.startswith('http://') or base.startswith('https://')
94
 
        super(HttpTransport, self).__init__(base)
95
 
        # In the future we might actually connect to the remote host
96
 
        # rather than using get_url
97
 
        # self._connection = None
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 clone(self, offset=None):
105
 
        """Return a new HttpTransport with root at self.base + offset
106
 
        For now HttpTransport does not actually connect, so just return
107
 
        a new HttpTransport object.
108
 
        """
109
 
        if offset is None:
110
 
            return HttpTransport(self.base)
111
 
        else:
112
 
            return HttpTransport(self.abspath(offset))
113
 
 
114
 
    def abspath(self, relpath):
115
 
        """Return the full url to the given relative path.
116
 
        This can be supplied with a string or a list
117
 
        """
118
 
        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('/')
129
 
 
130
 
    def has(self, relpath):
131
 
        """Does the target location exist?
132
 
 
133
 
        TODO: HttpTransport.has() should use a HEAD request,
134
 
        not a full GET request.
135
 
        """
136
 
        try:
137
 
            f = get_url(self.abspath(relpath))
138
 
            return True
139
 
        except BzrError:
140
 
            return False
141
 
        except urllib2.URLError:
142
 
            return False
143
 
        except IOError, e:
144
 
            if e.errno == errno.ENOENT:
145
 
                return False
146
 
            raise HttpTransportError(orig_error=e)
147
 
 
148
 
    def get(self, relpath, decode=False):
149
 
        """Get the file at the given relative path.
150
 
 
151
 
        :param relpath: The relative path to the file
152
 
        """
153
 
        try:
154
 
            return get_url(self.abspath(relpath))
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:
160
 
            raise NoSuchFile(orig_error=e)
161
 
        except Exception,e:
162
 
            raise HttpTransportError(orig_error=e)
163
 
 
164
 
    def put(self, relpath, f):
165
 
        """Copy the file-like or string object into the location.
166
 
 
167
 
        :param relpath: Location to put the contents, relative to base.
168
 
        :param f:       File-like or string object.
169
 
        """
170
 
        raise TransportNotPossible('http PUT not supported')
171
 
 
172
 
    def mkdir(self, relpath):
173
 
        """Create a directory at the given path."""
174
 
        raise TransportNotPossible('http does not support mkdir()')
175
 
 
176
 
    def append(self, relpath, f):
177
 
        """Append the text in the file-like object into the final
178
 
        location.
179
 
        """
180
 
        raise TransportNotPossible('http does not support append()')
181
 
 
182
 
    def copy(self, rel_from, rel_to):
183
 
        """Copy the item at rel_from to the location at rel_to"""
184
 
        raise TransportNotPossible('http does not support copy()')
185
 
 
186
 
    def copy_to(self, relpaths, other, pb=None):
187
 
        """Copy a set of entries from self into another Transport.
188
 
 
189
 
        :param relpaths: A list/generator of entries to be copied.
190
 
 
191
 
        TODO: if other is LocalTransport, is it possible to
192
 
              do better than put(get())?
193
 
        """
194
 
        # At this point HttpTransport might be able to check and see if
195
 
        # the remote location is the same, and rather than download, and
196
 
        # then upload, it could just issue a remote copy_this command.
197
 
        if isinstance(other, HttpTransport):
198
 
            raise TransportNotPossible('http cannot be the target of copy_to()')
199
 
        else:
200
 
            return super(HttpTransport, self).copy_to(relpaths, other, pb=pb)
201
 
 
202
 
    def move(self, rel_from, rel_to):
203
 
        """Move the item at rel_from to the location at rel_to"""
204
 
        raise TransportNotPossible('http does not support move()')
205
 
 
206
 
    def delete(self, relpath):
207
 
        """Delete the item at relpath"""
208
 
        raise TransportNotPossible('http does not support delete()')
209
 
 
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()')
224
 
 
225
 
    def stat(self, relpath):
226
 
        """Return the stat information for a file.
227
 
        """
228
 
        raise TransportNotPossible('http does not support stat()')
229
 
 
230
 
    def lock_read(self, relpath):
231
 
        """Lock the given file for shared (read) access.
232
 
        :return: A lock object, which should be passed to Transport.unlock()
233
 
        """
234
 
        # The old RemoteBranch ignore lock for reading, so we will
235
 
        # continue that tradition and return a bogus lock object.
236
 
        class BogusLock(object):
237
 
            def __init__(self, path):
238
 
                self.path = path
239
 
            def unlock(self):
240
 
                pass
241
 
        return BogusLock(relpath)
242
 
 
243
 
    def lock_write(self, relpath):
244
 
        """Lock the given file for exclusive (write) access.
245
 
        WARNING: many transports do not support this, so trying avoid using it
246
 
 
247
 
        :return: A lock object, which should be passed to Transport.unlock()
248
 
        """
249
 
        raise TransportNotPossible('http does not support lock_write()')
250
 
 
251
 
register_transport('http://', HttpTransport)
252
 
register_transport('https://', HttpTransport)
253