1 # Copyright 2010 Google Inc.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
18 # Uploadable media support. Holds an IO stream & content type.
20 # @see Faraday::UploadIO
22 # media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
23 class UploadIO < Faraday::UploadIO
25 # Get the length of the stream
27 # Length of stream, in bytes
29 io.respond_to?(:length) ? io.length : File.size(local_path)
39 attr_accessor :chunk_size
41 attr_accessor :location
44 # Creates a new uploader.
46 # @param [Google::APIClient::Result] result
47 # Result of the initial request that started the upload
48 # @param [Google::APIClient::UploadIO] media
50 # @param [String] location
52 def initialize(result, media, location)
54 self.location = location
55 self.chunk_size = 256 * 1024
57 @api_method = result.reference.api_method
64 # Sends all remaining chunks to the server
66 # @param [Google::APIClient] api_client
67 # API Client instance to use for sending
68 def send_all(api_client)
70 send_chunk(api_client)
71 break unless result.status == 308
78 # Sends the next chunk to the server
80 # @param [Google::APIClient] api_client
81 # API Client instance to use for sending
82 def send_chunk(api_client)
84 return resync_range(api_client)
87 start_offset = @offset
88 self.media.io.pos = start_offset
89 chunk = self.media.io.read(chunk_size)
90 content_length = chunk.bytesize
92 end_offset = start_offset + content_length - 1
93 @result = api_client.execute(
94 :uri => self.location,
97 'Content-Length' => "#{content_length}",
98 'Content-Type' => self.media.content_type,
99 'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" },
101 return process_result(@result)
105 # Check if upload is complete
107 # @return [TrueClass, FalseClass]
108 # Whether or not the upload complete successfully
114 # Check if the upload URL expired (upload not completed in alotted time.)
115 # Expired uploads must be restarted from the beginning
117 # @return [TrueClass, FalseClass]
118 # Whether or not the upload has expired and can not be resumed
120 return @result.status == 404 || @result.status == 410
124 # Get the last saved range from the server in case an error occurred
125 # and the offset is not known.
127 # @param [Google::APIClient] api_client
128 # API Client instance to use for sending
129 def resync_range(api_client)
130 r = api_client.execute(
131 :uri => self.location,
132 :http_method => :put,
134 'Content-Length' => "0",
135 'Content-Range' => "bytes */#{media.length}" })
136 return process_result(r)
140 # Check the result from the server, updating the offset and/or location
143 # @param [Google::APIClient::Result] r
144 # Result of a chunk upload or range query
145 def process_result(result)
150 # Inject the original API method so data is parsed correctly
151 result.reference.api_method = @api_method
155 range = result.headers['range']
157 @offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
159 if result.headers['location']
160 self.location = result.headers['location']
163 # Invalidate the offset to mark it needs to be queried on the