Upload a file to a key into a bucket on GS, using GS resumable upload
        protocol.
        
        :type key: :class:`boto.s3.key.Key` or subclass
        :param key: The Key object to which data is to be uploaded
        
        :type fp: file-like object
        :param fp: The file pointer to upload
        
        :type headers: dict
        :param headers: The headers to pass along with the PUT request
        
        :type cb: function
        :param cb: a callback function that will be called to report progress on
            the upload.  The callback should accept two integer parameters, the
            first representing the number of bytes that have been successfully
            transmitted to GS, and the second representing the total number of
            bytes that need to be transmitted.
                    
        :type num_cb: int
        :param num_cb: (optional) If a callback is specified with the cb
            parameter, this parameter determines the granularity of the callback
            by defining the maximum number of times the callback will be called
            during the file transfer. Providing a negative integer will cause
            your callback to be called with each buffer read.
             
        Raises ResumableUploadException if a problem occurs during the transfer.

def send_file(self, key, fp, headers, cb=None, num_cb=10):
        """
        Upload a file to a key into a bucket on GS, using GS resumable upload
        protocol.
        
        :type key: :class:`boto.s3.key.Key` or subclass
        :param key: The Key object to which data is to be uploaded
        
        :type fp: file-like object
        :param fp: The file pointer to upload
        
        :type headers: dict
        :param headers: The headers to pass along with the PUT request
        
        :type cb: function
        :param cb: a callback function that will be called to report progress on
            the upload.  The callback should accept two integer parameters, the
            first representing the number of bytes that have been successfully
            transmitted to GS, and the second representing the total number of
            bytes that need to be transmitted.
                    
        :type num_cb: int
        :param num_cb: (optional) If a callback is specified with the cb
            parameter, this parameter determines the granularity of the callback
            by defining the maximum number of times the callback will be called
            during the file transfer. Providing a negative integer will cause
            your callback to be called with each buffer read.
             
        Raises ResumableUploadException if a problem occurs during the transfer.
        """

        if not headers:
            headers = {}

        fp.seek(0, os.SEEK_END)
        file_length = fp.tell()
        fp.seek(0)
        debug = key.bucket.connection.debug

        # Use num-retries from constructor if one was provided; else check
        # for a value specified in the boto config file; else default to 5.
        if self.num_retries is None:
            self.num_retries = config.getint('Boto', 'num_retries', 5)
        progress_less_iterations = 0

        while True:  # Retry as long as we're making progress.
            server_had_bytes_before_attempt = self.server_has_bytes
            try:
                etag = self._attempt_resumable_upload(key, fp, file_length,
                                                      headers, cb, num_cb)
                # Upload succceded, so remove the tracker file (if have one).
                self._remove_tracker_file()
                self._check_final_md5(key, etag)
                if debug >= 1:
                    print 'Resumable upload complete.'
                return
            except self.RETRYABLE_EXCEPTIONS, e:
                if debug >= 1:
                    print('Caught exception (%s)' % e.__repr__())
                if isinstance(e, IOError) and e.errno == errno.EPIPE:
                    # Broken pipe error causes httplib to immediately
                    # close the socket (http://bugs.python.org/issue5542),
                    # so we need to close the connection before we resume
                    # the upload (which will cause a new connection to be
                    # opened the next time an HTTP request is sent).
                    key.bucket.connection.connection.close()
            except ResumableUploadException, e:
                if (e.disposition ==
                    ResumableTransferDisposition.ABORT_CUR_PROCESS):
                    if debug >= 1:
                        print('Caught non-retryable ResumableUploadException '
                              '(%s); aborting but retaining tracker file' %
                              e.message)
                    raise
                elif (e.disposition ==
                    ResumableTransferDisposition.ABORT):
                    if debug >= 1:
                        print('Caught non-retryable ResumableUploadException '
                              '(%s); aborting and removing tracker file' %
                              e.message)
                    self._remove_tracker_file()
                    raise
                else:
                    if debug >= 1:
                        print('Caught ResumableUploadException (%s) - will '
                              'retry' % e.message)

            # At this point we had a re-tryable failure; see if made progress.
            if self.server_has_bytes > server_had_bytes_before_attempt:
                progress_less_iterations = 0
            else:
                progress_less_iterations += 1

            if progress_less_iterations > self.num_retries:
                # Don't retry any longer in the current process.
                raise ResumableUploadException(
                    'Too many resumable upload attempts failed without '
                    'progress. You might try this upload again later',
                    ResumableTransferDisposition.ABORT_CUR_PROCESS)

            # Use binary exponential backoff to desynchronize client requests
            sleep_time_secs = random.random() * (2**progress_less_iterations)
            if debug >= 1:
                print ('Got retryable failure (%d progress-less in a row).\n'
                       'Sleeping %3.1f seconds before re-trying' %
                       (progress_less_iterations, sleep_time_secs))
            time.sleep(sleep_time_secs)