How to cancel file being uploaded to Amazon S3?
ajax, amazon-s3, jquery, paperclip, ruby-on-rails
Solution
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.
Problem
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. 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?