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.
14 require 'google/api_client/reference'
19 # Uploadable media support. Holds an IO stream & content type.
21 # @see Faraday::UploadIO
23 # media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
24 class UploadIO < Faraday::UploadIO
26 # Get the length of the stream
28 # Length of stream, in bytes
30 io.respond_to?(:length) ? io.length : File.size(local_path)
37 class ResumableUpload < Request
38 attr_accessor :chunk_size
41 # Creates a new uploader.
43 # @param [Google::APIClient::Result] result
44 # Result of the initial request that started the upload
45 # @param [Google::APIClient::UploadIO] media
47 # @param [String] location
49 def initialize(options={})
51 self.uri = options[:uri]
52 self.http_method = :put
53 @offset = options[:offset] || 0
59 # Sends all remaining chunks to the server
61 # @param [Google::APIClient] api_client
62 # API Client instance to use for sending
63 def send_all(api_client)
66 result = send_chunk(api_client)
67 break unless result.status == 308
74 # Sends the next chunk to the server
76 # @param [Google::APIClient] api_client
77 # API Client instance to use for sending
78 def send_chunk(api_client)
79 return api_client.execute(self)
83 # Check if upload is complete
85 # @return [TrueClass, FalseClass]
86 # Whether or not the upload complete successfully
92 # Check if the upload URL expired (upload not completed in alotted time.)
93 # Expired uploads must be restarted from the beginning
95 # @return [TrueClass, FalseClass]
96 # Whether or not the upload has expired and can not be resumed
103 raise Google::APIClient::ClientError, "Upload already complete"
105 self.headers.update({
106 'Content-Length' => "0",
107 'Content-Range' => "bytes */#{media.length}" })
109 start_offset = @offset
110 self.media.io.pos = start_offset
111 chunk = self.media.io.read(chunk_size)
112 content_length = chunk.bytesize
113 end_offset = start_offset + content_length - 1
115 self.headers.update({
116 'Content-Length' => "#{content_length}",
117 'Content-Type' => self.media.content_type,
118 'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" })
125 super.merge(:offset => @offset)
129 # Check the result from the server, updating the offset and/or location
132 # @param [Faraday::Response] r
133 # Result of a chunk upload or range query
134 def process_response(response)
139 range = response.headers['range']
141 @offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
143 if response.headers['location']
144 self.uri = response.headers['location']
149 # Invalidate the offset to mark it needs to be queried on the
153 return Google::APIClient::Result.new(self, response)