curb 0.1.4 → 0.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/curb_errors.h CHANGED
@@ -1,7 +1,7 @@
1
1
  /* curb_errors.h - Ruby exception types for curl errors
2
- * Copyright (c)2006 Ross Bamford.
2
+ * Copyright (c)2006 Ross Bamford.
3
3
  * Licensed under the Ruby License. See LICENSE for details.
4
- *
4
+ *
5
5
  * $Id: curb_errors.h 4 2006-11-17 18:35:31Z roscopeco $
6
6
  */
7
7
  #ifndef __CURB_ERRORS_H
@@ -12,6 +12,7 @@
12
12
  /* base errors */
13
13
  extern VALUE cCurlErr;
14
14
 
15
+ /* easy errors */
15
16
  extern VALUE mCurlErr;
16
17
  extern VALUE eCurlErrError;
17
18
  extern VALUE eCurlErrFTPError;
@@ -96,11 +97,33 @@ extern VALUE eCurlErrTFTPIllegalOperation;
96
97
  extern VALUE eCurlErrTFTPUnknownID;
97
98
  extern VALUE eCurlErrTFTPFileExists;
98
99
  extern VALUE eCurlErrTFTPNoSuchUser;
100
+ extern VALUE eCurlErrConvFailed;
101
+ extern VALUE eCurlErrConvReqd;
102
+ extern VALUE eCurlErrSSLCacertBadfile;
103
+ extern VALUE eCurlErrRemoteFileNotFound;
104
+ extern VALUE eCurlErrSSH;
105
+ extern VALUE eCurlErrSSLShutdownFailed;
106
+ extern VALUE eCurlErrAgain;
107
+ extern VALUE eCurlErrSSLCRLBadfile;
108
+ extern VALUE eCurlErrSSLIssuerError;
109
+
110
+ /* multi errors */
111
+ extern VALUE mCurlErrFailedInit;
112
+ extern VALUE mCurlErrCallMultiPerform;
113
+ extern VALUE mCurlErrBadHandle;
114
+ extern VALUE mCurlErrBadEasyHandle;
115
+ extern VALUE mCurlErrOutOfMemory;
116
+ extern VALUE mCurlErrInternalError;
117
+ extern VALUE mCurlErrBadSocket;
118
+ extern VALUE mCurlErrUnknownOption;
99
119
 
100
120
  /* binding errors */
101
121
  extern VALUE eCurlErrInvalidPostField;
102
122
 
103
123
  void init_curb_errors();
104
124
  void raise_curl_easy_error_exception(CURLcode code);
125
+ void raise_curl_multi_error_exception(CURLMcode code);
126
+ VALUE rb_curl_easy_error(CURLcode code);
127
+ VALUE rb_curl_multi_error(CURLMcode code);
105
128
 
106
129
  #endif
data/ext/curb_macros.h CHANGED
@@ -8,6 +8,17 @@
8
8
  #ifndef __CURB_MACROS_H
9
9
  #define __CURB_MACROS_H
10
10
 
11
+ #define rb_easy_hkey(key) ID2SYM(rb_intern(key))
12
+ #define rb_easy_set(key,val) rb_hash_aset(rbce->opts, rb_easy_hkey(key) , val)
13
+ #define rb_easy_get(key) rb_hash_aref(rbce->opts, rb_easy_hkey(key))
14
+ #define rb_easy_del(key) rb_hash_delete(rbce->opts, rb_easy_hkey(key))
15
+ #define rb_easy_nil(key) (rb_hash_aref(rbce->opts, rb_easy_hkey(key)) == Qnil)
16
+ #define rb_easy_type_check(key,type) (rb_type(rb_hash_aref(rbce->opts, rb_easy_hkey(key))) == type)
17
+
18
+ // TODO: rb_sym_to_s may not be defined?
19
+ #define rb_easy_get_str(key) \
20
+ RSTRING_PTR((rb_easy_type_check(key,T_STRING) ? rb_easy_get(key) : rb_str_to_str(rb_easy_get(key))))
21
+
11
22
  /* getter/setter macros for various things */
12
23
  /* setter for anything that stores a ruby VALUE in the struct */
13
24
  #define CURB_OBJECT_SETTER(type, attr) \
@@ -25,6 +36,22 @@
25
36
  Data_Get_Struct(self, type, ptr); \
26
37
  return ptr->attr;
27
38
 
39
+ /* setter for anything that stores a ruby VALUE in the struct opts hash */
40
+ #define CURB_OBJECT_HSETTER(type, attr) \
41
+ type *ptr; \
42
+ \
43
+ Data_Get_Struct(self, type, ptr); \
44
+ rb_hash_aset(ptr->opts, rb_easy_hkey(#attr), attr); \
45
+ \
46
+ return attr;
47
+
48
+ /* getter for anything that stores a ruby VALUE in the struct opts hash */
49
+ #define CURB_OBJECT_HGETTER(type, attr) \
50
+ type *ptr; \
51
+ \
52
+ Data_Get_Struct(self, type, ptr); \
53
+ return rb_hash_aref(ptr->opts, rb_easy_hkey(#attr));
54
+
28
55
  /* setter for bool flags */
29
56
  #define CURB_BOOLEAN_SETTER(type, attr) \
30
57
  type *ptr; \
@@ -57,6 +84,20 @@
57
84
  \
58
85
  return oldproc; \
59
86
 
87
+ /* special setter for on_event handlers that take a block, same as above but stores int he opts hash */
88
+ #define CURB_HANDLER_PROC_HSETTER(type, handler) \
89
+ type *ptr; \
90
+ VALUE oldproc, newproc; \
91
+ \
92
+ Data_Get_Struct(self, type, ptr); \
93
+ \
94
+ oldproc = rb_hash_aref(ptr->opts, rb_easy_hkey(#handler)); \
95
+ rb_scan_args(argc, argv, "0&", &newproc); \
96
+ \
97
+ rb_hash_aset(ptr->opts, rb_easy_hkey(#handler), newproc); \
98
+ \
99
+ return oldproc;
100
+
60
101
  /* setter for numerics that are kept in c ints */
61
102
  #define CURB_IMMED_SETTER(type, attr, nilval) \
62
103
  type *ptr; \
data/ext/curb_multi.c ADDED
@@ -0,0 +1,565 @@
1
+ /* curb_multi.c - Curl multi mode
2
+ * Copyright (c)2008 Todd A. Fisher.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ */
6
+
7
+ #include "curb_config.h"
8
+ #ifdef HAVE_RUBY19_ST_H
9
+ #include <ruby.h>
10
+ #include <ruby/st.h>
11
+ #else
12
+ #include <ruby.h>
13
+ #include <st.h>
14
+ #endif
15
+ #include "curb_easy.h"
16
+ #include "curb_errors.h"
17
+ #include "curb_postfield.h"
18
+ #include "curb_multi.h"
19
+
20
+ #include <errno.h>
21
+
22
+ #ifdef _WIN32
23
+ // for O_RDWR and O_BINARY
24
+ #include <fcntl.h>
25
+ #endif
26
+
27
+ extern VALUE mCurl;
28
+ static VALUE idCall;
29
+
30
+ #ifdef RDOC_NEVER_DEFINED
31
+ mCurl = rb_define_module("Curl");
32
+ #endif
33
+
34
+ VALUE cCurlMulti;
35
+
36
+ static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
37
+
38
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
39
+ static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
40
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
41
+
42
+ static void rb_curl_multi_mark_all_easy(VALUE key, VALUE rbeasy, ruby_curl_multi *rbcm) {
43
+ rb_gc_mark(rbeasy);
44
+ }
45
+
46
+ static void curl_multi_mark(ruby_curl_multi *rbcm) {
47
+ rb_gc_mark(rbcm->requests);
48
+ rb_hash_foreach( rbcm->requests, (int (*)())rb_curl_multi_mark_all_easy, (VALUE)rbcm );
49
+ }
50
+
51
+ static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
52
+ CURLMcode result;
53
+ ruby_curl_easy *rbce;
54
+
55
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
56
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
57
+ if (result != 0) {
58
+ raise_curl_multi_error_exception(result);
59
+ }
60
+ }
61
+
62
+ static int
63
+ rb_hash_clear_i(VALUE key, VALUE value, VALUE dummy) {
64
+ return ST_DELETE;
65
+ }
66
+
67
+ static void curl_multi_free(ruby_curl_multi *rbcm) {
68
+
69
+ if (rbcm && !rbcm->requests == Qnil && rb_type(rbcm->requests) == T_HASH && RHASH_LEN(rbcm->requests) > 0) {
70
+
71
+ rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
72
+
73
+ rb_hash_foreach(rbcm->requests, rb_hash_clear_i, 0); //rb_hash_clear(rbcm->requests);
74
+ rbcm->requests = Qnil;
75
+ }
76
+ curl_multi_cleanup(rbcm->handle);
77
+ free(rbcm);
78
+ }
79
+
80
+ /*
81
+ * call-seq:
82
+ * Curl::Multi.new => #&lt;Curl::Easy...&gt;
83
+ *
84
+ * Create a new Curl::Multi instance
85
+ */
86
+ VALUE ruby_curl_multi_new(VALUE klass) {
87
+ VALUE new_curlm;
88
+
89
+ ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
90
+
91
+ rbcm->handle = curl_multi_init();
92
+ if (!rbcm->handle) {
93
+ rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
94
+ }
95
+
96
+ rbcm->requests = rb_hash_new();
97
+
98
+ rbcm->active = 0;
99
+ rbcm->running = 0;
100
+
101
+ new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
102
+
103
+ return new_curlm;
104
+ }
105
+
106
+ /*
107
+ * call-seq:
108
+ * Curl::Multi.default_timeout = 4 => 4
109
+ *
110
+ * Set the global default time out for all Curl::Multi Handles. This value is used
111
+ * when libcurl cannot determine a timeout value when calling curl_multi_timeout.
112
+ *
113
+ */
114
+ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
115
+ cCurlMutiDefaulttimeout = FIX2LONG(timeout);
116
+ return timeout;
117
+ }
118
+
119
+ /*
120
+ * call-seq:
121
+ * Curl::Multi.default_timeout = 4 => 4
122
+ *
123
+ * Get the global default time out for all Curl::Multi Handles.
124
+ *
125
+ */
126
+ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
127
+ return INT2FIX(cCurlMutiDefaulttimeout);
128
+ }
129
+
130
+ /* Hash#foreach callback for ruby_curl_multi_requests */
131
+ static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
132
+ rb_ary_push(result_array, value);
133
+
134
+ return ST_CONTINUE;
135
+ }
136
+
137
+ /*
138
+ * call-seq:
139
+ * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
140
+ *
141
+ * Returns an array containing all the active requests on this Curl::Multi object.
142
+ */
143
+ static VALUE ruby_curl_multi_requests(VALUE self) {
144
+ ruby_curl_multi *rbcm;
145
+ VALUE result_array;
146
+
147
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
148
+
149
+ result_array = rb_ary_new();
150
+
151
+ /* iterate over the requests hash, and stuff references into the array. */
152
+ rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
153
+
154
+ return result_array;
155
+ }
156
+
157
+ /*
158
+ * call-seq:
159
+ * multi.idle? => true or false
160
+ *
161
+ * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
162
+ * true when multi.requests.length == 0.
163
+ */
164
+ static VALUE ruby_curl_multi_idle(VALUE self) {
165
+ ruby_curl_multi *rbcm;
166
+
167
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
168
+
169
+ if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
170
+ return Qtrue;
171
+ } else {
172
+ return Qfalse;
173
+ }
174
+ }
175
+
176
+ /*
177
+ * call-seq:
178
+ * multi = Curl::Multi.new
179
+ * multi.max_connects = 800
180
+ *
181
+ * Set the max connections in the cache for a multi handle
182
+ */
183
+ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
184
+ #ifdef HAVE_CURLMOPT_MAXCONNECTS
185
+ ruby_curl_multi *rbcm;
186
+
187
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
188
+ curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
189
+ #endif
190
+
191
+ return count;
192
+ }
193
+
194
+ /*
195
+ * call-seq:
196
+ * multi = Curl::Multi.new
197
+ * multi.pipeline = true
198
+ *
199
+ * Pass a long set to 1 to enable or 0 to disable. Enabling pipelining on a multi handle will make it
200
+ * attempt to perform HTTP Pipelining as far as possible for transfers using this handle. This means
201
+ * that if you add a second request that can use an already existing connection, the second request will
202
+ * be "piped" on the same connection rather than being executed in parallel. (Added in 7.16.0)
203
+ *
204
+ */
205
+ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE onoff) {
206
+ #ifdef HAVE_CURLMOPT_PIPELINING
207
+ ruby_curl_multi *rbcm;
208
+
209
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
210
+ curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, onoff == Qtrue ? 1 : 0);
211
+ #endif
212
+ return onoff;
213
+ }
214
+
215
+ /*
216
+ * call-seq:
217
+ * multi = Curl::Multi.new
218
+ * easy = Curl::Easy.new('url')
219
+ *
220
+ * multi.add(easy)
221
+ *
222
+ * Add an easy handle to the multi stack
223
+ */
224
+ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
225
+ CURLMcode mcode;
226
+ ruby_curl_easy *rbce;
227
+ ruby_curl_multi *rbcm;
228
+
229
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
230
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
231
+
232
+ /* setup the easy handle */
233
+ ruby_curl_easy_setup( rbce );
234
+
235
+ mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
236
+ if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
237
+ raise_curl_multi_error_exception(mcode);
238
+ }
239
+
240
+ rbcm->active++;
241
+
242
+ /* Increase the running count, so that the perform loop keeps running.
243
+ * If this number is not correct, the next call to curl_multi_perform will correct it. */
244
+ rbcm->running++;
245
+
246
+ rb_hash_aset( rbcm->requests, easy, easy );
247
+
248
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
249
+
250
+ return self;
251
+ }
252
+
253
+ /*
254
+ * call-seq:
255
+ * multi = Curl::Multi.new
256
+ * easy = Curl::Easy.new('url')
257
+ *
258
+ * multi.add(easy)
259
+ *
260
+ * # sometime later
261
+ * multi.remove(easy)
262
+ *
263
+ * Remove an easy handle from a multi stack.
264
+ *
265
+ * Will raise an exception if the easy handle is not found
266
+ */
267
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
268
+ ruby_curl_multi *rbcm;
269
+ ruby_curl_easy *rbce;
270
+
271
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
272
+
273
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
274
+
275
+ rb_curl_multi_remove(rbcm,easy);
276
+
277
+ return self;
278
+ }
279
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
280
+ CURLMcode result;
281
+ ruby_curl_easy *rbce;
282
+ VALUE r;
283
+
284
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
285
+
286
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
287
+ if (result != 0) {
288
+ raise_curl_multi_error_exception(result);
289
+ }
290
+
291
+ rbcm->active--;
292
+
293
+ ruby_curl_easy_cleanup( easy, rbce );
294
+
295
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
296
+ r = rb_hash_delete( rbcm->requests, easy );
297
+ if( r != easy || r == Qnil ) {
298
+ rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
299
+ }
300
+ }
301
+
302
+ /* Hash#foreach callback for ruby_curl_multi_cancel */
303
+ static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
304
+ rb_curl_multi_remove(rbcm, value);
305
+
306
+ return ST_CONTINUE;
307
+ }
308
+
309
+ /*
310
+ * call-seq:
311
+ * multi.cancel!
312
+ *
313
+ * Cancels all requests currently being made on this Curl::Multi handle.
314
+ */
315
+ static VALUE ruby_curl_multi_cancel(VALUE self) {
316
+ ruby_curl_multi *rbcm;
317
+
318
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
319
+
320
+ rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
321
+
322
+ /* for chaining */
323
+ return self;
324
+ }
325
+
326
+ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
327
+
328
+ long response_code = -1;
329
+ VALUE easy;
330
+ ruby_curl_easy *rbce = NULL;
331
+
332
+ CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
333
+
334
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
335
+
336
+ rbce->last_result = result; /* save the last easy result code */
337
+
338
+ ruby_curl_multi_remove( self, easy );
339
+
340
+ /* after running a request cleanup the headers, these are set before each request */
341
+ if (rbce->curl_headers) {
342
+ curl_slist_free_all(rbce->curl_headers);
343
+ rbce->curl_headers = NULL;
344
+ }
345
+
346
+ if (ecode != 0) {
347
+ raise_curl_easy_error_exception(ecode);
348
+ }
349
+
350
+ if (!rb_easy_nil("complete_proc")) {
351
+ rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
352
+ }
353
+
354
+ curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
355
+
356
+ if (result != 0) {
357
+ if (!rb_easy_nil("failure_proc")) {
358
+ rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
359
+ }
360
+ }
361
+ else if (!rb_easy_nil("success_proc") &&
362
+ ((response_code >= 200 && response_code < 300) || response_code == 0)) {
363
+ /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
364
+ rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
365
+ }
366
+ else if (!rb_easy_nil("failure_proc") &&
367
+ (response_code >= 300 && response_code <= 999)) {
368
+ rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
369
+ }
370
+
371
+ }
372
+
373
+ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
374
+ int msgs_left, result;
375
+ CURLMsg *msg;
376
+ CURL *easy_handle;
377
+
378
+ /* check for finished easy handles and remove from the multi handle */
379
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
380
+ if (msg->msg == CURLMSG_DONE) {
381
+ easy_handle = msg->easy_handle;
382
+ result = msg->data.result;
383
+ if (easy_handle) {
384
+ rb_curl_mutli_handle_complete(self, easy_handle, result);
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ /* called within ruby_curl_multi_perform */
391
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
392
+ CURLMcode mcode;
393
+
394
+ do {
395
+ mcode = curl_multi_perform(multi_handle, still_running);
396
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
397
+
398
+ if (mcode != CURLM_OK) {
399
+ raise_curl_multi_error_exception(mcode);
400
+ }
401
+
402
+ }
403
+
404
+ #ifdef _WIN32
405
+ void create_crt_fd(fd_set *os_set, fd_set *crt_set)
406
+ {
407
+ int i;
408
+ crt_set->fd_count = os_set->fd_count;
409
+ for (i = 0; i < os_set->fd_count; i++) {
410
+ WSAPROTOCOL_INFO wsa_pi;
411
+ // dupicate the SOCKET
412
+ int r = WSADuplicateSocket(os_set->fd_array[i], GetCurrentProcessId(), &wsa_pi);
413
+ SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
414
+ // create the CRT fd so ruby can get back to the SOCKET
415
+ int fd = _open_osfhandle(s, O_RDWR|O_BINARY);
416
+ os_set->fd_array[i] = s;
417
+ crt_set->fd_array[i] = fd;
418
+ }
419
+ }
420
+
421
+ void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
422
+ {
423
+ int i;
424
+ for (i = 0; i < os_set->fd_count; i++) {
425
+ // cleanup the CRT fd
426
+ _close(crt_set->fd_array[i]);
427
+ // cleanup the duplicated SOCKET
428
+ closesocket(os_set->fd_array[i]);
429
+ }
430
+ }
431
+ #endif
432
+
433
+ /*
434
+ * call-seq:
435
+ * multi = Curl::Multi.new
436
+ * easy1 = Curl::Easy.new('url')
437
+ * easy2 = Curl::Easy.new('url')
438
+ *
439
+ * multi.add(easy1)
440
+ * multi.add(easy2)
441
+ *
442
+ * multi.perform do
443
+ * # while idle other code my execute here
444
+ * end
445
+ *
446
+ * Run multi handles, looping selecting when data can be transfered
447
+ */
448
+ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
449
+ CURLMcode mcode;
450
+ ruby_curl_multi *rbcm;
451
+ int maxfd, rc;
452
+ fd_set fdread, fdwrite, fdexcep;
453
+ #ifdef _WIN32
454
+ fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
455
+ #endif
456
+
457
+ long timeout_milliseconds;
458
+ struct timeval tv = {0, 0};
459
+ VALUE block = Qnil;
460
+
461
+ rb_scan_args(argc, argv, "0&", &block);
462
+
463
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
464
+
465
+ timeout_milliseconds = cCurlMutiDefaulttimeout;
466
+
467
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
468
+
469
+ do {
470
+ while (rbcm->running) {
471
+
472
+ #ifdef HAVE_CURL_MULTI_TIMEOUT
473
+ /* get the curl suggested time out */
474
+ mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
475
+ if (mcode != CURLM_OK) {
476
+ raise_curl_multi_error_exception(mcode);
477
+ }
478
+ #else
479
+ /* libcurl doesn't have a timeout method defined, initialize to -1 we'll pick up the default later */
480
+ timeout_milliseconds = -1;
481
+ #endif
482
+
483
+ if (timeout_milliseconds == 0) { /* no delay */
484
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
485
+ rb_curl_multi_read_info( self, rbcm->handle );
486
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
487
+ continue;
488
+ }
489
+
490
+ if (timeout_milliseconds < 0 || timeout_milliseconds > cCurlMutiDefaulttimeout) {
491
+ timeout_milliseconds = cCurlMutiDefaulttimeout; /* libcurl doesn't know how long to wait, use a default timeout */
492
+ /* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
493
+ }
494
+
495
+ tv.tv_sec = 0; /* never wait longer than 1 second */
496
+ tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */
497
+
498
+ FD_ZERO(&fdread);
499
+ FD_ZERO(&fdwrite);
500
+ FD_ZERO(&fdexcep);
501
+
502
+ /* load the fd sets from the multi handle */
503
+ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
504
+ if (mcode != CURLM_OK) {
505
+ raise_curl_multi_error_exception(mcode);
506
+ }
507
+
508
+ #ifdef _WIN32
509
+ create_crt_fd(&fdread, &crt_fdread);
510
+ create_crt_fd(&fdwrite, &crt_fdwrite);
511
+ create_crt_fd(&fdexcep, &crt_fdexcep);
512
+ #endif
513
+
514
+ rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
515
+
516
+ #ifdef _WIN32
517
+ cleanup_crt_fd(&fdread, &crt_fdread);
518
+ cleanup_crt_fd(&fdwrite, &crt_fdwrite);
519
+ cleanup_crt_fd(&fdexcep, &crt_fdexcep);
520
+ #endif
521
+
522
+ switch(rc) {
523
+ case -1:
524
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
525
+ break;
526
+ case 0: /* timeout */
527
+ default: /* action */
528
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
529
+ rb_curl_multi_read_info( self, rbcm->handle );
530
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
531
+ break;
532
+ }
533
+ }
534
+
535
+ } while( rbcm->running );
536
+
537
+ rb_curl_multi_read_info( self, rbcm->handle );
538
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
539
+
540
+ return Qtrue;
541
+ }
542
+
543
+ /* =================== INIT LIB =====================*/
544
+ void init_curb_multi() {
545
+ idCall = rb_intern("call");
546
+
547
+ cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
548
+
549
+ /* Class methods */
550
+ rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
551
+ rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
552
+ rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
553
+
554
+ /* "Attributes" */
555
+ rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
556
+ rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
557
+
558
+ /* Instnace methods */
559
+ rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
560
+ rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
561
+ rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
562
+ rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
563
+ rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
564
+ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
565
+ }
data/ext/curb_multi.h ADDED
@@ -0,0 +1,26 @@
1
+ /* curb_multi.h - Curl easy mode
2
+ * Copyright (c)2008 Todd A. Fisher.
3
+ * Licensed under the Ruby License. See LICENSE for details.
4
+ *
5
+ * $Id$
6
+ */
7
+ #ifndef __CURB_MULTI_H
8
+ #define __CURB_MULTI_H
9
+
10
+ #include "curb.h"
11
+ #include "curb_easy.h"
12
+ #include <curl/multi.h>
13
+
14
+ typedef struct {
15
+ int active;
16
+ int running;
17
+ VALUE requests; /* hash of handles currently added */
18
+ CURLM *handle;
19
+ } ruby_curl_multi;
20
+
21
+ extern VALUE cCurlMulti;
22
+ void init_curb_multi();
23
+ VALUE ruby_curl_multi_new(VALUE klass);
24
+
25
+
26
+ #endif