Add _media methods and support for resumable media download.

TBR: http://codereview.appspot.com/6295077/
diff --git a/apiclient/http.py b/apiclient/http.py
index 022bdce..c3ef13b 100644
--- a/apiclient/http.py
+++ b/apiclient/http.py
@@ -76,6 +76,32 @@
       return 0.0
 
 
+class MediaDownloadProgress(object):
+  """Status of a resumable download."""
+
+  def __init__(self, resumable_progress, total_size):
+    """Constructor.
+
+    Args:
+      resumable_progress: int, bytes received so far.
+      total_size: int, total bytes in complete download.
+    """
+    self.resumable_progress = resumable_progress
+    self.total_size = total_size
+
+  def progress(self):
+    """Percent of download completed, as a float.
+
+    Returns:
+      the percentage complete as a float, returning 0.0 if the total size of
+      the download is unknown.
+    """
+    if self.total_size is not None:
+      return float(self.resumable_progress) / float(self.total_size)
+    else:
+      return 0.0
+
+
 class MediaUpload(object):
   """Describes a media object to upload.
 
@@ -268,7 +294,7 @@
     return self._fd.read(length)
 
   def to_json(self):
-    """Creating a JSON representation of an instance of Credentials.
+    """Creating a JSON representation of an instance of MediaFileUpload.
 
     Returns:
        string, a JSON representation of this instance, suitable to pass to
@@ -472,6 +498,86 @@
                                d['_resumable'])
 
 
+class MediaIoBaseDownload(object):
+  """"Download media resources.
+
+  Note that the Python file object is compatible with io.Base and can be used
+  with this class also.
+
+
+  Example:
+    request = service.objects().get_media(
+        bucket='a_bucket_id',
+        name='smiley.png')
+
+    fh = io.FileIO('image.png', mode='wb')
+    downloader = MediaIoBaseDownload(fh, request, chunksize=1024*1024)
+
+    done = False
+    while done is False:
+      status, done = downloader.next_chunk()
+      if status:
+        print "Download %d%%." % int(status.progress() * 100)
+    print "Download Complete!"
+  """
+
+  def __init__(self, fh, request, chunksize=DEFAULT_CHUNK_SIZE):
+    """Constructor.
+
+    Args:
+      fh: io.Base or file object, The stream in which to write the downloaded
+        bytes.
+      request: apiclient.http.HttpRequest, the media request to perform in
+        chunks.
+      chunksize: int, File will be downloaded in chunks of this many bytes.
+    """
+    self.fh_ = fh
+    self.request_ = request
+    self.uri_ = request.uri
+    self.chunksize_ = chunksize
+    self.progress_ = 0
+    self.total_size_ = None
+    self.done_ = False
+
+  def next_chunk(self):
+    """Get the next chunk of the download.
+
+    Returns:
+      (status, done): (MediaDownloadStatus, boolean)
+         The value of 'done' will be True when the media has been fully
+         downloaded.
+
+    Raises:
+      apiclient.errors.HttpError if the response was not a 2xx.
+      httplib2.Error if a transport error has occured.
+    """
+    headers = {
+        'range': 'bytes=%d-%d' % (
+            self.progress_, self.progress_ + self.chunksize_)
+        }
+    http = self.request_.http
+    http.follow_redirects = False
+
+    resp, content = http.request(self.uri_, headers=headers)
+    if resp.status in [301, 302, 303, 307, 308] and 'location' in resp:
+        self.uri_ = resp['location']
+        resp, content = http.request(self.uri_, headers=headers)
+    if resp.status in [200, 206]:
+      self.progress_ += len(content)
+      self.fh_.write(content)
+
+      if 'content-range' in resp:
+        content_range = resp['content-range']
+        length = content_range.rsplit('/', 1)[1]
+        self.total_size_ = int(length)
+
+      if self.progress_ == self.total_size_:
+        self.done_ = True
+      return MediaDownloadProgress(self.progress_, self.total_size_), self.done_
+    else:
+      raise HttpError(resp, content, self.uri_)
+
+
 class HttpRequest(object):
   """Encapsulates a single HTTP request."""
 
@@ -1219,6 +1325,7 @@
       iterable: iterable, a sequence of pairs of (headers, body)
     """
     self._iterable = iterable
+    self.follow_redirects = True
 
   def request(self, uri,
               method='GET',