172 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# Copyright 2010 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.
 | 
						|
 | 
						|
module Google
 | 
						|
  class APIClient
 | 
						|
    ##
 | 
						|
    # Uploadable media support.  Holds an IO stream & content type.
 | 
						|
    #
 | 
						|
    # @see Faraday::UploadIO
 | 
						|
    # @example
 | 
						|
    #   media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
 | 
						|
    class UploadIO < Faraday::UploadIO      
 | 
						|
      ##
 | 
						|
      # Get the length of the stream
 | 
						|
      # @return [Integer]
 | 
						|
      #   Length of stream, in bytes
 | 
						|
      def length
 | 
						|
        io.respond_to?(:length) ? io.length : File.size(local_path)
 | 
						|
      end
 | 
						|
    end
 | 
						|
    
 | 
						|
    ##
 | 
						|
    # Resumable uploader.
 | 
						|
    #
 | 
						|
    class ResumableUpload
 | 
						|
      attr_reader :result
 | 
						|
      attr_accessor :client
 | 
						|
      attr_accessor :chunk_size
 | 
						|
      attr_accessor :media
 | 
						|
      attr_accessor :location
 | 
						|
  
 | 
						|
      ##
 | 
						|
      # Creates a new uploader.
 | 
						|
      #
 | 
						|
      # @param [Google::APIClient::Result] result
 | 
						|
      #   Result of the initial request that started the upload
 | 
						|
      # @param [Google::APIClient::UploadIO] media
 | 
						|
      #   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
 | 
						|
        @complete = false
 | 
						|
      end
 | 
						|
      
 | 
						|
      ##
 | 
						|
      # Sends all remaining chunks to the server
 | 
						|
      #
 | 
						|
      # @param [Google::APIClient] api_client
 | 
						|
      #   API Client instance to use for sending
 | 
						|
      def send_all(api_client)
 | 
						|
        until complete?
 | 
						|
          send_chunk(api_client)
 | 
						|
          break unless result.status == 308
 | 
						|
        end
 | 
						|
        return result
 | 
						|
      end
 | 
						|
      
 | 
						|
      
 | 
						|
      ##
 | 
						|
      # Sends the next chunk to the server
 | 
						|
      #
 | 
						|
      # @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)
 | 
						|
      end
 | 
						|
 | 
						|
      ##
 | 
						|
      # Check if upload is complete
 | 
						|
      #
 | 
						|
      # @return [TrueClass, FalseClass]
 | 
						|
      #   Whether or not the upload complete successfully
 | 
						|
      def complete?
 | 
						|
        return @complete
 | 
						|
      end
 | 
						|
 | 
						|
      ##
 | 
						|
      # Check if the upload URL expired (upload not completed in alotted time.)
 | 
						|
      # Expired uploads must be restarted from the beginning
 | 
						|
      #
 | 
						|
      # @return [TrueClass, FalseClass]
 | 
						|
      #   Whether or not the upload has expired and can not be resumed
 | 
						|
      def expired?
 | 
						|
        return @result.status == 404 || @result.status == 410
 | 
						|
      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 => { 
 | 
						|
            'Content-Length' => "0", 
 | 
						|
            'Content-Range' => "bytes */#{media.length}" })
 | 
						|
        return process_result(r)
 | 
						|
      end
 | 
						|
      
 | 
						|
      ##
 | 
						|
      # Check the result from the server, updating the offset and/or location
 | 
						|
      # if available.
 | 
						|
      #
 | 
						|
      # @param [Google::APIClient::Result] r
 | 
						|
      #  Result of a chunk upload or range query
 | 
						|
      def process_result(result)
 | 
						|
        case result.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']
 | 
						|
          if range
 | 
						|
            @offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
 | 
						|
          end
 | 
						|
          if result.headers['location']
 | 
						|
            self.location = result.headers['location']
 | 
						|
          end
 | 
						|
        when 500...599
 | 
						|
          # Invalidate the offset to mark it needs to be queried on the
 | 
						|
          # next request
 | 
						|
          @offset = nil
 | 
						|
        end
 | 
						|
        return nil
 | 
						|
      end
 | 
						|
      
 | 
						|
    end
 | 
						|
  end
 | 
						|
end |