Better handling of streams that report their size as 0.

Reviewed in http://codereview.appspot.com/6300093/.

Fixes issue #146.
diff --git a/apiclient/http.py b/apiclient/http.py
index 9155cab..022bdce 100644
--- a/apiclient/http.py
+++ b/apiclient/http.py
@@ -289,7 +289,6 @@
   Note that the Python file object is compatible with io.Base and can be used
   with this class also.
 
-
     fh = io.BytesIO('...Some data to upload...')
     media = MediaIoBaseUpload(fh, mimetype='image/png',
       chunksize=1024*1024, resumable=True)
@@ -304,7 +303,8 @@
     """Constructor.
 
     Args:
-      fh: io.Base or file object, The source of the bytes to upload.
+      fh: io.Base or file object, The source of the bytes to upload. MUST be
+        opened in blocking mode, do not use streams opened in non-blocking mode.
       mimetype: string, Mime-type of the file. If None then a mime-type will be
         guessed from the file extension.
       chunksize: int, File will be uploaded in chunks of this many bytes. Only
@@ -320,7 +320,11 @@
     try:
       if hasattr(self._fh, 'fileno'):
         fileno = self._fh.fileno()
-        self._size = os.fstat(fileno).st_size
+
+        # Pipes and such show up as 0 length files.
+        size = os.fstat(fileno).st_size
+        if size:
+          self._size = os.fstat(fileno).st_size
     except IOError:
       pass
 
@@ -616,6 +620,11 @@
 
     data = self.resumable.getbytes(
         self.resumable_progress, self.resumable.chunksize())
+
+    # A short read implies that we are at EOF, so finish the upload.
+    if len(data) < self.resumable.chunksize():
+      size = str(self.resumable_progress + len(data))
+
     headers = {
         'Content-Range': 'bytes %d-%d/%s' % (
             self.resumable_progress, self.resumable_progress + len(data) - 1,
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 91db712..bb77154 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -560,12 +560,32 @@
 
     # Create an upload that doesn't know the full size of the media.
     upload = MediaIoBaseUpload(
-        fh=fh, mimetype='image/png', chunksize=500, resumable=True)
+        fh=fh, mimetype='image/png', chunksize=10, resumable=True)
 
     request = zoo.animals().insert(media_body=upload, body=None)
     status, body = request.next_chunk(http)
-    self.assertEqual(body, {'Content-Range': 'bytes 0-13/*'},
-      'Should be 14 out of * bytes.')
+    self.assertEqual(body, {'Content-Range': 'bytes 0-9/*'},
+      'Should be 10 out of * bytes.')
+
+  def test_resumable_media_handle_uploads_of_unknown_size_eof(self):
+    http = HttpMockSequence([
+      ({'status': '200',
+        'location': 'http://upload.example.com'}, ''),
+      ({'status': '200'}, 'echo_request_headers_as_json'),
+      ])
+
+    self.http = HttpMock(datafile('zoo.json'), {'status': '200'})
+    zoo = build('zoo', 'v1', self.http)
+
+    fh = StringIO.StringIO('data goes here')
+
+    # Create an upload that doesn't know the full size of the media.
+    upload = MediaIoBaseUpload(
+        fh=fh, mimetype='image/png', chunksize=15, resumable=True)
+
+    request = zoo.animals().insert(media_body=upload, body=None)
+    status, body = request.next_chunk(http)
+    self.assertEqual(body, {'Content-Range': 'bytes 0-13/14'})
 
   def test_resumable_media_handle_resume_of_upload_of_unknown_size(self):
     http = HttpMockSequence([