aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rwxr-xr-xactionpack/lib/action_controller/base.rb6
-rw-r--r--actionpack/lib/action_controller/cgi_ext/multipart_progress.rb169
-rw-r--r--actionpack/lib/action_controller/upload_progress.rb473
3 files changed, 0 insertions, 648 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 9479b93a96..a5b74d6a19 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -348,12 +348,6 @@ module ActionController #:nodoc:
path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)
self.template_root = path_of_controller_root
end
-
- # Temporary method for enabling upload progress until it's ready to leave experimental mode
- def enable_upload_progress # :nodoc:
- require 'action_controller/upload_progress'
- include ActionController::UploadProgress
- end
end
public
diff --git a/actionpack/lib/action_controller/cgi_ext/multipart_progress.rb b/actionpack/lib/action_controller/cgi_ext/multipart_progress.rb
deleted file mode 100644
index 07961a03d2..0000000000
--- a/actionpack/lib/action_controller/cgi_ext/multipart_progress.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-# == Overview
-#
-# This module will extend the CGI module with methods to track the upload
-# progress for multipart forms for use with progress meters. The progress is
-# saved in the session to be used from any request from any server with the
-# same session. In other words, this module will work across application
-# instances.
-#
-# === Usage
-#
-# Just do your file-uploads as you normally would, but include an upload_id in
-# the query string of your form action. Your form post action should look
-# like:
-#
-# <form method="post" enctype="multipart/form-data" action="postaction?upload_id=SOMEIDYOUSET">
-# <input type="file" name="client_file"/>
-# </form>
-#
-# Query the upload state in a progress by reading the progress from the session
-#
-# class UploadController < ApplicationController
-# def upload_status
-# render :text => "Percent complete: " + @session[:uploads]['SOMEIDYOUSET'].completed_percent"
-# end
-# end
-#
-# === Session options
-#
-# Upload progress uses the session options defined in
-# ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS. If you are passing
-# custom session options to your dispatcher then please follow the
-# "recommended way to change session options":http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
-#
-# === Update frequency
-#
-# During an upload, the progress will be written to the session every 2
-# seconds. This prevents excessive writes yet maintains a decent picture of
-# the upload progress for larger files.
-#
-# User interfaces that update more often that every 2 seconds will display the same results.
-# Consider this update frequency when designing your progress polling.
-#
-
-require 'cgi'
-
-# For integration with ActionPack
-require 'action_controller/base'
-require 'action_controller/cgi_process'
-require 'action_controller/upload_progress'
-
-class CGI #:nodoc:
- class ProgressIO < SimpleDelegator #:nodoc:
- MIN_SAVE_INTERVAL = 1.0 # Number of seconds between session saves
-
- attr_reader :progress, :session
-
- def initialize(orig_io, progress, session)
- @session = session
- @progress = progress
-
- @start_time = Time.now
- @last_save_time = @start_time
- save_progress
-
- super(orig_io)
- end
-
- def read(*args)
- data = __getobj__.read(*args)
-
- if data and data.size > 0
- now = Time.now
- elapsed = now - @start_time
- progress.update!(data.size, elapsed)
-
- if now - @last_save_time > MIN_SAVE_INTERVAL
- save_progress
- @last_save_time = now
- end
- else
- 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")
-
- progress.reset!
- save_progress
- end
-
- data
- end
-
- def save_progress
- @session.update
- end
-
- def finish
- @session.update
- ActionController::Base.logger.debug("Finished processing multipart upload in #{@progress.elapsed_seconds.to_s}s")
- end
- end
-
- module QueryExtension #:nodoc:
- # Need to do lazy aliasing on the instance that we are extending because of the way QueryExtension
- # gets included for each instance of the CGI object rather than on a module level. This method is a
- # bit obtrusive because we are overriding CGI::QueryExtension::extended which could be used in the
- # future. Need to research a better method
- def self.extended(obj)
- obj.instance_eval do
- # unless defined? will prevent clobbering the progress IO on multiple extensions
- alias :stdinput_without_progress :stdinput unless defined? stdinput_without_progress
- alias :stdinput :stdinput_with_progress
- end
- end
-
- def stdinput_with_progress
- @stdin_with_progress or stdinput_without_progress
- end
-
- private
- # Bootstrapped on ActionController::UploadProgress::upload_status_for
- def read_multipart_with_progress(boundary, content_length)
- begin
- begin
- # Session disabled if the default session options have been set to 'false'
- options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
- raise RuntimeError.new("Multipart upload progress disabled, no session options") unless options
-
- options = options.stringify_keys
-
- # Pull in the application controller to satisfy any dependencies on class definitions
- # of instances stored in the session.
- Controllers.const_load!(:ApplicationController, "application") unless Controllers.const_defined?(:ApplicationController)
-
- # Assumes that @cookies has already been setup
- # Raises nomethod if upload_id is not defined
- @params = CGI::parse(read_params_from_query)
- upload_id = @params[(options['upload_key'] || 'upload_id')].first
- raise RuntimeError.new("Multipart upload progress disabled, no upload id in query string") unless upload_id
-
- upload_progress = ActionController::UploadProgress::Progress.new(content_length)
-
- session = Session.new(self, options)
- session[:uploads] = {} unless session[:uploads]
- session[:uploads].delete(upload_id) # in case the same upload id is used twice
- session[:uploads][upload_id] = upload_progress
-
- @stdin_with_progress = CGI::ProgressIO.new(stdinput_without_progress, upload_progress, session)
- ActionController::Base.logger.debug("Multipart upload with progress (id: #{upload_id}, size: #{content_length})")
- rescue
- ActionController::Base.logger.debug("Exception during setup of read_multipart_with_progress: #{$!}")
- end
- ensure
- begin
- params = read_multipart_without_progress(boundary, content_length)
- @stdin_with_progress.finish if @stdin_with_progress.respond_to? :finish
- ensure
- @stdin_with_progress = nil
- session.close if session
- end
- end
- params
- end
-
- # Prevent redefinition of aliases on multiple includes
- unless private_instance_methods.include?("read_multipart_without_progress")
- alias_method :read_multipart_without_progress, :read_multipart
- alias_method :read_multipart, :read_multipart_with_progress
- end
-
- end
-end
diff --git a/actionpack/lib/action_controller/upload_progress.rb b/actionpack/lib/action_controller/upload_progress.rb
deleted file mode 100644
index 76fdcf4e00..0000000000
--- a/actionpack/lib/action_controller/upload_progress.rb
+++ /dev/null
@@ -1,473 +0,0 @@
-# Unfortunately we need to require multipart_progress here and not in
-# uplaod_status_for because if the upload happens to hit a fresh FCGI instance
-# the upload_status_for method will be called after the CGI object is created
-# Requiring here means that multipart progress will be enabled for all multipart
-# postings.
-require 'action_controller/cgi_ext/multipart_progress'
-
-module ActionController #:nodoc:
- # == THIS IS AN EXPERIMENTAL FEATURE
- #
- # Which means that it doesn't yet work on all systems. We're still working on full
- # compatibility. It's thus not advised to use this unless you've verified it to work
- # fully on all the systems that is a part of your environment. Consider this an extended
- # preview.
- #
- # To enable this module, add <tt>ActionController::Base.enable_upload_progress</tt> to your
- # config/environment.rb file.
- #
- # == Action Pack Upload Progress for multipart uploads
- #
- # The UploadProgress module aids in the process of viewing an Ajax driven
- # upload status when working with multipart forms. It offers a macro that
- # will prepare an action for handling the cleanup of the Ajax updating including
- # passing the redirect URL and custom parameters to the Javascript finish handler.
- #
- # UploadProgress is available for all multipart uploads when the +upload_status_for+
- # macro is called in one of your controllers.
- #
- # The progress is stored as an UploadProgress::Progress object in the session and
- # is accessible in the controller and view with the +upload_progress+ method.
- #
- # For help rendering the UploadProgress enabled form and supported elements, see
- # ActionView::Helpers::UploadProgressHelper.
- #
- # === Automatic updating on upload actions
- #
- # class DocumentController < ApplicationController
- # upload_status_for :create
- #
- # def create
- # # ... Your document creation action
- # end
- # end
- #
- # The +upload_status_for+ macro will override the rendering of the action passed
- # if +upload_id+ is found in the query string. This allows for default
- # behavior if Javascript is disabled. If you are tracking the upload progress
- # then +create+ will now return the cleanup scripts that will terminate the polling
- # of the upload status.
- #
- # === Customized status rendering
- #
- # class DocumentController < ApplicationController
- # upload_status_for :create, :status => :custom_status
- #
- # def create
- # # ... Your document creation action
- # end
- #
- # def custom_status
- # # ... Override this action to return content to be replaced in
- # # the status container
- # render :inline => "<%= upload_progress.completed_percent rescue 0 %> % complete", :layout => false
- # end
- #
- # The default status action is +upload_status+. The results of this action
- # are added used to replace the contents of the HTML elements defined in
- # +upload_status_tag+. Within +upload_status+, you can load the Progress
- # object from the session with the +upload_progress+ method and display your own
- # results.
- #
- # Completion of the upload status updating occurs automatically with an +after_filter+ call to
- # +finish_upload_status+. Because the upload must be posted into a hidden IFRAME to enable
- # Ajax updates during the upload, +finish_upload_status+ overwrites the results of any previous
- # +render+ or +redirect_to+ so it can render the necessary Javascript that will properly terminate
- # the status updating loop, trigger the completion callback or redirect to the appropriate URL.
- #
- # ==== Basic Example (View):
- #
- # <%= form_tag_with_upload_progress({:action => 'create'}, {:finish => 'alert("Document Uploaded")'}) %>
- # <%= upload_status_tag %>
- # <%= file_field 'document', 'file' %>
- # <%= end_form_tag %>
- #
- # ==== Basic Example (Controller):
- #
- # class DocumentController < ApplicationController
- # upload_status_for :create
- #
- # def create
- # @document = Document.create(params[:document])
- # end
- # end
- #
- # ==== Extended Example (View):
- #
- # <%= form_tag_with_upload_progress({:action => 'create'}, {}, {:action => :custom_status}) %>
- # <%= upload_status_tag %>
- # <%= file_field 'document', 'file' %>
- # <%= submit_tag "Upload" %>
- # <%= end_form_tag %>
- #
- # <%= form_tag_with_upload_progress({:action => 'add_preview'}, {:finish => 'alert(arguments[0])'}, {:action => :custom_status}) %>
- # <%= upload_status_tag %>
- # <%= submit_tag "Upload" %>
- # <%= file_field 'preview', 'file' %>
- # <%= end_form_tag %>
- #
- # ==== Extended Example (Controller):
- #
- # class DocumentController < ApplicationController
- # upload_status_for :add_preview, :create, {:status => :custom_status}
- #
- # def add_preview
- # @document = Document.find(params[:id])
- # @document.preview = Preview.create(params[:preview])
- # if @document.save
- # finish_upload_status "'Preview added'"
- # else
- # finish_upload_status "'Preview not added'"
- # end
- # end
- #
- # def create
- # @document = Document.new(params[:document])
- #
- # upload_progress.message = "Processing document..."
- # session.update
- #
- # @document.save
- # redirect_to :action => 'show', :id => @document.id
- # end
- #
- # def custom_status
- # render :inline => '<%= upload_progress_status %> <div>Updated at <%= Time.now %></div>', :layout => false
- # end
- #
- # ==== Environment checklist
- #
- # This is an experimental feature that requires a specific webserver environment. Use the following checklist
- # to confirm that you have an environment that supports upload progress.
- #
- # ===== Ruby:
- #
- # * Running the command `ruby -v` should print "ruby 1.8.2 (2004-12-25)" or older
- #
- # ===== Web server:
- #
- # * Apache 1.3, Apache 2.0 or Lighttpd *1.4* (need to build lighttpd from CVS)
- #
- # ===== FastCGI bindings:
- #
- # * > 0.8.6 and must be the compiled C version of the bindings
- # * The command `ruby -e "p require('fcgi.so')"` should print "true"
- #
- # ===== Apache/Lighttpd FastCGI directives:
- #
- # * You must allow more than one FCGI server process to allow concurrent requests.
- # * If there is only a single FCGI process you will not get the upload status updates.
- # * You can check this by taking a look for running FCGI servers in your process list during a progress upload.
- # * Apache directive: FastCGIConfig -minProcesses 2
- # * Lighttpd directives taken from config/lighttpd.conf (min-procs):
- #
- # fastcgi.server = (
- # ".fcgi" => (
- # "APP_NAME" => (
- # "socket" => "/tmp/APP_NAME1.socket",
- # "bin-path" => "RAILS_ROOT/public/dispatch.fcgi",
- # "min-procs" => 2
- # )
- # )
- # )
- #
- # ===== config/environment.rb:
- #
- # * Add the following line to your config/environment.rb and restart your web server.
- # * <tt>ActionController::Base.enable_upload_progress</tt>
- #
- # ===== Development log:
- #
- # * When the upload progress is enabled by you will find something the following lines:
- # * "Multipart upload with progress (id: 1, size: 85464)"
- # * "Finished processing multipart upload in 0.363729s"
- # * If you are properly running multiple FCGI processes, then you will see multiple entries for rendering the "upload_status" action before the "Finish processing..." log entry. This is a *good thing* :)
- #
- module UploadProgress
- def self.append_features(base) #:nodoc:
- super
- base.extend(ClassMethods)
- base.helper_method :upload_progress, :next_upload_id, :last_upload_id, :current_upload_id
- end
-
- module ClassMethods #:nodoc:
- # Creates an +after_filter+ which will call +finish_upload_status+
- # creating the document that will be loaded into the hidden IFRAME, terminating
- # the status polling forms created with +form_with_upload_progress+.
- #
- # Also defines an action +upload_status+ or a action name passed as
- # the <tt>:status</tt> option. This status action must match the one expected
- # in the +form_tag_with_upload_progress+ helper.
- #
- def upload_status_for(*actions)
- after_filter :finish_upload_status, :only => actions
-
- define_method(actions.last.is_a?(Hash) && actions.last[:status] || :upload_status) do
- render(:inline => '<%= upload_progress_status %>', :layout => false)
- end
- end
- end
-
- # Overwrites the body rendered if the upload comes from a form that tracks
- # the progress of the upload. After clearing the body and any redirects, this
- # method then renders the helper +finish_upload_status+
- #
- # This method only needs to be called if you wish to pass a
- # javascript parameter to your finish event handler that you optionally
- # define in +form_with_upload_progress+
- #
- # === Parameter:
- #
- # client_js_argument:: a string containing a Javascript expression that will
- # be evaluated and passed to your +finish+ handler of
- # +form_tag_with_upload_progress+.
- #
- # You can pass a String, Number or Boolean.
- #
- # === Strings
- #
- # Strings contain Javascript code that will be evaluated on the client. If you
- # wish to pass a string to the client finish callback, you will need to include
- # quotes in the +client_js_argument+ you pass to this method.
- #
- # ==== Example
- #
- # finish_upload_status("\"Finished\"")
- # finish_upload_status("'Finished #{@document.title}'")
- # finish_upload_status("{success: true, message: 'Done!'}")
- # finish_upload_status("function() { alert('Uploaded!'); }")
- #
- # === Numbers / Booleans
- #
- # Numbers and Booleans can either be passed as Number objects or string versions
- # of number objects as they are evaluated by Javascript the same way as in Ruby.
- #
- # ==== Example
- #
- # finish_upload_status(0)
- # finish_upload_status(@document.file.size)
- # finish_upload_status("10")
- #
- # === Nil
- #
- # To pass +nil+ to the finish callback, use a string "undefined"
- #
- # ==== Example
- #
- # finish_upload_status(@message || "undefined")
- #
- # == Redirection
- #
- # If you action performs a redirection then +finish_upload_status+ will recognize
- # the redirection and properly create the Javascript to perform the redirection in
- # the proper location.
- #
- # It is possible to redirect and pass a parameter to the finish callback.
- #
- # ==== Example
- #
- # redirect_to :action => 'show', :id => @document.id
- # finish_upload_status("'Redirecting you to your new file'")
- #
- #
- def finish_upload_status(client_js_argument='')
- if not @rendered_finish_upload_status and params[:upload_id]
- @rendered_finish_upload_status = true
-
- erase_render_results
- location = erase_redirect_results || ''
-
- ## TODO determine if #inspect is the appropriate way to marshall values
- ## in inline templates
-
- template = "<%= finish_upload_status({"
- template << ":client_js_argument => #{client_js_argument.inspect}, "
- template << ":redirect_to => #{location.to_s.inspect}, "
- template << "}) %>"
-
- render({ :inline => template, :layout => false })
- end
- end
-
- # Returns and saves the next unique +upload_id+ in the instance variable
- # <tt>@upload_id</tt>
- def next_upload_id
- @upload_id = last_upload_id.succ
- end
-
- # Either returns the last saved +upload_id+ or looks in the session
- # for the last used +upload_id+ and saves it as the intance variable
- # <tt>@upload_id</tt>
- def last_upload_id
- @upload_id ||= ((session[:uploads] || {}).keys.map{|k| k.to_i}.sort.last || 0).to_s
- end
-
- # Returns the +upload_id+ from the query parameters or if it cannot be found
- # in the query parameters, then return the +last_upload_id+
- def current_upload_id
- params[:upload_id] or last_upload_id
- end
-
- # Get the UploadProgress::Progress object for the supplied +upload_id+ from the
- # session. If no +upload_id+ is given, then use the +current_upload_id+
- #
- # If an UploadProgress::Progress object cannot be found, a new instance will be
- # returned with <code>total_bytes == 0</code>, <code>started? == false</code>,
- # and <code>finished? == true</code>.
- def upload_progress(upload_id = nil)
- upload_id ||= current_upload_id
- session[:uploads] && session[:uploads][upload_id] || UploadProgress::Progress.new(0)
- end
-
- # == THIS IS AN EXPERIMENTAL FEATURE
- #
- # Which means that it doesn't yet work on all systems. We're still working on full
- # compatibility. It's thus not advised to use this unless you've verified it to work
- # fully on all the systems that is a part of your environment. Consider this an extended
- # preview.
- #
- # Upload Progress abstracts the progress of an upload. It's used by the
- # multipart progress IO that keeps track of the upload progress and creating
- # the application depends on. It contians methods to update the progress
- # during an upload and read the statistics such as +received_bytes+,
- # +total_bytes+, +completed_percent+, +bitrate+, and
- # +remaining_seconds+
- #
- # You can get the current +Progress+ object by calling +upload_progress+ instance
- # method in your controller or view.
- #
- class Progress
- unless const_defined? :MIN_SAMPLE_TIME
- # Number of seconds between bitrate samples. Updates that occur more
- # frequently than +MIN_SAMPLE_TIME+ will not be queued until this
- # time passes. This behavior gives a good balance of accuracy and load
- # for both fast and slow transfers.
- MIN_SAMPLE_TIME = 0.150
-
- # Number of seconds between updates before giving up to try and calculate
- # bitrate anymore
- MIN_STALL_TIME = 10.0
-
- # Number of samples used to calculate bitrate
- MAX_SAMPLES = 20
- end
-
- # Number bytes received from the multipart post
- attr_reader :received_bytes
-
- # Total number of bytes expected from the mutlipart post
- attr_reader :total_bytes
-
- # The last time the upload history was updated
- attr_reader :last_update_time
-
- # A message you can set from your controller or view to be rendered in the
- # +upload_status_text+ helper method. If you set a messagein a controller
- # then call <code>session.update</code> to make that message available to
- # your +upload_status+ action.
- attr_accessor :message
-
- # Create a new Progress object passing the expected number of bytes to receive
- def initialize(total)
- @total_bytes = total
- reset!
- end
-
- # Resets the received_bytes, last_update_time, message and bitrate, but
- # but maintains the total expected bytes
- def reset!
- @received_bytes, @last_update_time, @stalled, @message = 0, 0, false, ''
- reset_history
- end
-
- # Number of bytes left for this upload
- def remaining_bytes
- @total_bytes - @received_bytes
- end
-
- # Completed percent in integer form from 0..100
- def completed_percent
- (@received_bytes * 100 / @total_bytes).to_i rescue 0
- end
-
- # Updates this UploadProgress object with the number of bytes received
- # since last update time and the absolute number of seconds since the
- # beginning of the upload.
- #
- # This method is used by the +MultipartProgress+ module and should
- # not be called directly.
- def update!(bytes, elapsed_seconds)#:nodoc:
- if @received_bytes + bytes > @total_bytes
- #warn "Progress#update received bytes exceeds expected bytes"
- bytes = @total_bytes - @received_bytes
- end
-
- @received_bytes += bytes
-
- # Age is the duration of time since the last update to the history
- age = elapsed_seconds - @last_update_time
-
- # Record the bytes received in the first element of the history
- # in case the sample rate is exceeded and we shouldn't record at this
- # time
- @history.first[0] += bytes
- @history.first[1] += age
-
- history_age = @history.first[1]
-
- @history.pop while @history.size > MAX_SAMPLES
- @history.unshift([0,0]) if history_age > MIN_SAMPLE_TIME
-
- if history_age > MIN_STALL_TIME
- @stalled = true
- reset_history
- else
- @stalled = false
- end
-
- @last_update_time = elapsed_seconds
-
- self
- end
-
- # Calculates the bitrate in bytes/second. If the transfer is stalled or
- # just started, the bitrate will be 0
- def bitrate
- history_bytes, history_time = @history.transpose.map { |vals| vals.inject { |sum, v| sum + v } }
- history_bytes / history_time rescue 0
- end
-
- # Number of seconds elapsed since the start of the upload
- def elapsed_seconds
- @last_update_time
- end
-
- # Calculate the seconds remaining based on the current bitrate. Returns
- # O seconds if stalled or if no bytes have been received
- def remaining_seconds
- remaining_bytes / bitrate rescue 0
- end
-
- # Returns true if there are bytes pending otherwise returns false
- def finished?
- remaining_bytes <= 0
- end
-
- # Returns true if some bytes have been received
- def started?
- @received_bytes > 0
- end
-
- # Returns true if there has been a delay in receiving bytes. The delay
- # is set by the constant MIN_STALL_TIME
- def stalled?
- @stalled
- end
-
- private
- def reset_history
- @history = [[0,0]]
- end
- end
- end
-end