No longer buffer chunks/files when using resumable upload
authorSteven Bazyl <sqrrrl@gmail.com>
Tue, 18 Jun 2013 19:15:02 +0000 (12:15 -0700)
committerSteven Bazyl <sqrrrl@gmail.com>
Tue, 18 Jun 2013 19:15:02 +0000 (12:15 -0700)
lib/google/api_client/media.rb
lib/google/api_client/request.rb
spec/google/api_client/media_spec.rb

index 45726cdedab28d2daebcf69e4e412cf5ab02577d..5066bcebdfa1ba747cb4b3d8d966b6f6f3ee8979 100644 (file)
@@ -21,7 +21,11 @@ module Google
     # @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
       #
@@ -32,6 +36,77 @@ module Google
       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.
     #
@@ -124,11 +199,11 @@ module Google
             '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, 
index 71cfa0b4805bab7402cc9008221e205181f18158..5dc4d91a620364dcead9f97d79d05e97efe8044b 100644 (file)
@@ -165,7 +165,7 @@ module Google
         # Resumamble slightly different than other upload protocols in that it requires at least
         # 2 requests.
         if result.status == 200 && self.upload_type == 'resumable'
-          upload =  result.resumable_upload
+          upload = result.resumable_upload
           unless upload.complete?
             logger.debug { "#{self.class} Sending upload body" }
             result = upload.send(connection)
index ed70ec309fd6dda486b43bb90b47980142f075fa..0123443cfc7ef4ad00721de531473b97bbfabb83 100644 (file)
@@ -57,6 +57,54 @@ describe Google::APIClient::UploadIO do
   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)