Rails, S3, Paperclip changes attachment file .epp extension to .txt - ruby-on-rails

I have trouble setting my application correctly, to store attachments with .epp extension (which has file -b --mime-type = text/plain) and serve them to users with correct extenstion (it changes to .txt).
After adding to initializers:
Paperclip.options[:content_type_mappings] = {
:epp => 'text/plain'
}
I am able to upload the file to s3, without receiving the spoofed_media_type error. However, when I try to serve the file for user with:
redirect_to #job.file.expiring_url
it downloads with .txt extension. The file is saved with the following code in the job class:
...
has_attached_file :file, :s3_permissions => 'authenticated-read', :s3_headers => {"Content-Disposition" => "attachment"}
do_not_validate_attachment_file_type :file
...
self.file = File.open(file_path)
self.save!
Any ideas what could be the problem?
Update:
Actually it happens only with Chrome, Firefox downloads the file correctly, so it might be the browser issue...

I am guessing your paperclip.rb initializer looks something like this:
Paperclip::Attachment.default_options[:url] = ':s3_domain_url'
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
Try changing the path to include the extension like so:
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename.:extension'

Related

S3 save old url, change paperclip config, set new url as old

So here is the thing: currently our files, when user downloads them, have names like 897123uiojdkashdu182uiej.pdf. I need to change that to file-name.pdf.
And logically I go and change paperclip.rb config from this:
Paperclip::Attachment.default_options.update({
path: '/:hash.:extension',
hash_secret: Rails.application.secrets.secret_key_base
})
to this:
Paperclip::Attachment.default_options.update({
path: "/attachment/#{SecureRandom.urlsafe_base64(64)}/:filename",
hash_secret: Rails.application.secrets.secret_key_base
})
which works just fine, filenames are great. However, old files are now unaccessable due to the change in the path. So I came up with the following decision
First I made a rake task which will store the old paths in the database:
namespace :paperclip do
desc "Set old urls for attachments"
task :update_old_urls => :environment do
Asset.find_each do |asset|
if asset.attachment
attachment_url = asset.attachment.try!(:url)
file_url = "https:#{attachment_url}"
puts "Set old url asset attachment #{asset.id} - #{file_url}"
asset.update(old_url: file_url)
else
puts "No attachment found in asset #{asset.id}"
end
end
end
end
Now the asset.old_url stores the current url of the file. Then I go and change the config, making the file unaccessable.
Now it's time for the new rake task:
require 'uri'
require 'open-uri'
namespace :paperclip do
desc "Recreate attachments and save them to new destination"
task :move_attachments => :environment do
Asset.find_each do |asset|
unless asset.old_url.blank?
url = asset.old_url
filename = File.basename(asset.attachment.path)
file = File.new("#{Rails.root}/tmp/#{filename}", "wb")
file.write(open(url).read)
if File.exists? file
puts "Re-saving asset attachment #{asset.id} - #{filename}"
asset.attachment = file
asset.save
# if there are multiple styles, you want to recreate them :
asset.attachment.reprocess!
file.close
else
puts "Missing file attachment #{asset.id} - #{filename}"
end
File.delete(file)
end
end
end
end
But my plan didn't work at all, I didn't get access to the files, and the asset.url still isn't equal to asset.old_url.
Would appreciate help very much!
With S3, you can set the "filename upon saving" as a header. Specifically, the user will get to an url https://foo.bar.com/mangled/path/some/weird/hash/whatever?options and when the browser will offer to save, you can control the filename (not the url).
The trick to that relies on the browser reading the Content-Disposition header from the response, if it reads Content-Disposition: attachment; filename="filename.jpg" it will save (or ask the user to save as) filename.jpg, independently on the original URL.
You can force S3 to add this header by adding one more parameter to the URL or by setting a metadata on the file.
The former can be done by passing it to the url method:
has_attached_file :attachment,
s3_url_options: ->(instance) {
{response_content_disposition: "attachment; filename=\"#{instance.filename}\""}
}
Check https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/storage/s3.rb#L221-L225 for the relevant source code.
The latter can be done in bulk via paperclip (and you should also configure it to do it on new uploads). It will also take a long time!!
Asset.find_each do |asset|
next unless asset.attachment
s3_object = asset.attachment.s3_object
s3_object.copy_to(
s3_object,
metadata_directive: 'REPLACE',
content_disposition: "attachment; filename=\"#{asset.filename}\")"
)
end
# for new uploads
has_attached_file :attachment,
s3_headers: ->(att) {
{content_disposition: "attachment; filename=\"#{att.model.filename}\""}
}

Incorrect key path for S3 using Paperclip to persist image from URL

I have images uploading to S3 from a user selected file and it works. I want to add saving images from URLs to the database as well. I get the following error:
[AWS S3 404 0.103582 0 retries] head_object(:bucket_name=>"my-bucket",:key=>"merches/merch_photos//medium/c211d05de2831cba60fa31ac21e9d52a_m.jpg") AWS::S3::Errors::NoSuchKey No Such Key
My guess is that the // in the key path is the problem. There should be something like 000/001/234 in between those two slashes. That's how it works for file uploads and it's what the :id_partition should be doing.
In my paperclip.rb initializer I have
Paperclip::Attachment.default_options[:url] = ':s3_domain_url'
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
In my model I have
def picture_from_url(url)
self.merch_photo = URI.parse(url)
end
In my controller I am assigning the photo property in the create method like so:
#merch.picture_from_url(params[:merch][:merch_photo])
In my config I have
config.paperclip_defaults = {
:storage => :s3,
:s3_permissions => :public_read_write,
:s3_credentials => {
:bucket => Rails.application.secrets.my_bucket,
:access_key_id => Rails.application.secrets.my_aws_id,
:secret_access_key => Rails.application.secrets.my_aws_secret
}
}
Everything looks correct as far as I can tell and the general setup should be fine since regular uploads work. However with URL uploads, I get the error with the improperly formatted key. Any ideas?

PaperClip -- save a new attachment without deleting the old one

I am using Paperclip (w/ Amazon s3) on Rails 3. I want to attach a new file to my model without replacing the old file. I don't want the old file to be accessible, I only want to have it there on s3 as a back up.
Do you know if there is a way of telling paperclip to take care of it, itself?
in post.rb I have:
has_attached_file :sound,
:storage => :s3,
:s3_credentials => "....",
:styles => {:mp3 => {:format => :mp3}},
:processors => [:sound_processor],
:s3_host_alias => '....',
:bucket => '....',
:path => ":attachment/:id/:style/out.:extension",
:url => ":s3_alias_url"
and the processor is as follows:
class Paperclip::SoundProcessor < Paperclip::Processor
def initialize file, options = {}, attachment = nil
super
#format = options[:format] || "mp3"
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
end
def make
src = #file
dst = Tempfile.new([#basename,".#{#format}"])
dst.binmode
cmd = "ffmpeg -y -ab 128k -t 600 -i #{File.expand_path(src.path)} #{File.expand_path(dst.path)}"
Paperclip.log(cmd)
out = `#{cmd}`
raise Paperclip::PaperclipError, "processor does not accept the given audio file" unless $?.exitstatus == 0
dst
end
end
Here's what I do. I timestamp the filename before saving it (to prevent another file with the same name overwriting the original), and force paperclip to never delete anything.
before_create :timestamp_filename
def timestamp_filename
fname = Time.now.to_s(:db).gsub(/[^0-9]/,'') + '_' + sound_file_name
sound.instance_write(:file_name, fname)
end
# override paperclip's destroy files
# method to always keep them around
def destroy_attached_files
true
end
It's quite straightforward to add versioning to your models, recently I user CarrierWave and paper_trail in combo to achieve this. We were deploying to Heroku so S3 was in the mix too.
The reason I'm posting this as an answer is because, although controversial, I don't think libraries like PaperClip should support backing up files, a library specific to solve that problem feels better to me personally.
Here is a good resource that you can take a look at: http://eggsonbread.com/2009/07/23/file-versioning-in-ruby-on-rails-with-paperclip-acts_as_versioned/
It creates new versions of a file. Hope this helps.

Rails / Paperclip attaching via command line

I have a bunch of jpeg files in a folder on my server, and I'm attempting to attach them to their corresponding Property instances through a rake task.
property.rb has the following code:
has_attached_file :temp_photo,
:styles => PropertyImage::STYLES,
:url => "/assets/:class/:attachment/:id_partition/:style_:basename.:extension",
:path => "#{Rails.root}/public/assets/:class/:attachment/:id_partition/:style_:basename.:extension"
I use paperclip on other models, and there are no issues whatsoever, but I get a problem when I attempt the following:
p = Property.find(id)
file = File.open(temp_file_path)
p.temp_photo = file
p.save
# => false
file.close
p.errors
# => "/tmp/stream20110524-1126-1cunv0y-0.jpg is not recognized by the 'identify' command."
The file definitely exists, and I've tried changing the permissions. Restarting the server doesn't help. The problem seems to be with using the command line, as the normal form / HTTP approach works fine. This is only a temporary set-up, so I'm looking for a working way to import a batch of files into my rails app paperclip model.
Any suggestions?
path = 'target_file_path'
attach_name = 'temp_photo'
p = Property.find(id)
attach = Paperclip::Attachment.new(attach_name, p, p.class.attachment_definitions[attach_name.to_suym])
file = File.open(path)
attach.assign file
attach.save
file.close

Rails 3 - Tempfile Path?

I have the following:
attachments.each do |a|
Rails.logger.info a.filename
tempfile = Tempfile.new("#{a.filename}", "#{Rails.root.to_s}/tmp/")
Rails.logger.info tempfile.path
end
Where attachments is from paperclip.
Here's the output:
billgates.jpg
/Users/bhellman/Sites/cline/tmp/billgates.jpg20101204-17402-of0u9o-0
Why is the file name getting 20101204-17402-of0u9o-0 appended to at the end? That's breaking everything with paperclip etc. Anyone seen this before? For the life of I have no idea what's doing it?
Thanks
UPDATE
Paperclip: Paperclip on github
a is the attachment file
tempfile = Tempfile.new("#{a.filename}", "#{Rails.root.to_s}/tmp/")
tempfile << a.body
tempfile.puts
attachments.build(
:attachment => File.open(tempfile.path)
)
best make sure your tempfile has the correct extension, saving you to trying and change it after:
file = Tempfile.new(['hello', '.jpg'])
file.path # => something like: "/tmp/hello2843-8392-92849382--0.jpg"
more here: http://apidock.com/ruby/v1_9_3_125/Tempfile/new/class
The first argument for Tempfile.new is just a basename. To make sure each Tempfile is unique the characters are appended to the end of the file.
You should use Paperclip's API for this:
tempfiles = []
attachments.each do |a|
# use Attachment#to_file to get a :filesystem => file, :s3 => tempfile
tempfiles << a.to_file
end
tempfiles.each do |tf|
Rails.logger.debug tf.filename
end
attachment = attachments.build(
:attachment => File.open(tempfile.path)
)
# change the displayed file name stored in the db record here
attachment.attachment_file_name = a.filename # or whatever else you like
attachment.save!
The best way I found to deal with this was to specify the file extension in the Paperclip attribute. For example:
has_attached_file :picture,
:url => "/system/:hash.jpg",
:hash_secret => "long_secret_string",
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml"
Note that the :url is declared as '.jpg' rather than the traditional .:extension.
Good luck!

Resources