Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel file being uploaded to Amazon S3?

I added an ability to upload files from Dropbox to my site via Dropbox Chooser. Everything works fine except one thing - I don't know how to implement Cancel functionality correctly. It should work this way - when you click Cancel - corresponding file panel should be removed from UI and uploading from Dropbox should stop.

When user clicks Choose from Dropbox and selects file and click Submit I initiate an Ajax request to Rails, to a method add_file_via_dropbox. In this method I get an URL for Dropbox file to be uploaded to Amazon S3 server via a help of Paperclip gem.

When user clicks Cancel I want to get rid of partly-uploaded to S3 file, which will be stored (upon successful uploading) in content.attachment attribute. But when he clicks Cancel - a file is still uploading (otherwise he won't see Cancel link) and I don't know how to delete it right away.

My other thought is that I can simply add cancelled attribute to content and then don't display any content where such attribute == true. But this approach won't save me from wasting space on Amazon S3 so I should run some background tasks one time per day to delete cancelled attachments. I would prefer not to save them on S3 in first place. Is it possible? I thought about moving controller's action add_file_via_dropbox to background job so I can kill it as soon as I receive cancel ajax request from a client. I am a bit confused about all of this. How would you solve this problem? Thanks.

http://i61.tinypic.com/am9p8n.png

product_form.html.erb

$.ajax({

  // Cancel file uploading on client side via jqXHR.abort()
  // It's quite useless because with 99% probability that request     
  // already was sent to Rails and is processing by appropriate action

  beforeSend: function(jqXHR, options) {
    tmpl.find('a').click(function(e) {
      e.preventDefault();
      jqXHR.abort();
      $(this).parents('li').remove();
    });
  },
  type: "POST",
  dataType: "json",
  url: "<%= add_file_via_dropbox_url %>",
  data: { product_id: $("#fileupload").data("product_id"), file: dropbox_file },
})

// When Rails response is returned - update UI with "successful upload state"
.done(function(json_file) {
  var done_tmpl = $(tmpl("template-download", json_file));
  var li = $(".files_list").find('[data-uuid="' + json_file.uuid + '"]');
  li.replaceWith(done_tmpl);
}
})

product_controller.rb

# This method is called by Ajax request
def add_file_via_dropbox
  product = Product.find(params[:product_id])

  # Each Product has_many Contents(files)
  file = params[:file]
  content = product.contents.build
  content.attachment_remote_url = file[:url]
  content.save

  # Construct json response object for Ajax call
  json_obj = []
  json_obj << {name: content.attachment_file_name,
               size: content.attachment_file_size,
               url: content.attachment.url,
               thumbnailUrl: content.attachment.url,
               deleteUrl: "#{root_url}profile/products/delete_content/#{product.id}/#{content.id}",
               deleteType: "DELETE",
               uuid: file[:uuid]}

  respond_to do |format|
    format.json { render json: json_obj }
  end
end

content.rb

class Content
  attr_reader :attachment_remote_url
  has_attached_file :attachment, bucket: ENV['S3_BUCKET_NAME']

  def attachment_remote_url=(url_value)
    self.attachment = URI.parse(url_value)
  end
end

ADDED LATER

I investigated in more details how Paperclip works:

1) When this line of code executes

content.attachment_remote_url = file[:url]

This paperclip code executes from paperclip/.../uri_adapter.rb Which downloads file from given URL (Dropbox URL in my example) and save it locally (in temp file in Rails server)

class UriAdapter < AbstractAdapter
  def initialize(target)
    @target = target
    @content = download_content # <----
    cache_current_values
    @tempfile = copy_to_tempfile(@content)
  end

...

def download_content
  open(@target) # method from 'open-uri' library
end

2) This file will be pushed to S3 only when I save my Model here:

content.attachment_remote_url = file[:url]
content.save # <----

Paperclip save method will be invoked: (paperclip/.../attachment.rb)

def save
  flush_deletes unless @options[:keep_old_files]
  flush_writes # <-----
  @dirty = false
  true
end

And flush_writes then pushes local file to S3 (paperclip/.../s3.rb)

def flush_writes    
  # omitted code       
  s3_object(style).write(file, write_options) # method from `aws-sdk` gem
  # omitted code                 
  end

Therefore I narrow my original question to these two:

1) How to cancel call to open(@target) when it's already in action (file is downloading to my Rails server)

2) How to cancel call to s3_object(style).write(file, write_options) when file is uploading from my Rails server to S3?

like image 766
yaru Avatar asked Mar 27 '14 13:03

yaru


People also ask

How do you abort a multipart upload?

To stop a multipart upload, you provide the upload ID, and the bucket and key names that are used in the upload. After you have stopped a multipart upload, you can't use the upload ID to upload additional parts.

What happens if you upload the same file to S3?

It will overwrite the existing file. In case you want to have the previous file available, you need to enable versioning in the bucket. Please note that if you click 'Upload' in the S3 UI, drag-and-drop the file, and then simply click 'Upload' then it may change the file's permissions.


1 Answers

If the file is still uploading to your server before server-side processes the file to S3, you'll want to abort the connection like you have already. I.e. How to cancel/abort jQuery AJAX request?

If the file has uploaded to the server and is now uploading to S3: Because ruby is not async based, you're essentially going to have to let the file upload, but because the server is now uploading it, your client can technically leave the page. So: I'd pass a unique ID back when your server receives the file, if the user still clicks cancel, then shoot the ID back in and have your server poll or i.e. cron for file to delete.

like image 193
MKN Web Solutions Avatar answered Oct 16 '22 01:10

MKN Web Solutions