actionpack 1.8.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (101) hide show
  1. data/CHANGELOG +309 -16
  2. data/README +1 -1
  3. data/lib/action_controller.rb +5 -0
  4. data/lib/action_controller/assertions.rb +57 -12
  5. data/lib/action_controller/auto_complete.rb +47 -0
  6. data/lib/action_controller/base.rb +288 -258
  7. data/lib/action_controller/benchmarking.rb +8 -3
  8. data/lib/action_controller/caching.rb +88 -42
  9. data/lib/action_controller/cgi_ext/cgi_ext.rb +1 -1
  10. data/lib/action_controller/cgi_ext/cgi_methods.rb +41 -11
  11. data/lib/action_controller/cgi_ext/multipart_progress.rb +169 -0
  12. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +30 -12
  13. data/lib/action_controller/cgi_process.rb +39 -11
  14. data/lib/action_controller/code_generation.rb +235 -0
  15. data/lib/action_controller/cookies.rb +14 -8
  16. data/lib/action_controller/deprecated_renders_and_redirects.rb +76 -0
  17. data/lib/action_controller/filters.rb +8 -7
  18. data/lib/action_controller/helpers.rb +41 -6
  19. data/lib/action_controller/layout.rb +45 -16
  20. data/lib/action_controller/request.rb +86 -23
  21. data/lib/action_controller/rescue.rb +1 -0
  22. data/lib/action_controller/response.rb +1 -1
  23. data/lib/action_controller/routing.rb +536 -272
  24. data/lib/action_controller/scaffolding.rb +30 -25
  25. data/lib/action_controller/session/active_record_store.rb +251 -50
  26. data/lib/action_controller/streaming.rb +133 -0
  27. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -7
  28. data/lib/action_controller/templates/scaffolds/edit.rhtml +2 -2
  29. data/lib/action_controller/templates/scaffolds/layout.rhtml +22 -18
  30. data/lib/action_controller/templates/scaffolds/list.rhtml +3 -3
  31. data/lib/action_controller/templates/scaffolds/new.rhtml +2 -2
  32. data/lib/action_controller/templates/scaffolds/show.rhtml +1 -1
  33. data/lib/action_controller/test_process.rb +68 -47
  34. data/lib/action_controller/upload_progress.rb +421 -0
  35. data/lib/action_controller/url_rewriter.rb +8 -11
  36. data/lib/action_controller/vendor/html-scanner/html/document.rb +6 -5
  37. data/lib/action_controller/vendor/html-scanner/html/node.rb +70 -14
  38. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +17 -10
  39. data/lib/action_controller/vendor/html-scanner/html/version.rb +3 -3
  40. data/lib/action_controller/vendor/xml_simple.rb +1019 -0
  41. data/lib/action_controller/verification.rb +36 -30
  42. data/lib/action_view/base.rb +21 -14
  43. data/lib/action_view/helpers/active_record_helper.rb +15 -13
  44. data/lib/action_view/helpers/asset_tag_helper.rb +26 -9
  45. data/lib/action_view/helpers/benchmark_helper.rb +24 -0
  46. data/lib/action_view/helpers/capture_helper.rb +7 -5
  47. data/lib/action_view/helpers/date_helper.rb +63 -46
  48. data/lib/action_view/helpers/form_helper.rb +7 -1
  49. data/lib/action_view/helpers/form_options_helper.rb +19 -11
  50. data/lib/action_view/helpers/form_tag_helper.rb +5 -1
  51. data/lib/action_view/helpers/javascript_helper.rb +403 -35
  52. data/lib/action_view/helpers/javascripts/controls.js +261 -0
  53. data/lib/action_view/helpers/javascripts/dragdrop.js +476 -0
  54. data/lib/action_view/helpers/javascripts/effects.js +570 -0
  55. data/lib/action_view/helpers/javascripts/prototype.js +633 -371
  56. data/lib/action_view/helpers/number_helper.rb +11 -13
  57. data/lib/action_view/helpers/tag_helper.rb +1 -2
  58. data/lib/action_view/helpers/text_helper.rb +69 -6
  59. data/lib/action_view/helpers/upload_progress_helper.rb +433 -0
  60. data/lib/action_view/helpers/url_helper.rb +98 -3
  61. data/lib/action_view/partials.rb +14 -8
  62. data/lib/action_view/vendor/builder/xmlmarkup.rb +11 -0
  63. data/rakefile +13 -5
  64. data/test/abstract_unit.rb +1 -1
  65. data/test/controller/action_pack_assertions_test.rb +52 -9
  66. data/test/controller/active_record_assertions_test.rb +119 -120
  67. data/test/controller/active_record_store_test.rb +111 -0
  68. data/test/controller/addresses_render_test.rb +45 -0
  69. data/test/controller/caching_filestore.rb +92 -0
  70. data/test/controller/capture_test.rb +39 -0
  71. data/test/controller/cgi_test.rb +40 -3
  72. data/test/controller/helper_test.rb +65 -13
  73. data/test/controller/multipart_progress_testx.rb +365 -0
  74. data/test/controller/new_render_test.rb +263 -0
  75. data/test/controller/redirect_test.rb +64 -0
  76. data/test/controller/render_test.rb +20 -21
  77. data/test/controller/request_test.rb +83 -3
  78. data/test/controller/routing_test.rb +702 -0
  79. data/test/controller/send_file_test.rb +2 -0
  80. data/test/controller/test_test.rb +44 -8
  81. data/test/controller/upload_progress_testx.rb +89 -0
  82. data/test/controller/verification_test.rb +94 -29
  83. data/test/fixtures/addresses/list.rhtml +1 -0
  84. data/test/fixtures/test/capturing.rhtml +4 -0
  85. data/test/fixtures/test/list.rhtml +1 -1
  86. data/test/fixtures/test/update_element_with_capture.rhtml +9 -0
  87. data/test/template/active_record_helper_test.rb +30 -15
  88. data/test/template/asset_tag_helper_test.rb +12 -5
  89. data/test/template/benchmark_helper_test.rb +72 -0
  90. data/test/template/date_helper_test.rb +69 -0
  91. data/test/template/form_helper_test.rb +18 -10
  92. data/test/template/form_options_helper_test.rb +40 -5
  93. data/test/template/javascript_helper.rb +149 -2
  94. data/test/template/number_helper_test.rb +2 -0
  95. data/test/template/tag_helper_test.rb +4 -0
  96. data/test/template/text_helper_test.rb +36 -0
  97. data/test/template/upload_progress_helper_testx.rb +272 -0
  98. data/test/template/url_helper_test.rb +30 -0
  99. metadata +30 -6
  100. data/test/controller/layout_test.rb +0 -49
  101. data/test/controller/routing_tests.rb +0 -543
@@ -15,18 +15,22 @@ module ActionController #:nodoc:
15
15
  }
16
16
  end
17
17
 
18
- def render_with_benchmark(template_name = default_template_name, status = "200 OK")
18
+ def render_with_benchmark(options = {}, deprecated_status = nil)
19
19
  if logger.nil?
20
- render_without_benchmark(template_name, status)
20
+ render_without_benchmark(options, deprecated_status)
21
21
  else
22
22
  db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
23
- @rendering_runtime = Benchmark::measure{ render_without_benchmark(template_name, status) }.real
23
+
24
+ render_output = nil
25
+ @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, deprecated_status) }.real
24
26
 
25
27
  if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
26
28
  @db_rt_before_render = db_runtime
27
29
  @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
28
30
  @rendering_runtime -= @db_rt_after_render
29
31
  end
32
+
33
+ render_output
30
34
  end
31
35
  end
32
36
 
@@ -38,6 +42,7 @@ module ActionController #:nodoc:
38
42
  log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
39
43
  log_message << rendering_runtime(runtime) if @rendering_runtime
40
44
  log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
45
+ log_message << " [#{complete_request_uri}]"
41
46
  logger.info(log_message)
42
47
  end
43
48
  end
@@ -256,13 +256,8 @@ module ActionController #:nodoc:
256
256
  end
257
257
  end
258
258
 
259
- def cache_base_url
260
- @@cache_base_url ||= url_for(:controller => '')
261
- end
262
-
263
259
  def fragment_cache_key(name)
264
- key = name.is_a?(Hash) ? url_for(name) : cache_base_url + name
265
- key.split("://").last
260
+ name.is_a?(Hash) ? url_for(name).split("://").last : name
266
261
  end
267
262
 
268
263
  # Called by CacheHelper#cache
@@ -281,6 +276,8 @@ module ActionController #:nodoc:
281
276
  end
282
277
 
283
278
  def write_fragment(name, content, options = {})
279
+ return unless perform_caching
280
+
284
281
  key = fragment_cache_key(name)
285
282
  fragment_cache_store.write(key, content, options)
286
283
  logger.info "Cached fragment: #{key}" unless logger.nil?
@@ -288,6 +285,8 @@ module ActionController #:nodoc:
288
285
  end
289
286
 
290
287
  def read_fragment(name, options = {})
288
+ return unless perform_caching
289
+
291
290
  key = fragment_cache_key(name)
292
291
  if cache = fragment_cache_store.read(key, options)
293
292
  logger.info "Fragment hit: #{key}" unless logger.nil?
@@ -297,16 +296,27 @@ module ActionController #:nodoc:
297
296
  end
298
297
  end
299
298
 
299
+ # Name can take one of three forms:
300
+ # * String: This would normally take the form of a path like "pages/45/notes"
301
+ # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
302
+ # * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes}
300
303
  def expire_fragment(name, options = {})
304
+ return unless perform_caching
305
+
301
306
  key = fragment_cache_key(name)
302
- fragment_cache_store.delete(key, options)
303
- logger.info "Expired fragment: #{key}" unless logger.nil?
307
+
308
+ if key.is_a?(Regexp)
309
+ fragment_cache_store.delete_matched(key, options)
310
+ logger.info "Expired fragments matching: #{key.source}" unless logger.nil?
311
+ else
312
+ fragment_cache_store.delete(key, options)
313
+ logger.info "Expired fragment: #{key}" unless logger.nil?
314
+ end
304
315
  end
305
316
 
306
- def expire_matched_fragments(re=Regexp.new('/.*/'), options = {})
307
- rp = cache_base_url.split("://").last
308
- fragment_cache_store.delete_matched(re, { :root_path => rp })
309
- logger.info "Expired all fragments matching: #{rp}#{re.source}" unless logger.nil?
317
+ # Deprecated -- just call expire_fragment with a regular expression
318
+ def expire_matched_fragments(matcher = /.*/, options = {}) #:nodoc:
319
+ expire_fragment(matcher, options)
310
320
  end
311
321
 
312
322
  class MemoryStore #:nodoc:
@@ -326,9 +336,8 @@ module ActionController #:nodoc:
326
336
  @mutex.synchronize { @data.delete(name) }
327
337
  end
328
338
 
329
- def delete_matched(re, options) #:nodoc:
330
- re = Regexp.new("#{Regexp.escape(options[:root_path])}#{re.source}")
331
- @mutex.synchronize { @data.delete_if { |k,v| k =~ re } }
339
+ def delete_matched(matcher, options) #:nodoc:
340
+ @mutex.synchronize { @data.delete_if { |k,v| k =~ matcher } }
332
341
  end
333
342
  end
334
343
 
@@ -361,38 +370,42 @@ module ActionController #:nodoc:
361
370
  end
362
371
 
363
372
  def delete(name, options) #:nodoc:
364
- File.delete(real_file_path(name)) if File.exist?(real_file_path(name))
373
+ File.delete(real_file_path(name))
374
+ rescue SystemCallError => e
375
+ Base.logger.info "Couldn't expire cache #{name} (#{e.message})" unless Base.logger.nil?
365
376
  end
366
377
 
367
- def delete_matched(re, options) #:nodoc:
368
- rootPath = real_file_path(options[:root_path])
369
- search_dir(@cache_path).each do |f|
370
- File.delete(f) if f.index(rootPath) == 0 and f =~ re and File.exist?(f)
378
+ def delete_matched(matcher, options) #:nodoc:
379
+ search_dir(@cache_path) do |f|
380
+ if f =~ matcher
381
+ begin
382
+ File.delete(f)
383
+ rescue Object => e
384
+ Base.logger.info "Couldn't expire cache: #{f} (#{e.message})" unless Base.logger.nil?
385
+ end
386
+ end
371
387
  end
372
388
  end
373
389
 
374
390
  private
375
391
  def real_file_path(name)
376
- '%s/%s' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
392
+ '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
377
393
  end
378
394
 
379
395
  def ensure_cache_path(path)
380
396
  FileUtils.makedirs(path) unless File.exists?(path)
381
397
  end
382
398
 
383
- def search_dir(dir)
384
- require 'pathname'
385
- files = []
386
- dir = Dir.new(dir)
387
- dir.each do |d|
388
- unless d == '.' or d == '..'
389
- d = File.join(dir.path, d)
390
- p = Pathname.new(d)
391
- files << p.to_s if p.file?
392
- files += search_dir(d) if p.directory?
399
+ def search_dir(dir, &callback)
400
+ Dir.foreach(dir) do |d|
401
+ next if d == "." || d == ".."
402
+ name = File.join(dir, d)
403
+ if File.directory?(name)
404
+ search_dir(name, &callback)
405
+ else
406
+ callback.call name
393
407
  end
394
408
  end
395
- files
396
409
  end
397
410
  end
398
411
  end
@@ -400,17 +413,14 @@ module ActionController #:nodoc:
400
413
  # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
401
414
  # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
402
415
  #
403
- # class ListSweeper < ActiveRecord::Observer
416
+ # class ListSweeper < ActionController::Caching::Sweeper
404
417
  # observe List, Item
405
418
  #
406
419
  # def after_save(record)
407
- # @list = record.is_a?(List) ? record : record.list
408
- # end
409
- #
410
- # def filter(controller)
411
- # controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id)
412
- # controller.expire_action(:controller => "lists", :action => "all")
413
- # @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
420
+ # list = record.is_a?(List) ? record : record.list
421
+ # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
422
+ # expire_action(:controller => "lists", :action => "all")
423
+ # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
414
424
  # end
415
425
  # end
416
426
  #
@@ -432,12 +442,48 @@ module ActionController #:nodoc:
432
442
  def cache_sweeper(*sweepers)
433
443
  return unless perform_caching
434
444
  configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
435
- sweepers.each do |sweeper|
445
+ sweepers.each do |sweeper|
436
446
  observer(sweeper)
437
- after_filter(Object.const_get(Inflector.classify(sweeper)).instance, :only => configuration[:only])
447
+
448
+ sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
449
+
450
+ if sweeper_instance.is_a?(Sweeper)
451
+ around_filter(sweeper_instance, :only => configuration[:only])
452
+ else
453
+ after_filter(sweeper_instance, :only => configuration[:only])
454
+ end
438
455
  end
439
456
  end
440
457
  end
441
458
  end
459
+
460
+ if defined?(ActiveRecord::Observer)
461
+ class Sweeper < ActiveRecord::Observer #:nodoc:
462
+ attr_accessor :controller
463
+
464
+ def before(controller)
465
+ self.controller = controller
466
+ callback(:before)
467
+ end
468
+
469
+ def after(controller)
470
+ callback(:after)
471
+ end
472
+
473
+ private
474
+ def callback(timing)
475
+ controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
476
+ action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
477
+
478
+ send(controller_callback_method_name) if respond_to?(controller_callback_method_name)
479
+ send(action_callback_method_name) if respond_to?(action_callback_method_name)
480
+ end
481
+
482
+ def method_missing(method, *arguments)
483
+ return if @controller.nil?
484
+ @controller.send(method, *arguments)
485
+ end
486
+ end
487
+ end
442
488
  end
443
489
  end
@@ -25,7 +25,7 @@ class CGI #:nodoc:
25
25
  end
26
26
 
27
27
  def request_parameters
28
- CGIMethods.parse_request_parameters(params)
28
+ CGIMethods.parse_request_parameters(params, env_table)
29
29
  end
30
30
 
31
31
  def redirect(where)
@@ -1,4 +1,5 @@
1
1
  require 'cgi'
2
+ require 'action_controller/vendor/xml_simple'
2
3
 
3
4
  # Static methods for parsing the query and request parameters that can be used in
4
5
  # a CGI extension class or testing in isolation.
@@ -10,25 +11,25 @@ class CGIMethods #:nodoc:
10
11
  parsed_params = {}
11
12
 
12
13
  query_string.split(/[&;]/).each { |p|
13
- k, v = p.split('=')
14
+ k, v = p.split('=',2)
15
+ v = nil if (!v.nil? && v.empty?)
14
16
 
15
17
  k = CGI.unescape(k) unless k.nil?
16
18
  v = CGI.unescape(v) unless v.nil?
17
19
 
18
- if k =~ /(.*)\[\]$/
19
- if parsed_params.has_key? $1
20
- parsed_params[$1] << v
21
- else
22
- parsed_params[$1] = [v]
23
- end
24
- else
25
- parsed_params[k] = v.nil? ? nil : v
20
+ keys = split_key(k)
21
+ last_key = keys.pop
22
+ last_key = keys.pop if (use_array = last_key.empty?)
23
+ parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
24
+
25
+ if use_array then (parent[last_key] ||= []) << v
26
+ else parent[last_key] = v
26
27
  end
27
28
  }
28
29
 
29
30
  return parsed_params
30
31
  end
31
-
32
+
32
33
  # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
33
34
  # "Somewhere cool!" are translated into a full hash hierarchy, like
34
35
  # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
@@ -47,7 +48,36 @@ class CGIMethods #:nodoc:
47
48
  return parsed_params
48
49
  end
49
50
 
51
+ def self.parse_formatted_request_parameters(format, raw_post_data)
52
+ case format
53
+ when :xml
54
+ return XmlSimple.xml_in(raw_post_data, 'ForceArray' => false)
55
+ when :yaml
56
+ return YAML.load(raw_post_data)
57
+ end
58
+ rescue Object => e
59
+ { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
60
+ "raw_post_data" => raw_post_data, "format" => format }
61
+ end
62
+
50
63
  private
64
+
65
+ # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
66
+ # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
67
+ # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
68
+ def CGIMethods.split_key(key)
69
+ if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
70
+ keys = [$1]
71
+
72
+ keys.concat($2[1..-2].split(']['))
73
+ keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
74
+
75
+ return keys
76
+ else
77
+ return [key]
78
+ end
79
+ end
80
+
51
81
  def CGIMethods.get_typed_value(value)
52
82
  if value.respond_to?(:content_type) && !value.content_type.empty?
53
83
  # Uploaded file
@@ -56,7 +86,7 @@ class CGIMethods #:nodoc:
56
86
  # Value as part of a multipart request
57
87
  value.read
58
88
  elsif value.class == Array
59
- value
89
+ value.collect { |v| CGIMethods.get_typed_value(v) }
60
90
  else
61
91
  # Standard value (not a multipart request)
62
92
  value.to_s
@@ -0,0 +1,169 @@
1
+ # == Overview
2
+ #
3
+ # This module will extend the CGI module with methods to track the upload
4
+ # progress for multipart forms for use with progress meters. The progress is
5
+ # saved in the session to be used from any request from any server with the
6
+ # same session. In other words, this module will work across application
7
+ # instances.
8
+ #
9
+ # === Usage
10
+ #
11
+ # Just do your file-uploads as you normally would, but include an upload_id in
12
+ # the query string of your form action. Your form post action should look
13
+ # like:
14
+ #
15
+ # <form method="post" enctype="multipart/form-data" action="postaction?upload_id=SOMEIDYOUSET">
16
+ # <input type="file" name="client_file"/>
17
+ # </form>
18
+ #
19
+ # Query the upload state in a progress by reading the progress from the session
20
+ #
21
+ # class UploadController < ApplicationController
22
+ # def upload_status
23
+ # render :text => "Percent complete: " + @session[:uploads]['SOMEIDYOUSET'].completed_percent"
24
+ # end
25
+ # end
26
+ #
27
+ # === Session options
28
+ #
29
+ # Upload progress uses the session options defined in
30
+ # ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS. If you are passing
31
+ # custom session options to your dispatcher then please follow the
32
+ # "recommended way to change session options":http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
33
+ #
34
+ # === Update frequency
35
+ #
36
+ # During an upload, the progress will be written to the session every 2
37
+ # seconds. This prevents excessive writes yet maintains a decent picture of
38
+ # the upload progress for larger files.
39
+ #
40
+ # User interfaces that update more often that every 2 seconds will display the same results.
41
+ # Consider this update frequency when designing your progress polling.
42
+ #
43
+
44
+ require 'cgi'
45
+
46
+ # For integration with ActionPack
47
+ require 'action_controller/base'
48
+ require 'action_controller/cgi_process'
49
+ require 'action_controller/upload_progress'
50
+
51
+ class CGI #:nodoc:
52
+ class ProgressIO < SimpleDelegator #:nodoc:
53
+ MIN_SAVE_INTERVAL = 1.0 # Number of seconds between session saves
54
+
55
+ attr_reader :progress, :session
56
+
57
+ def initialize(orig_io, progress, session)
58
+ @session = session
59
+ @progress = progress
60
+
61
+ @start_time = Time.now
62
+ @last_save_time = @start_time
63
+ save_progress
64
+
65
+ super(orig_io)
66
+ end
67
+
68
+ def read(*args)
69
+ data = __getobj__.read(*args)
70
+
71
+ if data and data.size > 0
72
+ now = Time.now
73
+ elapsed = now - @start_time
74
+ progress.update!(data.size, elapsed)
75
+
76
+ if now - @last_save_time > MIN_SAVE_INTERVAL
77
+ save_progress
78
+ @last_save_time = now
79
+ end
80
+ else
81
+ ActionController::Base.logger.debug("CGI::ProgressIO#read returns nothing when it should return nil if IO is finished: [#{args.inspect}], a cancelled upload or old FCGI bindings. Resetting the upload progress")
82
+
83
+ progress.reset!
84
+ save_progress
85
+ end
86
+
87
+ data
88
+ end
89
+
90
+ def save_progress
91
+ @session.update
92
+ end
93
+
94
+ def finish
95
+ @session.update
96
+ ActionController::Base.logger.debug("Finished processing multipart upload in #{@progress.elapsed_seconds.to_s}s")
97
+ end
98
+ end
99
+
100
+ module QueryExtension #:nodoc:
101
+ # Need to do lazy aliasing on the instance that we are extending because of the way QueryExtension
102
+ # gets included for each instance of the CGI object rather than on a module level. This method is a
103
+ # bit obtrusive because we are overriding CGI::QueryExtension::extended which could be used in the
104
+ # future. Need to research a better method
105
+ def self.extended(obj)
106
+ obj.instance_eval do
107
+ # unless defined? will prevent clobbering the progress IO on multiple extensions
108
+ alias :stdinput_without_progress :stdinput unless defined? stdinput_without_progress
109
+ alias :stdinput :stdinput_with_progress
110
+ end
111
+ end
112
+
113
+ def stdinput_with_progress
114
+ @stdin_with_progress or stdinput_without_progress
115
+ end
116
+
117
+ private
118
+ # Bootstrapped on ActionController::UploadProgress::upload_status_for
119
+ def read_multipart_with_progress(boundary, content_length)
120
+ begin
121
+ begin
122
+ # Session disabled if the default session options have been set to 'false'
123
+ options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
124
+ raise RuntimeError.new("Multipart upload progress disabled, no session options") unless options
125
+
126
+ options = options.stringify_keys
127
+
128
+ # Pull in the application controller to satisfy any dependencies on class definitions
129
+ # of instances stored in the session.
130
+ Controllers.const_load!(:ApplicationController, "application") unless Controllers.const_defined?(:ApplicationController)
131
+
132
+ # Assumes that @cookies has already been setup
133
+ # Raises nomethod if upload_id is not defined
134
+ @params = CGI::parse(read_params_from_query)
135
+ upload_id = @params[(options['upload_key'] || 'upload_id')].first
136
+ raise RuntimeError.new("Multipart upload progress disabled, no upload id in query string") unless upload_id
137
+
138
+ upload_progress = ActionController::UploadProgress::Progress.new(content_length)
139
+
140
+ session = Session.new(self, options)
141
+ session[:uploads] = {} unless session[:uploads]
142
+ session[:uploads].delete(upload_id) # in case the same upload id is used twice
143
+ session[:uploads][upload_id] = upload_progress
144
+
145
+ @stdin_with_progress = CGI::ProgressIO.new(stdinput_without_progress, upload_progress, session)
146
+ ActionController::Base.logger.debug("Multipart upload with progress (id: #{upload_id}, size: #{content_length})")
147
+ rescue
148
+ ActionController::Base.logger.debug("Exception during setup of read_multipart_with_progress: #{$!}")
149
+ end
150
+ ensure
151
+ begin
152
+ params = read_multipart_without_progress(boundary, content_length)
153
+ @stdin_with_progress.finish if @stdin_with_progress.respond_to? :finish
154
+ ensure
155
+ @stdin_with_progress = nil
156
+ session.close if session
157
+ end
158
+ end
159
+ params
160
+ end
161
+
162
+ # Prevent redefinition of aliases on multiple includes
163
+ unless private_instance_methods.include?("read_multipart_without_progress")
164
+ alias_method :read_multipart_without_progress, :read_multipart
165
+ alias_method :read_multipart, :read_multipart_with_progress
166
+ end
167
+
168
+ end
169
+ end