~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/__init__.py

  • Committer: John Arbash Meinel
  • Date: 2006-06-17 15:52:51 UTC
  • mto: (1750.1.4 http)
  • mto: This revision was merged to the branch mainline in revision 1869.
  • Revision ID: john@arbash-meinel.com-20060617155251-c445c9574fcdf6aa
Move the common Multipart stuff into plain http, and wrap pycurl response so that it matches the urllib response object.

Show diffs side-by-side

added added

removed removed

Lines of Context:
193
193
        :param offsets: A list of (offset, size) tuples.
194
194
        :param return: A list or generator of (offset, data) tuples
195
195
        """
196
 
        # Ideally we would pass one big request asking for all the ranges in
197
 
        # one go; however then the server will give a multipart mime response
198
 
        # back, and we can't parse them yet.  So instead we just get one range
199
 
        # per region, and try to coallesce the regions as much as possible.
200
 
        #
201
 
        # The read-coallescing code is not quite regular enough to have a
202
 
        # single driver routine and
203
 
        # helper method in Transport.
204
 
        def do_combined_read(combined_offsets):
205
 
            # read one coalesced block
206
 
            total_size = 0
207
 
            for offset, size in combined_offsets:
208
 
                total_size += size
209
 
            mutter('readv coalesced %d reads.', len(combined_offsets))
210
 
            offset = combined_offsets[0][0]
211
 
            byte_range = (offset, offset + total_size - 1)
212
 
            code, result_file = self._get(relpath, [byte_range])
213
 
            if code == 206:
214
 
                for off, size in combined_offsets:
215
 
                    result_bytes = result_file.read(size)
216
 
                    assert len(result_bytes) == size
217
 
                    yield off, result_bytes
218
 
            elif code == 200:
219
 
                data = result_file.read(offset + total_size)[offset:offset + total_size]
220
 
                pos = 0
221
 
                for offset, size in combined_offsets:
222
 
                    yield offset, data[pos:pos + size]
223
 
                    pos += size
224
 
                del data
225
 
        if not len(offsets):
226
 
            return
227
 
        pending_offsets = deque(offsets)
228
 
        combined_offsets = []
229
 
        while len(pending_offsets):
230
 
            offset, size = pending_offsets.popleft()
231
 
            if not combined_offsets:
232
 
                combined_offsets = [[offset, size]]
233
 
            else:
234
 
                if (len (combined_offsets) < 500 and
235
 
                    combined_offsets[-1][0] + combined_offsets[-1][1] == offset):
236
 
                    # combatible offset:
237
 
                    combined_offsets.append([offset, size])
238
 
                else:
239
 
                    # incompatible, or over the threshold issue a read and yield
240
 
                    pending_offsets.appendleft((offset, size))
241
 
                    for result in do_combined_read(combined_offsets):
242
 
                        yield result
243
 
                    combined_offsets = []
244
 
        # whatever is left is a single coalesced request
245
 
        if len(combined_offsets):
246
 
            for result in do_combined_read(combined_offsets):
247
 
                yield result
 
196
        mutter('readv of %s [%s]', relpath, offsets)
 
197
        ranges = self._offsets_to_ranges(offsets)
 
198
        code, f = self._get(relpath, ranges)
 
199
        for start, size in offsets:
 
200
            f.seek(start, 0)
 
201
            data = f.read(size)
 
202
            assert len(data) == size
 
203
            yield start, data
 
204
 
 
205
    def _is_multipart(self, content_type):
 
206
        return content_type.startswith('multipart/byteranges;')
 
207
 
 
208
    def _handle_response(self, path, response):
 
209
        """Interpret the code & headers and return a HTTP response.
 
210
 
 
211
        This is a factory method which returns an appropriate HTTP response
 
212
        based on the code & headers it's given.
 
213
        """
 
214
        content_type = response.headers['Content-Type']
 
215
        mutter('handling response code %s ctype %s', response.code,
 
216
            content_type)
 
217
 
 
218
        if response.code == 206 and self._is_multipart(content_type):
 
219
            # Full fledged multipart response
 
220
            return HttpMultipartRangeResponse(path, content_type, response)
 
221
        elif response.code == 206:
 
222
            # A response to a range request, but not multipart
 
223
            content_range = response.headers['Content-Range']
 
224
            return HttpRangeResponse(path, content_range, response)
 
225
        elif response.code == 200:
 
226
            # A regular non-range response, unfortunately the result from
 
227
            # urllib doesn't support seek, so we wrap it in a StringIO
 
228
            return StringIO(response.read())
 
229
        elif response.code == 404:
 
230
            raise NoSuchFile(path)
 
231
 
 
232
        raise BzrError("HTTP couldn't handle code %s", response.code)
248
233
 
249
234
    def put(self, relpath, f, mode=None):
250
235
        """Copy the file-like or string object into the location.
352
337
        """
353
338
        # We need a copy of the offsets, as the caller might expect it to
354
339
        # remain unsorted. This doesn't seem expensive for memory at least.
355
 
        offsets = offsets[:]
356
 
        offsets.sort(key=lambda i: i[0])
 
340
        offsets = sorted(offsets)
357
341
 
358
342
        start, size = offsets[0]
359
343
        prev_end = start + size - 1