3
An implementation of the Transport object for http access.
1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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.
6
from bzrlib.transport import Transport, register_transport, \
7
TransportNotPossible, NoSuchFile, NonRelativePath, \
19
from bzrlib.transport import Transport, register_transport
20
from bzrlib.errors import (TransportNotPossible, NoSuchFile,
21
NonRelativePath, TransportError)
10
23
from cStringIO import StringIO
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
17
# velocitynet.com.au transparently proxies connections and thereby
18
# breaks keep-alive -- sucks!
21
ENABLE_URLGRABBER = False
26
import urlgrabber.keepalive
27
import urlgrabber.grabber
28
urlgrabber.keepalive.DEBUG = 0
29
def get_url(path, compressed=False):
34
mutter("grab url %s" % url)
35
url_f = urlgrabber.urlopen(url, keepalive=1, close_connection=0)
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))
45
def get_url(url, compressed=False):
49
mutter("get_url %s" % url)
50
url_f = urllib2.urlopen(url)
52
return gzip.GzipFile(fileobj=StringIO(url_f.read()))
56
def _find_remote_root(url):
57
"""Return the prefix URL that corresponds to the branch root."""
61
ff = get_url(url + '/.bzr/branch-format')
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"
72
except urllib2.URLError:
78
raise BzrError('no branch root found for URL %s' % orig_url)
34
mutter("get_url %s" % url)
35
url_f = urllib2.urlopen(url)
82
38
class HttpTransportError(TransportError):
115
74
"""Return the full url to the given relative path.
116
75
This can be supplied with a string or a list
77
assert isinstance(relpath, basestring)
118
78
if isinstance(relpath, basestring):
120
baseurl = self.base.rstrip('/')
121
return '/'.join([baseurl] + relpath)
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))
128
return abspath[pl:].lstrip('/')
79
relpath_parts = relpath.split('/')
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:
95
if len(basepath) == 0:
96
# In most filesystems, a request for the parent
97
# of root, just returns root.
100
elif p == '.' or 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, '', '', ''))
130
111
def has(self, relpath):
131
112
"""Does the target location exist?
133
114
TODO: HttpTransport.has() should use a HEAD request,
134
115
not a full GET request.
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
137
123
f = get_url(self.abspath(relpath))
124
# Without the read and then close()
125
# we tend to have busy sockets.
141
except urllib2.URLError:
129
except urllib2.URLError, e:
143
133
except IOError, e:
144
134
if e.errno == errno.ENOENT:
154
144
return get_url(self.abspath(relpath))
156
raise NoSuchFile(orig_error=e)
157
145
except urllib2.URLError, e:
158
raise NoSuchFile(orig_error=e)
160
raise NoSuchFile(orig_error=e)
162
raise HttpTransportError(orig_error=e)
147
raise NoSuchFile(msg = "Error retrieving %s: %s"
148
% (self.abspath(relpath), str(e)),
151
except (BzrError, IOError), e:
152
raise NoSuchFile(msg = "Error retrieving %s: %s"
153
% (self.abspath(relpath), str(e)),
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()')
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.
216
raise NotImplementedError
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.
223
raise TransportNotPossible('http does not support list_dir()')
203
"""See Transport.listable."""
225
206
def stat(self, relpath):
226
207
"""Return the stat information for a file.