multipart_progress.rb
上传用户:netsea168
上传日期:2022-07-22
资源大小:4652k
文件大小:6k
源码类别:

Ajax

开发平台:

Others

  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. require 'cgi'
  44. class CGI #:nodoc:
  45.   class ProgressIO < SimpleDelegator #:nodoc:
  46.     MIN_SAVE_INTERVAL = 1.0         # Number of seconds between session saves
  47.     attr_reader :progress, :session
  48.     def initialize(orig_io, progress, session)
  49.       @session = session
  50.       @progress = progress
  51.       
  52.       @start_time = Time.now
  53.       @last_save_time = @start_time
  54.       save_progress
  55.       
  56.       super(orig_io)
  57.     end
  58.     def read(*args)
  59.       data = __getobj__.read(*args)
  60.       if data and data.size > 0
  61.         now = Time.now
  62.         elapsed =  now - @start_time
  63.         progress.update!(data.size, elapsed)
  64.         if now - @last_save_time > MIN_SAVE_INTERVAL
  65.           save_progress 
  66.           @last_save_time = now 
  67.         end
  68.       else
  69.         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")
  70.         progress.reset!
  71.         save_progress
  72.       end
  73.       data
  74.     end
  75.     def save_progress
  76.       @session.update 
  77.     end
  78.     def finish
  79.       @session.update
  80.       ActionController::Base.logger.debug("Finished processing multipart upload in #{@progress.elapsed_seconds.to_s}s")
  81.     end
  82.   end
  83.   module QueryExtension #:nodoc:
  84.     # Need to do lazy aliasing on the instance that we are extending because of the way QueryExtension
  85.     # gets included for each instance of the CGI object rather than on a module level.  This method is a 
  86.     # bit obtrusive because we are overriding CGI::QueryExtension::extended which could be used in the 
  87.     # future.  Need to research a better method
  88.     def self.extended(obj)
  89.       obj.instance_eval do
  90.         # unless defined? will prevent clobbering the progress IO on multiple extensions
  91.         alias :stdinput_without_progress :stdinput unless defined? stdinput_without_progress
  92.         alias :stdinput :stdinput_with_progress 
  93.       end
  94.     end
  95.     def stdinput_with_progress
  96.       @stdin_with_progress or stdinput_without_progress
  97.     end
  98.     private
  99.     # Bootstrapped on UploadProgress::upload_status_for
  100.     def read_multipart_with_progress(boundary, content_length)
  101.       begin
  102.         begin
  103.           # Session disabled if the default session options have been set to 'false'
  104.           options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
  105.           raise RuntimeError.new("Multipart upload progress disabled, no session options") unless options
  106.           options = options.stringify_keys
  107.        
  108.           # Pull in the application controller to satisfy any dependencies on class definitions
  109.           # of instances stored in the session.
  110.           # Be sure to stay compatible with Rails 1.0/const_load!
  111.           if Object.const_defined?(:Controllers) and Controllers.respond_to?(:const_load!)
  112.             Controllers.const_load!(:ApplicationController, "application") unless Controllers.const_defined?(:ApplicationController)
  113.           else
  114.             require_dependency('application.rb') unless Object.const_defined?(:ApplicationController)
  115.           end
  116.           # Assumes that @cookies has already been setup
  117.           # Raises nomethod if upload_id is not defined
  118.           @params = CGI::parse(read_params_from_query)
  119.           upload_id = @params[(options['upload_key'] || 'upload_id')].first
  120.           raise RuntimeError.new("Multipart upload progress disabled, no upload id in query string") unless upload_id
  121.           upload_progress = UploadProgress::Progress.new(content_length)
  122.           session = Session.new(self, options)
  123.           session[:uploads] = {} unless session[:uploads]
  124.           session[:uploads].delete(upload_id) # in case the same upload id is used twice
  125.           session[:uploads][upload_id] = upload_progress
  126.           @stdin_with_progress = CGI::ProgressIO.new(stdinput_without_progress, upload_progress, session)
  127.           ActionController::Base.logger.debug("Multipart upload with progress (id: #{upload_id}, size: #{content_length})")
  128.         rescue
  129.           ActionController::Base.logger.debug("Exception during setup of read_multipart_with_progress: #{$!}")
  130.         end
  131.       ensure
  132.         begin
  133.           params = read_multipart_without_progress(boundary, content_length)
  134.           @stdin_with_progress.finish if @stdin_with_progress.respond_to? :finish
  135.         ensure
  136.           @stdin_with_progress = nil
  137.           session.close if session
  138.         end
  139.       end
  140.       params 
  141.     end
  142.     # Prevent redefinition of aliases on multiple includes
  143.     unless private_instance_methods.include?("read_multipart_without_progress")
  144.       alias_method :read_multipart_without_progress, :read_multipart 
  145.       alias_method :read_multipart, :read_multipart_with_progress
  146.     end
  147.   end
  148. end