~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-18 01:58:00 UTC
  • mfrom: (1185.3.27)
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050918015800-40f508f61fe35833
Merging up-to-date (1216) from mainline, mostly just run_bzr uses in-memory bzrlib.

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