juozasg-curl-multi 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Manifest.txt ADDED
@@ -0,0 +1,25 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/curl-multi.rb
9
+ lib/curl-multi/version.rb
10
+ lib/uri-extensions.rb
11
+ log/debug.log
12
+ script/destroy
13
+ script/generate
14
+ script/txt2html
15
+ setup.rb
16
+ tasks/deployment.rake
17
+ tasks/environment.rake
18
+ tasks/website.rake
19
+ test/test_curl-multi.rb
20
+ test/test_helper.rb
21
+ website/index.html
22
+ website/index.txt
23
+ website/javascripts/rounded_corners_lite.inc.js
24
+ website/stylesheets/screen.css
25
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,7 @@
1
+ curl-multi - Ruby bindings for the libcurl multi interface
2
+ Copyright (C) 2007 Philotic, Inc.
3
+
4
+ See http://curl-multi.rubyforge.net/ for instructions.
5
+
6
+ If you enjoy curl-multi, check out http://xph.us/software/ for other software
7
+ you might find useful.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'curl-multi/version'
2
+
3
+ AUTHOR = ['Kristjan Petursson', 'Keith Rarick']
4
+ EMAIL = "kr@essembly.com"
5
+ DESCRIPTION = 'Ruby bindings for the libcurl multi interface'
6
+ GEM_NAME = 'curl-multi' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'curl-multi' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Curl::Multi::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'curl-multi documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ p.extra_deps = [['RubyInline', '>= 3.6.2'], ['curb', '>= 0.1.2']]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
71
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'curl-multi'
data/lib/curl-multi.rb ADDED
@@ -0,0 +1,380 @@
1
+ # curl-multi - Ruby bindings for the libcurl multi interface
2
+ # Copyright (C) 2007 Philotic, Inc.
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ $:.unshift File.dirname(__FILE__)
18
+
19
+ require 'rubygems'
20
+ require 'inline'
21
+ require 'uri-extensions'
22
+ require 'curb'
23
+
24
+ module Curl
25
+ class Error < RuntimeError
26
+ def initialize(code)
27
+ super("Curl error #{code}")
28
+ end
29
+ end
30
+
31
+ class HTTPError < RuntimeError
32
+ def initialize(status)
33
+ super("Curl HTTP error #{status}")
34
+ end
35
+ end
36
+
37
+ class MultiError < RuntimeError
38
+ attr_reader :errors
39
+ def initialize(errors)
40
+ @errors = errors
41
+ super("Multiple errors: #{errors}")
42
+ end
43
+ end
44
+
45
+ # An instance of this class collects the response for each curl easy handle.
46
+ class Req
47
+ CURLE_OK = 0
48
+
49
+ attr_reader :url, :body
50
+
51
+ def done(code, status)
52
+ @done = true
53
+ if code != CURLE_OK
54
+ @error = Error.new(code)
55
+ elsif status != 200
56
+ @error = HTTPError.new(status)
57
+ end
58
+ end
59
+
60
+ def done?() @done end
61
+
62
+ def failed?() !!@error end
63
+
64
+ def do_success() @success.call(body) end
65
+
66
+ def do_failure() @failure.call(@error) end
67
+
68
+ def inspect() "{Curl::Req #{url}}" end
69
+ alias_method :to_s, :inspect
70
+
71
+ def initialize(url, success, failure)
72
+ @url = url
73
+ @success = success
74
+ @failure = failure
75
+ @body = ''
76
+ @done = false
77
+ @error = nil
78
+ end
79
+
80
+ def add_chunk(chunk)
81
+ @body = body + chunk
82
+ end
83
+ end
84
+
85
+ class Multi
86
+ def size() @handles.size end
87
+
88
+ def inspect() '{Curl::Multi' + @handles.map{|h| ' '+h.url}.join + '}' end
89
+ alias_method :to_s, :inspect
90
+
91
+ def get(url, success, failure=lambda{})
92
+ add(url, nil, success, failure)
93
+ end
94
+
95
+ def post(url, params, success, failure=lambda{})
96
+ add(url, URI.escape_params(params), success, failure)
97
+ end
98
+
99
+ def select(rfds, wfds)
100
+ loop do
101
+ ready_rfds, ready_wfds = c_select(rfds.map{|s|s.fileno},
102
+ wfds.map{|s|s.fileno})
103
+ work() # Curl may or may not have work to do, but we can't tell.
104
+ return ready_rfds, ready_wfds if ready_rfds.any? or ready_wfds.any?
105
+ return [], [] if rfds == [] and wfds == []
106
+ end
107
+ end
108
+
109
+ STACK_DIVIDER = ' -- request stack trace is below this line -- :0'.freeze
110
+
111
+ # Do as much work as possible without blocking on the network. That is,
112
+ # read as much data as is ready and write as much data as we have buffer
113
+ # space for. If any complete responses arrive, call their handlers.
114
+ def work
115
+ perform()
116
+
117
+ done, @handles = @handles.partition{|h| h.done?}
118
+ failed, done = done.partition{|h| h.failed?}
119
+
120
+ errors = []
121
+ errors += post_process(done) {|x| x.do_success}
122
+ errors += post_process(failed) {|x| x.do_failure}
123
+ raise errors[0] if errors.size == 1
124
+ raise MultiError.new(errors) if errors.size > 1
125
+
126
+ :ok
127
+ rescue Exception => ex
128
+ ex.set_backtrace(ex.backtrace + [STACK_DIVIDER] + @creation_stack)
129
+ raise ex
130
+ end
131
+
132
+ def cleanup
133
+ @handles = []
134
+ :ok
135
+ end
136
+
137
+ def initialize
138
+ @handles = []
139
+ end
140
+
141
+ # Add a URL to the queue of items to fetch
142
+ def add(url, body, success, failure=lambda{})
143
+ @creation_stack = caller() # save for later, just in case
144
+ while (h = add_to_curl(Req.new(url, success, failure), url, body)) == nil
145
+ select([], [])
146
+ end
147
+ @handles << h
148
+ work()
149
+ :ok
150
+ end
151
+
152
+ def post_process(handles)
153
+ errors = []
154
+ handles.each do |h|
155
+ begin
156
+ yield(h) # This ought not to raise anything.
157
+ rescue => ex
158
+ errors << ex
159
+ end
160
+ end
161
+ return errors
162
+ end
163
+
164
+ inline do |builder|
165
+ builder.include('<errno.h>')
166
+ if File.exists?('/usr/include/curl/curl.h')
167
+ builder.include('"/usr/include/curl/curl.h"')
168
+ elsif File.exists?('/opt/csw/include/curl/curl.h')
169
+ builder.include('"/opt/csw/include/curl/curl.h"')
170
+ else
171
+ builder.include('"/usr/local/include/curl/curl.h"')
172
+ end
173
+
174
+ builder.prefix <<-end
175
+ #define GET_MULTI_HANDLE(name) \
176
+ CURLM *(name);\
177
+ Data_Get_Struct(self, CURLM, (name));\
178
+ if (!(name)) rb_raise(rb_eTypeError,\
179
+ "Expected initialized curl multi handle")
180
+
181
+ #define CHECK(e) do {\
182
+ if (!(e)) rb_raise(rb_eTypeError, "curl error");\
183
+ } while (0)
184
+
185
+ #define CHECKN(e) CHECK(!(e))
186
+
187
+ ID id_initialize, id_done, id_add_chunk, id_size;
188
+ end
189
+
190
+ builder.c_raw_singleton <<-end
191
+ void c_curl_multi_cleanup(void *x)
192
+ {
193
+ curl_multi_cleanup(x);
194
+ }
195
+ end
196
+
197
+ # Creates a new CurlMulti handle
198
+ builder.c_singleton <<-end
199
+ VALUE new() {
200
+ CURLcode r;
201
+ VALUE inst;
202
+
203
+ /* can't think of a better place to put this */
204
+ id_initialize = rb_intern("initialize");
205
+ id_done = rb_intern("done");
206
+ id_add_chunk = rb_intern("add_chunk");
207
+ id_size = rb_intern("size");
208
+
209
+ /* must be called at least once before any other curl functions */
210
+ r = curl_global_init(CURL_GLOBAL_ALL);
211
+ CHECKN(r);
212
+
213
+ inst = Data_Wrap_Struct(self, 0, c_curl_multi_cleanup, curl_multi_init());
214
+ rb_funcall(inst, id_initialize, 0);
215
+ return inst;
216
+ }
217
+ end
218
+
219
+ # We tell CurlEasy handles to write to this function in the constructor.
220
+ # The self argument is a parameter we set up with CURLOPT_WRITEDATA that
221
+ # lets us pass whatever data we'd like into the write fn.
222
+ builder.c_raw_singleton <<-end
223
+ uint c_add_chunk(char *chunk, uint size, uint nmemb, VALUE rb_req) {
224
+ uint bytes = size * nmemb; /* Number of bytes of data */
225
+ if (bytes == 0) return 0;
226
+
227
+ /* This is (rubyInstance, methodId, numArgs, arg1, arg2...) */
228
+ rb_funcall(rb_req, id_add_chunk, 1, rb_str_new(chunk, bytes));
229
+ return bytes;
230
+ }
231
+ end
232
+
233
+ # Adds an easy handle to the multi handle's list
234
+ builder.c <<-end
235
+ VALUE add_to_curl(VALUE rb_req, VALUE url, VALUE body) {
236
+ CURLMcode r;
237
+ GET_MULTI_HANDLE(multi_handle);
238
+
239
+ /* We start getting errors if we have too many open connections at
240
+ once, so make a hard limit. */
241
+ if (FIX2INT(rb_funcall(self, id_size, 0)) > 500) return Qnil;
242
+
243
+ CURL *easy_handle = curl_easy_init();
244
+ char *c_url = StringValuePtr(url);
245
+
246
+ /* Pass it the URL */
247
+ curl_easy_setopt(easy_handle, CURLOPT_URL, c_url);
248
+
249
+ /* GET or POST? */
250
+ if (body != Qnil) {
251
+ char *c_body = StringValuePtr(body);
252
+ uint body_sz = RSTRING(body)->len;
253
+ curl_easy_setopt(easy_handle, CURLOPT_POST, 1);
254
+ curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, c_body);
255
+ curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDSIZE, body_sz);
256
+ }
257
+
258
+ /* Tell curl to use our callbacks */
259
+ curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, c_add_chunk);
260
+
261
+ /* Make curl give us a ruby pointer in the callbacks */
262
+ curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, rb_req);
263
+ curl_easy_setopt(easy_handle, CURLOPT_PRIVATE, rb_req);
264
+
265
+ /* Add it to the multi handle */
266
+ r = curl_multi_add_handle(multi_handle, easy_handle);
267
+ CHECKN(r);
268
+
269
+ return rb_req;
270
+ }
271
+ end
272
+
273
+ # Wait until one of the given fds or one of curl's fds is ready.
274
+ # Return two arrays of fds that are ready.
275
+ builder.c <<-end
276
+ void c_select(VALUE rfda, VALUE wfda) {
277
+ int i, r, n = -1;
278
+ long timeout;
279
+ fd_set rfds, wfds, efds;
280
+ CURLMcode cr;
281
+ VALUE ready_rfda, ready_wfda;
282
+ struct timeval tv = {0, 0};
283
+ GET_MULTI_HANDLE(multi_handle);
284
+
285
+ FD_ZERO(&rfds);
286
+ FD_ZERO(&wfds);
287
+ FD_ZERO(&efds);
288
+
289
+ /* Put curl's fds into the sets. */
290
+ cr = curl_multi_fdset(multi_handle, &rfds, &wfds, &efds, &n);
291
+ CHECKN(cr);
292
+
293
+ /* Put the given fds into the sets. */
294
+ for (i = 0; i < RARRAY(rfda)->len; i++) {
295
+ int fd = FIX2INT(RARRAY(rfda)->ptr[i]);
296
+ FD_SET(fd, &rfds);
297
+ n = n > fd ? n : fd;
298
+ }
299
+ for (i = 0; i < RARRAY(wfda)->len; i++) {
300
+ int fd = FIX2INT(RARRAY(wfda)->ptr[i]);
301
+ FD_SET(fd, &wfds);
302
+ n = n > fd ? n : fd;
303
+ }
304
+
305
+ cr = curl_multi_timeout(multi_handle, &timeout);
306
+ CHECKN(cr);
307
+
308
+ tv.tv_sec = timeout / 1000;
309
+ tv.tv_usec = (timeout * 1000) % 1000000;
310
+
311
+ /* Wait */
312
+ r = select(n + 1, &rfds, &wfds, &efds, (timeout < 0) ? NULL : &tv);
313
+ if (r < 0) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
314
+
315
+ ready_rfda = rb_ary_new();
316
+ ready_wfda = rb_ary_new();
317
+
318
+ /* Collect the fds that are ready */
319
+ for (i = 0; i < RARRAY(rfda)->len; i++) {
320
+ VALUE fd = FIX2INT(RARRAY(rfda)->ptr[i]);
321
+ if (FD_ISSET(fd, &rfds)) rb_ary_push(ready_rfda, INT2FIX(fd));
322
+ }
323
+ for (i = 0; i < RARRAY(wfda)->len; i++) {
324
+ VALUE fd = FIX2INT(RARRAY(wfda)->ptr[i]);
325
+ if (FD_ISSET(fd, &wfds)) rb_ary_push(ready_wfda, INT2FIX(fd));
326
+ }
327
+
328
+ return rb_ary_new3(2, ready_rfda, ready_wfda);
329
+ }
330
+ end
331
+
332
+ # Basically just a wrapper for curl_multi_perform().
333
+ builder.c <<-end
334
+ void perform() {
335
+ CURLMsg *msg;
336
+ CURLcode er;
337
+ CURLMcode r;
338
+ int status;
339
+ int running;
340
+ GET_MULTI_HANDLE(multi_handle);
341
+
342
+ /* do some work */
343
+ do {
344
+ r = curl_multi_perform(multi_handle, &running);
345
+ } while (r == CURLM_CALL_MULTI_PERFORM);
346
+ CHECK(r == CURLM_OK);
347
+
348
+ /* check which ones are done and mark them as done */
349
+ while ((msg = curl_multi_info_read(multi_handle, &r))) {
350
+ VALUE rb_req;
351
+ CURL *easy_handle;
352
+
353
+ if (msg->msg != CURLMSG_DONE) continue;
354
+
355
+ /* Save out the easy handle, because the msg struct becomes invalid
356
+ * whene we call curl_easy_cleanup, curl_multi_remove_handle, or
357
+ * curl_easy_cleanup. */
358
+ easy_handle = msg->easy_handle;
359
+
360
+ r = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &rb_req);
361
+ CHECKN(r);
362
+
363
+ er = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE,
364
+ &status);
365
+ CHECKN(er);
366
+
367
+ rb_funcall(rb_req, id_done, 2, INT2FIX(msg->data.result),
368
+ INT2FIX(status));
369
+
370
+ r = curl_multi_remove_handle(DATA_PTR(self), easy_handle);
371
+ CHECKN(r);
372
+
373
+ /* Free the handle */
374
+ curl_easy_cleanup(easy_handle);
375
+ }
376
+ }
377
+ end
378
+ end
379
+ end
380
+ end