~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
#!/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