开发者

How can I modify a zip file in memory only?

I have a Ruby application, and I need to modify an existing zip file.

I want to build the zip file in memory and stream back the bytes without ever writing the file to the filesystem. If I end up hosting this on Heroku I don't think I can write to the filesystem. Does anyone know of a way to do that?

I looked at Zip::ZipFile but it looks like it always wants to write to the filesystem. I figured being "based on the java implementation" I would be able to just get the compressed file's bytes, which you can d开发者_Python百科o in java, but I don't see a way to do that.


Edit:

What I am asking is basically the same as this, but for Ruby instead of Python: Function to create in-memory zip file and return as http response


had same issue, got to get it work by closing the file and reading the data and streaming it as send_data

then found another library that works fine on heroku and can handle with in-memory buffers: it's zipruby (not rubyzip).

buffer = ''
Zip::Archive.open_buffer(buffer, Zip::CREATE) do |archive|
  files.each do |wood, report|
    title = wood.abbreviation+".txt"
    archive.add_buffer(title, report);
  end
end
file_name = "dimter_#{@offer.customerName}_#{Time.now.strftime("%m%d%Y_%H%M")}.zip"
send_data buffer, :type => 'application/zip', :disposition => 'attachment', :filename => file_name


Here's a blog post which deals with this issue. It uses Tempfile and seems like an excellent solution to me (though read through the comments for some useful additional discussion).

An example, from the post:

def download_zip(image_list)
  if !image_list.blank?
    file_name = "pictures.zip"
    t = Tempfile.new("my-temp-filename-#{Time.now}")
    Zip::ZipOutputStream.open(t.path) do |z|
      image_list.each do |img|
        title = img.title
        title += ".jpg" unless title.end_with?(".jpg")
        z.put_next_entry(title)
        z.print IO.read(img.path)
      end
    end
    send_file t.path, :type => 'application/zip',
                      :disposition => 'attachment',
                      :filename => file_name
    t.close
  end
end

This solution should play nice with Heroku.


You could always patch the new and open methods of Zip::ZipFile to allow use of StringIO handles, then do your I/O directly to memory.


Going to propose an answer to my own question here, that I think better fits what I was trying to do. This method really makes no file (no temp file).

Since ZipFile extends, and is really just a bunch of convenience methods around ZipCentralDirectory, you can work directly with ZipCentralDirectory instead of ZipFile. That will alow you to use IO streams for creating and writing a zip file. Plus throw in use of StringIO and you can do it from a string:

  # load a zip file from a URL into a string
  resp = Net::HTTP.new("www.somewhere.com", 80).get("/some.zip")
  zip_as_string = response.body

  # open as a zip
  zip = Zip::ZipCentralDirectory.read_from_stream(StringIO.new(zip_as_string))

  # work with the zip file.
  # i just output the names of each entry to show that it was read correctly
  zip.each { |zf| puts zf.name }

  # write zip back to an output stream
  out = StringIO.new
  zip.write_to_stream(out)

  # use 'out' or 'out.string' to do whatever with the resulting zip file.
  out.string

Update:

This actually doesn't work at all. It will write a readable zip file, but ONLY the zip file's 'table of contents'. All the internal files are 0 length. Digging further into the Zip implementation, it looks like it only holds the zip entry 'metadata' in memory, and it goes back to the underlying file to read everything else. Based on this, it looks like it is impossible to use the Zip implementation at all without writing to the filesystem.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜