I added the ability to upload files from Dropbox to my site using Dropbox Chooser. Everything works fine, except for one - I don’t know how to correctly implement Cancel functionality. It should work this way - when you click Cancel - the corresponding file panel should be removed from the user interface, and downloading from Dropbox should stop.
When the user clicks Choose from Dropbox and selects the file and clicks Submit , I run an Ajax request to the Rails add_file_via_dropbox method. In this method, I get the URL for the Dropbox file to upload to the Amazon S3 server using Paperclip gem.
When the user clicks Cancel , I want to get rid of the partially downloaded S3 file, which will be saved (after successful loading) in the content.attachment attribute. But when he clicks Cancel , the file is still loading (otherwise he won’t see the Cancel link), and I don’t know how to delete it right away.
My other thought is that I can just add the cancelled attribute to content and then not show content where such attribute == true . But this approach will not save me from losing space on Amazon S3 , so I have to run some background tasks once a day to remove the cancelled attachments. I would prefer not to save them on S3 in the first place. Is it possible? I was thinking of moving the add_file_via_dropbox controller add_file_via_dropbox to a background job so that I can kill it as soon as I received the Cancel ajax request from the client. I am a little confused about all this. How would you solve this problem? Thanks.

product_form.html.erb
$.ajax({ // Cancel file uploading on client side via jqXHR.abort() // It 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
ADD AFTER
I examined in more detail how Paperclip works:
1) When this line of code executes
content.attachment_remote_url = file[:url]
This clip code is executed from paperclip/.../uri_adapter.rb Which file is downloaded from the given URL (Dropbox URL in my example) and saves it locally (in a temporary file on the 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 transferred to S3 only when saving my model here:
content.attachment_remote_url = file[:url] content.save # <----
The Paperclip save method is called: ( paperclip/.../attachment.rb )
def save flush_deletes unless @options[:keep_old_files] flush_writes # <----- @dirty = false true end
And flush_writes then pushes the 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 will judge my initial question by the following two:
1) How to cancel a call to open(@target) when it is already in action (the file is uploaded to my Rails server)
2) How to cancel the call to s3_object(style).write(file, write_options) when the file is downloaded from my Rails server to S3?