2011-10-21 7 views
5

Básicamente quiero transmitir datos de la memoria a un formato tar/gz (posiblemente varios archivos en el tar, pero NUNCA TOCAR LA HARDDRIVE, ¡solo la transmisión!), Luego transmitirlos a otro lugar (Cuerpo de solicitud HTTP en mi caso).Ruby streaming tar/gz

¿Alguien sabe de una biblioteca existente que puede hacer esto? ¿Hay algo en Rails?

libarchive-ruby es solo un contenedor C y parece que dependería de la plataforma (¿los documentos quieren que compile como paso de instalación ?!).

SOLUCIÓN:

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 

eso es todo! Puede intercambiar el archivo en el GzipWriter con cualquier IO para mantenerlo en línea. ¡Cookies para dw11wtq!

+0

Debo señalar también que esto requiere mucha memoria: llenará el StringIO con todo el alquitrán antes de pasar al flujo de gzip. Una mejor solución para archivos grandes sería crear un búfer entre las secuencias. Agregaré un código para esto cuando lo implemente ... –

+1

También tenga en cuenta que gz.close también cerrará el IO de salida (Archivo en este caso). Para mantenerlo abierto, use gz.finish –

Respuesta

6

Eche un vistazo a la clase TarWriter en rubygems: http://rubygems.rubyforge.org/rubygems-update/Gem/Package/TarWriter.html solo funciona en una secuencia IO, que puede ser 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 

También proporciona métodos para agregar directorios si necesita un diseño de directorio en el tarball.

Como referencia, se podría lograr el gzipping con IO.popen, simplemente canalizando los datos de entrada/salida del proceso del sistema:

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

El gzipping sí sería algo como esto:

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

¿Es posible escribir tanto en las funciones tar/gz Y leer la salida AMBAS de las transmisiones IO? No quiero tocar el disco duro, ¡entonces no hay archivos permitidos! –

+0

Además, necesita ser una plataforma independiente, y prefiero no confiar en las llamadas al sistema. Las herramientas que uso deben ser bibliotecas que pueda empaquetar, como gemas o archivos rb. Es por eso que me alejé de libarchive-ruby. –

+0

Mirando de nuevo, esto puede funcionar. Creo que zlib'z Zlib :: GzipWriter puede usar flujos de entrada y salida, y TarWriter también puede usar StringIO, como mencionaste. Lo probaré y te daré cookies si funciona. –

0

Basado en la solución que OP escribió, escribí la función de archivo tgz totalmente en la memoria lo que quiero usar para POSTAR al servidor 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