ruby on rails - rails - Descarga y compresión de archivos que se cargaron en S3 con CarrierWave
rails image upload (2)
Aquí está mi toma. Podría haber errores tipográficos, pero creo que esto es lo esencial :)
# action method, stream the zip
def download_photos_as_zip # silly name but you get the idea
generate_zip do |zipname, zip_path|
File.open(zip_path, ''rb'') do |zf|
# you may need to set these to get the file to stream (if you care about that)
# self.last_modified
# self.etag
# self.response.headers[''Content-Length'']
self.response.headers[''Content-Type''] = "application/zip"
self.response.headers[''Content-Disposition''] = "attachment; filename=#{zipname}"
self.response.body = Enumerator.new do |out| # Enumerator is ruby 1.9
while !zf.eof? do
out << zf.read(4096)
end
end
end
end
end
# Zipfile generator
def generate_zip(&block)
photos = Photo.all
# base temp dir
temp_dir = Dir.mktempdir
# path for zip we are about to create, I find that ruby zip needs to write to a real file
zip_path = File.join(temp_dir, ''export.zip'')
Zip::ZipFile::open(zip_path, true) do |zipfile|
photos.each do |photo|
zipfile.get_output_stream(photo.photo.identifier) do |io|
io.write photo.photo.file.read
end
end
end
# yield the zipfile to the action
block.call ''export.zip'', zip_path
ensure
# clean up the tempdir now!
FileUtils.rm_rf temp_dir if temp_dir
end
Tengo una pequeña aplicación Rails 3.2.1 que usa CarrierWave 0.5.8 para subir archivos a S3 (usando Fog)
Quiero que los usuarios puedan seleccionar algunas imágenes que les gustaría descargar, luego comprimirlas y enviarles una postal. Esto es lo que he encontrado:
def generate_zip
#A collection of Photo objects. The Photo object has a PhotoUploader mounted.
photos = Photo.all
tmp_filename = "#{Rails.root}/tmp/" << Time.now.strftime(''%Y-%m-%d-%H%M%S-%N'').to_s << ".zip"
zip = Zip::ZipFile.open(tmp_filename, Zip::ZipFile::CREATE)
zip.close
photos.each do |photo|
file_to_add = photo.photo.file
zip = Zip::ZipFile.open(tmp_filename)
zip.add("tmp/", file_to_add.path)
zip.close
end
#do the rest.. like send zip or upload file and e-mail link
end
Esto no funciona porque photo.photo.file devuelve una instancia de CarrierWave :: Storage :: Fog :: File en lugar de un archivo normal.
EDITAR: El error que esto lleva a:
Errno :: ENOENT: No existe tal archivo o directorio - cargas / fotos / nombre.jpg
También probé lo siguiente:
tmp_filename = "#{Rails.root}/tmp/" << Time.now.strftime(''%Y-%m-%d-%H%M%S-%N'').to_s << ".zip"
zip = Zip::ZipFile.open(tmp_filename, Zip::ZipFile::CREATE)
zip.close
photos.each do |photo|
processed_uri = URI.parse(URI.escape(URI.unescape(photo.photo.file.authenticated_url)).gsub("[", "%5B").gsub("]", "%5D"))
file_to_add = CarrierWave::Uploader::Download::RemoteFile.new(processed_uri)
zip = Zip::ZipFile.open(tmp_filename)
zip.add("tmp/", file_to_add.path)
zip.close
end
Pero esto me da un 403. Alguna ayuda sería muy apreciada. Probablemente no sea tan difícil. Simplemente lo estoy haciendo mal.
He logrado resolver el problema con la ayuda de @ffoeg
La solución ofrecida por @ffoeg no funcionó tan bien para mí ya que estaba tratando con archivos zip> 500 MB, lo que me causó problemas en Heroku. Por lo tanto, moví la compresión a un proceso en segundo plano usando resque :
app / workers / photo_zipper.rb:
require ''zip/zip''
require ''zip/zipfilesystem''
require ''open-uri''
class PhotoZipper
@queue = :photozip_queue
#I pass
def self.perform(id_of_object_with_images, id_of_user_to_be_notified)
user_mail = User.where(:id => id_of_user_to_be_notified).pluck(:email)
export = PhotoZipper.generate_zip(id_of_object_with_images, id_of_user_to_be_notified)
Notifications.zip_ready(export.archive_url, user_mail).deliver
end
# Zipfile generator
def self.generate_zip(id_of_object_with_images, id_of_user_to_be_notified)
object = ObjectWithImages.find(id_of_object_with_images)
photos = object.images
# base temp dir
temp_dir = Dir.mktmpdir
# path for zip we are about to create, I find that ruby zip needs to write to a real file
# This assumes the ObjectWithImages object has an attribute title which is a string.
zip_path = File.join(temp_dir, "#{object.title}_#{Date.today.to_s}.zip")
Zip::ZipOutputStream.open(zip_path) do |zos|
photos.each do |photo|
path = photo.photo.path
zos.put_next_entry(path)
zos.write photo.photo.file.read
end
end
#Find the user that made the request
user = User.find(id_of_user_to_be_notified)
#Create an export object associated to the user
export = user.exports.build
#Associate the created zip to the export
export.archive = File.open(zip_path)
#Upload the archive
export.save!
#return the export object
export
ensure
# clean up the tempdir now!
FileUtils.rm_rf temp_dir if temp_dir
end
end
app / controllers / photos_controller.rb:
format.zip do
#pick the last ObjectWithImages.. ofcourse you should include your own logic here
id_of_object_with_images = ObjectWithImages.last.id
#enqueue the Photozipper task
Resque.enqueue(PhotoZipper, id_of_object_with_images, current_user.id)
#don''t keep the user waiting and flash a message with information about what''s happening behind the scenes
redirect_to some_path, :notice => "Your zip is being created, you will receive an e-mail once this process is complete"
end
Muchas gracias a @ffoeg por ayudarme. Si tus cremalleras son más pequeñas, puedes probar la solución de @ffoeg.