2012-03-28 4 views
19

Ho un piccolo Rails 3.2.1 applicazione che utilizza CarrierWave 0.5.8 per il caricamento dei file di S3 (con nebbia)Download e file zippare che sono stati caricati a S3 con CarrierWave

voglio che gli utenti siano in grado di selezionare alcune immagini che vorrebbero scaricare, quindi comprimerle e inviarle via zip. Ecco quello che è venuta in mente:

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 

Questo non funziona perché photo.photo.file restituisce un'istanza di CarrierWave :: :: :: bagagli File nebbia invece di un file normale.

EDIT: L'errore questo porta a:

Errno :: ENOENT: No such file or directory - uploads/foto/name.jpg

Ho anche provato il seguente:

Ma questo mi dà un 403. Un po 'di aiuto sarebbe molto apprezzato .. Probabilmente non è così difficile Sto solo facendo Wrong ™

+0

Quale gemma stavi usando? Gemma Rubyzip? –

risposta

16

sono riuscito a risolvere il problema con l'aiuto di @ffoeg

La soluzione offerta da @ffoeg non ha funzionato abbastanza così bene per me dato che avevo a che fare con file zip> 500 MB che mi hanno causato problemi su Heroku. Ho Perciò spostato il zippare a un processo in background utilizzando resque:

app/lavoratori/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 

Mille grazie a @ffoeg per avermi aiutato. Se le tue cerniere sono più piccole potresti provare la soluzione di @ffoeg.

+0

Salve, è archiviare una colonna di una tabella db denominata export? Qual è il tipo di dati? binario? Grazie – user1883793

10

Ecco la mia opinione. Ci potrebbe essere errori di battitura, ma credo che questo sia l'essenza di esso :)

# 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 
+0

Grazie mille per la risposta. Sto usando Ruby 1.9.3-p125 e ottengo l'errore (dopo aver corretto alcuni refusi) che zipfile.open chiama un metodo privato. (metodo privato 'open 'chiamato # ) – Gidogeek

+0

hmm, controllerò un altro progetto in cui l'ho fatto. – ffoeg

+0

puoi mostrarmi la linea nel tuo Gemfile dove porti la gemma del rubyzip? – ffoeg