module UploadProgress #:nodoc: # 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 session.update 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