Recover from non-Ranged responses without rewinding

Not all IO objects know how to #rewind themselves. For example, Ruby
pipes (returned by `IO.pipe`) do implement #rewind, but they will throw
an error if you try to call it.

  rd, wr = IO.pipe
  wr.rewind # Errno::ESPIPE: Illegal seek

But we don't need to rewind and overwrite the IO object if we didn't get
the Ranged response we expected, we could instead wait out the content
that has already been downloaded, and start appending again once we
reached where we left off. This is what this commit does.
This commit is contained in:
Janko Marohnić 2017-06-23 11:22:37 +02:00
parent e7f64be149
commit 0eeb3ee9f9
1 changed files with 18 additions and 12 deletions

View File

@ -67,12 +67,11 @@ module Google
def execute_once(client, &block)
request_header = header.dup
apply_request_options(request_header)
download_offset = nil
check_if_rewind_needed = false
if @offset > 0
logger.debug { sprintf('Resuming download from offset %d', @offset) }
request_header[RANGE_HEADER] = sprintf('bytes=%d-', @offset)
check_if_rewind_needed = true
end
http_res = client.get(url.to_s,
@ -80,17 +79,24 @@ module Google
header: request_header,
follow_redirect: true) do |res, chunk|
status = res.http_header.status_code.to_i
if OK_STATUS.include?(status)
if check_if_rewind_needed && status != 206
# Oh no! Requested a chunk, but received the entire content
# Attempt to rewind the stream
@download_io.rewind
check_if_rewind_needed = false
end
# logger.debug { sprintf('Writing chunk (%d bytes, %d total)', chunk.length, bytes_read) }
@download_io.write(chunk)
@offset += chunk.length
next unless OK_STATUS.include?(status)
download_offset ||= (status == 206 ? @offset : 0)
download_offset += chunk.bytesize
if download_offset - chunk.bytesize == @offset
next_chunk = chunk
else
# Oh no! Requested a chunk, but received the entire content
chunk_index = @offset - (download_offset - chunk.bytesize)
next_chunk = chunk.byteslice(chunk_index..-1)
next if next_chunk.nil?
end
# logger.debug { sprintf('Writing chunk (%d bytes, %d total)', chunk.length, bytes_read) }
@download_io.write(next_chunk)
@offset += next_chunk.bytesize
end
@download_io.flush