Unify processing of api/resumable/batch requests
This commit is contained in:
		
							parent
							
								
									5d2a6d4842
								
							
						
					
					
						commit
						2c6bf97b20
					
				| 
						 | 
				
			
			@ -527,7 +527,7 @@ module Google
 | 
			
		|||
        :connection => Faraday.default_connection
 | 
			
		||||
      }.merge(options)
 | 
			
		||||
      
 | 
			
		||||
      options[:api_method] = self.normalize_api_method(options)
 | 
			
		||||
      options[:api_method] = self.normalize_api_method(options) unless options[:api_method].nil?
 | 
			
		||||
      return Google::APIClient::Reference.new(options)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -589,20 +589,9 @@ module Google
 | 
			
		|||
    #
 | 
			
		||||
    # @see Google::APIClient#generate_request
 | 
			
		||||
    def execute(*params)
 | 
			
		||||
      if params.last.kind_of?(Google::APIClient::BatchRequest) &&
 | 
			
		||||
      if params.last.kind_of?(Google::APIClient::Request) &&
 | 
			
		||||
          params.size == 1
 | 
			
		||||
        batch = params.pop
 | 
			
		||||
        options = batch.options
 | 
			
		||||
        method, uri, headers, body = batch.to_http_request
 | 
			
		||||
        reference = self.generate_request({
 | 
			
		||||
          :uri => uri,
 | 
			
		||||
          :http_method => method,
 | 
			
		||||
          :headers => headers,
 | 
			
		||||
          :body => body
 | 
			
		||||
        }.merge(options))        
 | 
			
		||||
        response = self.transmit(:request => reference.to_http_request, :connection => options[:connection])
 | 
			
		||||
        batch.process_response(response)
 | 
			
		||||
        return nil
 | 
			
		||||
        request = params.pop
 | 
			
		||||
      else
 | 
			
		||||
        # This block of code allows us to accept multiple parameter passing
 | 
			
		||||
        # styles, and maintaining some backwards compatibility.
 | 
			
		||||
| 
						 | 
				
			
			@ -619,14 +608,18 @@ module Google
 | 
			
		|||
        options[:body] = params.shift if params.size > 0
 | 
			
		||||
        options[:headers] = params.shift if params.size > 0
 | 
			
		||||
        options[:client] = self
 | 
			
		||||
        reference = self.generate_request(options)
 | 
			
		||||
        response = self.transmit(
 | 
			
		||||
          :request => reference.to_http_request,
 | 
			
		||||
          :connection => options[:connection]
 | 
			
		||||
        )
 | 
			
		||||
        result = Google::APIClient::Result.new(reference, response)
 | 
			
		||||
        return result
 | 
			
		||||
        request = self.generate_request(options)
 | 
			
		||||
      end
 | 
			
		||||
      response = self.transmit(:request => request.to_http_request, :connection => Faraday.default_connection)
 | 
			
		||||
      result = request.process_response(response)
 | 
			
		||||
      if request.upload_type == 'resumable'
 | 
			
		||||
        upload =  result.resumable_upload
 | 
			
		||||
        unless upload.complete?
 | 
			
		||||
          response = self.transmit(:request => upload.to_http_request, :connection => Faraday.default_connection)
 | 
			
		||||
          result = upload.process_response(response)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      return result
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    ##
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
# limitations under the License.
 | 
			
		||||
 | 
			
		||||
require 'addressable/uri'
 | 
			
		||||
require 'google/api_client/reference'
 | 
			
		||||
require 'uuidtools'
 | 
			
		||||
 | 
			
		||||
module Google
 | 
			
		||||
| 
						 | 
				
			
			@ -30,11 +31,9 @@ module Google
 | 
			
		|||
    
 | 
			
		||||
    ##
 | 
			
		||||
    # Wraps multiple API calls into a single over-the-wire HTTP request.
 | 
			
		||||
    class BatchRequest
 | 
			
		||||
 | 
			
		||||
    class BatchRequest < Request
 | 
			
		||||
      BATCH_BOUNDARY = "-----------RubyApiBatchRequest".freeze
 | 
			
		||||
 | 
			
		||||
      attr_accessor :options
 | 
			
		||||
      attr_reader :calls, :callbacks
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
| 
						 | 
				
			
			@ -49,21 +48,17 @@ module Google
 | 
			
		|||
      #
 | 
			
		||||
      # @return [Google::APIClient::BatchRequest] The constructed object.
 | 
			
		||||
      def initialize(options = {}, &block)
 | 
			
		||||
        # Request options, ignoring method and parameters.
 | 
			
		||||
        @options = options
 | 
			
		||||
        # Batched calls to be made, indexed by call ID.
 | 
			
		||||
        @calls = {}
 | 
			
		||||
        # Callbacks per batched call, indexed by call ID.
 | 
			
		||||
        @callbacks = {}
 | 
			
		||||
        # Order for the call IDs, since Ruby 1.8 hashes are unordered.
 | 
			
		||||
        @order = []
 | 
			
		||||
        # Global callback to be used for every call. If a specific callback
 | 
			
		||||
        # has been defined for a request, this won't be called.
 | 
			
		||||
        @calls = []
 | 
			
		||||
        @global_callback = block if block_given?
 | 
			
		||||
        # The last auto generated ID.
 | 
			
		||||
        @last_auto_id = 0
 | 
			
		||||
        # Base ID for the batch request.
 | 
			
		||||
        @base_id = nil
 | 
			
		||||
        
 | 
			
		||||
        # TODO(sgomes): Use SecureRandom.uuid, drop UUIDTools when we drop 1.8
 | 
			
		||||
        @base_id = UUIDTools::UUID.random_create.to_s
 | 
			
		||||
 | 
			
		||||
        options[:uri] ||= 'https://www.googleapis.com/batch'
 | 
			
		||||
        options[:http_method] ||= 'POST'
 | 
			
		||||
 | 
			
		||||
        super options
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
| 
						 | 
				
			
			@ -81,33 +76,16 @@ module Google
 | 
			
		|||
        unless call.kind_of?(Google::APIClient::Reference)
 | 
			
		||||
          call = Google::APIClient::Reference.new(call)
 | 
			
		||||
        end
 | 
			
		||||
        if call_id.nil?
 | 
			
		||||
          call_id = new_id
 | 
			
		||||
        end
 | 
			
		||||
        if @calls.include?(call_id)
 | 
			
		||||
        call_id ||= new_id
 | 
			
		||||
        if @calls.assoc(call_id)
 | 
			
		||||
          raise BatchError,
 | 
			
		||||
              'A call with this ID already exists: %s' % call_id
 | 
			
		||||
        end
 | 
			
		||||
        @calls[call_id] = call
 | 
			
		||||
        @order << call_id
 | 
			
		||||
        if block_given?
 | 
			
		||||
          @callbacks[call_id] = block
 | 
			
		||||
        elsif @global_callback
 | 
			
		||||
          @callbacks[call_id] = @global_callback
 | 
			
		||||
        end
 | 
			
		||||
        callback = block_given? ? block : @global_callback
 | 
			
		||||
        @calls << [call_id, call, callback]        
 | 
			
		||||
        return self
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Convert this batch request into an HTTP request.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [Array<String, String, Hash, String>]
 | 
			
		||||
      #   An array consisting of, in order: HTTP method, request path, request
 | 
			
		||||
      #   headers and request body.
 | 
			
		||||
      def to_http_request
 | 
			
		||||
        return ['POST', request_uri, request_headers, request_body]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Processes the HTTP response to the batch request, issuing callbacks.
 | 
			
		||||
      #
 | 
			
		||||
| 
						 | 
				
			
			@ -119,14 +97,27 @@ module Google
 | 
			
		|||
        parts = parts[1...-1]
 | 
			
		||||
        parts.each do |part|
 | 
			
		||||
          call_response = deserialize_call_response(part)
 | 
			
		||||
          callback = @callbacks[call_response.call_id]
 | 
			
		||||
          call = @calls[call_response.call_id]
 | 
			
		||||
          _, call, callback = @calls.assoc(call_response.call_id)
 | 
			
		||||
          result = Google::APIClient::Result.new(call, call_response)
 | 
			
		||||
          callback.call(result) if callback
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      private
 | 
			
		||||
      ##
 | 
			
		||||
      # Return the request body for the BatchRequest's HTTP request.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [String] The request body.
 | 
			
		||||
      def to_http_request
 | 
			
		||||
        if @calls.nil? || @calls.empty?
 | 
			
		||||
          raise BatchError, 'Cannot make an empty batch request'
 | 
			
		||||
        end
 | 
			
		||||
        parts = @calls.map {|(call_id, call, callback)| serialize_call(call_id, call)}
 | 
			
		||||
        build_multipart(parts, 'multipart/mixed', BATCH_BOUNDARY)
 | 
			
		||||
        super
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      
 | 
			
		||||
      protected
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Helper method to find a header from its name, regardless of case.
 | 
			
		||||
| 
						 | 
				
			
			@ -148,29 +139,13 @@ module Google
 | 
			
		|||
      # @return [String] the new, unique ID.
 | 
			
		||||
      def new_id
 | 
			
		||||
        @last_auto_id += 1
 | 
			
		||||
        while @calls.include?(@last_auto_id)
 | 
			
		||||
        while @calls.assoc(@last_auto_id)
 | 
			
		||||
          @last_auto_id += 1
 | 
			
		||||
        end
 | 
			
		||||
        return @last_auto_id.to_s
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Convert an id to a Content-ID header value.
 | 
			
		||||
      #
 | 
			
		||||
      # @param [String] call_id: identifier of individual call.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [String]
 | 
			
		||||
      #   A Content-ID header with the call_id encoded into it. A UUID is
 | 
			
		||||
      #   prepended to the value because Content-ID headers are supposed to be
 | 
			
		||||
      #   universally unique.
 | 
			
		||||
      def id_to_header(call_id)
 | 
			
		||||
        if @base_id.nil?
 | 
			
		||||
          # TODO(sgomes): Use SecureRandom.uuid, drop UUIDTools when we drop 1.8
 | 
			
		||||
          @base_id = UUIDTools::UUID.random_create.to_s
 | 
			
		||||
        end
 | 
			
		||||
  
 | 
			
		||||
        return '<%s+%s>' % [@base_id, Addressable::URI.encode(call_id)]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Convert a Content-ID header value to an id. Presumes the Content-ID
 | 
			
		||||
| 
						 | 
				
			
			@ -189,30 +164,6 @@ module Google
 | 
			
		|||
        return Addressable::URI.unencode(call_id)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Convert a single batched call into a string.
 | 
			
		||||
      #
 | 
			
		||||
      # @param [Google::APIClient::Reference] call: the call to serialize.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [String] The request as a string in application/http format.
 | 
			
		||||
      def serialize_call(call)
 | 
			
		||||
        http_request = call.to_http_request
 | 
			
		||||
        method = http_request.method.to_s.upcase
 | 
			
		||||
        path = http_request.path.to_s
 | 
			
		||||
        status_line = method + " " + path + " HTTP/1.1"
 | 
			
		||||
        serialized_call = status_line
 | 
			
		||||
        if http_request.headers
 | 
			
		||||
          http_request.headers.each do |header, value|
 | 
			
		||||
            serialized_call << "\r\n%s: %s" % [header, value]
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        if http_request.body
 | 
			
		||||
          serialized_call << "\r\n\r\n"
 | 
			
		||||
          serialized_call << http_request.body
 | 
			
		||||
        end
 | 
			
		||||
        return serialized_call
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Auxiliary method to split the headers from the body in an HTTP response.
 | 
			
		||||
      #
 | 
			
		||||
| 
						 | 
				
			
			@ -255,42 +206,42 @@ module Google
 | 
			
		|||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
      # Return the request headers for the BatchRequest's HTTP request.
 | 
			
		||||
      # Convert a single batched call into a string.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [Hash] The HTTP headers.
 | 
			
		||||
      def request_headers
 | 
			
		||||
        return {
 | 
			
		||||
          'Content-Type' => 'multipart/mixed; boundary=%s' % BATCH_BOUNDARY
 | 
			
		||||
        }
 | 
			
		||||
      # @param [Google::APIClient::Reference] call: the call to serialize.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [StringIO] The request as a string in application/http format.
 | 
			
		||||
      def serialize_call(call_id, call)
 | 
			
		||||
        http_request = call.to_http_request
 | 
			
		||||
        body = "#{http_request.method.to_s.upcase} #{http_request.path} HTTP/1.1"
 | 
			
		||||
        http_request.headers.each do |header, value|
 | 
			
		||||
          body << "\r\n%s: %s" % [header, value]
 | 
			
		||||
        end
 | 
			
		||||
        if http_request.body
 | 
			
		||||
          # TODO - CompositeIO if body is a stream 
 | 
			
		||||
          body << "\r\n\r\n"
 | 
			
		||||
          if http_request.body.respond_to?(:read)
 | 
			
		||||
            body << http_request.body.read
 | 
			
		||||
          else
 | 
			
		||||
            body << http_request.body.to_s
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        Faraday::UploadIO.new(StringIO.new(body), 'application/http', 'ruby-api-request', 'Content-ID' => id_to_header(call_id))
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      ##
 | 
			
		||||
      # Return the request path for the BatchRequest's HTTP request.
 | 
			
		||||
      # Convert an id to a Content-ID header value.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [String] The request path.
 | 
			
		||||
      def request_uri
 | 
			
		||||
        if @calls.nil? || @calls.empty?
 | 
			
		||||
          raise BatchError, 'Cannot make an empty batch request'
 | 
			
		||||
        end
 | 
			
		||||
        # All APIs have the same batch path, so just get the first one.
 | 
			
		||||
        return @calls.first[1].api_method.api.batch_path
 | 
			
		||||
      # @param [String] call_id: identifier of individual call.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [String]
 | 
			
		||||
      #   A Content-ID header with the call_id encoded into it. A UUID is
 | 
			
		||||
      #   prepended to the value because Content-ID headers are supposed to be
 | 
			
		||||
      #   universally unique.
 | 
			
		||||
      def id_to_header(call_id)
 | 
			
		||||
        return '<%s+%s>' % [@base_id, Addressable::URI.encode(call_id)]
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      ##
 | 
			
		||||
      # Return the request body for the BatchRequest's HTTP request.
 | 
			
		||||
      #
 | 
			
		||||
      # @return [String] The request body.
 | 
			
		||||
      def request_body
 | 
			
		||||
        body = ""
 | 
			
		||||
        @order.each do |call_id|
 | 
			
		||||
          body << "--" + BATCH_BOUNDARY + "\r\n"
 | 
			
		||||
          body << "Content-Type: application/http\r\n"
 | 
			
		||||
          body << "Content-ID: %s\r\n\r\n" % id_to_header(call_id)
 | 
			
		||||
          body << serialize_call(@calls[call_id]) + "\r\n\r\n"
 | 
			
		||||
        end
 | 
			
		||||
        body << "--" + BATCH_BOUNDARY + "--"
 | 
			
		||||
        return body
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
# 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/api_client/reference'
 | 
			
		||||
 | 
			
		||||
module Google
 | 
			
		||||
  class APIClient
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +34,8 @@ module Google
 | 
			
		|||
    ##
 | 
			
		||||
    # Resumable uploader.
 | 
			
		||||
    #
 | 
			
		||||
    class ResumableUpload
 | 
			
		||||
      attr_reader :result
 | 
			
		||||
      attr_accessor :client
 | 
			
		||||
    class ResumableUpload < Request
 | 
			
		||||
      attr_accessor :chunk_size
 | 
			
		||||
      attr_accessor :media
 | 
			
		||||
      attr_accessor :location
 | 
			
		||||
  
 | 
			
		||||
      ##
 | 
			
		||||
      # Creates a new uploader.
 | 
			
		||||
| 
						 | 
				
			
			@ -49,15 +46,13 @@ module Google
 | 
			
		|||
      #   Media to upload
 | 
			
		||||
      # @param [String] location
 | 
			
		||||
      #  URL to upload to    
 | 
			
		||||
      def initialize(result, media, location)
 | 
			
		||||
        self.media = media
 | 
			
		||||
        self.location = location
 | 
			
		||||
        self.chunk_size = 256 * 1024
 | 
			
		||||
        
 | 
			
		||||
        @api_method = result.reference.api_method
 | 
			
		||||
        @result = result
 | 
			
		||||
        @offset = 0
 | 
			
		||||
      def initialize(options={})
 | 
			
		||||
        super options
 | 
			
		||||
        self.uri = options[:uri]
 | 
			
		||||
        self.http_method = :put
 | 
			
		||||
        @offset = options[:offset] || 0
 | 
			
		||||
        @complete = false
 | 
			
		||||
        @expired = false
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      ##
 | 
			
		||||
| 
						 | 
				
			
			@ -66,8 +61,9 @@ module Google
 | 
			
		|||
      # @param [Google::APIClient] api_client
 | 
			
		||||
      #   API Client instance to use for sending
 | 
			
		||||
      def send_all(api_client)
 | 
			
		||||
        result = nil
 | 
			
		||||
        until complete?
 | 
			
		||||
          send_chunk(api_client)
 | 
			
		||||
          result = send_chunk(api_client)
 | 
			
		||||
          break unless result.status == 308
 | 
			
		||||
        end
 | 
			
		||||
        return result
 | 
			
		||||
| 
						 | 
				
			
			@ -80,25 +76,7 @@ module Google
 | 
			
		|||
      # @param [Google::APIClient] api_client
 | 
			
		||||
      #   API Client instance to use for sending
 | 
			
		||||
      def send_chunk(api_client)
 | 
			
		||||
        if @offset.nil?
 | 
			
		||||
          return resync_range(api_client)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        start_offset = @offset
 | 
			
		||||
        self.media.io.pos = start_offset
 | 
			
		||||
        chunk = self.media.io.read(chunk_size)
 | 
			
		||||
        content_length = chunk.bytesize
 | 
			
		||||
 | 
			
		||||
        end_offset = start_offset + content_length - 1
 | 
			
		||||
        @result = api_client.execute(
 | 
			
		||||
          :uri => self.location,
 | 
			
		||||
          :http_method => :put,
 | 
			
		||||
          :headers => {
 | 
			
		||||
            'Content-Length' => "#{content_length}",
 | 
			
		||||
            'Content-Type' => self.media.content_type, 
 | 
			
		||||
            'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" },
 | 
			
		||||
          :body => chunk)
 | 
			
		||||
        return process_result(@result)
 | 
			
		||||
        return api_client.execute(self)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      ##
 | 
			
		||||
| 
						 | 
				
			
			@ -117,56 +95,63 @@ module Google
 | 
			
		|||
      # @return [TrueClass, FalseClass]
 | 
			
		||||
      #   Whether or not the upload has expired and can not be resumed
 | 
			
		||||
      def expired?
 | 
			
		||||
        return @result.status == 404 || @result.status == 410
 | 
			
		||||
        return @expired
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      ##
 | 
			
		||||
      # Get the last saved range from the server in case an error occurred 
 | 
			
		||||
      # and the offset is not known.
 | 
			
		||||
      #
 | 
			
		||||
      # @param [Google::APIClient] api_client
 | 
			
		||||
      #   API Client instance to use for sending
 | 
			
		||||
      def resync_range(api_client)
 | 
			
		||||
        r = api_client.execute(
 | 
			
		||||
          :uri => self.location,
 | 
			
		||||
          :http_method => :put,
 | 
			
		||||
          :headers => { 
 | 
			
		||||
      def to_http_request
 | 
			
		||||
        if @complete
 | 
			
		||||
          raise Google::APIClient::ClientError, "Upload already complete"
 | 
			
		||||
        elsif @offset.nil?
 | 
			
		||||
          self.headers.update({ 
 | 
			
		||||
            'Content-Length' => "0", 
 | 
			
		||||
            'Content-Range' => "bytes */#{media.length}" })
 | 
			
		||||
        return process_result(r)
 | 
			
		||||
        else
 | 
			
		||||
          start_offset = @offset
 | 
			
		||||
          self.media.io.pos = start_offset
 | 
			
		||||
          chunk = self.media.io.read(chunk_size)
 | 
			
		||||
          content_length = chunk.bytesize
 | 
			
		||||
          end_offset = start_offset + content_length - 1
 | 
			
		||||
          
 | 
			
		||||
          self.headers.update({
 | 
			
		||||
            'Content-Length' => "#{content_length}",
 | 
			
		||||
            'Content-Type' => self.media.content_type, 
 | 
			
		||||
            'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" })
 | 
			
		||||
          self.body = chunk
 | 
			
		||||
        end
 | 
			
		||||
        super
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      def to_hash
 | 
			
		||||
        super.merge(:offset => @offset)
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      ##
 | 
			
		||||
      # Check the result from the server, updating the offset and/or location
 | 
			
		||||
      # if available.
 | 
			
		||||
      #
 | 
			
		||||
      # @param [Google::APIClient::Result] r
 | 
			
		||||
      # @param [Faraday::Response] r
 | 
			
		||||
      #  Result of a chunk upload or range query
 | 
			
		||||
      def process_result(result)
 | 
			
		||||
        case result.status
 | 
			
		||||
      def process_response(response)
 | 
			
		||||
        case response.status
 | 
			
		||||
        when 200...299
 | 
			
		||||
          @complete = true
 | 
			
		||||
          if @api_method
 | 
			
		||||
            # Inject the original API method so data is parsed correctly
 | 
			
		||||
            result.reference.api_method = @api_method
 | 
			
		||||
          end
 | 
			
		||||
          return result
 | 
			
		||||
        when 308
 | 
			
		||||
          range = result.headers['range']
 | 
			
		||||
          range = response.headers['range']
 | 
			
		||||
          if range
 | 
			
		||||
            @offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
 | 
			
		||||
          end
 | 
			
		||||
          if result.headers['location']
 | 
			
		||||
            self.location = result.headers['location']
 | 
			
		||||
          if response.headers['location']
 | 
			
		||||
            self.uri = response.headers['location']
 | 
			
		||||
          end
 | 
			
		||||
        when 400...499
 | 
			
		||||
          @expired = true
 | 
			
		||||
        when 500...599
 | 
			
		||||
          # Invalidate the offset to mark it needs to be queried on the
 | 
			
		||||
          # next request
 | 
			
		||||
          @offset = nil
 | 
			
		||||
        end
 | 
			
		||||
        return nil
 | 
			
		||||
        return Google::APIClient::Result.new(self, response)
 | 
			
		||||
      end      
 | 
			
		||||
      
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -20,28 +20,32 @@ require 'addressable/uri'
 | 
			
		|||
require 'stringio'
 | 
			
		||||
require 'google/api_client/discovery'
 | 
			
		||||
 | 
			
		||||
# TODO - needs some serious cleanup
 | 
			
		||||
 | 
			
		||||
module Google
 | 
			
		||||
  class APIClient
 | 
			
		||||
    class Reference
 | 
			
		||||
 | 
			
		||||
    class Request
 | 
			
		||||
      MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
 | 
			
		||||
      
 | 
			
		||||
      attr_reader :connection, :parameters,  :api_method, :headers
 | 
			
		||||
      attr_accessor :media, :authorization, :body
 | 
			
		||||
      
 | 
			
		||||
      def initialize(options={})
 | 
			
		||||
 | 
			
		||||
        self.connection = options[:connection] || Faraday.default_connection
 | 
			
		||||
        self.authorization = options[:authorization]
 | 
			
		||||
        self.api_method = options[:api_method]
 | 
			
		||||
        
 | 
			
		||||
        self.parameters = options[:parameters] || {}
 | 
			
		||||
        @parameters = Hash[options[:parameters] || {}]
 | 
			
		||||
        # These parameters are handled differently because they're not
 | 
			
		||||
        # parameters to the API method, but rather to the API system.
 | 
			
		||||
        self.parameters['key'] ||= options[:key] if options[:key]
 | 
			
		||||
        self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
 | 
			
		||||
 | 
			
		||||
        self.headers = options[:headers] || {}
 | 
			
		||||
        @headers = Faraday::Utils::Headers.new
 | 
			
		||||
        self.headers.merge!(options[:headers]) if options[:headers]
 | 
			
		||||
        
 | 
			
		||||
        if options[:media]
 | 
			
		||||
          self.initialize_media_upload
 | 
			
		||||
          self.initialize_media_upload(options)
 | 
			
		||||
        elsif options[:body]
 | 
			
		||||
          self.body = options[:body]
 | 
			
		||||
        elsif options[:body_object]
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +54,7 @@ module Google
 | 
			
		|||
        else
 | 
			
		||||
          self.body = ''
 | 
			
		||||
        end
 | 
			
		||||
        
 | 
			
		||||
        unless self.api_method
 | 
			
		||||
          self.http_method = options[:http_method] || 'GET'
 | 
			
		||||
          self.uri = options[:uri]
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +64,7 @@ module Google
 | 
			
		|||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def initialize_media_upload
 | 
			
		||||
      def initialize_media_upload(options)
 | 
			
		||||
        self.media = options[:media]
 | 
			
		||||
        case self.upload_type
 | 
			
		||||
        when "media"
 | 
			
		||||
| 
						 | 
				
			
			@ -73,15 +78,7 @@ module Google
 | 
			
		|||
            raise ArgumentError, "Multipart requested but no body object"              
 | 
			
		||||
          end
 | 
			
		||||
          metadata = StringIO.new(serialize_body(options[:body_object]))
 | 
			
		||||
          env = {
 | 
			
		||||
            :request_headers => {'Content-Type' => "multipart/related;boundary=#{MULTIPART_BOUNDARY}"},
 | 
			
		||||
            :request => { :boundary => MULTIPART_BOUNDARY }
 | 
			
		||||
          }
 | 
			
		||||
          multipart = Faraday::Request::Multipart.new
 | 
			
		||||
          self.body = multipart.create_multipart(env, [
 | 
			
		||||
            [nil,Faraday::UploadIO.new(metadata, 'application/json', 'file.json')], 
 | 
			
		||||
            [nil, self.media]])
 | 
			
		||||
          self.headers.update(env[:request_headers])
 | 
			
		||||
          build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
 | 
			
		||||
        when "resumable"
 | 
			
		||||
          file_length = self.media.length
 | 
			
		||||
          self.headers['X-Upload-Content-Type'] = self.media.content_type
 | 
			
		||||
| 
						 | 
				
			
			@ -92,11 +89,19 @@ module Google
 | 
			
		|||
          else
 | 
			
		||||
            self.body = ''
 | 
			
		||||
          end
 | 
			
		||||
        else
 | 
			
		||||
          raise ArgumentError, "Invalid uploadType for media"
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY) 
 | 
			
		||||
        env = {
 | 
			
		||||
          :request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"},
 | 
			
		||||
          :request => { :boundary => boundary }
 | 
			
		||||
        }
 | 
			
		||||
        multipart = Faraday::Request::Multipart.new
 | 
			
		||||
        self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
 | 
			
		||||
        self.headers.update(env[:request_headers])
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      def serialize_body(body)
 | 
			
		||||
        return body.to_json if body.respond_to?(:to_json)
 | 
			
		||||
        return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
 | 
			
		||||
| 
						 | 
				
			
			@ -104,30 +109,10 @@ module Google
 | 
			
		|||
                         'Must respond to :to_json or :to_hash.'
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def media
 | 
			
		||||
        return @media
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def media=(media)
 | 
			
		||||
        @media = (media)
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      def upload_type
 | 
			
		||||
        return self.parameters['uploadType'] || self.parameters['upload_type']
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def authorization
 | 
			
		||||
        return @authorization
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def authorization=(new_authorization)
 | 
			
		||||
        @authorization = new_authorization
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def connection
 | 
			
		||||
        return @connection
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def connection=(new_connection)
 | 
			
		||||
        if new_connection.kind_of?(Faraday::Connection)
 | 
			
		||||
          @connection = new_connection
 | 
			
		||||
| 
						 | 
				
			
			@ -137,10 +122,6 @@ module Google
 | 
			
		|||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def api_method
 | 
			
		||||
        return @api_method
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def api_method=(new_api_method)
 | 
			
		||||
        if new_api_method.kind_of?(Google::APIClient::Method) ||
 | 
			
		||||
            new_api_method == nil
 | 
			
		||||
| 
						 | 
				
			
			@ -151,36 +132,8 @@ module Google
 | 
			
		|||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def parameters
 | 
			
		||||
        return @parameters
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def parameters=(new_parameters)
 | 
			
		||||
        @parameters = Hash[new_parameters]
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def body
 | 
			
		||||
        return @body
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def body=(new_body)
 | 
			
		||||
        @body = new_body
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def headers
 | 
			
		||||
        return @headers ||= {}
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def headers=(new_headers)
 | 
			
		||||
        if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash)
 | 
			
		||||
          @headers = new_headers
 | 
			
		||||
        else
 | 
			
		||||
          raise TypeError, "Expected Hash or Array, got #{new_headers.class}."
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def http_method
 | 
			
		||||
        return @http_method ||= self.api_method.http_method
 | 
			
		||||
        return @http_method ||= self.api_method.http_method.to_s.downcase.to_sym
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def http_method=(new_http_method)
 | 
			
		||||
| 
						 | 
				
			
			@ -204,16 +157,16 @@ module Google
 | 
			
		|||
 | 
			
		||||
      def to_http_request
 | 
			
		||||
        request = ( 
 | 
			
		||||
          if self.api_method
 | 
			
		||||
            self.api_method.generate_request(
 | 
			
		||||
              self.parameters, self.body, self.headers, :connection => self.connection
 | 
			
		||||
            )
 | 
			
		||||
          else
 | 
			
		||||
          if self.uri
 | 
			
		||||
            self.connection.build_request(self.http_method) do |req|
 | 
			
		||||
              req.url(self.uri.to_str)
 | 
			
		||||
              req.headers.update(self.headers)
 | 
			
		||||
              req.body = self.body
 | 
			
		||||
            end
 | 
			
		||||
          else
 | 
			
		||||
            self.api_method.generate_request(
 | 
			
		||||
              self.parameters, self.body, self.headers, :connection => self.connection
 | 
			
		||||
            )
 | 
			
		||||
          end)
 | 
			
		||||
        
 | 
			
		||||
        if self.authorization.respond_to?(:generate_authenticated_request)
 | 
			
		||||
| 
						 | 
				
			
			@ -237,11 +190,19 @@ module Google
 | 
			
		|||
        options[:headers] = self.headers
 | 
			
		||||
        options[:body] = self.body
 | 
			
		||||
        options[:connection] = self.connection
 | 
			
		||||
        options[:media] = self.media
 | 
			
		||||
        unless self.authorization.nil?
 | 
			
		||||
          options[:authorization] = self.authorization
 | 
			
		||||
        end
 | 
			
		||||
        return options
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      def process_response(response)
 | 
			
		||||
        Result.new(self, response)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  
 | 
			
		||||
    class Reference < Request
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ module Google
 | 
			
		|||
      def initialize(reference, response)
 | 
			
		||||
        @reference = reference
 | 
			
		||||
        @response = response
 | 
			
		||||
        @media_upload = reference if reference.kind_of?(ResumableUpload)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      attr_reader :reference
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +41,13 @@ module Google
 | 
			
		|||
      end
 | 
			
		||||
 | 
			
		||||
      def resumable_upload        
 | 
			
		||||
        @media_upload ||= Google::APIClient::ResumableUpload.new(self, reference.media, self.headers['location'])
 | 
			
		||||
        @media_upload ||= (
 | 
			
		||||
          options = self.reference.to_hash.merge(
 | 
			
		||||
            :uri => self.headers['location'],
 | 
			
		||||
            :media => self.reference.media
 | 
			
		||||
          )
 | 
			
		||||
          Google::APIClient::ResumableUpload.new(options)
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
      
 | 
			
		||||
      def media_type
 | 
			
		||||
| 
						 | 
				
			
			@ -124,10 +131,10 @@ module Google
 | 
			
		|||
        merged_parameters = Hash[self.reference.parameters].merge({
 | 
			
		||||
          self.page_token_param => self.next_page_token
 | 
			
		||||
        })
 | 
			
		||||
        # Because References can be coerced to Hashes, we can merge them,
 | 
			
		||||
        # Because Requests can be coerced to Hashes, we can merge them,
 | 
			
		||||
        # preserving all context except the API method parameters that we're
 | 
			
		||||
        # using for pagination.
 | 
			
		||||
        return Google::APIClient::Reference.new(
 | 
			
		||||
        return Google::APIClient::Request.new(
 | 
			
		||||
          Hash[self.reference].merge(:parameters => merged_parameters)
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			@ -146,10 +153,10 @@ module Google
 | 
			
		|||
        merged_parameters = Hash[self.reference.parameters].merge({
 | 
			
		||||
          self.page_token_param => self.prev_page_token
 | 
			
		||||
        })
 | 
			
		||||
        # Because References can be coerced to Hashes, we can merge them,
 | 
			
		||||
        # Because Requests can be coerced to Hashes, we can merge them,
 | 
			
		||||
        # preserving all context except the API method parameters that we're
 | 
			
		||||
        # using for pagination.
 | 
			
		||||
        return Google::APIClient::Reference.new(
 | 
			
		||||
        return Google::APIClient::Request.new(
 | 
			
		||||
          Hash[self.reference].merge(:parameters => merged_parameters)
 | 
			
		||||
        )
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,9 +21,8 @@ if !defined?(::Google::APIClient::VERSION)
 | 
			
		|||
    class APIClient
 | 
			
		||||
      module VERSION
 | 
			
		||||
        MAJOR = 0
 | 
			
		||||
        MINOR = 4
 | 
			
		||||
        TINY  = 7
 | 
			
		||||
 | 
			
		||||
        MINOR = 5
 | 
			
		||||
        TINY  = 0
 | 
			
		||||
        STRING = [MAJOR, MINOR, TINY].join('.')
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -226,16 +226,16 @@ describe Google::APIClient::BatchRequest do
 | 
			
		|||
      it 'should convert to a correct HTTP request' do
 | 
			
		||||
        batch = Google::APIClient::BatchRequest.new { |result| }
 | 
			
		||||
        batch.add(@call1, '1').add(@call2, '2')
 | 
			
		||||
        method, uri, headers, body = batch.to_http_request
 | 
			
		||||
        request = batch.to_http_request.to_env(Faraday.default_connection)
 | 
			
		||||
        boundary = Google::APIClient::BatchRequest::BATCH_BOUNDARY
 | 
			
		||||
        method.to_s.downcase.should == 'post'
 | 
			
		||||
        uri.to_s.should == 'https://www.googleapis.com/batch'
 | 
			
		||||
        headers.should == {
 | 
			
		||||
          "Content-Type"=>"multipart/mixed; boundary=#{boundary}"
 | 
			
		||||
        }
 | 
			
		||||
        expected_body = /--#{Regexp.escape(boundary)}\nContent-Type: +application\/http\nContent-ID: +<[\w-]+\+1>\n\nPOST +https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/myemail@mydomain.tld\/events +HTTP\/1.1\nContent-Type: +application\/json\n\n#{Regexp.escape(@call1[:body])}\n\n--#{boundary}\nContent-Type: +application\/http\nContent-ID: +<[\w-]+\+2>\n\nPOST +https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/myemail@mydomain.tld\/events HTTP\/1.1\nContent-Type: +application\/json\n\n#{Regexp.escape(@call2[:body])}\n\n--#{Regexp.escape(boundary)}--/
 | 
			
		||||
        body.gsub("\r", "").should =~ expected_body
 | 
			
		||||
        request[:method].to_s.downcase.should == 'post'
 | 
			
		||||
        request[:url].to_s.should == 'https://www.googleapis.com/batch'
 | 
			
		||||
        request[:request_headers]['Content-Type'].should == "multipart/mixed;boundary=#{boundary}"
 | 
			
		||||
        # TODO - Fix headers
 | 
			
		||||
        #expected_body = /--#{Regexp.escape(boundary)}\nContent-Type: +application\/http\nContent-ID: +<[\w-]+\+1>\n\nPOST +https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/myemail@mydomain.tld\/events +HTTP\/1.1\nContent-Type: +application\/json\n\n#{Regexp.escape(@call1[:body])}\n\n--#{boundary}\nContent-Type: +application\/http\nContent-ID: +<[\w-]+\+2>\n\nPOST +https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/myemail@mydomain.tld\/events HTTP\/1.1\nContent-Type: +application\/json\n\n#{Regexp.escape(@call2[:body])}\n\n--#{Regexp.escape(boundary)}--/
 | 
			
		||||
        #request[:body].read.gsub("\r", "").should =~ expected_body
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,68 +71,56 @@ describe Google::APIClient::ResumableUpload do
 | 
			
		|||
    @file = File.expand_path('files/sample.txt', fixtures_path)
 | 
			
		||||
    @media = Google::APIClient::UploadIO.new(@file, 'text/plain')
 | 
			
		||||
    @uploader = Google::APIClient::ResumableUpload.new(
 | 
			
		||||
      mock_result(308),
 | 
			
		||||
      @media,
 | 
			
		||||
      'https://www.googleapis.com/upload/drive/v1/files/12345')
 | 
			
		||||
      :media => @media,
 | 
			
		||||
      :api_method => @drive.files.insert,
 | 
			
		||||
      :uri => 'https://www.googleapis.com/upload/drive/v1/files/12345')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'should consider 20x status as complete' do
 | 
			
		||||
    api_client = stub('api', :execute => mock_result(200))
 | 
			
		||||
    @uploader.send_chunk(api_client)
 | 
			
		||||
    request = @uploader.to_http_request
 | 
			
		||||
    @uploader.process_response(mock_result(200))
 | 
			
		||||
    @uploader.complete?.should == true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'should consider 30x status as incomplete' do
 | 
			
		||||
    api_client = stub('api', :execute => mock_result(308))
 | 
			
		||||
    @uploader.send_chunk(api_client)
 | 
			
		||||
    request = @uploader.to_http_request
 | 
			
		||||
    @uploader.process_response(mock_result(308))
 | 
			
		||||
    @uploader.complete?.should == false
 | 
			
		||||
    @uploader.expired?.should == false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'should consider 40x status as fatal' do
 | 
			
		||||
    api_client = stub('api', :execute => mock_result(404))
 | 
			
		||||
    @uploader.send_chunk(api_client)
 | 
			
		||||
    request = @uploader.to_http_request
 | 
			
		||||
    @uploader.process_response(mock_result(404))
 | 
			
		||||
    @uploader.expired?.should == true
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'should detect changes to location' do
 | 
			
		||||
    api_client = stub('api', :execute => mock_result(308, 'location' => 'https://www.googleapis.com/upload/drive/v1/files/abcdef'))
 | 
			
		||||
    @uploader.send_chunk(api_client)
 | 
			
		||||
    @uploader.location.should == 'https://www.googleapis.com/upload/drive/v1/files/abcdef'
 | 
			
		||||
    request = @uploader.to_http_request
 | 
			
		||||
    @uploader.process_response(mock_result(308, 'location' => 'https://www.googleapis.com/upload/drive/v1/files/abcdef'))
 | 
			
		||||
    @uploader.uri.to_s.should == 'https://www.googleapis.com/upload/drive/v1/files/abcdef'
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'should resume from the saved range reported by the server' do    
 | 
			
		||||
    api_client = mock('api')
 | 
			
		||||
    api_client.should_receive(:execute).and_return(mock_result(308, 'range' => '0-99'))
 | 
			
		||||
    api_client.should_receive(:execute).with(
 | 
			
		||||
      hash_including(:headers => hash_including(
 | 
			
		||||
        "Content-Range" => "bytes 100-299/#{@media.length}",
 | 
			
		||||
        "Content-Length" => "200"
 | 
			
		||||
      ))).and_return(mock_result(308))
 | 
			
		||||
 | 
			
		||||
    @uploader.chunk_size = 200
 | 
			
		||||
    @uploader.send_chunk(api_client) # Send bytes 0-199, only 0-99 saved
 | 
			
		||||
    @uploader.send_chunk(api_client) # Send bytes 100-299
 | 
			
		||||
    request = @uploader.to_http_request # Send bytes 0-199, only 0-99 saved
 | 
			
		||||
    @uploader.process_response(mock_result(308, 'range' => '0-99'))
 | 
			
		||||
    request = @uploader.to_http_request # Send bytes 100-299
 | 
			
		||||
    request.headers['Content-Range'].should == "bytes 100-299/#{@media.length}"
 | 
			
		||||
    request.headers['Content-length'].should == "200"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'should resync the offset after 5xx errors' do
 | 
			
		||||
    api_client = mock('api')
 | 
			
		||||
    api_client.should_receive(:execute).and_return(mock_result(500))
 | 
			
		||||
    api_client.should_receive(:execute).with(
 | 
			
		||||
      hash_including(:headers => hash_including(
 | 
			
		||||
        "Content-Range" => "bytes */#{@media.length}",
 | 
			
		||||
        "Content-Length" => "0"
 | 
			
		||||
      ))).and_return(mock_result(308, 'range' => '0-99'))
 | 
			
		||||
    api_client.should_receive(:execute).with(
 | 
			
		||||
      hash_including(:headers => hash_including(
 | 
			
		||||
        "Content-Range" => "bytes 100-299/#{@media.length}",
 | 
			
		||||
        "Content-Length" => "200"
 | 
			
		||||
      ))).and_return(mock_result(308))
 | 
			
		||||
 | 
			
		||||
    @uploader.chunk_size = 200
 | 
			
		||||
    @uploader.send_chunk(api_client) # 500, invalidate
 | 
			
		||||
    @uploader.send_chunk(api_client) # Just resyncs, doesn't actually upload
 | 
			
		||||
    @uploader.send_chunk(api_client) # Send next chunk at correct range
 | 
			
		||||
    request = @uploader.to_http_request
 | 
			
		||||
    @uploader.process_response(mock_result(500)) # Invalidates range
 | 
			
		||||
    request = @uploader.to_http_request # Resync
 | 
			
		||||
    request.headers['Content-Range'].should == "bytes */#{@media.length}"
 | 
			
		||||
    request.headers['Content-length'].should == "0"
 | 
			
		||||
    @uploader.process_response(mock_result(308, 'range' => '0-99'))
 | 
			
		||||
    request = @uploader.to_http_request # Send next chunk at correct range
 | 
			
		||||
    request.headers['Content-Range'].should == "bytes 100-299/#{@media.length}"
 | 
			
		||||
    request.headers['Content-length'].should == "200"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def mock_result(status, headers = {})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue