2011-10-21 12 views
5

Fondamentalmente voglio trasmettere i dati dalla memoria in un formato tar/gz (possibilmente più file nel tar, ma non dovrebbe MAI TOCCARE L'HARDDRIVE, solo streaming!), Quindi riversarli da qualche altra parte (un Corpo della richiesta HTTP nel mio caso).Ruby streaming tar/gz

Qualcuno sa di una libreria esistente che può fare questo? C'è qualcosa in Rails?

libarchive-ruby è solo un wrapper C e sembra essere molto dipendente dalla piattaforma (i documenti vogliono compilare come una fase di installazione ?!).

SOLUZIONE:

require 'zlib' 
require 'rubygems/package' 

tar = StringIO.new 

Gem::Package::TarWriter.new(tar) { |writer| 
    writer.add_file("a_file.txt", 0644) { |f| 
    (1..1000).each { |i| 
     f.write("some text\n") 
    } 
    } 
    writer.add_file("another_file.txt", 0644) { |f| 
    f.write("some more text\n") 
    } 
} 
tar.seek(0) 

gz = Zlib::GzipWriter.new(File.new('this_is_a_tar_gz.tar.gz', 'wb')) # Make sure you use 'wb' for binary write! 
gz.write(tar.read) 
tar.close 
gz.close 

Questo è tutto! Puoi scambiare il file in GzipWriter con qualsiasi IO per tenerlo in streaming. Cookie per dw11wtq!

+0

Vorrei anche sottolineare che questo è davvero un uso intensivo della memoria: riempirà lo StringIO con tutto il tar prima di andare al flusso gzip. Una soluzione migliore per i file di grandi dimensioni sarebbe quella di creare un buffer tra i flussi. Aggiungerò il codice per questo quando cerco di implementarlo ... –

+1

Si noti inoltre che gz.close chiuderà anche l'output IO (File in questo caso). Per tenerlo aperto, usa gz.finish –

risposta

6

Dai un'occhiata alla classe TarWriter in rubygems: http://rubygems.rubyforge.org/rubygems-update/Gem/Package/TarWriter.html funziona su un flusso IO, che può essere un StringIO.

tar = StringIO.new 

Gem::Package::TarWriter.new(tar) do |writer| 
    writer.add_file("hello_world.txt", 0644) { |f| f.write("Hello world!\n") } 
end 

tar.seek(0) 

p tar.read #=> mostly padding, but a tar nonetheless 

Fornisce inoltre metodi per aggiungere directory se è necessario un layout di directory nel tarball.

Per riferimento, si potrebbe raggiungere la gzipping con IO.popen, solo il piping dei dati in/out del processo di sistema:

http://www.ruby-doc.org/core-1.9.2/IO.html#method-c-popen

Il gzipping stessa sarebbe simile a questa:

gzippped_data = IO.popen("gzip", "w+") do |gzip| 
    gzip.puts "Hello world!" 
    gzip.close_write 
    gzip.read 
end 
# => "\u001F\x8B\b\u0000\xFD\u001D\xA2N\u0000\u0003\xF3H\xCD\xC9\xC9W(\xCF/\xCAIQ\xE4\u0002\u0000A䩲\r\u0000\u0000\u0000" 
+0

E 'possibile scrivere entrambi nelle funzioni tar/gz E leggere l'output ENTRAMBE dai flussi IO? Non voglio toccare il disco rigido, quindi non sono ammessi file! –

+0

Inoltre, deve essere indipendente dalla piattaforma e preferirei non fare affidamento sulle chiamate di sistema. Gli strumenti che uso devono essere librerie che posso impacchettare, come gemme o file rb. Questo è il motivo per cui mi sono allontanato dal libarchivo-rubino. –

+0

Guardando ancora, questo potrebbe funzionare. Credo che zlib'z Zlib :: GzipWriter possa usare gli stream sia per l'input che per l'output, e TarWriter può anche usare StringIO, come hai detto. Lo proverò e ti darò dei biscotti se funziona. –

0

In base alla soluzione scritta da OP, ho scritto completamente la funzione di archivio tgz in memoria che cosa voglio utilizzare su POST sul server web.

# Create tar gz archive file from files, on the memory. 
    # Parameters: 
    # files: Array of hash with key "filename" and "body" 
    #  Ex: [{"filename": "foo.txt", "body": "This is foo.txt"},...] 
    # 
    # Return:: tar_gz archived image as string 
    def create_tgz_archive_from_files(files) 
    tar = StringIO.new 
    Gem::Package::TarWriter.new(tar){ |tar_writer| 
     files.each{|file| 
     tar_writer.add_file(file['filename'], 0644){|f| 
      f.write(file['body']) 
     } 
     } 
    } 
    tar.rewind 

    gz = StringIO.new('', 'r+b') 
    gz.set_encoding("BINARY") 
    gz_writer = Zlib::GzipWriter.new(gz) 
    gz_writer.write(tar.read) 
    tar.close 
    gz_writer.finish 
    gz.rewind 
    tar_gz_buf = gz.read 
    return tar_gz_buf 
    end