| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  | # Copyright 2015 Google Inc. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  | # you may not use this file except in compliance with the License. | 
					
						
							|  |  |  | # You may obtain a copy of the License at | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #      http://www.apache.org/licenses/LICENSE-2.0 | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  | # distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  | # See the License for the specific language governing permissions and | 
					
						
							|  |  |  | # limitations under the License. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require 'google/apis/core/api_command' | 
					
						
							|  |  |  | require 'google/apis/errors' | 
					
						
							|  |  |  | require 'addressable/uri' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Google | 
					
						
							|  |  |  |   module Apis | 
					
						
							|  |  |  |     module Core | 
					
						
							|  |  |  |       # Streaming/resumable media download support | 
					
						
							|  |  |  |       class DownloadCommand < ApiCommand | 
					
						
							| 
									
										
										
										
											2016-08-17 20:51:09 +00:00
										 |  |  |         RANGE_HEADER = 'Range' | 
					
						
							| 
									
										
										
										
											2016-09-19 21:16:36 +00:00
										 |  |  |         OK_STATUS = [200, 201, 206] | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # File or IO to write content to | 
					
						
							|  |  |  |         # @return [String, File, #write] | 
					
						
							|  |  |  |         attr_accessor :download_dest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Ensure the download destination is a writable stream. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @return [void] | 
					
						
							|  |  |  |         def prepare! | 
					
						
							|  |  |  |           @state = :start | 
					
						
							|  |  |  |           @download_url = nil | 
					
						
							|  |  |  |           @offset = 0
 | 
					
						
							|  |  |  |           if download_dest.respond_to?(:write) | 
					
						
							|  |  |  |             @download_io = download_dest | 
					
						
							|  |  |  |             @close_io_on_finish = false | 
					
						
							|  |  |  |           elsif download_dest.is_a?(String) | 
					
						
							|  |  |  |             @download_io = File.open(download_dest, 'wb') | 
					
						
							|  |  |  |             @close_io_on_finish = true | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             @download_io = StringIO.new('', 'wb') | 
					
						
							|  |  |  |             @close_io_on_finish = false | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           super | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Close IO stream when command done. Only closes the stream if it was opened by the command. | 
					
						
							|  |  |  |         def release! | 
					
						
							|  |  |  |           @download_io.close if @close_io_on_finish | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Execute the upload request once. Overrides the default implementation to handle streaming/chunking | 
					
						
							|  |  |  |         # of file content. | 
					
						
							|  |  |  |         # | 
					
						
							|  |  |  |         # @private | 
					
						
							| 
									
										
										
										
											2016-08-17 20:51:09 +00:00
										 |  |  |         # @param [HTTPClient] client | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |         #   HTTP client | 
					
						
							|  |  |  |         # @yield [result, err] Result or error if block supplied | 
					
						
							|  |  |  |         # @return [Object] | 
					
						
							|  |  |  |         # @raise [Google::Apis::ServerError] An error occurred on the server and the request can be retried | 
					
						
							|  |  |  |         # @raise [Google::Apis::ClientError] The request is invalid and should not be retried without modification | 
					
						
							|  |  |  |         # @raise [Google::Apis::AuthorizationError] Authorization is required | 
					
						
							|  |  |  |         def execute_once(client, &block) | 
					
						
							| 
									
										
										
										
											2016-08-17 20:51:09 +00:00
										 |  |  |           request_header = header.dup | 
					
						
							|  |  |  |           apply_request_options(request_header) | 
					
						
							| 
									
										
										
										
											2017-06-23 09:22:37 +00:00
										 |  |  |           download_offset = nil | 
					
						
							| 
									
										
										
										
											2016-08-17 20:51:09 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if @offset > 0
 | 
					
						
							|  |  |  |             logger.debug { sprintf('Resuming download from offset %d', @offset) } | 
					
						
							|  |  |  |             request_header[RANGE_HEADER] = sprintf('bytes=%d-', @offset) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           http_res = client.get(url.to_s, | 
					
						
							|  |  |  |                      query: query, | 
					
						
							|  |  |  |                      header: request_header, | 
					
						
							|  |  |  |                      follow_redirect: true) do |res, chunk| | 
					
						
							|  |  |  |             status = res.http_header.status_code.to_i | 
					
						
							| 
									
										
										
										
											2017-06-23 09:22:37 +00:00
										 |  |  |             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? | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |             end | 
					
						
							| 
									
										
										
										
											2017-06-23 09:22:37 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # logger.debug { sprintf('Writing chunk (%d bytes, %d total)', chunk.length, bytes_read) } | 
					
						
							|  |  |  |             @download_io.write(next_chunk) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             @offset += next_chunk.bytesize | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-08-17 20:51:09 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-04 21:09:11 +00:00
										 |  |  |          @download_io.flush if @download_io.respond_to?(:flush) | 
					
						
							| 
									
										
										
										
											2017-04-03 18:10:54 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           if @close_io_on_finish | 
					
						
							|  |  |  |             result = nil | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             result = @download_io | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2016-08-17 20:51:09 +00:00
										 |  |  |           check_status(http_res.status.to_i, http_res.header, http_res.body) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           success(result, &block) | 
					
						
							|  |  |  |         rescue => e | 
					
						
							| 
									
										
										
										
											2018-12-04 21:09:11 +00:00
										 |  |  |           @download_io.flush if @download_io.respond_to?(:flush) | 
					
						
							| 
									
										
										
										
											2015-04-17 00:28:38 +00:00
										 |  |  |           error(e, rethrow: true, &block) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |