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