Return a Flo object that can be written to to send data to S3.
        This will result in a multi-part upload, possibly with each part
        being sent in its own thread

def put_stream(self, rel_path, metadata=None, cb=None):
        '''Return a Flo object that can be written to to send data to S3.
        This will result in a multi-part upload, possibly with each part
        being sent in its own thread '''

        import Queue
        import time
        import threading

        md5 = metadata.get('md5', None) if metadata else None

        # Horrible, but doing it anyway because I can.
        acl = ('public-read' if metadata.get('public',
                                             False) else metadata.get('acl',
                                                                      'public-read')) if metadata else 'public-read'

        path = self._prefix(self._rename(rel_path))

        class ThreadUploader(threading.Thread):

            """Thread class for uploading a part to S3"""

            def __init__(self, n, queue):
                threading.Thread.__init__(self)
                self.n = n
                self.queue = queue

            def run(self):

                while True:
                    mp, part_number, buf = self.queue.get()
                    if mp is None:  # Signal to die
                        logger.debug(
                            "put_stream: Thread {} exiting".format(
                                self.n))
                        self.queue.task_done()
                        return
                    logger.debug(
                        "put_stream: Thread {}: processing part: {}".format(
                            self.n,
                            part_number))
                    t1 = time.time()
                    try:
                        mp.upload_part_from_file(buf, part_number)
                    finally:
                        self.queue.task_done()
                        t2 = time.time()
                        logger.debug("put_stream: Thread {}, part {}. time = {} rate =  {} b/s" .format(
                            self.n, part_number, round(t2 - t1, 3), round((float(buf.tell()) / (t2 - t1)), 2)))

        if metadata is None:
            metadata = {}

        if md5:
            metadata['md5'] = md5  # Multipart uploads don't use md5 for etag

        # Some libraries, including apparently, boto, have troubles properly calcing the hash when
        # metadata values are null, empty, 0, etc.
        # https://forums.aws.amazon.com/thread.jspa?threadID=117580
        for k, v in metadata.items():
            if not v:
                del metadata[k]

        this = self

        buffer_size = 50 * 1024 * 1024  # Min part size is 5MB
        num_threads = 4
        thread_upload_queue = Queue.Queue(maxsize=100)

        for i in range(num_threads):
            t = ThreadUploader(i, thread_upload_queue)
            t.setDaemon(True)
            t.start()

        class flo:

            '''Object that is returned to the caller, for the caller to issue
            write() or writeline() calls on '''

            def __init__(self, rel_path):
                import io

                self.mp = this.bucket.initiate_multipart_upload(
                    path,
                    metadata=metadata)
                self.part_number = 1
                self.buffer = io.BytesIO()
                self.total_size = 0

                self.rel_path = rel_path

            def _send_buffer(self):
                '''Schedules a buffer to be sent in a thread by queuing it'''
                logger.debug(
                    "_send_buffer: sending part {} to thread pool size: {}, total_size = {}" .format(
                        self.part_number,
                        self.buffer.tell(),
                        self.total_size))
                self.buffer.seek(0)
                thread_upload_queue.put(
                    (self.mp, self.part_number, self.buffer))

            def write(self, d):
                import io

                self.buffer.write(d)  # Load the requested data into a buffer
                self.total_size += len(d)
                # After the buffer is large enough, send it, then create a new
                # buffer.
                if self.buffer.tell() > buffer_size:
                    self._send_buffer()

                    self.part_number += 1
                    self.buffer = io.BytesIO()

            def writelines(self, lines):
                raise NotImplemented()

            def close(self):

                if self.buffer.tell() > 0:
                    self._send_buffer()

                # Wait for all of th upload to complete
                thread_upload_queue.join()

                for i in range(num_threads):
                    thread_upload_queue.put(
                        (None, None, None))  # Tell all of the threads to die

                # Wait for all of the threads to exit
                thread_upload_queue.join()

                # Multi-part uploads throw a 400 Bad Request if they are
                # completed without writing any data.
                if self.total_size > 0:
                    self.mp.complete_upload()

                    this.bucket.set_acl(acl, path)

                this.put_metadata(self.rel_path, metadata)

            def __enter__(self):
                return self

            def __exit__(self, type_, value, traceback):
                if type_:
                    return False

                self.close()

        return flo(rel_path)