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?