# @see Faraday::UploadIO
# @example
# media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
- class UploadIO < Faraday::UploadIO
+ class UploadIO < Faraday::UploadIO
+
+ # @return [Fixnum] Size of chunks to upload. Default is nil, meaning upload the entire file in a single request
+ attr_accessor :chunk_size
+
##
# Get the length of the stream
#
end
end
+ ##
+ # Wraps an input stream and limits data to a given range
+ #
+ # @example
+ # chunk = Google::APIClient::RangedIO.new(io, 0, 1000)
+ class RangedIO
+ ##
+ # Bind an input stream to a specific range.
+ #
+ # @param [IO] io
+ # Source input stream
+ # @param [Fixnum] offset
+ # Starting offset of the range
+ # @param [Fixnum] length
+ # Length of range
+ def initialize(io, offset, length)
+ @io = io
+ @offset = offset
+ @length = length
+ self.rewind
+ end
+
+ ##
+ # @see IO#read
+ def read(amount = nil, buf = nil)
+ buffer = buf || ''
+ if amount.nil?
+ size = @length - @pos
+ done = ''
+ elsif amount == 0
+ size = 0
+ done = ''
+ else
+ size = [@length - @pos, amount].min
+ done = nil
+ end
+
+ if size > 0
+ result = @io.read(size)
+ result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
+ buffer << result if result
+ @pos = @pos + size
+ end
+
+ if buffer.length > 0
+ buffer
+ else
+ done
+ end
+ end
+
+ ##
+ # @see IO#rewind
+ def rewind
+ self.pos = 0
+ end
+
+ ##
+ # @see IO#pos
+ def pos
+ @pos
+ end
+
+ ##
+ # @see IO#pos=
+ def pos=(pos)
+ @pos = pos
+ @io.pos = @offset + pos
+ end
+ end
+
##
# Resumable uploader.
#
'Content-Range' => "bytes */#{media.length}" })
else
start_offset = @offset
- self.media.io.pos = start_offset
- chunk = self.media.io.read(chunk_size)
- content_length = chunk.bytesize
+ remaining = self.media.length - start_offset
+ chunk_size = self.media.chunk_size || self.chunk_size || self.media.length
+ content_length = [remaining, chunk_size].min
+ chunk = RangedIO.new(self.media.io, start_offset, content_length)
end_offset = start_offset + content_length - 1
-
self.headers.update({
'Content-Length' => "#{content_length}",
'Content-Type' => self.media.content_type,
end
end
+describe Google::APIClient::RangedIO do
+ before do
+ @source = StringIO.new("1234567890abcdef")
+ @io = Google::APIClient::RangedIO.new(@source, 1, 5)
+ end
+
+ it 'should return the correct range when read entirely' do
+ @io.read.should == "23456"
+ end
+
+ it 'should maintain position' do
+ @io.read(1).should == '2'
+ @io.read(2).should == '34'
+ @io.read(2).should == '56'
+ end
+
+ it 'should allow rewinds' do
+ @io.read(2).should == '23'
+ @io.rewind()
+ @io.read(2).should == '23'
+ end
+
+ it 'should allow setting position' do
+ @io.pos = 3
+ @io.read.should == '56'
+ end
+
+ it 'should not allow position to be set beyond range' do
+ @io.pos = 10
+ @io.read.should == ''
+ end
+
+ it 'should return empty string when read amount is zero' do
+ @io.read(0).should == ''
+ end
+
+ it 'should return empty string at EOF if amount is nil' do
+ @io.read
+ @io.read.should == ''
+ end
+
+ it 'should return nil at EOF if amount is positive int' do
+ @io.read
+ @io.read(1).should == nil
+ end
+
+end
+
describe Google::APIClient::ResumableUpload do
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)