class Curl::Multi

def add(easy)

def add(easy)
  return self if requests[easy.object_id]
  requests[easy.object_id] = easy
  _add(easy)
  self
end

def cancel!

def cancel!
  requests.each do |_,easy|
    remove(easy)
  end
end

def close

def close
  requests.values.each {|easy|
    _remove(easy)
  }
  @requests = {}
  _close
  self
end

def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)


when using the :post or :put method, urls should be a hash, including the individual post fields per post

2 files will be opened, and remain open until the call completes

will create 2 new files file1.txt and file2.txt

Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}

call-seq:
def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)
  errors = []
  procs = []
  files = []
  urls_with_config = []
  url_to_download_paths = {}
  urls.each_with_index do|urlcfg,i|
    if urlcfg.is_a?(Hash)
      url = url[:url]
    else
      url = urlcfg
    end
    if download_paths and download_paths[i]
      download_path = download_paths[i]
    else
      download_path = File.basename(url)
    end
    file = lambda do|dp|
      file = File.open(dp,"wb")
      procs << (lambda {|data| file.write data; data.size })
      files << file
      file
    end.call(download_path)
    if urlcfg.is_a?(Hash)
      urls_with_config << urlcfg.merge({:on_body => procs.last}.merge(easy_options))
    else
      urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options)
    end
    url_to_download_paths[url] = {:path => download_path, :file => file} # store for later
  end
  if blk
    # when injecting the block, ensure file is closed before yielding
    Curl::Multi.http(urls_with_config, multi_options) do |c,code,method|
      info = url_to_download_paths[c.url]
      begin
        file = info[:file]
        files.reject!{|f| f == file }
        file.close
      rescue => e
        errors << e
      end
      blk.call(c,info[:path])
    end
  else
    Curl::Multi.http(urls_with_config, multi_options)
  end
ensure
  files.each {|f|
    begin
      f.close
    rescue => e
      errors << e
    end
  }
  if errors.any?
    de = Curl::Multi::DownloadError.new
    de.errors = errors
    raise de
  end
end

def get(urls, easy_options={}, multi_options={}, &blk)

Blocking call to fetch multiple url's in parallel.

end
easy
Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy|
call-seq:
def get(urls, easy_options={}, multi_options={}, &blk)
  url_confs = []
  urls.each do|url|
    url_confs << {:url => url, :method => :get}.merge(easy_options)
  end
  self.http(url_confs, multi_options) {|c,code,method| blk.call(c) if blk }
end

def http(urls_with_config, multi_options={}, &blk)


blk: a callback, that yeilds when a handle is completed
multi_options: options for the multi handle
urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used

Blocking call to issue multiple HTTP requests with varying verb's.

], {:pipeline => Curl::CURLPIPE_HTTP1})
{ :url => 'url4', :method => :head }
{ :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
:follow_location => true, :max_redirects => 3 },
{ :url => 'url2', :method => :get,
:post_fields => {'field1' => 'value1', 'field2' => 'value2'} },
{ :url => 'url1', :method => :post,
Curl::Multi.http( [

call-seq:
def http(urls_with_config, multi_options={}, &blk)
  m = Curl::Multi.new
  # maintain a sane number of easy handles
  multi_options[:max_connects] = max_connects = multi_options.key?(:max_connects) ? multi_options[:max_connects] : 10
  free_handles = [] # keep a list of free easy handles
  # configure the multi handle
  multi_options.each { |k,v| m.send("#{k}=", v) }
  callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_body,:on_header]
  add_free_handle = proc do|conf, easy|
    c       = conf.dup # avoid being destructive to input
    url     = c.delete(:url)
    method  = c.delete(:method)
    headers = c.delete(:headers)
    easy    = Curl::Easy.new if easy.nil?
    easy.url = url
    # assign callbacks
    callbacks.each do |cb|
      cbproc = c.delete(cb)
      easy.send(cb,&cbproc) if cbproc
    end
    case method
    when :post
      fields = c.delete(:post_fields)
      # set the post post using the url fields
      easy.post_body = fields.map{|f,k| "#{easy.escape(f)}=#{easy.escape(k)}"}.join('&')
    when :put
      easy.put_data = c.delete(:put_data)
    when :head
      easy.head = true
    when :delete
      easy.delete = true
    when :get
    else
      # XXX: nil is treated like a GET
    end
    # headers is a special key
    headers.each {|k,v| easy.headers[k] = v } if headers
    #
    # use the remaining options as specific configuration to the easy handle
    # bad options should raise an undefined method error
    #
    c.each { |k,v| easy.send("#{k}=",v) }
    easy.on_complete {|curl|
      free_handles << curl
      blk.call(curl,curl.response_code,method) if blk
    }
    m.add(easy)
  end
  max_connects.times do
    conf = urls_with_config.pop
    add_free_handle.call(conf, nil) if conf
    break if urls_with_config.empty?
  end
  consume_free_handles = proc do
    # as we idle consume free handles
    if urls_with_config.size > 0 && free_handles.size > 0
      easy = free_handles.pop
      conf = urls_with_config.pop
      add_free_handle.call(conf, easy) if conf
    end
  end
  if urls_with_config.empty?
    m.perform
  else
    until urls_with_config.empty?
      m.perform do
        consume_free_handles.call
      end
      consume_free_handles.call
    end
    free_handles = nil
  end
end

def idle?

def idle?
  requests.empty?
end

def post(urls_with_config, easy_options={}, multi_options={}, &blk)


multi_options: options to set on the Curl::Multi handle
easy_options: are a set of common options to set on all easy handles
urls_with_config: is a hash of url's pointing to the postfields to send

Blocking call to POST multiple form's in parallel.

end
easy_handle_on_request_complete
{:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
{ :follow_location => true, :multipart_form_post => true },
{:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
{:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
Curl::Multi.post([{:url => 'url1', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},

call-seq:
def post(urls_with_config, easy_options={}, multi_options={}, &blk)
  url_confs = []
  urls_with_config.each do|uconf|
    url_confs << uconf.merge(:method => :post).merge(easy_options)
  end
  self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
end

def put(urls_with_config, easy_options={}, multi_options={}, &blk)


multi_options: options to set on the Curl::Multi handle
easy_options: are a set of common options to set on all easy handles
urls_with_config: is a hash of url's pointing to the postfields to send

Blocking call to POST multiple form's in parallel.

end
easy_handle_on_request_complete
{:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
{:follow_location => true},
{:url => 'url3', :put_data => "maybe another string or socket?"],
{:url => 'url2', :put_data => IO.read('filepath')},
Curl::Multi.put([{:url => 'url1', :put_data => "some message"},

call-seq:
def put(urls_with_config, easy_options={}, multi_options={}, &blk)
  url_confs = []
  urls_with_config.each do|uconf|
    url_confs << uconf.merge(:method => :put).merge(easy_options)
  end
  self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
end

def remove(easy)

def remove(easy)
  return self if !requests[easy.object_id]
  requests.delete(easy.object_id)
  _remove(easy)
  self
end

def requests

def requests
  @requests ||= {}
end