24
from __future__ import absolute_import
26
28
from cStringIO import StringIO
29
31
from bzrlib import (
37
class ResponseFile(object):
38
"""A wrapper around the http socket containing the result of a GET request.
40
Only read() and seek() (forward) are supported.
43
def __init__(self, path, infile):
46
:param path: File url, for error reports.
48
:param infile: File-like socket set at body start.
57
Dummy implementation for consistency with the 'file' API.
60
def read(self, size=-1):
61
"""Read size bytes from the current position in the file.
63
:param size: The number of bytes to read. Leave unspecified or pass
66
data = self._file.read(size)
67
self._pos += len(data)
71
data = self._file.readline()
72
self._pos += len(data)
77
line = self.readline()
85
def seek(self, offset, whence=os.SEEK_SET):
86
if whence == os.SEEK_SET:
87
if offset < self._pos:
89
"Can't seek backwards, pos: %s, offset: %s"
90
% (self._pos, offset))
91
to_discard = offset - self._pos
92
elif whence == os.SEEK_CUR:
95
raise AssertionError("Can't seek backwards")
97
# Just discard the unwanted bytes
36
100
# A RangeFile expects the following grammar (simplified to outline the
37
101
# assumptions we rely upon).
41
104
# | multiple_range
43
# whole_file: [content_length_header] data
45
106
# single_range: content_range_header data
47
108
# multiple_range: boundary_header boundary (content_range_header data boundary)+
49
class RangeFile(object):
110
class RangeFile(ResponseFile):
50
111
"""File-like object that allow access to partial available data.
52
113
All accesses should happen sequentially since the acquisition occurs during
73
134
:param path: File url, for error reports.
74
136
:param infile: File-like socket set at body start.
138
super(RangeFile, self).__init__(path, infile)
78
139
self._boundary = None
79
140
# When using multi parts response, this will be set with the headers
80
141
# associated with the range currently read.
228
289
% (size, self._start, self._size))
230
291
# read data from file
233
294
if self._size > 0:
234
295
# Don't read past the range definition
235
296
limited = self._start + self._size - self._pos
237
298
limited = min(limited, size)
238
osutils.pumpfile(self._file, buffer, limited, self._max_read_size)
239
data = buffer.getvalue()
299
osutils.pumpfile(self._file, buf, limited, self._max_read_size)
300
data = buf.getvalue()
241
302
# Update _pos respecting the data effectively read
242
303
self._pos += len(data)
298
359
:return: A file-like object that can seek()+read() the
299
360
ranges indicated by the headers.
301
rfile = RangeFile(url, data)
304
size = msg.getheader('content-length', None)
309
rfile.set_range(0, size)
364
rfile = ResponseFile(url, data)
310
365
elif code == 206:
366
rfile = RangeFile(url, data)
311
367
content_type = msg.getheader('content-type', None)
312
368
if content_type is None:
313
369
# When there is no content-type header we treat the response as