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?
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With