How to unzip a zipped folder with rubyzip
Asked Answered
L

3

7

I know how to retrieve the contents of a normal zip-file with rubyzip. But i got trouble unzipping the contents of a zipped folder and i hope any of u guys can help me out.

this is the code i use to unzip:

Zip::ZipFile::open(@file_location) do |zip|
 zip.each do |entry|
  next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/ or !entry.file?
  logger.debug "#{entry.name}"
  @data = File.new("#{Rails.root.to_s}/tmp/#{entry.name}")
 end
end

entry.name gives me the name of the file inside the zip-file. This works perfectly with a normal zipfile. But when the zipfile is created from a folder, then the name of the entries are something like: test-folder/test.pdf. When i then try to create the file, it tells me the file can not be found. This is probably because it is inside the "test"-folder that is inside the zip.

If i check the entry to be a folder, no folder can be found. So i thought the solution to be to read the entry as a stream and then save it as a file. It is easy to get the entry-stream, but how do I save it as a file? This is what i got so far.

Zip::ZipFile::open(@file_location) do |zip|
 zip.each do |entry|
  next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/ or !entry.file?
  logger.debug "#{entry.name}"
  @data = entry.get_input_stream.read
  # How do i create a file from a stream?
 end
end

Basically my question is: how can i create a file from a stream? Or is there an easier approach to this than mine?

===EDIT=== I use paperclip to store the files.

Locris answered 16/5, 2011 at 12:31 Comment(0)
R
6

I found that a simpler approach based on jhwist's worked ok:

Zip::File.open(@file_location) do |zipfile|
  zipfile.each do |entry|
    # The 'next if...' code can go here, though I didn't use it
    unless File.exist?(entry.name)
      FileUtils::mkdir_p(File.dirname(entry.name))
      zipfile.extract(entry, entry.name) 
    end
  end
end

The conditional is obviously optional, but without it the code will raise an error if it tries to overwrite an existing file.

Retaliation answered 15/4, 2015 at 14:50 Comment(2)
This approach is not preserving the permissions of the files. How can I preserve the permissions after extracting?Subtropics
That's weird that zip.mkdir does not work as mkdir_p though entry.name includes the whole path. Because of this we have to use FileUtils (Leotaleotard
C
0

I think your problem is not whether you need to write a file from a stream or not. Basically, if you call File.new it will create a new IO-Stream (File is a subclass of IO). Therefore whatever you want to do with the stream from the zipfile should also work with a regular file.

When you say

When i then try to create the file, it tells me the file can not be found

I think what happens is that the parent-directory for the file you want to create does not exist (in your case the test-folder). What you want to do is something like that (not tested):

Zip::ZipFile::open(@file_location) do |zip|
 zip.each do |entry|
   next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/ or !entry.file?
   logger.debug "#{entry.name}"
   FileUtils::mkdir_p(File.dirname(entry.name)) # might want to check if it already exists    
   @data = File.new("#{Rails.root.to_s}/tmp/#{entry.name}")
 end
end
Cresting answered 16/5, 2011 at 14:51 Comment(1)
I tried your code, but it only creates the parent folder of the entry in the root of my project. I still can't access the entry it self. It is actually an Errno::ENOENT - i thought i should mention this. thx thoLocris
L
0

I solved it by using a stream and creating a StringIO. Here is the code

Zip::ZipFile::open(@file_location) do |zip|
 zip.each do |entry|
  next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/ or !entry.file?

  begin
   # the normal unzip-code
  rescue Errno::ENOENT
   # when the entry can not be found
   @data = entry.get_input_stream.read
   @file = StringIO.new(@data)
   @file.class.class_eval { attr_accessor :original_filename, :content_type }
   @file.original_filename = entry.name
   @file.content_type = MIME::Types.type_for(entry.name)

   # save it / whatever
  end
 end
end
Locris answered 17/5, 2011 at 10:23 Comment(1)
That first line should be: Zip::ZipFile.open(@file_location) do |zip|Bane

© 2022 - 2024 — McMap. All rights reserved.