curb 0.7.15 → 0.8.4

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/lib/curl/easy.rb ADDED
@@ -0,0 +1,468 @@
1
+ module Curl
2
+ class Easy
3
+
4
+ alias post http_post
5
+ alias put http_put
6
+
7
+ #
8
+ # call-seq:
9
+ # easy.status => String
10
+ #
11
+ def status
12
+ parts = self.header_str.split(/\s/)
13
+ status = []
14
+ parts.shift
15
+ while parts.size > 0 && parts.first != ''
16
+ status << parts.shift
17
+ end
18
+ status.join(' ')
19
+ end
20
+
21
+ #
22
+ # call-seq:
23
+ # easy.set :sym|Fixnum, value
24
+ #
25
+ # set options on the curl easy handle see http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
26
+ #
27
+ def set(opt,val)
28
+ if opt.is_a?(Symbol)
29
+ setopt(sym2curl(opt), val)
30
+ else
31
+ setopt(opt.to_i, val)
32
+ end
33
+ end
34
+
35
+ #
36
+ # call-seq:
37
+ # easy.sym2curl :symbol => Fixnum
38
+ #
39
+ # translates ruby symbols to libcurl options
40
+ #
41
+ def sym2curl(opt)
42
+ Curl.const_get("CURLOPT_#{opt.to_s.upcase}")
43
+ end
44
+
45
+ #
46
+ # call-seq:
47
+ # easy.perform => true
48
+ #
49
+ # Transfer the currently configured URL using the options set for this
50
+ # Curl::Easy instance. If this is an HTTP URL, it will be transferred via
51
+ # the configured HTTP Verb.
52
+ #
53
+ def perform
54
+ self.multi = Curl::Multi.new if self.multi.nil?
55
+ self.multi.add self
56
+ ret = self.multi.perform
57
+
58
+ if self.last_result != 0 && self.on_failure.nil?
59
+ error = Curl::Easy.error(self.last_result)
60
+ raise error.first
61
+ end
62
+
63
+ ret
64
+ end
65
+
66
+ #
67
+ # call-seq:
68
+ #
69
+ # easy = Curl::Easy.new
70
+ # easy.nosignal = true
71
+ #
72
+ def nosignal=(onoff)
73
+ set :nosignal, !!onoff
74
+ end
75
+
76
+ #
77
+ # call-seq:
78
+ # easy = Curl::Easy.new("url") do|c|
79
+ # c.delete = true
80
+ # end
81
+ # easy.perform
82
+ #
83
+ def delete=(onoff)
84
+ set :customrequest, onoff ? 'delete' : nil
85
+ onoff
86
+ end
87
+ #
88
+ # call-seq:
89
+ #
90
+ # easy = Curl::Easy.new("url")
91
+ # easy.version = Curl::HTTP_1_1
92
+ # easy.version = Curl::HTTP_1_0
93
+ # easy.version = Curl::HTTP_NONE
94
+ #
95
+ def version=(http_version)
96
+ set :http_version, http_version
97
+ end
98
+
99
+ #
100
+ # call-seq:
101
+ # easy.url = "http://some.url/" => "http://some.url/"
102
+ #
103
+ # Set the URL for subsequent calls to +perform+. It is acceptable
104
+ # (and even recommended) to reuse Curl::Easy instances by reassigning
105
+ # the URL between calls to +perform+.
106
+ #
107
+ def url=(u)
108
+ set :url, u
109
+ end
110
+
111
+ #
112
+ # call-seq:
113
+ # easy.proxy_url = string => string
114
+ #
115
+ # Set the URL of the HTTP proxy to use for subsequent calls to +perform+.
116
+ # The URL should specify the the host name or dotted IP address. To specify
117
+ # port number in this string, append :[port] to the end of the host name.
118
+ # The proxy string may be prefixed with [protocol]:// since any such prefix
119
+ # will be ignored. The proxy's port number may optionally be specified with
120
+ # the separate option proxy_port .
121
+ #
122
+ # When you tell the library to use an HTTP proxy, libcurl will transparently
123
+ # convert operations to HTTP even if you specify an FTP URL etc. This may have
124
+ # an impact on what other features of the library you can use, such as
125
+ # FTP specifics that don't work unless you tunnel through the HTTP proxy. Such
126
+ # tunneling is activated with proxy_tunnel = true.
127
+ #
128
+ # libcurl respects the environment variables *http_proxy*, *ftp_proxy*,
129
+ # *all_proxy* etc, if any of those is set. The proxy_url option does however
130
+ # override any possibly set environment variables.
131
+ #
132
+ # Starting with libcurl 7.14.1, the proxy host string given in environment
133
+ # variables can be specified the exact same way as the proxy can be set with
134
+ # proxy_url, including protocol prefix (http://) and embedded user + password.
135
+ #
136
+ def proxy_url=(url)
137
+ set :proxy, url
138
+ end
139
+
140
+ def ssl_verify_host=(value)
141
+ value = 1 if value.class == TrueClass
142
+ value = 0 if value.class == FalseClass
143
+ self.ssl_verify_host_integer=value
144
+ end
145
+
146
+ #
147
+ # call-seq:
148
+ # easy.ssl_verify_host? => boolean
149
+ #
150
+ # Deprecated: call easy.ssl_verify_host instead
151
+ # can be one of [0,1,2]
152
+ #
153
+ # Determine whether this Curl instance will verify that the server cert
154
+ # is for the server it is known as.
155
+ #
156
+ def ssl_verify_host?
157
+ ssl_verify_host.nil? ? false : (ssl_verify_host > 0)
158
+ end
159
+
160
+ #
161
+ # call-seq:
162
+ # easy.interface = string => string
163
+ #
164
+ # Set the interface name to use as the outgoing network interface.
165
+ # The name can be an interface name, an IP address or a host name.
166
+ #
167
+ def interface=(value)
168
+ set :interface, value
169
+ end
170
+
171
+ #
172
+ # call-seq:
173
+ # easy.userpwd = string => string
174
+ #
175
+ # Set the username/password string to use for subsequent calls to +perform+.
176
+ # The supplied string should have the form "username:password"
177
+ #
178
+ def userpwd=(value)
179
+ set :userpwd, value
180
+ end
181
+
182
+ #
183
+ # call-seq:
184
+ # easy.proxypwd = string => string
185
+ #
186
+ # Set the username/password string to use for proxy connection during
187
+ # subsequent calls to +perform+. The supplied string should have the
188
+ # form "username:password"
189
+ #
190
+ def proxypwd=(value)
191
+ set :proxyuserpwd, value
192
+ end
193
+
194
+ #
195
+ # call-seq:
196
+ # easy.cookies = "name1=content1; name2=content2;" => string
197
+ #
198
+ # Set cookies to be sent by this Curl::Easy instance. The format of the string should
199
+ # be NAME=CONTENTS, where NAME is the cookie name and CONTENTS is what the cookie should contain.
200
+ # Set multiple cookies in one string like this: "name1=content1; name2=content2;" etc.
201
+ #
202
+ def cookies=(value)
203
+ set :cookie, value
204
+ end
205
+
206
+ #
207
+ # call-seq:
208
+ # easy.cookiefile = string => string
209
+ #
210
+ # Set a file that contains cookies to be sent in subsequent requests by this Curl::Easy instance.
211
+ #
212
+ # *Note* that you must set enable_cookies true to enable the cookie
213
+ # engine, or this option will be ignored.
214
+ #
215
+ def cookiefile=(value)
216
+ set :cookiefile, value
217
+ end
218
+
219
+ #
220
+ # call-seq:
221
+ # easy.cookiejar = string => string
222
+ #
223
+ # Set a cookiejar file to use for this Curl::Easy instance.
224
+ # Cookies from the response will be written into this file.
225
+ #
226
+ # *Note* that you must set enable_cookies true to enable the cookie
227
+ # engine, or this option will be ignored.
228
+ #
229
+ def cookiejar=(value)
230
+ set :cookiejar, value
231
+ end
232
+
233
+ #
234
+ # call-seq:
235
+ # easy = Curl::Easy.new("url") do|c|
236
+ # c.head = true
237
+ # end
238
+ # easy.perform
239
+ #
240
+ def head=(onoff)
241
+ set :nobody, onoff
242
+ end
243
+
244
+ #
245
+ # call-seq:
246
+ # easy.follow_location = boolean => boolean
247
+ #
248
+ # Configure whether this Curl instance will follow Location: headers
249
+ # in HTTP responses. Redirects will only be followed to the extent
250
+ # specified by +max_redirects+.
251
+ #
252
+ def follow_location=(onoff)
253
+ set :followlocation, onoff
254
+ end
255
+
256
+ #
257
+ # call-seq:
258
+ # easy.http_head => true
259
+ #
260
+ # Request headers from the currently configured URL using the HEAD
261
+ # method and current options set for this Curl::Easy instance. This
262
+ # method always returns true, or raises an exception (defined under
263
+ # Curl::Err) on error.
264
+ #
265
+ def http_head
266
+ set :nobody, true
267
+ ret = self.perform
268
+ set :nobody, false
269
+ ret
270
+ end
271
+
272
+ #
273
+ # call-seq:
274
+ # easy.http_get => true
275
+ #
276
+ # GET the currently configured URL using the current options set for
277
+ # this Curl::Easy instance. This method always returns true, or raises
278
+ # an exception (defined under Curl::Err) on error.
279
+ #
280
+ def http_get
281
+ set :httpget, true
282
+ http :GET
283
+ end
284
+ alias get http_get
285
+
286
+ #
287
+ # call-seq:
288
+ # easy.http_delete
289
+ #
290
+ # DELETE the currently configured URL using the current options set for
291
+ # this Curl::Easy instance. This method always returns true, or raises
292
+ # an exception (defined under Curl::Err) on error.
293
+ #
294
+ def http_delete
295
+ self.http :DELETE
296
+ end
297
+ alias delete http_delete
298
+
299
+ class << self
300
+
301
+ #
302
+ # call-seq:
303
+ # Curl::Easy.perform(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
304
+ #
305
+ # Convenience method that creates a new Curl::Easy instance with
306
+ # the specified URL and calls the general +perform+ method, before returning
307
+ # the new instance. For HTTP URLs, this is equivalent to calling +http_get+.
308
+ #
309
+ # If a block is supplied, the new instance will be yielded just prior to
310
+ # the +http_get+ call.
311
+ #
312
+ def perform(*args)
313
+ c = Curl::Easy.new(*args)
314
+ yield c if block_given?
315
+ c.perform
316
+ c
317
+ end
318
+
319
+ #
320
+ # call-seq:
321
+ # Curl::Easy.http_get(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
322
+ #
323
+ # Convenience method that creates a new Curl::Easy instance with
324
+ # the specified URL and calls +http_get+, before returning the new instance.
325
+ #
326
+ # If a block is supplied, the new instance will be yielded just prior to
327
+ # the +http_get+ call.
328
+ #
329
+ def http_get(*args)
330
+ c = Curl::Easy.new(*args)
331
+ yield c if block_given?
332
+ c.http_get
333
+ c
334
+ end
335
+
336
+ #
337
+ # call-seq:
338
+ # Curl::Easy.http_head(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
339
+ #
340
+ # Convenience method that creates a new Curl::Easy instance with
341
+ # the specified URL and calls +http_head+, before returning the new instance.
342
+ #
343
+ # If a block is supplied, the new instance will be yielded just prior to
344
+ # the +http_head+ call.
345
+ #
346
+ def http_head(*args)
347
+ c = Curl::Easy.new(*args)
348
+ yield c if block_given?
349
+ c.http_head
350
+ c
351
+ end
352
+
353
+ #
354
+ # call-seq:
355
+ # Curl::Easy.http_put(url, data) {|c| ... }
356
+ #
357
+ # see easy.http_put
358
+ #
359
+ def http_put(url, data)
360
+ c = Curl::Easy.new url
361
+ yield c if block_given?
362
+ c.http_put data
363
+ c
364
+ end
365
+
366
+ #
367
+ # call-seq:
368
+ # Curl::Easy.http_post(url, "some=urlencoded%20form%20data&and=so%20on") => true
369
+ # Curl::Easy.http_post(url, "some=urlencoded%20form%20data", "and=so%20on", ...) => true
370
+ # Curl::Easy.http_post(url, "some=urlencoded%20form%20data", Curl::PostField, "and=so%20on", ...) => true
371
+ # Curl::Easy.http_post(url, Curl::PostField, Curl::PostField ..., Curl::PostField) => true
372
+ #
373
+ # POST the specified formdata to the currently configured URL using
374
+ # the current options set for this Curl::Easy instance. This method
375
+ # always returns true, or raises an exception (defined under
376
+ # Curl::Err) on error.
377
+ #
378
+ # If you wish to use multipart form encoding, you'll need to supply a block
379
+ # in order to set multipart_form_post true. See #http_post for more
380
+ # information.
381
+ #
382
+ def http_post(*args)
383
+ url = args.shift
384
+ c = Curl::Easy.new url
385
+ yield c if block_given?
386
+ c.http_post(*args)
387
+ c
388
+ end
389
+
390
+ #
391
+ # call-seq:
392
+ # Curl::Easy.http_delete(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
393
+ #
394
+ # Convenience method that creates a new Curl::Easy instance with
395
+ # the specified URL and calls +http_delete+, before returning the new instance.
396
+ #
397
+ # If a block is supplied, the new instance will be yielded just prior to
398
+ # the +http_delete+ call.
399
+ #
400
+ def http_delete(*args)
401
+ c = Curl::Easy.new(*args)
402
+ yield c if block_given?
403
+ c.http_delete
404
+ c
405
+ end
406
+
407
+ # call-seq:
408
+ # Curl::Easy.download(url, filename = url.split(/\?/).first.split(/\//).last) { |curl| ... }
409
+ #
410
+ # Stream the specified url (via perform) and save the data directly to the
411
+ # supplied filename (defaults to the last component of the URL path, which will
412
+ # usually be the filename most simple urls).
413
+ #
414
+ # If a block is supplied, it will be passed the curl instance prior to the
415
+ # perform call.
416
+ #
417
+ # *Note* that the semantics of the on_body handler are subtly changed when using
418
+ # download, to account for the automatic routing of data to the specified file: The
419
+ # data string is passed to the handler *before* it is written
420
+ # to the file, allowing the handler to perform mutative operations where
421
+ # necessary. As usual, the transfer will be aborted if the on_body handler
422
+ # returns a size that differs from the data chunk size - in this case, the
423
+ # offending chunk will *not* be written to the file, the file will be closed,
424
+ # and a Curl::Err::AbortedByCallbackError will be raised.
425
+ def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk)
426
+ curl = Curl::Easy.new(url, &blk)
427
+
428
+ output = if filename.is_a? IO
429
+ filename.binmode if filename.respond_to?(:binmode)
430
+ filename
431
+ else
432
+ File.open(filename, 'wb')
433
+ end
434
+
435
+ begin
436
+ old_on_body = curl.on_body do |data|
437
+ result = old_on_body ? old_on_body.call(data) : data.length
438
+ output << data if result == data.length
439
+ result
440
+ end
441
+ curl.perform
442
+ ensure
443
+ output.close rescue IOError
444
+ end
445
+
446
+ return curl
447
+ end
448
+ end
449
+
450
+ # Allow the incoming cert string to be file:password
451
+ # but be careful to not use a colon from a windows file path
452
+ # as the split point. Mimic what curl's main does
453
+ if respond_to?(:cert=)
454
+ alias_method :native_cert=, :cert=
455
+ def cert=(cert_file)
456
+ pos = cert_file.rindex(':')
457
+ if pos && pos > 1
458
+ self.native_cert= cert_file[0..pos-1]
459
+ self.certpassword= cert_file[pos+1..-1]
460
+ else
461
+ self.native_cert= cert_file
462
+ end
463
+ self.cert
464
+ end
465
+ end
466
+
467
+ end
468
+ end
data/lib/curl/multi.rb ADDED
@@ -0,0 +1,248 @@
1
+ module Curl
2
+
3
+ class Multi
4
+ class << self
5
+ # call-seq:
6
+ # Curl::Multi.get('url1','url2','url3','url4','url5', :follow_location => true) do|easy|
7
+ # easy
8
+ # end
9
+ #
10
+ # Blocking call to fetch multiple url's in parallel.
11
+ def get(urls, easy_options={}, multi_options={}, &blk)
12
+ url_confs = []
13
+ urls.each do|url|
14
+ url_confs << {:url => url, :method => :get}.merge(easy_options)
15
+ end
16
+ self.http(url_confs, multi_options) {|c,code,method| blk.call(c) if blk }
17
+ end
18
+
19
+ # call-seq:
20
+ #
21
+ # Curl::Multi.post([{:url => 'url1', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
22
+ # {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
23
+ # {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
24
+ # { :follow_location => true, :multipart_form_post => true },
25
+ # {:pipeline => true }) do|easy|
26
+ # easy_handle_on_request_complete
27
+ # end
28
+ #
29
+ # Blocking call to POST multiple form's in parallel.
30
+ #
31
+ # urls_with_config: is a hash of url's pointing to the postfields to send
32
+ # easy_options: are a set of common options to set on all easy handles
33
+ # multi_options: options to set on the Curl::Multi handle
34
+ #
35
+ def post(urls_with_config, easy_options={}, multi_options={}, &blk)
36
+ url_confs = []
37
+ urls_with_config.each do|uconf|
38
+ url_confs << uconf.merge(:method => :post).merge(easy_options)
39
+ end
40
+ self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
41
+ end
42
+
43
+ # call-seq:
44
+ #
45
+ # Curl::Multi.put([{:url => 'url1', :put_data => "some message"},
46
+ # {:url => 'url2', :put_data => IO.read('filepath')},
47
+ # {:url => 'url3', :put_data => "maybe another string or socket?"],
48
+ # {:follow_location => true},
49
+ # {:pipeline => true }) do|easy|
50
+ # easy_handle_on_request_complete
51
+ # end
52
+ #
53
+ # Blocking call to POST multiple form's in parallel.
54
+ #
55
+ # urls_with_config: is a hash of url's pointing to the postfields to send
56
+ # easy_options: are a set of common options to set on all easy handles
57
+ # multi_options: options to set on the Curl::Multi handle
58
+ #
59
+ def put(urls_with_config, easy_options={}, multi_options={}, &blk)
60
+ url_confs = []
61
+ urls_with_config.each do|uconf|
62
+ url_confs << uconf.merge(:method => :put).merge(easy_options)
63
+ end
64
+ self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
65
+ end
66
+
67
+
68
+ # call-seq:
69
+ #
70
+ # Curl::Multi.http( [
71
+ # { :url => 'url1', :method => :post,
72
+ # :post_fields => {'field1' => 'value1', 'field2' => 'value2'} },
73
+ # { :url => 'url2', :method => :get,
74
+ # :follow_location => true, :max_redirects => 3 },
75
+ # { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
76
+ # { :url => 'url4', :method => :head }
77
+ # ], {:pipeline => true})
78
+ #
79
+ # Blocking call to issue multiple HTTP requests with varying verb's.
80
+ #
81
+ # urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used
82
+ # multi_options: options for the multi handle
83
+ # blk: a callback, that yeilds when a handle is completed
84
+ #
85
+ def http(urls_with_config, multi_options={}, &blk)
86
+ m = Curl::Multi.new
87
+
88
+ # maintain a sane number of easy handles
89
+ multi_options[:max_connects] = max_connects = multi_options.key?(:max_connects) ? multi_options[:max_connects] : 10
90
+
91
+ free_handles = [] # keep a list of free easy handles
92
+
93
+ # configure the multi handle
94
+ multi_options.each { |k,v| m.send("#{k}=", v) }
95
+ callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_body,:on_header]
96
+
97
+ add_free_handle = proc do|conf, easy|
98
+ c = conf.dup # avoid being destructive to input
99
+ url = c.delete(:url)
100
+ method = c.delete(:method)
101
+ headers = c.delete(:headers)
102
+
103
+ easy = Curl::Easy.new if easy.nil?
104
+
105
+ easy.url = url
106
+
107
+ # assign callbacks
108
+ callbacks.each do |cb|
109
+ cbproc = c.delete(cb)
110
+ easy.send(cb,&cbproc) if cbproc
111
+ end
112
+
113
+ case method
114
+ when :post
115
+ fields = c.delete(:post_fields)
116
+ # set the post post using the url fields
117
+ easy.post_body = fields.map{|f,k| "#{easy.escape(f)}=#{easy.escape(k)}"}.join('&')
118
+ when :put
119
+ easy.put_data = c.delete(:put_data)
120
+ when :head
121
+ easy.head = true
122
+ when :delete
123
+ easy.delete = true
124
+ when :get
125
+ else
126
+ # XXX: nil is treated like a GET
127
+ end
128
+
129
+ # headers is a special key
130
+ headers.each {|k,v| easy.headers[k] = v } if headers
131
+
132
+ #
133
+ # use the remaining options as specific configuration to the easy handle
134
+ # bad options should raise an undefined method error
135
+ #
136
+ c.each { |k,v| easy.send("#{k}=",v) }
137
+
138
+ easy.on_complete {|curl,code|
139
+ free_handles << curl
140
+ blk.call(curl,code,method) if blk
141
+ }
142
+ m.add(easy)
143
+ end
144
+
145
+ max_connects.times do
146
+ conf = urls_with_config.pop
147
+ add_free_handle.call conf, nil
148
+ break if urls_with_config.empty?
149
+ end
150
+
151
+ consume_free_handles = proc do
152
+ # as we idle consume free handles
153
+ if urls_with_config.size > 0 && free_handles.size > 0
154
+ easy = free_handles.pop
155
+ conf = urls_with_config.pop
156
+ add_free_handle.call conf, easy
157
+ end
158
+ end
159
+
160
+ if urls_with_config.empty?
161
+ m.perform
162
+ else
163
+ until urls_with_config.empty?
164
+ m.perform do
165
+ consume_free_handles.call
166
+ end
167
+ consume_free_handles.call
168
+ end
169
+ free_handles = nil
170
+ end
171
+ end
172
+
173
+ # call-seq:
174
+ #
175
+ # Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
176
+ #
177
+ # will create 2 new files file1.txt and file2.txt
178
+ #
179
+ # 2 files will be opened, and remain open until the call completes
180
+ #
181
+ # when using the :post or :put method, urls should be a hash, including the individual post fields per post
182
+ #
183
+ def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)
184
+ errors = []
185
+ procs = []
186
+ files = []
187
+ urls_with_config = []
188
+ url_to_download_paths = {}
189
+
190
+ urls.each_with_index do|urlcfg,i|
191
+ if urlcfg.is_a?(Hash)
192
+ url = url[:url]
193
+ else
194
+ url = urlcfg
195
+ end
196
+
197
+ if download_paths and download_paths[i]
198
+ download_path = download_paths[i]
199
+ else
200
+ download_path = File.basename(url)
201
+ end
202
+
203
+ file = lambda do|dp|
204
+ file = File.open(dp,"wb")
205
+ procs << (lambda {|data| file.write data; data.size })
206
+ files << file
207
+ file
208
+ end.call(download_path)
209
+
210
+ if urlcfg.is_a?(Hash)
211
+ urls_with_config << urlcfg.merge({:on_body => procs.last}.merge(easy_options))
212
+ else
213
+ urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options)
214
+ end
215
+ url_to_download_paths[url] = {:path => download_path, :file => file} # store for later
216
+ end
217
+
218
+ if blk
219
+ # when injecting the block, ensure file is closed before yielding
220
+ Curl::Multi.http(urls_with_config, multi_options) do |c,code,method|
221
+ info = url_to_download_paths[c.url]
222
+ begin
223
+ file = info[:file]
224
+ files.reject!{|f| f == file }
225
+ file.close
226
+ rescue => e
227
+ errors << e
228
+ end
229
+ blk.call(c,info[:path])
230
+ end
231
+ else
232
+ Curl::Multi.http(urls_with_config, multi_options)
233
+ end
234
+
235
+ ensure
236
+ files.each {|f|
237
+ begin
238
+ f.close
239
+ rescue => e
240
+ errors << e
241
+ end
242
+ }
243
+ raise errors unless errors.empty?
244
+ end
245
+
246
+ end
247
+ end
248
+ end