Ruby on Rails Web File Download Indicator

    Objective: To show how to implement the file download indicator for various Ruby on Rails configurations:
    I will divide the article series into three parts.
    mongrel
    mongrel (s) + nginx
    mod_rails

    The following is verified with:
    Ruby on Rails 2.2.2
    ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
    mongrel 1.1.5
    Mac OS X 10.5.6


    Part I. How to do this with mongrel


    The article is based on a link to which is at the very end. But repeating it on my new rails and mnogrels nothing happened. I changed it a little.

    Frame Building: Adding Routes:
    rails upload
    cd upload
    rm public/index.html
    script/generate controller home index upload progress
    touch app/views/layouts/application.html.erb



    Copy Source | Copy HTML
    1. ActionController::Routing::Routes.draw do |map|
    2.   map.root :controller => 'home'
    3.   map.upload '/upload', :controller => 'home', :action => 'upload'
    4.   map.progress '/progress', :controller => 'home', :action => 'progress'
    5. end
    6.  


    The preparation phase is completed. Now we need to explain a little how the file upload indicator functions. The form submission should go to the iframe, and using the ajax request from the page itself, you can use the right frequency to contact a certain controller that will return the current state (you can use json, you can use html, here it is done by javascript), and depending on them, on the page move the indicator.

    So, there is only one controller, and there are three methods in it: index to display the form for uploading, upload - processing the downloaded file, progress - to obtain data on the download status. The controller is as follows:

    Copy Source | Copy HTML
    1. class HomeController < ApplicationController
    2.   skip_before_filter :verify_authenticity_token, :only => [:progress]
    3.  
    4.   def index
    5.     @upid = Time.now.tv_sec.to_s
    6.   end
    7.  
    8.   def upload
    9.     render :text => 'done'
    10.   end
    11.  
    12.   def progress
    13.     @status = Mongrel::Uploads.check(params[:upload_id])
    14.     respond_to do |format|
    15.       format.js
    16.     end
    17.   end
    18. end


    skip_before_filter is needed since requests will go without a token. @upid is a unique identifier.
    Now you need to write some kind of plugin for mongrel, which will intercept the file upload (more specifically, access to a specific url) and store data about the download status, in addition, you need to access this data somehow. It’s good that this has already been implemented and the code can be taken from svn:

    svn co svn://rubyforge.org/var/svn/mongrel/trunk/projects/mongrel_upload_progress

    I suggest doing this as follows: create the lib / progress_plugin.rb file with the following contents:
    Copy Source | Copy HTML
    1. class Upload < GemPlugin::Plugin "/handlers"
    2.   include Mongrel::HttpHandlerPlugin
    3.  
    4.   def initialize(options = {})
    5.     @path_info = Array(options[:path_info])
    6.     @frequency = options[:frequency] || 3
    7.     @request_notify = true
    8.     if options[:drb]
    9.       require 'drb'
    10.       DRb.start_service
    11.       Mongrel.const_set :Uploads, DRbObject.new(nil, options[:drb])
    12.     else
    13.       Mongrel.const_set :Uploads, Mongrel::UploadProgress.new
    14.     end
    15.     Mongrel::Uploads.debug = true if options[:debug]
    16.   end
    17.  
    18.   def request_begins(params)
    19.     upload_notify(:add, params, params[Mongrel::Const::CONTENT_LENGTH].to_i)
    20.   end
    21.  
    22.   def request_progress(params, clen, total)
    23.     upload_notify(:mark, params, clen)
    24.   end
    25.  
    26.   def process(request, response)
    27.     upload_notify(:finish, request.params)
    28.   end
    29.  
    30.   private
    31.     def upload_notify(action, params, *args)
    32.       return unless @path_info.include?(params['PATH_INFO']) &&
    33.         params[Mongrel::Const::REQUEST_METHOD] == 'POST' &&
    34.         upload_id = Mongrel::HttpRequest.query_parse(params['QUERY_STRING'])['upload_id']
    35.       if action == :mark
    36.         last_checked_time = Mongrel::Uploads.last_checked(upload_id)
    37.         return unless last_checked_time && Time.now - last_checked_time > @frequency
    38.       end
    39.       Mongrel::Uploads.send(action, upload_id, *args)
    40.       Mongrel::Uploads.update_checked_time(upload_id) unless action == :finish
    41.     end
    42. end
    43.  
    44. # Keeps track of the status of all currently processing uploads
    45. class Mongrel::UploadProgress
    46.   attr_accessor :debug
    47.   def initialize
    48.     @guard = Mutex.new
    49.     @counters = {}
    50.   end
    51.  
    52.   def check(upid)
    53.     @counters[upid].last rescue nil
    54.   end
    55.  
    56.   def last_checked(upid)
    57.     @counters[upid].first rescue nil
    58.   end
    59.  
    60.   def update_checked_time(upid)
    61.     @guard.synchronize { @counters[upid][0] = Time.now }
    62.   end
    63.  
    64.   def add(upid, size)
    65.     @guard.synchronize do
    66.       @counters[upid] = [Time.now, {:size => size, :received => 0}]
    67.       puts "#{upid}: Added" if @debug
    68.     end
    69.   end
    70.  
    71.   def mark(upid, len)
    72.     return unless status = check(upid)
    73.     puts "#{upid}: Marking" if @debug
    74.     @guard.synchronize { status[:received] = status[:size] - len }
    75.   end
    76.  
    77.   def finish(upid)
    78.     @guard.synchronize do
    79.       puts "#{upid}: Finished" if @debug
    80.       @counters.delete(upid)
    81.     end
    82.   end
    83.  
    84.   def list
    85.     @counters.keys.sort
    86.   end
    87. end
    88.  


    After that, create a config for mongrel (essentially a regular ruby-file) that will connect this plugin to mongrel: Put in mongrel_upload_progress.conf the following:
    touch config/mongrel_upload_progress.conf



    Copy Source | Copy HTML
    1. require 'progress_plugin'
    2. uri "/", :handler => plugin('/handlers/upload', :path_info => '/upload'), :in_front => true
    3.  


    path_info is a route that will be intercepted by the plugin to monitor the loading status.
    The download form and all the necessary js-nicknames can be taken here .

    It remains to simply run the mongrel with the configuration file: The indicator in the browser will look something like this: If you can use drb several mongrel, the details are described in the link below. I did not make any cosmetic changes in the framework of this article. In a more beautiful form, the indicator can be viewed on rghost.ru. Of course, it is a pity to admit that all this was written most likely in vain, since few people manage with a simple mingrel. Usually they put nginx in front of the mongrel or generally replace it with apache with mod_passenger. I plan to write articles about them later.
    mongrel_rails start -S config/mongrel_upload_progress.conf










    List of references


    mongrel upload progress

    Also popular now: