0

Im using jquery-fileupload-rails and paperclip Im trying to get video files to upload asynchronously. I keep receiving internal server error A picture of the client side error

Migration

class CreateCourses < ActiveRecord::Migration[5.1]
  def change
    create_table :courses do |t|

      t.string :name, null: false
      t.string :status, null: false, index: true
      t.attachment :upload
      t.boolean :visible, default: false, null: false
      t.timestamps null: false
    end
  end
end

Model

class Course < ActiveRecord::Base
  # Variables
  COURSE_STATUSES = %w(new uploading uploaded)

  # Validations
  validates :name, presence: true
  validates :status, inclusion: { in: COURSE_STATUSES } 




  has_attached_file :upload, styles: {
      :medium => {
          :geometry => "640x480",
          :format => 'mp4'
      },
      :thumb => { :geometry => "160x120", :format => 'jpeg', :time => 10}
  }, :processors => [:transcoder]


  def to_jq_upload(error=nil)
    {
      files: [
        {
          name: read_attribute(:upload_file_name),
          size: read_attribute(:upload_file_size),
          url: upload.url(:original),
          delete_url: Rails.application.routes.url_helpers.course_path(self),
          delete_type: "DELETE" 
        }
      ]
    }
  end

  def all_formats_encoded?
    self.webm_file.path && self.mp4_file.path && self.ogg_file.path ? true : false
  end


  def upload_done?
    status.in? %w(uploaded) 
  end

  def run_encoders
    if video_file?
      Mp4VideoEncoder.perform_async(self.id)
      OgvVideoEncoder.perform_async(self.id)
      WebmVideoEncoder.perform_async(self.id)
    end
  end
end

Controller

class CoursesController < ApplicationController
  before_action :set_course, only: [:show, :edit, :update, :destroy, :upload, :do_upload, :resume_upload, :update_status, :reset_upload]

  # GET /courses
  # GET /courses.json
  def index
    @courses = Course.all.order(name: :asc)
  end

  # GET /courses/1
  # GET /courses/1.json
  def show
    # If upload is not commenced or finished, redirect to upload page
    return redirect_to upload_course_path(@course) if @course.status.in?(%w(new uploading))
  end

  # GET /courses/new
  def new
    @course = Course.new
  end

  # GET /courses/1/edit
  def edit
  end

  # POST /courses
  # POST /courses.json
  def create
    @course = Course.new(course_params)
    @course.status = 'new'

    respond_to do |format|
      if @course.save
        format.html { redirect_to course_path(@course), notice: 'Course was successfully created.' }
        format.json { render :show, status: :created, location: @course }
      else
        format.html { render :new }
        format.json { render json: @course.errors, status: :unprocessable_entity }
      end
    end
  end


  def update
    @course.assign_attributes(status: 'new', upload: nil) if params[:delete_upload] == 'yes'

    respond_to do |format|
      if @course.update(course_params)
        format.html { redirect_to @course, notice: 'Course was successfully updated.' }
        format.json { render :show, status: :ok, location: @course }
      else
        format.html { render :edit }
        format.json { render json: @course.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /courses/1
  # DELETE /courses/1.json
  def destroy
    @course.destroy
    respond_to do |format|
      format.html { redirect_to courses_url, notice: 'Course was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  # GET /courses/:id/upload
  def upload

  end

  # PATCH /courses/:id/upload.json
  def do_upload
    unpersisted_course = Course.new(upload_params)

    # If no file has been uploaded or the uploaded file has a different filename,
    # do a new upload from scratch
      @course.assign_attributes(upload_params)
      @course.status = 'uploading'
      @course.save!
      render json: @course.to_jq_upload and return

    # If the already uploaded file has the same filename, try to resume

      current_size = @course.upload_file_size
      content_range = request.headers['CONTENT-RANGE']
      begin_of_chunk = content_range[/\ (.*?)-/,1].to_i # "bytes 100-999999/1973660678" will return '100'

      # If the there is a mismatch between the size of the incomplete upload and the content-range in the
      # headers, then it's the wrong chunk! 
      # In this case, start the upload from scratch
      unless begin_of_chunk == current_size
        @course.update!(upload_params)
        render json: @course.to_jq_upload and return
      end

      # Add the following chunk to the incomplete upload
      File.open(@course.upload.path, "ab") { |f| f.write(upload_params[:upload, :image].read) }

      # Update the upload_file_size attribute
      @course.upload_file_size = @course.upload_file_size.nil? ? unpersisted_course.upload_file_size : @course.upload_file_size + unpersisted_course.upload_file_size
      @course.save!

      render json: @course.to_jq_upload and return
  end

  # GET /courses/:id/reset_upload
  def reset_upload
    # Allow users to delete uploads only if they are incomplete
    raise StandardError, "Action not allowed" unless @course.status == 'uploading'
    @course.update!(status: 'new', upload: nil)
    redirect_to @course, notice: "Upload reset successfully. You can now start over"
  end

  # GET /courses/:id/resume_upload.json
  def resume_upload
    render json: { file: { name: @course.upload.url(:default, timestamp: false), size: @course.upload_file_size } } and return
  end

  # PATCH /courses/:id/update_upload_status
  def update_status
    raise ArgumentError, "Wrong status provided " + params[:status] unless @course.status == 'uploading' && params[:status] == 'uploaded'
    @course.update!(status: params[:status])
    head :ok
  end


  private
  # Use callbacks to share common setup or constraints between actions.
  def set_course
    @course = Course.find(params[:id])
  end

  # Never trust parameters from the scary internet, only allow the white list through.
  def course_params
    params.require(:course).permit(:name, :image, :upload)
  end

  def upload_params
    params.require(:course).permit(:upload, :image)
  end
end

Views upload.html.haml

%h1
  Upload a file
%hr

= form_for @course, url: upload_course_path, html: { multipart: true, id: "fileupload" } do |f|
  .row
    %h3
      Course name
    .padding-left
      = @course.name

  .row.empty-space

  .row 
    - unless @course.status == 'uploading'
      .alert.alert-success.smaller-text
        To begin an upload, please first select a file from your computer.
    - else
      .alert.alert-warning.smaller-text
        The system detected an unfinished upload. To resume it, please select the same file and press the green button. <br />
        Resume not working or receiving error messages? Then please click #{link_to 'here', reset_upload_course_path(@course)} to delete the temoporary file and start over.

  .row.fileupload-buttonbar
    #js-data{ data: { course: @course.id } }


    .col-lg-7
      .btn.btn-primary.fileinput-button
        %i.glyphicon.glyphicon-plus
        Select a file
        = f.file_field :upload

  %table.table.table-striped#js-file-container{ role: "presentation" }
    %tbody.files

:javascript
  $(document).ready(function () {
    var course_id = $('#js-data').data('course');
    var controller_name = $('body').data('controller');
    var model_instance_path = '/' + controller_name + '/' + course_id;
    var resume_path = model_instance_path + '/resume_upload';
    var update_status_path = model_instance_path + '/update_status';

    $('#fileupload').fileupload({
      maxNumberOfFiles: 3,
      maxChunkSize: 10000000, // 10 MB
      type: 'PATCH',
      add: function (e, data) {
        var that = this;
        $.getJSON(resume_path, { file: data.files[0].name }, function (result) {
            var file = result.file;
            data.uploadedBytes = file && file.size;
            $.blueimp.fileupload.prototype
                .options.add.call(that, e, data);
        });
      }
    });

    $('#fileupload')
      .bind('fileuploadchunkdone', function (e, data) {
        var perc = parseInt(data.loaded / data.total * 100, 10);
        $('#js-completed').html(perc + "%");
      })
      .bind('fileuploaddone', function (e, data) {
        $.ajax({
          url: update_status_path,
          type: "PATCH",
          data: { status: 'uploaded'},
          success: function(data) {
            window.location.replace(model_instance_path);
          } 
        });
      });
    });

:plain
  <script id="template-upload" type="text/x-tmpl">
    {% for (var i=0, file; file=o.files[i]; i++) { %}
        <tr class="template-upload fade">

            <td width="45%">
                <p class="name"><h3>{%=file.name%}</h3></p>
                <p class="size">Processing...</p>
                <strong class="error text-danger"></strong>

                {% if (!i && !o.options.autoUpload) { %}
                    <button class="btn btn-success start" disabled>
                        <i class="glyphicon glyphicon-upload"></i>
                        Start / resume
                    </button> 
                {% } %}
                {% if (!i) { %}
                    <button class="btn btn-danger cancel">
                        <i class="glyphicon glyphicon-ban-circle"></i>
                        Cancel upload
                    </button>
                {% } %}
            </td>
            <td width="45%" style="vertical-align: middle;">
                <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="progress-bar progress-bar-success" style="width:0%;"></div></div>
                <p><h1 id="js-completed" align="center"></h1></p>
            </td>
        </tr>
    {% } %}
  </script>

  <script id="template-download" type="text/x-tmpl">
    {% for (var i=0, file; file=o.files[i]; i++) { %}
        <tr class="template-download fade">
            <td>
                <span class="preview">
                    {% if (file.thumbnailUrl) { %}
                        <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" data-gallery><img src="{%=file.thumbnailUrl%}"></a>
                    {% } %}
                </span>
            </td>
            <td>
                <p class="name">
                    {% if (file.url) { %}
                        <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a>
                    {% } else { %}
                        <span>{%=file.name%}</span>
                    {% } %}
                </p>
                {% if (file.error) { %}
                    <div><span class="label label-danger">Error</span> {%=file.error%}</div>
                {% } %}
            </td>
            <td>
                <span class="size">{%=o.formatFileSize(file.size)%}</span>
            </td>
            <td>
                {% if (file.deleteUrl) { %}
                    <button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}>
                        <i class="glyphicon glyphicon-trash"></i>
                        <span>Delete</span>
                    </button>
                    <input type="checkbox" name="delete" value="1" class="toggle">
                {% } else { %}
                    <button class="btn btn-warning cancel">
                        <i class="glyphicon glyphicon-ban-circle"></i>
                        <span>Cancel</span>
                    </button>
                {% } %}
            </td>
        </tr>
      {% } %}
    </script>

I added a worker as well, and have sidekiq run in the background

mp4_video_encoder.rb

class Mp4VideoEncoder
  include Sidekiq::Worker

  def perform(video_id)
    video = Video.find(video_id)
    path = video.video_file.path
    output = "/tmp/#{Time.now.getutc.to_f.to_s.delete('.')}.mp4"
    _command = `ffmpeg -i #{path} -f mp4 -vcodec h264 -acodec aac -strict -2 #{output}`
    if $?.to_i == 0
      video.mp4_file = File.open(output, 'r')
      video.save
      FileUtils.rm(output)
    else
      raise $?
    end
  end
end

Here is my error logs

Started GET "/" for ::1 at 2017-03-14 17:21:28 -0400
Processing by CoursesController#index as HTML
  Rendering courses/index.html.haml within layouts/application
  Course Load (2.6ms)  SELECT "courses".* FROM "courses" ORDER BY "courses"."name" ASC
  Rendered courses/index.html.haml within layouts/application (61.3ms)
  Rendered shared/_topbar.html.haml (3.9ms)
Completed 200 OK in 694ms (Views: 613.6ms | ActiveRecord: 2.6ms | Solr: 0.0ms)


Started GET "/courses/16/resume_upload?file=Flipping_Physics_Trailer.mp4" for ::1 at 2017-03-14 17:22:48 -0400
Processing by CoursesController#resume_upload as JSON
  Parameters: {"file"=>"Flipping_Physics_Trailer.mp4", "id"=>"16"}
  Course Load (1.3ms)  SELECT  "courses".* FROM "courses" WHERE "courses"."id" = ? LIMIT ?  [["id", 16], ["LIMIT", 1]]
Completed 200 OK in 27ms (Views: 2.4ms | ActiveRecord: 1.3ms | Solr: 0.0ms)


Started PATCH "/courses/16/upload" for ::1 at 2017-03-14 17:22:50 -0400
Processing by CoursesController#do_upload as JSON
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"EqpP5O6LeE5/gIzmbA2nTNwyl6I0GryZlcenPbIWJGlIZbJ6+cZ/e1qhijxoP38vVIEFszo5RY7JVOucxbt3lg==", "course"=>{"upload"=>#<ActionDispatch::Http::UploadedFile:0x007fbd960130a0 @tempfile=#<Tempfile:/var/folders/j3/q9ym0gbj15l1w2d7_1x16nqc0000gn/T/RackMultipart20170314-69035-176nt7b.mp4>, @original_filename="Flipping_Physics_Trailer.mp4", @content_type="video/mp4", @headers="Content-Disposition: form-data; name=\"course[upload]\"; filename=\"Flipping_Physics_Trailer.mp4\"\r\nContent-Type: video/mp4\r\n">}, "id"=>"16"}
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 16ms (ActiveRecord: 0.0ms)



ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

actionpack (5.0.2) lib/action_controller/metal/request_forgery_protection.rb:195:in `handle_unverified_request'
actionpack (5.0.2) lib/action_controller/metal/request_forgery_protection.rb:223:in `handle_unverified_request'
devise (4.2.0) lib/devise/controllers/helpers.rb:253:in `handle_unverified_request'
actionpack (5.0.2) lib/action_controller/metal/request_forgery_protection.rb:218:in `verify_authenticity_token'
activesupport (5.0.2) lib/active_support/callbacks.rb:382:in `block in make_lambda'
activesupport (5.0.2) lib/active_support/callbacks.rb:169:in `block (2 levels) in halting'
actionpack (5.0.2) lib/abstract_controller/callbacks.rb:12:in `block (2 levels) in <module:Callbacks>'
activesupport (5.0.2) lib/active_support/callbacks.rb:170:in `block in halting'
activesupport (5.0.2) lib/active_support/callbacks.rb:454:in `block in call'
activesupport (5.0.2) lib/active_support/callbacks.rb:454:in `each'
activesupport (5.0.2) lib/active_support/callbacks.rb:454:in `call'
activesupport (5.0.2) lib/active_support/callbacks.rb:101:in `__run_callbacks__'
activesupport (5.0.2) lib/active_support/callbacks.rb:750:in `_run_process_action_callbacks'
activesupport (5.0.2) lib/active_support/callbacks.rb:90:in `run_callbacks'
actionpack (5.0.2) lib/abstract_controller/callbacks.rb:19:in `process_action'
actionpack (5.0.2) lib/action_controller/metal/rescue.rb:20:in `process_action'
actionpack (5.0.2) lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
activesupport (5.0.2) lib/active_support/notifications.rb:164:in `block in instrument'
activesupport (5.0.2) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
activesupport (5.0.2) lib/active_support/notifications.rb:164:in `instrument'
actionpack (5.0.2) lib/action_controller/metal/instrumentation.rb:30:in `process_action'
actionpack (5.0.2) lib/action_controller/metal/params_wrapper.rb:248:in `process_action'
searchkick (2.1.1) lib/searchkick/logging.rb:209:in `process_action'
activerecord (5.0.2) lib/active_record/railties/controller_runtime.rb:18:in `process_action'
actionpack (5.0.2) lib/abstract_controller/base.rb:126:in `process'
actionview (5.0.2) lib/action_view/rendering.rb:30:in `process'
actionpack (5.0.2) lib/action_controller/metal.rb:190:in `dispatch'
actionpack (5.0.2) lib/action_controller/metal.rb:262:in `dispatch'
actionpack (5.0.2) lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
actionpack (5.0.2) lib/action_dispatch/routing/route_set.rb:32:in `serve'
actionpack (5.0.2) lib/action_dispatch/journey/router.rb:39:in `block in serve'
actionpack (5.0.2) lib/action_dispatch/journey/router.rb:26:in `each'
actionpack (5.0.2) lib/action_dispatch/journey/router.rb:26:in `serve'
actionpack (5.0.2) lib/action_dispatch/routing/route_set.rb:725:in `call'
warden (1.2.7) lib/warden/manager.rb:36:in `block in call'
warden (1.2.7) lib/warden/manager.rb:35:in `catch'
warden (1.2.7) lib/warden/manager.rb:35:in `call'
rack (2.0.1) lib/rack/etag.rb:25:in `call'
rack (2.0.1) lib/rack/conditional_get.rb:38:in `call'
rack (2.0.1) lib/rack/head.rb:12:in `call'
rack (2.0.1) lib/rack/session/abstract/id.rb:222:in `context'
rack (2.0.1) lib/rack/session/abstract/id.rb:216:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/cookies.rb:613:in `call'
activerecord (5.0.2) lib/active_record/migration.rb:553:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/callbacks.rb:38:in `block in call'
activesupport (5.0.2) lib/active_support/callbacks.rb:97:in `__run_callbacks__'
activesupport (5.0.2) lib/active_support/callbacks.rb:750:in `_run_call_callbacks'
activesupport (5.0.2) lib/active_support/callbacks.rb:90:in `run_callbacks'
actionpack (5.0.2) lib/action_dispatch/middleware/callbacks.rb:36:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/debug_exceptions.rb:49:in `call'
web-console (3.4.0) lib/web_console/middleware.rb:135:in `call_app'
web-console (3.4.0) lib/web_console/middleware.rb:28:in `block in call'
web-console (3.4.0) lib/web_console/middleware.rb:18:in `catch'
web-console (3.4.0) lib/web_console/middleware.rb:18:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
railties (5.0.2) lib/rails/rack/logger.rb:36:in `call_app'
railties (5.0.2) lib/rails/rack/logger.rb:24:in `block in call'
activesupport (5.0.2) lib/active_support/tagged_logging.rb:69:in `block in tagged'
activesupport (5.0.2) lib/active_support/tagged_logging.rb:26:in `tagged'
activesupport (5.0.2) lib/active_support/tagged_logging.rb:69:in `tagged'
railties (5.0.2) lib/rails/rack/logger.rb:24:in `call'
sprockets-rails (3.2.0) lib/sprockets/rails/quiet_assets.rb:13:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/request_id.rb:24:in `call'
rack (2.0.1) lib/rack/method_override.rb:22:in `call'
rack (2.0.1) lib/rack/runtime.rb:22:in `call'
activesupport (5.0.2) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/executor.rb:12:in `call'
actionpack (5.0.2) lib/action_dispatch/middleware/static.rb:136:in `call'
rack (2.0.1) lib/rack/sendfile.rb:111:in `call'
railties (5.0.2) lib/rails/engine.rb:522:in `call'
puma (3.8.2) lib/puma/configuration.rb:224:in `call'
puma (3.8.2) lib/puma/server.rb:600:in `handle_request'
puma (3.8.2) lib/puma/server.rb:435:in `process_client'
puma (3.8.2) lib/puma/server.rb:299:in `block in run'
puma (3.8.2) lib/puma/thread_pool.rb:120:in `block in spawn_thread'
  Rendering /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
  Rendering /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_source.text.erb
  Rendered /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_source.text.erb (2.6ms)
  Rendering /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb
  Rendered /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb (3.1ms)
  Rendering /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
  Rendered /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb (10.1ms)
  Rendered /Users/jalenjackson/.rvm/gems/ruby-2.4.0/gems/actionpack-5.0.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb (96.8ms)

The app has one model Course, which has_attached_file :upload. Once a Course has been created, the user is redirected to the upload action of CoursesController. The user can choose a local file to upload.

When the user initiates the upload, jQuery File Upload does an AJAX request to the resume_upload action in order to establish whether there is an unfinished/interrupted upload.

I changed the model to match an image with paperclip , and it uploaded the image fine, im having trouble getting the video to upload. My main goal is to get the jquery file upload to upload videos, images, and pdf's. Ive tried to change that in the back end , but it only seems to work for one type of file upload.

jalen201
  • 193
  • 14
  • Can you output your error logs? – Rish Mar 14 '17 at 21:14
  • @Rish i don't think i'm receiving error logs, im seeing this error directly from the client side. All the processing is done within the `upload.html.haml` and the `do_upload method` – jalen201 Mar 14 '17 at 21:18
  • You would have at least 1 of a client side error and a server side error. Can you tail your local development log and your browser console log? – Rish Mar 14 '17 at 21:20
  • @Rish i added the error logs to my question. It looks like it started processing the video file, but then crashed – jalen201 Mar 14 '17 at 21:25
  • You should disable CSRF token checking for the PATCH do_upload controller method. See here: http://stackoverflow.com/questions/5669322/turn-off-csrf-token-in-rails-3 – Rish Mar 14 '17 at 21:32
  • @Rish i tried to disable CSRF and i got a interal server error again , but this time the error log is saying `Paperclip::Errors::MissingRequiredValidatorError (Paperclip::Errors::MissingRequiredValidatorError):` – jalen201 Mar 14 '17 at 21:36
  • 1
    See here: http://stackoverflow.com/questions/33151061/papercliperrorsmissingrequiredvalidatorerror – Rish Mar 14 '17 at 21:38
  • @Rish im really having trouble getting this to work. I fixed the validation problem, but now im getting an internal server error of `ActiveRecord::RecordInvalid (Validation failed: Upload has contents that are not what they are reported to be, Upload is invalid, Upload content type is invalid):` my model clearly says to accept mp4's, should i start a new question? – jalen201 Mar 14 '17 at 22:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138064/discussion-between-rish-and-jalen201). – Rish Mar 14 '17 at 22:50

0 Answers0