<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -7,6 +7,7 @@
 #include &quot;curb_easy.h&quot;
 #include &quot;curb_errors.h&quot;
 #include &quot;curb_postfield.h&quot;
+#include &quot;curb_upload.h&quot;
 
 #include &lt;errno.h&gt;
 #include &lt;string.h&gt;
@@ -34,20 +35,56 @@ static size_t default_data_handler(char *stream,
   rb_str_buf_cat(out, stream, size * nmemb);
   return size * nmemb;
 }
-typedef struct _PutStream {
-  char *buffer;
-  size_t offset, len;
-} PutStream;
 
 // size_t function( void *ptr, size_t size, size_t nmemb, void *stream);
 static size_t read_data_handler(void *ptr,
                                 size_t size, 
                                 size_t nmemb, 
-                                void *stream) {
+                                ruby_curl_easy *rbce) {
+  size_t read_bytes = (size*nmemb);
+  VALUE stream = ruby_curl_upload_stream_get(rbce-&gt;upload);
+
+  if (rb_respond_to(stream, rb_intern(&quot;read&quot;))) {//if (rb_respond_to(stream, rb_intern(&quot;to_s&quot;))) {
+    /* copy read_bytes from stream into ptr */
+    VALUE str = rb_funcall(stream, rb_intern(&quot;read&quot;), 1, rb_int_new(read_bytes) );
+    if( str != Qnil ) {
+      memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
+      return RSTRING_LEN(str);
+    }
+    else {
+      return 0;
+    }
+  }
+  else {
+    ruby_curl_upload *rbcu;
+    Data_Get_Struct(rbce-&gt;upload, ruby_curl_upload, rbcu);
+    VALUE str = rb_funcall(stream, rb_intern(&quot;to_s&quot;), 0);
+    size_t len = RSTRING_LEN(str);
+    size_t remaining = len - rbcu-&gt;offset;
+    char *str_ptr = RSTRING_PTR(str);
+    if( remaining &lt; read_bytes ) {
+      if( remaining &gt; 0 ) {
+        memcpy(ptr, str_ptr+rbcu-&gt;offset, remaining);
+        read_bytes = remaining;
+        rbcu-&gt;offset += remaining;
+      }
+      return remaining;
+    }
+    else if( remaining &gt; read_bytes ) { // read_bytes &lt;= remaining - send what we can fit in the buffer(ptr)
+      memcpy(ptr, str_ptr+rbcu-&gt;offset, read_bytes);
+      rbcu-&gt;offset += read_bytes;
+    }
+    else { // they're equal
+      memcpy(ptr, str_ptr+rbcu-&gt;offset, --read_bytes);
+      rbcu-&gt;offset += read_bytes;
+    }
+    return read_bytes;
+  }
 
-  PutStream *pstream = (PutStream*)stream;
-  size_t sent_bytes = (size * nmemb);
-  size_t remaining = pstream-&gt;len - pstream-&gt;offset;
+ // PutStream *pstream = (PutStream*)stream;
+ // size_t sent_bytes = (size * nmemb);
+ // size_t remaining = pstream-&gt;len - pstream-&gt;offset;
+ /*
 
   // amount remaining is less then the buffer to send  - can send it all
   if( remaining &lt; sent_bytes ) {
@@ -66,9 +103,10 @@ static size_t read_data_handler(void *ptr,
   if (sent_bytes == 0) {
     free(pstream);
   }
+  */
 
   //printf(&quot;sent_bytes: %ld of %ld\n&quot;, sent_bytes, remaining);
-  return sent_bytes;
+  //return sent_bytes;
 }
 
 static size_t proc_data_handler(char *stream, 
@@ -144,6 +182,10 @@ void curl_easy_mark(ruby_curl_easy *rbce) {
   if( rbce-&gt;self != Qnil ) {
     rb_gc_mark(rbce-&gt;self);
   }
+
+  if( rbce-&gt;upload != Qnil ) {
+    rb_gc_mark(rbce-&gt;upload);
+  }
 }
 
 void curl_easy_free(ruby_curl_easy *rbce) {
@@ -234,8 +276,9 @@ static VALUE ruby_curl_easy_new(int argc, VALUE *argv, VALUE klass) {
   rbce-&gt;curl_headers = NULL;
 
   rbce-&gt;self = Qnil;
+  rbce-&gt;upload = Qnil;
   
-  new_curl = Data_Wrap_Struct(cCurlEasy, curl_easy_mark, curl_easy_free, rbce);
+  new_curl = Data_Wrap_Struct(klass, curl_easy_mark, curl_easy_free, rbce);
   
   if (blk != Qnil) {
     rb_funcall(blk, idCall, 1, new_curl);
@@ -1532,6 +1575,10 @@ static VALUE handle_perform(VALUE self, ruby_curl_easy *rbce) {
   CURLM *multi_handle = curl_multi_init();
   struct curl_slist *headers = NULL;
   VALUE bodybuf = Qnil, headerbuf = Qnil;
+  long timeout;
+  struct timeval tv = {0, 0};
+  int rc; /* select() return code */
+  int maxfd;
 //  char errors[CURL_ERROR_SIZE*2];
 
   ruby_curl_easy_setup(rbce, &amp;bodybuf, &amp;headerbuf, &amp;headers);
@@ -1558,10 +1605,8 @@ static VALUE handle_perform(VALUE self, ruby_curl_easy *rbce) {
       raise_curl_multi_error_exception(mcode);
     }
 
+
     while(still_running) {
-      struct timeval timeout;
-      int rc; /* select() return code */
-      int maxfd;
 
       fd_set fdread;
       fd_set fdwrite;
@@ -1578,10 +1623,29 @@ static VALUE handle_perform(VALUE self, ruby_curl_easy *rbce) {
         raise_curl_multi_error_exception(mcode);
       }
 
+#ifdef HAVE_CURL_MULTI_TIMEOUT 
+      /* get the curl suggested time out */
+      mcode = curl_multi_timeout(multi_handle, &amp;timeout);
+      if (mcode != CURLM_OK) {
+        raise_curl_multi_error_exception(mcode);
+      }
+#else
+      /* libcurl doesn't have a timeout method defined... make a wild guess */
+      timeout = 1; /* wait a second */
+#endif
+
+      if (timeout == 0) { /* no delay */
+        while(CURLM_CALL_MULTI_PERFORM == (mcode=curl_multi_perform(multi_handle, &amp;still_running)) );
+        continue;
+      }
+      else if (timeout == -1) {
+        timeout = 1; /* wait a second */
+      }
+
       /* set a suitable timeout to play around with - ruby seems to be greedy about this and won't necessarily yield so the timeout is small.. */
-      timeout.tv_sec = 0.0;
-      timeout.tv_usec = 100000.0;
-      rc = rb_thread_select(maxfd+1, &amp;fdread, &amp;fdwrite, &amp;fdexcep, &amp;timeout);
+      tv.tv_sec = timeout / 1000;
+      tv.tv_usec = (timeout * 1000) % 1000000;
+      rc = rb_thread_select(maxfd+1, &amp;fdread, &amp;fdwrite, &amp;fdexcep, &amp;tv);
       if (rc &lt; 0) {
         rb_raise(rb_eRuntimeError, &quot;select(): %s&quot;, strerror(errno));
       }
@@ -1840,21 +1904,38 @@ static VALUE ruby_curl_easy_perform_put(VALUE self, VALUE data) {
   ruby_curl_easy *rbce;
   CURL *curl;
 
-  PutStream *pstream = (PutStream*)malloc(sizeof(PutStream));
-  memset(pstream, 0, sizeof(PutStream));
-
   Data_Get_Struct(self, ruby_curl_easy, rbce);
-  curl = rbce-&gt;curl;
 
-  pstream-&gt;len = RSTRING_LEN(data);
-  pstream-&gt;buffer = StringValuePtr(data);
+  VALUE upload = ruby_curl_upload_new(cCurlUpload);
+  ruby_curl_upload_stream_set(upload,data);
 
+  curl = rbce-&gt;curl;
+  rbce-&gt;upload = upload; /* keep the upload object alive as long as
+                            the easy handle is active or until the upload
+                            is complete or terminated... */
 
   curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
   curl_easy_setopt(curl, CURLOPT_READFUNCTION, (curl_read_callback)read_data_handler);
-  curl_easy_setopt(curl, CURLOPT_READDATA, pstream);
-  //printf(&quot;uploading %d bytes\n&quot;, pstream-&gt;len);
-  curl_easy_setopt(curl, CURLOPT_INFILESIZE, pstream-&gt;len);
+  curl_easy_setopt(curl, CURLOPT_READDATA, rbce);
+
+  if (rb_respond_to(data, rb_intern(&quot;read&quot;))) {
+    VALUE stat = rb_funcall(data, rb_intern(&quot;stat&quot;), 0);
+    if( stat ) {
+      ruby_curl_easy_headers_set(self,rb_str_new2(&quot;Expect:&quot;)); 
+      VALUE size = rb_funcall(stat, rb_intern(&quot;size&quot;), 0);
+      curl_easy_setopt(curl, CURLOPT_INFILESIZE, FIX2INT(size));
+    }
+    else {
+      ruby_curl_easy_headers_set(self,rb_str_new2(&quot;Transfer-Encoding: chunked&quot;)); 
+    }
+  }
+  else if (rb_respond_to(data, rb_intern(&quot;to_s&quot;))) {
+    curl_easy_setopt(curl, CURLOPT_INFILESIZE, RSTRING_LEN(data));
+    ruby_curl_easy_headers_set(self,rb_str_new2(&quot;Expect:&quot;)); 
+  }
+  else {
+    rb_raise(rb_eRuntimeError, &quot;PUT data must respond to read or to_s&quot;);
+  }
 
   VALUE ret = handle_perform(self, rbce);
   /* cleanup  */</diff>
      <filename>ext/curb_easy.c</filename>
    </modified>
    <modified>
      <diff>@@ -83,6 +83,7 @@ typedef struct {
   struct curl_slist *curl_headers;
 
   VALUE self; /* pointer to self, used by multi interface */
+  VALUE upload; /* pointer to an active upload otherwise Qnil */
 
 } ruby_curl_easy;
 </diff>
      <filename>ext/curb_easy.h</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), 'helper')
 
 class TestCurbCurlEasy &lt; Test::Unit::TestCase
   def test_class_perform_01   
-    assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL)    
+    assert_instance_of Curl::Easy, c = Curl::Easy.perform($TEST_URL)
     assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
     assert_equal &quot;&quot;, c.header_str
   end    
@@ -22,6 +22,7 @@ class TestCurbCurlEasy &lt; Test::Unit::TestCase
   
   def test_new_01
     c = Curl::Easy.new
+    assert_equal Curl::Easy, c.class
     assert_nil c.url
     assert_nil c.body_str
     assert_nil c.header_str
@@ -54,8 +55,19 @@ class TestCurbCurlEasy &lt; Test::Unit::TestCase
     assert_equal $TEST_URL, c.url
     assert_equal blk, c.on_body   # sets handler nil, returns old handler
     assert_equal nil, c.on_body
-  end    
-  
+  end
+
+  class Foo &lt; Curl::Easy
+  end
+  def test_new_05
+    # can use Curl::Easy as a base class
+    c = Foo.new
+    assert_equal Foo, c.class
+    c.url = $TEST_URL
+    c.perform
+    assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
+  end
+
   def test_escape
     c = Curl::Easy.new
     
@@ -525,6 +537,14 @@ class TestCurbCurlEasy &lt; Test::Unit::TestCase
     assert_match /message$/, curl.body_str
   end
 
+  def test_put_remote_file
+    curl = Curl::Easy.new(TestServlet.url)
+    File.open(__FILE__,'r') do|f|
+      assert curl.http_put(f)
+    end
+    assert_equal &quot;PUT\n#{File.read(__FILE__)}&quot;, curl.body_str
+  end
+
   include TestServerMethods 
 
   def setup</diff>
      <filename>tests/tc_curl_easy.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>ad7fa23ea31343ea6b5fc727c57b4da1c5b3b8b2</id>
    </parent>
  </parents>
  <author>
    <name>Todd A. Fisher</name>
    <email>taf2@web3.anerian.com</email>
  </author>
  <url>http://github.com/taf2/curb/commit/3da753ddec639c7cb79cca1fdab6e749e1303708</url>
  <id>3da753ddec639c7cb79cca1fdab6e749e1303708</id>
  <committed-date>2009-06-19T15:07:44-07:00</committed-date>
  <authored-date>2009-06-19T15:07:44-07:00</authored-date>
  <message>modifications to curb_easy to make use of the Curl::Upload class when
using http_put</message>
  <tree>d4a8e0ab1ff4eb0fed03e9189182f24d5124f8ba</tree>
  <committer>
    <name>Todd A. Fisher</name>
    <email>taf2@web3.anerian.com</email>
  </committer>
</commit>
