Permalink
Browse files

Merge branch 'master' of https://code.google.com/p/google-api-ruby-cl…

  • Loading branch information...
2 parents 8eea813 + 1121bb7 commit 2599184e887a03e25259df48ae19f1f696da0f88 Bob Aman committed Mar 2, 2012
@@ -25,7 +25,7 @@
require 'google/api_client/discovery'
require 'google/api_client/reference'
require 'google/api_client/result'
-
+require 'google/api_client/media'
module Google
# TODO(bobaman): Document all this stuff.
@@ -718,13 +718,15 @@ def execute(*params)
# @see Google::APIClient#execute
def execute!(*params)
result = self.execute(*params)
- if result.data.respond_to?(:error) &&
- result.data.error.respond_to?(:message)
- # You're going to get a terrible error message if the response isn't
- # parsed successfully as an error.
- error_message = result.data.error.message
- elsif result.data['error'] && result.data['error']['message']
- error_message = result.data['error']['message']
+ if result.data?
+ if result.data.respond_to?(:error) &&
+ result.data.error.respond_to?(:message)
+ # You're going to get a terrible error message if the response isn't
+ # parsed successfully as an error.
+ error_message = result.data.error.message
+ elsif result.data['error'] && result.data['error']['message']
+ error_message = result.data['error']['message']
+ end
end
if result.response.status >= 400
case result.response.status
@@ -18,7 +18,7 @@
require 'google/inflection'
require 'google/api_client/discovery/resource'
require 'google/api_client/discovery/method'
-
+require 'google/api_client/discovery/media'
module Google
class APIClient
@@ -149,8 +149,7 @@ def data_wrapper?
def method_base
if @discovery_document['basePath']
return @method_base ||= (
- self.document_base +
- Addressable::URI.parse(@discovery_document['basePath'])
+ self.document_base.join(Addressable::URI.parse(@discovery_document['basePath']))
).normalize
else
return nil
@@ -0,0 +1,77 @@
+# 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.
+
+
+require 'addressable/uri'
+require 'addressable/template'
+
+require 'google/api_client/errors'
+
+
+module Google
+ class APIClient
+ ##
+ # Media upload elements for discovered methods
+ class MediaUpload
+
+ ##
+ # Creates a description of a particular method.
+ #
+ # @param [Google::APIClient::API] api
+ # Base discovery document for the API
+ # @param [Addressable::URI] method_base
+ # The base URI for the service.
+ # @param [Hash] discovery_document
+ # The media upload section of the discovery document.
+ #
+ # @return [Google::APIClient::Method] The constructed method object.
+ def initialize(api, method_base, discovery_document)
+ @api = api
+ @method_base = method_base
+ @discovery_document = discovery_document
+ end
+
+ ##
+ # List of acceptable mime types
+ #
+ # @return [Array]
+ # List of acceptable mime types for uploaded content
+ def accepted_types
+ @discovery_document['accept']
+ end
+
+ ##
+ # Maximum size of an uplad
+ # TODO: Parse & convert to numeric value
+ #
+ # @return [String]
+ def max_size
+ @discovery_document['maxSize']
+ end
+
+ ##
+ # Returns the URI template for the method. A parameter list can be
+ # used to expand this into a URI.
+ #
+ # @return [Addressable::Template] The URI template.
+ def uri_template
+ return @uri_template ||= Addressable::Template.new(
+ @api.method_base.join(Addressable::URI.parse(@discovery_document['protocols']['simple']['path']))
+ )
+ end
+
+ end
+
+ end
+end
@@ -95,16 +95,24 @@ def http_method
#
# @return [Addressable::Template] The URI template.
def uri_template
- # TODO(bobaman) We shouldn't be calling #to_s here, this should be
- # a join operation on a URI, but we have to treat these as Strings
- # because of the way the discovery document provides the URIs.
- # This should be fixed soon.
return @uri_template ||= Addressable::Template.new(
- self.method_base + @discovery_document['path']
+ self.method_base.join(Addressable::URI.parse(@discovery_document['path']))
)
end
##
+ # Returns media upload information for this method, if supported
+ #
+ # @return [Google::APIClient::MediaUpload] Description of upload endpoints
+ def media_upload
+ if @discovery_document['mediaUpload']
+ return @media_upload ||= Google::APIClient::MediaUpload.new(self, self.method_base, @discovery_document['mediaUpload'])
+ else
+ return nil
+ end
+ end
+
+ ##
# Returns the Schema object for the method's request, if any.
#
# @return [Google::APIClient::Schema] The request schema.
@@ -168,7 +176,20 @@ def generate_uri(parameters={})
parameters = self.normalize_parameters(parameters)
self.validate_parameters(parameters)
template_variables = self.uri_template.variables
- uri = self.uri_template.expand(parameters)
+ upload_type = parameters.assoc('uploadType') || parameters.assoc('upload_type')
+ if upload_type
+ unless self.media_upload
+ raise ArgumentException, "Media upload not supported for this method"
+ end
+ case upload_type.last
+ when 'media', 'multipart', 'resumable'
+ uri = self.media_upload.uri_template.expand(parameters)
+ else
+ raise ArgumentException, "Invalid uploadType '#{upload_type}'"
+ end
+ else
+ uri = self.uri_template.expand(parameters)
+ end
query_parameters = parameters.reject do |k, v|
template_variables.include?(k)
end
@@ -211,6 +232,7 @@ def generate_request(parameters={}, body='', headers=[])
req.body = body
end
end
+
##
# Returns a <code>Hash</code> of the parameter descriptions for
@@ -0,0 +1,172 @@
+# 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
Oops, something went wrong.

0 comments on commit 2599184

Please sign in to comment.