From 0eeb3ee9f96f8a28c7ec3b18c506d0c2827c93f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janko=20Marohni=C4=87?= Date: Fri, 23 Jun 2017 11:22:37 +0200 Subject: [PATCH] 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. --- lib/google/apis/core/download.rb | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/google/apis/core/download.rb b/lib/google/apis/core/download.rb index de4f6bf9f..bc4a67423 100644 --- a/lib/google/apis/core/download.rb +++ b/lib/google/apis/core/download.rb @@ -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