# -*- encoding: binary -*-
# :enddoc:
# non-portable body response stuff goes here
#
# The sendfile 1.0.0 RubyGem includes IO#sendfile and
# IO#sendfile_nonblock.   Previous versions of "sendfile" didn't have
# IO#sendfile_nonblock, and IO#sendfile in previous versions could
# block other threads under 1.8 with large files
#
# IO#sendfile currently (June 2010) beats 1.9 IO.copy_stream with
# non-Linux support and large files on 32-bit.  We still fall back to
# IO.copy_stream (if available) if we're dealing with DevFdResponse
# objects, though.
#
# Linux-only splice(2) support via the "io_splice" gem will eventually
# be added for streaming sockets/pipes, too.
#
# * write_body_file - regular files (sendfile or pread+write)
# * write_body_stream - socket/pipes (read+write, splice later)
# * write_body_each - generic fallback
#
# callgraph is as follows:
#
#         write_body
#         `- write_body_each
#         `- write_body_path
#            `- write_body_file
#            `- write_body_stream
#
module Rainbows::Response::Body # :nodoc:
  ALIASES = {}

  FD_MAP = Rainbows::FD_MAP

  def io_for_fd(fd)
    FD_MAP.delete(fd) || IO.new(fd)
  end

  # to_io is not part of the Rack spec, but make an exception here
  # since we can conserve path lookups and file descriptors.
  # \Rainbows! will never get here without checking for the existence
  # of body.to_path first.
  def body_to_io(body)
    if body.respond_to?(:to_io)
      body.to_io
    else
      # try to take advantage of Rainbows::DevFdResponse, calling File.open
      # is a last resort
      path = body.to_path
      path =~ %r{\A/dev/fd/(\d+)\z} ? io_for_fd($1.to_i) : File.open(path)
    end
  end

  if IO.method_defined?(:sendfile_nonblock)
    def write_body_file(sock, body, range)
      range ? sock.sendfile(body, range[0], range[1]) : sock.sendfile(body, 0)
    end
  end

  if IO.respond_to?(:copy_stream)
    unless method_defined?(:write_body_file)
      # try to use sendfile() via IO.copy_stream, otherwise pread()+write()
      def write_body_file(sock, body, range)
        range ? IO.copy_stream(body, sock, range[1], range[0]) :
                IO.copy_stream(body, sock, nil, 0)
      end
    end

    # only used when body is a pipe or socket that can't handle
    # pread() semantics
    def write_body_stream(sock, body, range)
      IO.copy_stream(body, sock)
      ensure
        body.respond_to?(:close) and body.close
    end
  else
    # fall back to body#each, which is a Rack standard
    ALIASES[:write_body_stream] = :write_body_each
  end

  if method_defined?(:write_body_file)

    # middlewares/apps may return with a body that responds to +to_path+
    def write_body_path(sock, body, range)
      inp = body_to_io(body)
      if inp.stat.file?
        begin
          write_body_file(sock, inp, range)
        ensure
          inp.close if inp != body
        end
      else
        write_body_stream(sock, inp, range)
      end
      ensure
        body.respond_to?(:close) && inp != body and body.close
    end
  elsif method_defined?(:write_body_stream)
    def write_body_path(sock, body, range)
      write_body_stream(sock, inp = body_to_io(body), range)
      ensure
        body.respond_to?(:close) && inp != body and body.close
    end
  end

  if method_defined?(:write_body_path)
    def write_body(client, body, range)
      body.respond_to?(:to_path) ?
        write_body_path(client, body, range) :
        write_body_each(client, body, range)
    end
  else
    ALIASES[:write_body] = :write_body_each
  end

  # generic body writer, used for most dynamically generated responses
  def write_body_each(socket, body, range = nil)
    body.each { |chunk| socket.write(chunk) }
    ensure
      body.respond_to?(:close) and body.close
  end

  def self.included(klass)
    ALIASES.each do |new_method, orig_method|
      klass.__send__(:alias_method, new_method, orig_method)
    end
  end
end
