Using rubyzip to add files and nested directories to a zipoutputstream
I'm struggling with getting rubyzip to append directories to a zipoutputstream. (I want the output stream so I can send it from a rails controller). My code follows this example:
http://info.michael-simons.eu/2008/01/21/using-rubyzip-to-create-zip-files-on-the-fly/
When modified to include directories in the list of files to add I get the following error:
Any help would be greatly appreciated.
UPDATE
After trying a number of soluti开发者_运维技巧ons I had best success with zipruby which has a clean api and good examples: http://zipruby.rubyforge.org/.
Zip::ZipFile.open(path, Zip::ZipFile::CREATE) do |zip|
songs.each do |song|
zip.add "record/#{song.title.parameterize}.mp3", song.file.to_file.path
end
end
OOOOOuuuhh...you DEFINITELY want ZIPPY. It's a Rails plugin that abstracts a lot of the complexity in rubyzip, and lets you create what you're talking about, including directories (from what I recall).
Here you go:
http://github.com/toretore/zippy
And direct from the zippy site:
Example controller:
def show
@gallery = Gallery.find(params[:id])
respond_to do |format|
format.html
format.zip
end
end
Example view:
zip['description.txt'] = @gallery.description
@gallery.photos.each do |photo|
zip["photo_#{photo.id}.png"] = File.open(photo.url)
end
edit: Amending per user comment:
Hmm...the whole objective of using Zippy is to make it a whole lot easier to use ruby zip. Ya might want to take a second (or first) look...
Here's how to make a directory with directories:
some_var = Zippy.open('awsum.zip') do |zip|
%w{dir_a dir_b dir_c diri}.each do |dir|
zip["bin/#{dir}/"]
end
end
...
send_file some_var, :file_name => ...
Zippy will work for this. There may be a more cool way to do this but since there are essentially no docs, here's what I came up with for recursively copying directories with Zippy in a Rakefile. This Rakefile is used in a Rails environment so I put gem requirements in my Gemfile:
#Gemfile
source 'http://rubygems.org'
gem 'rails'
gem 'zippy'
And this is the Rakefile
#Rakefile
def add_file( zippyfile, dst_dir, f )
zippyfile["#{dst_dir}/#{f}"] = File.open(f)
end
def add_dir( zippyfile, dst_dir, d )
glob = "#{d}/**/*"
FileList.new( glob ).each { |f|
if (File.file?(f))
add_file zippyfile, dst_dir, f
end
}
end
task :myzip do
Zippy.create 'my.zip' do |z|
add_dir z, 'my', 'app'
add_dir z, 'my', 'config'
#...
add_file z, 'my', 'config.ru'
add_file z, 'my', 'Gemfile'
#...
end
end
Now I can use it like this:
C:\> cd my
C:\my> rake myzip
and it will produce my.zip
which contains an inner directory called 'my' with copies of selected files and directories.
I was able to get directories working with the same ZipOutputStream
used in the original article.
All I had to do was add the directory when calling zos.put_next_entry
.
For example:
require 'zip/zip'
require 'zip/zipfilesystem'
t = Tempfile.new("some-weird-temp-file-basename-#{request.remote_ip}")
# Give the path of the temp file to the zip outputstream, it won't try to open it as an archive.
Zip::ZipOutputStream.open(t.path) do |zos|
some_file_list.each do |file|
# Create a new entry with some arbitrary name
zos.put_next_entry("myfolder/some-funny-name.jpg") # Added myfolder/
# Add the contents of the file, don't read the stuff linewise if its binary, instead use direct IO
zos.print IO.read(file.path)
end
end
# End of the block automatically closes the file.
# Send it using the right mime type, with a download window and some nice file name.
send_file t.path, :type => 'application/zip', :disposition => 'attachment', :filename => "some-brilliant-file-name.zip"
# The temp file will be deleted some time...
t.close
I just changed zos.put_next_entry('some-funny-name.jpg')
to zos.put_next_entry('myfolder/some-funny-name.jpg')
, and the resulting zipfile had a nested folder called myfolder
that contained the files.
精彩评论