rhack 1.1.8 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +6 -14
  2. data/Rakefile +2 -1
  3. data/config/rhack.yml.template +1 -0
  4. data/ext/curb-original/curb.c +977 -977
  5. data/ext/curb-original/curb.h +52 -52
  6. data/ext/curb-original/curb_config.h +238 -238
  7. data/ext/curb-original/curb_easy.c +3404 -3404
  8. data/ext/curb-original/curb_easy.h +90 -90
  9. data/ext/curb-original/curb_errors.c +647 -647
  10. data/ext/curb-original/curb_errors.h +129 -129
  11. data/ext/curb-original/curb_macros.h +159 -159
  12. data/ext/curb-original/curb_multi.c +633 -633
  13. data/ext/curb-original/curb_multi.h +26 -26
  14. data/ext/curb-original/curb_postfield.c +523 -523
  15. data/ext/curb-original/curb_postfield.h +40 -40
  16. data/ext/curb-original/curb_upload.c +80 -80
  17. data/ext/curb-original/curb_upload.h +30 -30
  18. data/ext/curb/curb.c +977 -977
  19. data/ext/curb/curb.h +52 -52
  20. data/ext/curb/curb_config.h +270 -270
  21. data/ext/curb/curb_easy.c +3434 -3434
  22. data/ext/curb/curb_easy.h +94 -94
  23. data/ext/curb/curb_errors.c +647 -647
  24. data/ext/curb/curb_errors.h +129 -129
  25. data/ext/curb/curb_macros.h +162 -162
  26. data/ext/curb/curb_multi.c +702 -702
  27. data/ext/curb/curb_multi.h +26 -26
  28. data/ext/curb/curb_postfield.c +523 -523
  29. data/ext/curb/curb_postfield.h +40 -40
  30. data/ext/curb/curb_upload.c +80 -80
  31. data/ext/curb/curb_upload.h +30 -30
  32. data/lib/rhack.rb +4 -1
  33. data/lib/rhack/clients/oauth.rb +1 -1
  34. data/lib/rhack/js/browser/env.js +697 -697
  35. data/lib/rhack/js/browser/jquery.js +7180 -7180
  36. data/lib/rhack/js/browser/xmlsax.js +1564 -1564
  37. data/lib/rhack/js/browser/xmlw3cdom_1.js +1443 -1443
  38. data/lib/rhack/js/browser/xmlw3cdom_2.js +2744 -2744
  39. data/lib/rhack/page.rb +6 -2
  40. data/lib/rhack/storage.rb +1 -1
  41. data/lib/rhack/version.rb +1 -1
  42. data/rhack.gemspec +1 -1
  43. metadata +16 -16
@@ -1,633 +1,633 @@
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
- #include "curb_config.h"
7
- #ifdef HAVE_RUBY19_ST_H
8
- #include <ruby.h>
9
- #include <ruby/st.h>
10
- #else
11
- #include <ruby.h>
12
- #include <st.h>
13
- #endif
14
- #include "curb_easy.h"
15
- #include "curb_errors.h"
16
- #include "curb_postfield.h"
17
- #include "curb_multi.h"
18
-
19
- #include <errno.h>
20
-
21
- #ifdef _WIN32
22
- // for O_RDWR and O_BINARY
23
- #include <fcntl.h>
24
- #endif
25
-
26
- extern VALUE mCurl;
27
- static VALUE idCall;
28
-
29
- #ifdef RDOC_NEVER_DEFINED
30
- mCurl = rb_define_module("Curl");
31
- #endif
32
-
33
- VALUE cCurlMulti;
34
-
35
- static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
36
-
37
- static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
38
- static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
39
- static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
40
-
41
- static VALUE callback_exception(VALUE unused) {
42
- return Qfalse;
43
- }
44
-
45
- static void curl_multi_mark(ruby_curl_multi *rbcm) {
46
- rb_gc_mark(rbcm->requests);
47
- }
48
-
49
- static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
50
- CURLMcode result;
51
- ruby_curl_easy *rbce;
52
-
53
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
54
- result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
55
- if (result != 0) {
56
- raise_curl_multi_error_exception(result);
57
- }
58
- }
59
-
60
- static int
61
- rb_hash_clear_i(VALUE key, VALUE value, VALUE dummy) {
62
- return ST_DELETE;
63
- }
64
-
65
- static void curl_multi_free(ruby_curl_multi *rbcm) {
66
-
67
- if (rbcm && !rbcm->requests == Qnil && rb_type(rbcm->requests) == T_HASH && RHASH_LEN(rbcm->requests) > 0) {
68
-
69
- rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
70
-
71
- rb_hash_foreach(rbcm->requests, rb_hash_clear_i, 0); //rb_hash_clear(rbcm->requests);
72
- rbcm->requests = Qnil;
73
- }
74
- curl_multi_cleanup(rbcm->handle);
75
- free(rbcm);
76
- }
77
-
78
- /*
79
- * call-seq:
80
- * Curl::Multi.new => #&lt;Curl::Easy...&gt;
81
- *
82
- * Create a new Curl::Multi instance
83
- */
84
- VALUE ruby_curl_multi_new(VALUE klass) {
85
- VALUE new_curlm;
86
-
87
- ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
88
-
89
- rbcm->handle = curl_multi_init();
90
- if (!rbcm->handle) {
91
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
92
- }
93
-
94
- rbcm->requests = rb_hash_new();
95
-
96
- rbcm->active = 0;
97
- rbcm->running = 0;
98
-
99
- new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
100
-
101
- return new_curlm;
102
- }
103
-
104
- /*
105
- * call-seq:
106
- * Curl::Multi.default_timeout = 4 => 4
107
- *
108
- * Set the global default time out for all Curl::Multi Handles. This value is used
109
- * when libcurl cannot determine a timeout value when calling curl_multi_timeout.
110
- *
111
- */
112
- VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
113
- cCurlMutiDefaulttimeout = FIX2LONG(timeout);
114
- return timeout;
115
- }
116
-
117
- /*
118
- * call-seq:
119
- * Curl::Multi.default_timeout = 4 => 4
120
- *
121
- * Get the global default time out for all Curl::Multi Handles.
122
- *
123
- */
124
- VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
125
- return INT2FIX(cCurlMutiDefaulttimeout);
126
- }
127
-
128
- /* Hash#foreach callback for ruby_curl_multi_requests */
129
- static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
130
- rb_ary_push(result_array, value);
131
-
132
- return ST_CONTINUE;
133
- }
134
-
135
- /*
136
- * call-seq:
137
- * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
138
- *
139
- * Returns an array containing all the active requests on this Curl::Multi object.
140
- */
141
- static VALUE ruby_curl_multi_requests(VALUE self) {
142
- ruby_curl_multi *rbcm;
143
- VALUE result_array;
144
-
145
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
146
-
147
- result_array = rb_ary_new();
148
-
149
- /* iterate over the requests hash, and stuff references into the array. */
150
- rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
151
-
152
- return result_array;
153
- }
154
-
155
- /*
156
- * call-seq:
157
- * multi.idle? => true or false
158
- *
159
- * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
160
- * true when multi.requests.length == 0.
161
- */
162
- static VALUE ruby_curl_multi_idle(VALUE self) {
163
- ruby_curl_multi *rbcm;
164
-
165
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
166
-
167
- if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
168
- return Qtrue;
169
- } else {
170
- return Qfalse;
171
- }
172
- }
173
-
174
- /*
175
- * call-seq:
176
- * multi = Curl::Multi.new
177
- * multi.max_connects = 800
178
- *
179
- * Set the max connections in the cache for a multi handle
180
- */
181
- static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
182
- #ifdef HAVE_CURLMOPT_MAXCONNECTS
183
- ruby_curl_multi *rbcm;
184
-
185
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
186
- curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
187
- #endif
188
-
189
- return count;
190
- }
191
-
192
- /*
193
- * call-seq:
194
- * multi = Curl::Multi.new
195
- * multi.pipeline = true
196
- *
197
- * Pass a long set to 1 to enable or 0 to disable. Enabling pipelining on a multi handle will make it
198
- * attempt to perform HTTP Pipelining as far as possible for transfers using this handle. This means
199
- * that if you add a second request that can use an already existing connection, the second request will
200
- * be "piped" on the same connection rather than being executed in parallel. (Added in 7.16.0)
201
- *
202
- */
203
- static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE onoff) {
204
- #ifdef HAVE_CURLMOPT_PIPELINING
205
- ruby_curl_multi *rbcm;
206
-
207
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
208
- curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, onoff == Qtrue ? 1 : 0);
209
- #endif
210
- return onoff;
211
- }
212
-
213
- /*
214
- * call-seq:
215
- * multi = Curl::Multi.new
216
- * easy = Curl::Easy.new('url')
217
- *
218
- * multi.add(easy)
219
- *
220
- * Add an easy handle to the multi stack
221
- */
222
- VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
223
- CURLMcode mcode;
224
- ruby_curl_easy *rbce;
225
- ruby_curl_multi *rbcm;
226
-
227
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
228
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
229
-
230
- /* setup the easy handle */
231
- ruby_curl_easy_setup( rbce );
232
-
233
- mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
234
- if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
235
- raise_curl_multi_error_exception(mcode);
236
- }
237
-
238
- rbcm->active++;
239
-
240
- /* Increase the running count, so that the perform loop keeps running.
241
- * If this number is not correct, the next call to curl_multi_perform will correct it. */
242
- rbcm->running++;
243
-
244
- rb_hash_aset( rbcm->requests, easy, easy );
245
-
246
- return self;
247
- }
248
-
249
- /*
250
- * call-seq:
251
- * multi = Curl::Multi.new
252
- * easy = Curl::Easy.new('url')
253
- *
254
- * multi.add(easy)
255
- *
256
- * # sometime later
257
- * multi.remove(easy)
258
- *
259
- * Remove an easy handle from a multi stack.
260
- *
261
- * Will raise an exception if the easy handle is not found
262
- */
263
- VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
264
- ruby_curl_multi *rbcm;
265
- ruby_curl_easy *rbce;
266
-
267
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
268
-
269
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
270
-
271
- rb_curl_multi_remove(rbcm,easy);
272
-
273
- return self;
274
- }
275
- static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
276
- CURLMcode result;
277
- ruby_curl_easy *rbce;
278
- VALUE r;
279
-
280
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
281
-
282
- result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
283
- if (result != 0) {
284
- raise_curl_multi_error_exception(result);
285
- }
286
-
287
- rbcm->active--;
288
-
289
- ruby_curl_easy_cleanup( easy, rbce );
290
-
291
- // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
292
- r = rb_hash_delete( rbcm->requests, easy );
293
- if( r != easy || r == Qnil ) {
294
- rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
295
- }
296
- }
297
-
298
- /* Hash#foreach callback for ruby_curl_multi_cancel */
299
- static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
300
- rb_curl_multi_remove(rbcm, value);
301
-
302
- return ST_CONTINUE;
303
- }
304
-
305
- /*
306
- * call-seq:
307
- * multi.cancel!
308
- *
309
- * Cancels all requests currently being made on this Curl::Multi handle.
310
- */
311
- static VALUE ruby_curl_multi_cancel(VALUE self) {
312
- ruby_curl_multi *rbcm;
313
-
314
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
315
-
316
- rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
317
-
318
- /* for chaining */
319
- return self;
320
- }
321
-
322
- // on_success, on_failure, on_complete
323
- static VALUE call_status_handler1(VALUE ary) {
324
- return rb_funcall(rb_ary_entry(ary, 0), idCall, 1, rb_ary_entry(ary, 1));
325
- }
326
- static VALUE call_status_handler2(VALUE ary) {
327
- return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
328
- }
329
-
330
- static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
331
-
332
- long response_code = -1;
333
- VALUE easy;
334
- ruby_curl_easy *rbce = NULL;
335
- VALUE callargs, val = Qtrue;
336
-
337
- CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
338
-
339
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
340
-
341
- rbce->last_result = result; /* save the last easy result code */
342
-
343
- ruby_curl_multi_remove( self, easy );
344
-
345
- /* after running a request cleanup the headers, these are set before each request */
346
- if (rbce->curl_headers) {
347
- curl_slist_free_all(rbce->curl_headers);
348
- rbce->curl_headers = NULL;
349
- }
350
-
351
- if (ecode != 0) {
352
- raise_curl_easy_error_exception(ecode);
353
- }
354
-
355
- if (!rb_easy_nil("complete_proc")) {
356
- callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), easy);
357
- rbce->callback_active = 1;
358
- val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
359
- rbce->callback_active = 0;
360
- //rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
361
- }
362
-
363
- curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
364
-
365
- if (result != 0) {
366
- if (!rb_easy_nil("failure_proc")) {
367
- callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
368
- rbce->callback_active = 1;
369
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
370
- rbce->callback_active = 0;
371
- //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
372
- }
373
- }
374
- else if (!rb_easy_nil("success_proc") &&
375
- ((response_code >= 200 && response_code < 300) || response_code == 0)) {
376
- /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
377
- callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
378
- rbce->callback_active = 1;
379
- val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
380
- rbce->callback_active = 0;
381
- //rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
382
- }
383
- else if (!rb_easy_nil("redirect_proc") &&
384
- (response_code >= 300 && response_code < 400)) {
385
- rbce->callback_active = 1;
386
- callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
387
- rbce->callback_active = 0;
388
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
389
- }
390
- else if (!rb_easy_nil("missing_proc") &&
391
- (response_code >= 400 && response_code < 500)) {
392
- rbce->callback_active = 1;
393
- callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
394
- rbce->callback_active = 0;
395
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
396
- }
397
- else if (!rb_easy_nil("failure_proc") &&
398
- (response_code >= 500 && response_code <= 999)) {
399
- callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
400
- rbce->callback_active = 1;
401
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
402
- rbce->callback_active = 0;
403
- //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
404
- }
405
-
406
- if (val == Qfalse) {
407
- rb_warn("uncaught exception from callback");
408
- // exception was raised?
409
- //fprintf(stderr, "exception raised from callback\n");
410
- }
411
-
412
- }
413
-
414
- static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
415
- int msgs_left, result;
416
- CURLMsg *msg;
417
- CURL *easy_handle;
418
-
419
- /* check for finished easy handles and remove from the multi handle */
420
- while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
421
- if (msg->msg == CURLMSG_DONE) {
422
- easy_handle = msg->easy_handle;
423
- result = msg->data.result;
424
- if (easy_handle) {
425
- rb_curl_mutli_handle_complete(self, easy_handle, result);
426
- }
427
- }
428
- }
429
- }
430
-
431
- /* called within ruby_curl_multi_perform */
432
- static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
433
- CURLMcode mcode;
434
-
435
- do {
436
- mcode = curl_multi_perform(multi_handle, still_running);
437
- } while (mcode == CURLM_CALL_MULTI_PERFORM);
438
-
439
- if (mcode != CURLM_OK) {
440
- raise_curl_multi_error_exception(mcode);
441
- }
442
-
443
- }
444
-
445
- #ifdef _WIN32
446
- void create_crt_fd(fd_set *os_set, fd_set *crt_set)
447
- {
448
- int i;
449
- crt_set->fd_count = os_set->fd_count;
450
- for (i = 0; i < os_set->fd_count; i++) {
451
- WSAPROTOCOL_INFO wsa_pi;
452
- // dupicate the SOCKET
453
- int r = WSADuplicateSocket(os_set->fd_array[i], GetCurrentProcessId(), &wsa_pi);
454
- SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
455
- // create the CRT fd so ruby can get back to the SOCKET
456
- int fd = _open_osfhandle(s, O_RDWR|O_BINARY);
457
- os_set->fd_array[i] = s;
458
- crt_set->fd_array[i] = fd;
459
- }
460
- }
461
-
462
- void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
463
- {
464
- int i;
465
- for (i = 0; i < os_set->fd_count; i++) {
466
- // cleanup the CRT fd
467
- _close(crt_set->fd_array[i]);
468
- // cleanup the duplicated SOCKET
469
- closesocket(os_set->fd_array[i]);
470
- }
471
- }
472
- #endif
473
-
474
- #ifdef HAVE_RB_THREAD_BLOCKING_REGION
475
- struct _select_set {
476
- int maxfd;
477
- fd_set *fdread, *fdwrite, *fdexcep;
478
- struct timeval *tv;
479
- };
480
-
481
- static VALUE curb_select(void *args) {
482
- struct _select_set* set = args;
483
- int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
484
- return INT2FIX(rc);
485
- }
486
- #endif
487
-
488
- /*
489
- * call-seq:
490
- * multi = Curl::Multi.new
491
- * easy1 = Curl::Easy.new('url')
492
- * easy2 = Curl::Easy.new('url')
493
- *
494
- * multi.add(easy1)
495
- * multi.add(easy2)
496
- *
497
- * multi.perform do
498
- * # while idle other code my execute here
499
- * end
500
- *
501
- * Run multi handles, looping selecting when data can be transfered
502
- */
503
- VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
504
- CURLMcode mcode;
505
- ruby_curl_multi *rbcm;
506
- int maxfd, rc;
507
- fd_set fdread, fdwrite, fdexcep;
508
- #ifdef _WIN32
509
- fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
510
- #endif
511
- long timeout_milliseconds;
512
- struct timeval tv = {0, 0};
513
- VALUE block = Qnil;
514
- #ifdef HAVE_RB_THREAD_BLOCKING_REGION
515
- struct _select_set fdset_args;
516
- #endif
517
-
518
- rb_scan_args(argc, argv, "0&", &block);
519
-
520
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
521
-
522
- timeout_milliseconds = cCurlMutiDefaulttimeout;
523
-
524
- rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
525
- rb_curl_multi_read_info( self, rbcm->handle );
526
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
527
-
528
- do {
529
- while (rbcm->running) {
530
-
531
- #ifdef HAVE_CURL_MULTI_TIMEOUT
532
- /* get the curl suggested time out */
533
- mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
534
- if (mcode != CURLM_OK) {
535
- raise_curl_multi_error_exception(mcode);
536
- }
537
- #else
538
- /* libcurl doesn't have a timeout method defined, initialize to -1 we'll pick up the default later */
539
- timeout_milliseconds = -1;
540
- #endif
541
-
542
- if (timeout_milliseconds == 0) { /* no delay */
543
- rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
544
- rb_curl_multi_read_info( self, rbcm->handle );
545
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
546
- continue;
547
- }
548
-
549
- if (timeout_milliseconds < 0 || timeout_milliseconds > cCurlMutiDefaulttimeout) {
550
- timeout_milliseconds = cCurlMutiDefaulttimeout; /* libcurl doesn't know how long to wait, use a default timeout */
551
- /* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
552
- }
553
-
554
- tv.tv_sec = 0; /* never wait longer than 1 second */
555
- tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */
556
-
557
- FD_ZERO(&fdread);
558
- FD_ZERO(&fdwrite);
559
- FD_ZERO(&fdexcep);
560
-
561
- /* load the fd sets from the multi handle */
562
- mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
563
- if (mcode != CURLM_OK) {
564
- raise_curl_multi_error_exception(mcode);
565
- }
566
-
567
- #ifdef _WIN32
568
- create_crt_fd(&fdread, &crt_fdread);
569
- create_crt_fd(&fdwrite, &crt_fdwrite);
570
- create_crt_fd(&fdexcep, &crt_fdexcep);
571
- #endif
572
-
573
- #ifdef HAVE_RB_THREAD_BLOCKING_REGION
574
- fdset_args.maxfd = maxfd+1;
575
- fdset_args.fdread = &fdread;
576
- fdset_args.fdwrite = &fdwrite;
577
- fdset_args.fdexcep = &fdexcep;
578
- fdset_args.tv = &tv;
579
- rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
580
- #else
581
- rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
582
- #endif
583
-
584
- #ifdef _WIN32
585
- cleanup_crt_fd(&fdread, &crt_fdread);
586
- cleanup_crt_fd(&fdwrite, &crt_fdwrite);
587
- cleanup_crt_fd(&fdexcep, &crt_fdexcep);
588
- #endif
589
-
590
- switch(rc) {
591
- case -1:
592
- rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
593
- break;
594
- case 0: /* timeout */
595
- default: /* action */
596
- rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
597
- rb_curl_multi_read_info( self, rbcm->handle );
598
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
599
- break;
600
- }
601
- }
602
-
603
- } while( rbcm->running );
604
-
605
- rb_curl_multi_read_info( self, rbcm->handle );
606
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
607
-
608
- return Qtrue;
609
- }
610
-
611
- /* =================== INIT LIB =====================*/
612
- void init_curb_multi() {
613
- idCall = rb_intern("call");
614
-
615
- cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
616
-
617
- /* Class methods */
618
- rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
619
- rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
620
- rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
621
-
622
- /* "Attributes" */
623
- rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
624
- rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
625
-
626
- /* Instnace methods */
627
- rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
628
- rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
629
- rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
630
- rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
631
- rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
632
- rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
633
- }
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
+ #include "curb_config.h"
7
+ #ifdef HAVE_RUBY19_ST_H
8
+ #include <ruby.h>
9
+ #include <ruby/st.h>
10
+ #else
11
+ #include <ruby.h>
12
+ #include <st.h>
13
+ #endif
14
+ #include "curb_easy.h"
15
+ #include "curb_errors.h"
16
+ #include "curb_postfield.h"
17
+ #include "curb_multi.h"
18
+
19
+ #include <errno.h>
20
+
21
+ #ifdef _WIN32
22
+ // for O_RDWR and O_BINARY
23
+ #include <fcntl.h>
24
+ #endif
25
+
26
+ extern VALUE mCurl;
27
+ static VALUE idCall;
28
+
29
+ #ifdef RDOC_NEVER_DEFINED
30
+ mCurl = rb_define_module("Curl");
31
+ #endif
32
+
33
+ VALUE cCurlMulti;
34
+
35
+ static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
36
+
37
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
38
+ static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
39
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
40
+
41
+ static VALUE callback_exception(VALUE unused) {
42
+ return Qfalse;
43
+ }
44
+
45
+ static void curl_multi_mark(ruby_curl_multi *rbcm) {
46
+ rb_gc_mark(rbcm->requests);
47
+ }
48
+
49
+ static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
50
+ CURLMcode result;
51
+ ruby_curl_easy *rbce;
52
+
53
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
54
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
55
+ if (result != 0) {
56
+ raise_curl_multi_error_exception(result);
57
+ }
58
+ }
59
+
60
+ static int
61
+ rb_hash_clear_i(VALUE key, VALUE value, VALUE dummy) {
62
+ return ST_DELETE;
63
+ }
64
+
65
+ static void curl_multi_free(ruby_curl_multi *rbcm) {
66
+
67
+ if (rbcm && !rbcm->requests == Qnil && rb_type(rbcm->requests) == T_HASH && RHASH_LEN(rbcm->requests) > 0) {
68
+
69
+ rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
70
+
71
+ rb_hash_foreach(rbcm->requests, rb_hash_clear_i, 0); //rb_hash_clear(rbcm->requests);
72
+ rbcm->requests = Qnil;
73
+ }
74
+ curl_multi_cleanup(rbcm->handle);
75
+ free(rbcm);
76
+ }
77
+
78
+ /*
79
+ * call-seq:
80
+ * Curl::Multi.new => #&lt;Curl::Easy...&gt;
81
+ *
82
+ * Create a new Curl::Multi instance
83
+ */
84
+ VALUE ruby_curl_multi_new(VALUE klass) {
85
+ VALUE new_curlm;
86
+
87
+ ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
88
+
89
+ rbcm->handle = curl_multi_init();
90
+ if (!rbcm->handle) {
91
+ rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
92
+ }
93
+
94
+ rbcm->requests = rb_hash_new();
95
+
96
+ rbcm->active = 0;
97
+ rbcm->running = 0;
98
+
99
+ new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
100
+
101
+ return new_curlm;
102
+ }
103
+
104
+ /*
105
+ * call-seq:
106
+ * Curl::Multi.default_timeout = 4 => 4
107
+ *
108
+ * Set the global default time out for all Curl::Multi Handles. This value is used
109
+ * when libcurl cannot determine a timeout value when calling curl_multi_timeout.
110
+ *
111
+ */
112
+ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
113
+ cCurlMutiDefaulttimeout = FIX2LONG(timeout);
114
+ return timeout;
115
+ }
116
+
117
+ /*
118
+ * call-seq:
119
+ * Curl::Multi.default_timeout = 4 => 4
120
+ *
121
+ * Get the global default time out for all Curl::Multi Handles.
122
+ *
123
+ */
124
+ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
125
+ return INT2FIX(cCurlMutiDefaulttimeout);
126
+ }
127
+
128
+ /* Hash#foreach callback for ruby_curl_multi_requests */
129
+ static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
130
+ rb_ary_push(result_array, value);
131
+
132
+ return ST_CONTINUE;
133
+ }
134
+
135
+ /*
136
+ * call-seq:
137
+ * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
138
+ *
139
+ * Returns an array containing all the active requests on this Curl::Multi object.
140
+ */
141
+ static VALUE ruby_curl_multi_requests(VALUE self) {
142
+ ruby_curl_multi *rbcm;
143
+ VALUE result_array;
144
+
145
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
146
+
147
+ result_array = rb_ary_new();
148
+
149
+ /* iterate over the requests hash, and stuff references into the array. */
150
+ rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
151
+
152
+ return result_array;
153
+ }
154
+
155
+ /*
156
+ * call-seq:
157
+ * multi.idle? => true or false
158
+ *
159
+ * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
160
+ * true when multi.requests.length == 0.
161
+ */
162
+ static VALUE ruby_curl_multi_idle(VALUE self) {
163
+ ruby_curl_multi *rbcm;
164
+
165
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
166
+
167
+ if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
168
+ return Qtrue;
169
+ } else {
170
+ return Qfalse;
171
+ }
172
+ }
173
+
174
+ /*
175
+ * call-seq:
176
+ * multi = Curl::Multi.new
177
+ * multi.max_connects = 800
178
+ *
179
+ * Set the max connections in the cache for a multi handle
180
+ */
181
+ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
182
+ #ifdef HAVE_CURLMOPT_MAXCONNECTS
183
+ ruby_curl_multi *rbcm;
184
+
185
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
186
+ curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
187
+ #endif
188
+
189
+ return count;
190
+ }
191
+
192
+ /*
193
+ * call-seq:
194
+ * multi = Curl::Multi.new
195
+ * multi.pipeline = true
196
+ *
197
+ * Pass a long set to 1 to enable or 0 to disable. Enabling pipelining on a multi handle will make it
198
+ * attempt to perform HTTP Pipelining as far as possible for transfers using this handle. This means
199
+ * that if you add a second request that can use an already existing connection, the second request will
200
+ * be "piped" on the same connection rather than being executed in parallel. (Added in 7.16.0)
201
+ *
202
+ */
203
+ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE onoff) {
204
+ #ifdef HAVE_CURLMOPT_PIPELINING
205
+ ruby_curl_multi *rbcm;
206
+
207
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
208
+ curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, onoff == Qtrue ? 1 : 0);
209
+ #endif
210
+ return onoff;
211
+ }
212
+
213
+ /*
214
+ * call-seq:
215
+ * multi = Curl::Multi.new
216
+ * easy = Curl::Easy.new('url')
217
+ *
218
+ * multi.add(easy)
219
+ *
220
+ * Add an easy handle to the multi stack
221
+ */
222
+ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
223
+ CURLMcode mcode;
224
+ ruby_curl_easy *rbce;
225
+ ruby_curl_multi *rbcm;
226
+
227
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
228
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
229
+
230
+ /* setup the easy handle */
231
+ ruby_curl_easy_setup( rbce );
232
+
233
+ mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
234
+ if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
235
+ raise_curl_multi_error_exception(mcode);
236
+ }
237
+
238
+ rbcm->active++;
239
+
240
+ /* Increase the running count, so that the perform loop keeps running.
241
+ * If this number is not correct, the next call to curl_multi_perform will correct it. */
242
+ rbcm->running++;
243
+
244
+ rb_hash_aset( rbcm->requests, easy, easy );
245
+
246
+ return self;
247
+ }
248
+
249
+ /*
250
+ * call-seq:
251
+ * multi = Curl::Multi.new
252
+ * easy = Curl::Easy.new('url')
253
+ *
254
+ * multi.add(easy)
255
+ *
256
+ * # sometime later
257
+ * multi.remove(easy)
258
+ *
259
+ * Remove an easy handle from a multi stack.
260
+ *
261
+ * Will raise an exception if the easy handle is not found
262
+ */
263
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
264
+ ruby_curl_multi *rbcm;
265
+ ruby_curl_easy *rbce;
266
+
267
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
268
+
269
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
270
+
271
+ rb_curl_multi_remove(rbcm,easy);
272
+
273
+ return self;
274
+ }
275
+ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
276
+ CURLMcode result;
277
+ ruby_curl_easy *rbce;
278
+ VALUE r;
279
+
280
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
281
+
282
+ result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
283
+ if (result != 0) {
284
+ raise_curl_multi_error_exception(result);
285
+ }
286
+
287
+ rbcm->active--;
288
+
289
+ ruby_curl_easy_cleanup( easy, rbce );
290
+
291
+ // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
292
+ r = rb_hash_delete( rbcm->requests, easy );
293
+ if( r != easy || r == Qnil ) {
294
+ rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
295
+ }
296
+ }
297
+
298
+ /* Hash#foreach callback for ruby_curl_multi_cancel */
299
+ static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
300
+ rb_curl_multi_remove(rbcm, value);
301
+
302
+ return ST_CONTINUE;
303
+ }
304
+
305
+ /*
306
+ * call-seq:
307
+ * multi.cancel!
308
+ *
309
+ * Cancels all requests currently being made on this Curl::Multi handle.
310
+ */
311
+ static VALUE ruby_curl_multi_cancel(VALUE self) {
312
+ ruby_curl_multi *rbcm;
313
+
314
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
315
+
316
+ rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
317
+
318
+ /* for chaining */
319
+ return self;
320
+ }
321
+
322
+ // on_success, on_failure, on_complete
323
+ static VALUE call_status_handler1(VALUE ary) {
324
+ return rb_funcall(rb_ary_entry(ary, 0), idCall, 1, rb_ary_entry(ary, 1));
325
+ }
326
+ static VALUE call_status_handler2(VALUE ary) {
327
+ return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
328
+ }
329
+
330
+ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
331
+
332
+ long response_code = -1;
333
+ VALUE easy;
334
+ ruby_curl_easy *rbce = NULL;
335
+ VALUE callargs, val = Qtrue;
336
+
337
+ CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
338
+
339
+ Data_Get_Struct(easy, ruby_curl_easy, rbce);
340
+
341
+ rbce->last_result = result; /* save the last easy result code */
342
+
343
+ ruby_curl_multi_remove( self, easy );
344
+
345
+ /* after running a request cleanup the headers, these are set before each request */
346
+ if (rbce->curl_headers) {
347
+ curl_slist_free_all(rbce->curl_headers);
348
+ rbce->curl_headers = NULL;
349
+ }
350
+
351
+ if (ecode != 0) {
352
+ raise_curl_easy_error_exception(ecode);
353
+ }
354
+
355
+ if (!rb_easy_nil("complete_proc")) {
356
+ callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), easy);
357
+ rbce->callback_active = 1;
358
+ val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
359
+ rbce->callback_active = 0;
360
+ //rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
361
+ }
362
+
363
+ curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
364
+
365
+ if (result != 0) {
366
+ if (!rb_easy_nil("failure_proc")) {
367
+ callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
368
+ rbce->callback_active = 1;
369
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
370
+ rbce->callback_active = 0;
371
+ //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
372
+ }
373
+ }
374
+ else if (!rb_easy_nil("success_proc") &&
375
+ ((response_code >= 200 && response_code < 300) || response_code == 0)) {
376
+ /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
377
+ callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
378
+ rbce->callback_active = 1;
379
+ val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
380
+ rbce->callback_active = 0;
381
+ //rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
382
+ }
383
+ else if (!rb_easy_nil("redirect_proc") &&
384
+ (response_code >= 300 && response_code < 400)) {
385
+ rbce->callback_active = 1;
386
+ callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
387
+ rbce->callback_active = 0;
388
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
389
+ }
390
+ else if (!rb_easy_nil("missing_proc") &&
391
+ (response_code >= 400 && response_code < 500)) {
392
+ rbce->callback_active = 1;
393
+ callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
394
+ rbce->callback_active = 0;
395
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
396
+ }
397
+ else if (!rb_easy_nil("failure_proc") &&
398
+ (response_code >= 500 && response_code <= 999)) {
399
+ callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
400
+ rbce->callback_active = 1;
401
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
402
+ rbce->callback_active = 0;
403
+ //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
404
+ }
405
+
406
+ if (val == Qfalse) {
407
+ rb_warn("uncaught exception from callback");
408
+ // exception was raised?
409
+ //fprintf(stderr, "exception raised from callback\n");
410
+ }
411
+
412
+ }
413
+
414
+ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
415
+ int msgs_left, result;
416
+ CURLMsg *msg;
417
+ CURL *easy_handle;
418
+
419
+ /* check for finished easy handles and remove from the multi handle */
420
+ while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
421
+ if (msg->msg == CURLMSG_DONE) {
422
+ easy_handle = msg->easy_handle;
423
+ result = msg->data.result;
424
+ if (easy_handle) {
425
+ rb_curl_mutli_handle_complete(self, easy_handle, result);
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ /* called within ruby_curl_multi_perform */
432
+ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
433
+ CURLMcode mcode;
434
+
435
+ do {
436
+ mcode = curl_multi_perform(multi_handle, still_running);
437
+ } while (mcode == CURLM_CALL_MULTI_PERFORM);
438
+
439
+ if (mcode != CURLM_OK) {
440
+ raise_curl_multi_error_exception(mcode);
441
+ }
442
+
443
+ }
444
+
445
+ #ifdef _WIN32
446
+ void create_crt_fd(fd_set *os_set, fd_set *crt_set)
447
+ {
448
+ int i;
449
+ crt_set->fd_count = os_set->fd_count;
450
+ for (i = 0; i < os_set->fd_count; i++) {
451
+ WSAPROTOCOL_INFO wsa_pi;
452
+ // dupicate the SOCKET
453
+ int r = WSADuplicateSocket(os_set->fd_array[i], GetCurrentProcessId(), &wsa_pi);
454
+ SOCKET s = WSASocket(wsa_pi.iAddressFamily, wsa_pi.iSocketType, wsa_pi.iProtocol, &wsa_pi, 0, 0);
455
+ // create the CRT fd so ruby can get back to the SOCKET
456
+ int fd = _open_osfhandle(s, O_RDWR|O_BINARY);
457
+ os_set->fd_array[i] = s;
458
+ crt_set->fd_array[i] = fd;
459
+ }
460
+ }
461
+
462
+ void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
463
+ {
464
+ int i;
465
+ for (i = 0; i < os_set->fd_count; i++) {
466
+ // cleanup the CRT fd
467
+ _close(crt_set->fd_array[i]);
468
+ // cleanup the duplicated SOCKET
469
+ closesocket(os_set->fd_array[i]);
470
+ }
471
+ }
472
+ #endif
473
+
474
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
475
+ struct _select_set {
476
+ int maxfd;
477
+ fd_set *fdread, *fdwrite, *fdexcep;
478
+ struct timeval *tv;
479
+ };
480
+
481
+ static VALUE curb_select(void *args) {
482
+ struct _select_set* set = args;
483
+ int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
484
+ return INT2FIX(rc);
485
+ }
486
+ #endif
487
+
488
+ /*
489
+ * call-seq:
490
+ * multi = Curl::Multi.new
491
+ * easy1 = Curl::Easy.new('url')
492
+ * easy2 = Curl::Easy.new('url')
493
+ *
494
+ * multi.add(easy1)
495
+ * multi.add(easy2)
496
+ *
497
+ * multi.perform do
498
+ * # while idle other code my execute here
499
+ * end
500
+ *
501
+ * Run multi handles, looping selecting when data can be transfered
502
+ */
503
+ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
504
+ CURLMcode mcode;
505
+ ruby_curl_multi *rbcm;
506
+ int maxfd, rc;
507
+ fd_set fdread, fdwrite, fdexcep;
508
+ #ifdef _WIN32
509
+ fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
510
+ #endif
511
+ long timeout_milliseconds;
512
+ struct timeval tv = {0, 0};
513
+ VALUE block = Qnil;
514
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
515
+ struct _select_set fdset_args;
516
+ #endif
517
+
518
+ rb_scan_args(argc, argv, "0&", &block);
519
+
520
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
521
+
522
+ timeout_milliseconds = cCurlMutiDefaulttimeout;
523
+
524
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
525
+ rb_curl_multi_read_info( self, rbcm->handle );
526
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
527
+
528
+ do {
529
+ while (rbcm->running) {
530
+
531
+ #ifdef HAVE_CURL_MULTI_TIMEOUT
532
+ /* get the curl suggested time out */
533
+ mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
534
+ if (mcode != CURLM_OK) {
535
+ raise_curl_multi_error_exception(mcode);
536
+ }
537
+ #else
538
+ /* libcurl doesn't have a timeout method defined, initialize to -1 we'll pick up the default later */
539
+ timeout_milliseconds = -1;
540
+ #endif
541
+
542
+ if (timeout_milliseconds == 0) { /* no delay */
543
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
544
+ rb_curl_multi_read_info( self, rbcm->handle );
545
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
546
+ continue;
547
+ }
548
+
549
+ if (timeout_milliseconds < 0 || timeout_milliseconds > cCurlMutiDefaulttimeout) {
550
+ timeout_milliseconds = cCurlMutiDefaulttimeout; /* libcurl doesn't know how long to wait, use a default timeout */
551
+ /* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
552
+ }
553
+
554
+ tv.tv_sec = 0; /* never wait longer than 1 second */
555
+ tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */
556
+
557
+ FD_ZERO(&fdread);
558
+ FD_ZERO(&fdwrite);
559
+ FD_ZERO(&fdexcep);
560
+
561
+ /* load the fd sets from the multi handle */
562
+ mcode = curl_multi_fdset(rbcm->handle, &fdread, &fdwrite, &fdexcep, &maxfd);
563
+ if (mcode != CURLM_OK) {
564
+ raise_curl_multi_error_exception(mcode);
565
+ }
566
+
567
+ #ifdef _WIN32
568
+ create_crt_fd(&fdread, &crt_fdread);
569
+ create_crt_fd(&fdwrite, &crt_fdwrite);
570
+ create_crt_fd(&fdexcep, &crt_fdexcep);
571
+ #endif
572
+
573
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
574
+ fdset_args.maxfd = maxfd+1;
575
+ fdset_args.fdread = &fdread;
576
+ fdset_args.fdwrite = &fdwrite;
577
+ fdset_args.fdexcep = &fdexcep;
578
+ fdset_args.tv = &tv;
579
+ rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
580
+ #else
581
+ rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
582
+ #endif
583
+
584
+ #ifdef _WIN32
585
+ cleanup_crt_fd(&fdread, &crt_fdread);
586
+ cleanup_crt_fd(&fdwrite, &crt_fdwrite);
587
+ cleanup_crt_fd(&fdexcep, &crt_fdexcep);
588
+ #endif
589
+
590
+ switch(rc) {
591
+ case -1:
592
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
593
+ break;
594
+ case 0: /* timeout */
595
+ default: /* action */
596
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
597
+ rb_curl_multi_read_info( self, rbcm->handle );
598
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
599
+ break;
600
+ }
601
+ }
602
+
603
+ } while( rbcm->running );
604
+
605
+ rb_curl_multi_read_info( self, rbcm->handle );
606
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
607
+
608
+ return Qtrue;
609
+ }
610
+
611
+ /* =================== INIT LIB =====================*/
612
+ void init_curb_multi() {
613
+ idCall = rb_intern("call");
614
+
615
+ cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
616
+
617
+ /* Class methods */
618
+ rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
619
+ rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
620
+ rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
621
+
622
+ /* "Attributes" */
623
+ rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
624
+ rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
625
+
626
+ /* Instnace methods */
627
+ rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
628
+ rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
629
+ rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
630
+ rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
631
+ rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
632
+ rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
633
+ }