class QueueFile(object):
  """ Class which implements a file-like interface and reads QueueResult's from a blocking
      multiprocessing queue.
  """
  def __init__(self, queue, name=None):
    self._queue = queue
    self._closed = False
    self._done = False
    self._buffer = ''
    self._total_size = 0
    self._name = name
    self.raised_exception = False
    self._exception_handlers = []

  def add_exception_handler(self, handler):
    self._exception_handlers.append(handler)

  def read(self, size=-1):
    # If the queuefile was closed or we have finished, send back any remaining data.
    if self._closed or self._done:
      if size == -1:
        buf = self._buffer
        self._buffer = ''
        return buf

      buf = self._buffer[0:size]
      self._buffer = self._buffer[size:]
      return buf

    # Loop until we reach the requested data size (or forever if all data was requested).
    while (len(self._buffer) < size) or (size == -1):
      result = self._queue.get(block=True)

      # Check for any exceptions raised by the queue process.
      if result.exception is not None:
        self._closed = True
        self.raised_exception = True

        # Fire off the exception to any registered handlers. If no handlers were registered,
        # then raise the exception locally.
        handled = False
        for handler in self._exception_handlers:
          handler(result.exception)
          handled = True

        if handled:
          return ''
        else:
          raise result.exception

      # Check for no further data. If the QueueProcess has finished producing data, then break
      # out of the loop to return the data already acquired.
      if result.data is None:
        self._done = True
        break

      # Add the data to the buffer.
      self._buffer += result.data
      self._total_size += len(result.data)

    # Return the requested slice of the buffer.
    if size == -1:
      buf = self._buffer
      self._buffer = ''
      return buf

    buf = self._buffer[0:size]
    self._buffer = self._buffer[size:]
    return buf

  def flush(self):
    # Purposefully not implemented.
    pass

  def close(self):
    self._closed = True