~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http.py

  • Committer: Martin Pool
  • Date: 2005-07-22 22:36:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050722223654-93bf9d8cc0f0c128
todo

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
 
import urlparse
13
 
 
14
 
from bzrlib.errors import BzrError, BzrCheckError
15
 
from bzrlib.branch import Branch, BZR_BRANCH_FORMAT
16
 
from bzrlib.trace import mutter
17
 
 
18
 
# velocitynet.com.au transparently proxies connections and thereby
19
 
# breaks keep-alive -- sucks!
20
 
 
21
 
 
22
 
def get_url(url):
23
 
    import urllib2
24
 
    mutter("get_url %s" % url)
25
 
    url_f = urllib2.urlopen(url)
26
 
    return url_f
27
 
 
28
 
class HttpTransportError(TransportError):
29
 
    pass
30
 
 
31
 
class HttpTransport(Transport):
32
 
    """This is the transport agent for http:// access.
33
 
    
34
 
    TODO: Implement pipelined versions of all of the *_multi() functions.
35
 
    """
36
 
 
37
 
    def __init__(self, base):
38
 
        """Set the base path where files will be stored."""
39
 
        assert base.startswith('http://') or base.startswith('https://')
40
 
        super(HttpTransport, self).__init__(base)
41
 
        # In the future we might actually connect to the remote host
42
 
        # rather than using get_url
43
 
        # self._connection = None
44
 
        (self._proto, self._host,
45
 
            self._path, self._parameters,
46
 
            self._query, self._fragment) = urlparse.urlparse(self.base)
47
 
 
48
 
    def should_cache(self):
49
 
        """Return True if the data pulled across should be cached locally.
50
 
        """
51
 
        return True
52
 
 
53
 
    def clone(self, offset=None):
54
 
        """Return a new HttpTransport with root at self.base + offset
55
 
        For now HttpTransport does not actually connect, so just return
56
 
        a new HttpTransport object.
57
 
        """
58
 
        if offset is None:
59
 
            return HttpTransport(self.base)
60
 
        else:
61
 
            return HttpTransport(self.abspath(offset))
62
 
 
63
 
    def abspath(self, relpath):
64
 
        """Return the full url to the given relative path.
65
 
        This can be supplied with a string or a list
66
 
        """
67
 
        if isinstance(relpath, basestring):
68
 
            relpath = [relpath]
69
 
        basepath = self._path.split('/')
70
 
        if len(basepath) > 0 and basepath[-1] == '':
71
 
            basepath = basepath[:-1]
72
 
 
73
 
        for p in relpath:
74
 
            if p == '..':
75
 
                if len(basepath) < 0:
76
 
                    # In most filesystems, a request for the parent
77
 
                    # of root, just returns root.
78
 
                    continue
79
 
                basepath.pop()
80
 
            elif p == '.':
81
 
                continue # No-op
82
 
            else:
83
 
                basepath.append(p)
84
 
 
85
 
        # Possibly, we could use urlparse.urljoin() here, but
86
 
        # I'm concerned about when it chooses to strip the last
87
 
        # portion of the path, and when it doesn't.
88
 
        path = '/'.join(basepath)
89
 
        return urlparse.urlunparse((self._proto,
90
 
                self._host, path, '', '', ''))
91
 
 
92
 
    def relpath(self, abspath):
93
 
        if not abspath.startswith(self.base):
94
 
            raise NonRelativePath('path %r is not under base URL %r'
95
 
                           % (abspath, self.base))
96
 
        pl = len(self.base)
97
 
        return abspath[pl:].lstrip('/')
98
 
 
99
 
    def has(self, relpath):
100
 
        """Does the target location exist?
101
 
 
102
 
        TODO: HttpTransport.has() should use a HEAD request,
103
 
        not a full GET request.
104
 
 
105
 
        TODO: This should be changed so that we don't use
106
 
        urllib2 and get an exception, the code path would be
107
 
        cleaner if we just do an http HEAD request, and parse
108
 
        the return code.
109
 
        """
110
 
        try:
111
 
            f = get_url(self.abspath(relpath))
112
 
            # Without the read and then close()
113
 
            # we tend to have busy sockets.
114
 
            f.read()
115
 
            f.close()
116
 
            return True
117
 
        except BzrError:
118
 
            return False
119
 
        except urllib2.URLError:
120
 
            return False
121
 
        except IOError, e:
122
 
            if e.errno == errno.ENOENT:
123
 
                return False
124
 
            raise HttpTransportError(orig_error=e)
125
 
 
126
 
    def get(self, relpath, decode=False):
127
 
        """Get the file at the given relative path.
128
 
 
129
 
        :param relpath: The relative path to the file
130
 
        """
131
 
        try:
132
 
            return get_url(self.abspath(relpath))
133
 
        except BzrError, e:
134
 
            raise NoSuchFile(orig_error=e)
135
 
        except urllib2.URLError, e:
136
 
            raise NoSuchFile(orig_error=e)
137
 
        except IOError, e:
138
 
            raise NoSuchFile(orig_error=e)
139
 
        except Exception,e:
140
 
            raise HttpTransportError(orig_error=e)
141
 
 
142
 
    def put(self, relpath, f):
143
 
        """Copy the file-like or string object into the location.
144
 
 
145
 
        :param relpath: Location to put the contents, relative to base.
146
 
        :param f:       File-like or string object.
147
 
        """
148
 
        raise TransportNotPossible('http PUT not supported')
149
 
 
150
 
    def mkdir(self, relpath):
151
 
        """Create a directory at the given path."""
152
 
        raise TransportNotPossible('http does not support mkdir()')
153
 
 
154
 
    def append(self, relpath, f):
155
 
        """Append the text in the file-like object into the final
156
 
        location.
157
 
        """
158
 
        raise TransportNotPossible('http does not support append()')
159
 
 
160
 
    def copy(self, rel_from, rel_to):
161
 
        """Copy the item at rel_from to the location at rel_to"""
162
 
        raise TransportNotPossible('http does not support copy()')
163
 
 
164
 
    def copy_to(self, relpaths, other, pb=None):
165
 
        """Copy a set of entries from self into another Transport.
166
 
 
167
 
        :param relpaths: A list/generator of entries to be copied.
168
 
 
169
 
        TODO: if other is LocalTransport, is it possible to
170
 
              do better than put(get())?
171
 
        """
172
 
        # At this point HttpTransport might be able to check and see if
173
 
        # the remote location is the same, and rather than download, and
174
 
        # then upload, it could just issue a remote copy_this command.
175
 
        if isinstance(other, HttpTransport):
176
 
            raise TransportNotPossible('http cannot be the target of copy_to()')
177
 
        else:
178
 
            return super(HttpTransport, self).copy_to(relpaths, other, pb=pb)
179
 
 
180
 
    def move(self, rel_from, rel_to):
181
 
        """Move the item at rel_from to the location at rel_to"""
182
 
        raise TransportNotPossible('http does not support move()')
183
 
 
184
 
    def delete(self, relpath):
185
 
        """Delete the item at relpath"""
186
 
        raise TransportNotPossible('http does not support delete()')
187
 
 
188
 
    def async_get(self, relpath):
189
 
        """Make a request for an file at the given location, but
190
 
        don't worry about actually getting it yet.
191
 
 
192
 
        :rtype: AsyncFile
193
 
        """
194
 
        raise NotImplementedError
195
 
 
196
 
    def list_dir(self, relpath):
197
 
        """Return a list of all files at the given location.
198
 
        WARNING: many transports do not support this, so trying avoid using
199
 
        it if at all possible.
200
 
        """
201
 
        raise TransportNotPossible('http does not support list_dir()')
202
 
 
203
 
    def stat(self, relpath):
204
 
        """Return the stat information for a file.
205
 
        """
206
 
        raise TransportNotPossible('http does not support stat()')
207
 
 
208
 
    def lock_read(self, relpath):
209
 
        """Lock the given file for shared (read) access.
210
 
        :return: A lock object, which should be passed to Transport.unlock()
211
 
        """
212
 
        # The old RemoteBranch ignore lock for reading, so we will
213
 
        # continue that tradition and return a bogus lock object.
214
 
        class BogusLock(object):
215
 
            def __init__(self, path):
216
 
                self.path = path
217
 
            def unlock(self):
218
 
                pass
219
 
        return BogusLock(relpath)
220
 
 
221
 
    def lock_write(self, relpath):
222
 
        """Lock the given file for exclusive (write) access.
223
 
        WARNING: many transports do not support this, so trying avoid using it
224
 
 
225
 
        :return: A lock object, which should be passed to Transport.unlock()
226
 
        """
227
 
        raise TransportNotPossible('http does not support lock_write()')
228
 
 
229
 
register_transport('http://', HttpTransport)
230
 
register_transport('https://', HttpTransport)
231