rubyzip: open zip, modify it temporary, send to client - ruby

i want to temporary modify a zip file and send the changed file to the client.
right now i create a file stream and send it:
require 'zip'
zip_stream = Zip::OutputStream.write_buffer do |zip|
zip.put_next_entry 'new_folder/file'
zip.print "some text"
end
zip_stream.rewind
send_data zip_stream.read, type: 'application/zip', disposition: 'attachment', filename: 'thing.zip'
i dont get how i can open a existing zip in the filesystem and put additional file in it and send it without saving it do the disk.
can you give me a hint?

in the end i did it like this:
require 'zip'
zip_stream = Zip::OutputStream.write_buffer do |new_zip|
existing_zip = Zip::File.open('existing.zip')
existing_zip.entries.each do |e|
new_zip.put_next_entry(e.name)
new_zip.write e.get_input_stream.read
end
new_zip.put_next_entry 'new_file'
new_zip.print "text"
end

Check this https://github.com/rubyzip/rubyzip
require 'rubygems'
require 'zip'
folder = "Users/me/Desktop/stuff_to_zip"
input_filenames = ['image.jpg', 'description.txt', 'stats.csv']
zipfile_name = "/Users/me/Desktop/archive.zip"
Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
input_filenames.each do |filename|
# Two arguments:
# - The name of the file as it will appear in the archive
# - The original file, including the path to find it
zipfile.add(filename, File.join(folder, filename))
end
zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" }
end

Related

RubyZip docx issues with write_buffer instead of open

I'm adapting the RubyZip recursive zipping example (found here) to work with write_buffer instead of open and am coming across a host of issues. I'm doing this because the zip archive I'm producing has word documents in it and I'm getting errors on opening those word documents. Therefore, I'm trying the work-around that RubyZip suggests, which is using write_buffer instead of open (example found here).
The problem is, I'm getting errors because I'm using an absolute path, but I'm not sure how to get around that. I'm getting the error "#//', name must not start with />"
Second, I'm not sure what to do to mitigate the issue with word documents. When I used my original code, which worked and created an actual zip file, any word document in that zip file had the following error upon opening: "Word found unreadable content in Do you want to recover the contents of this document? If you trust the source of this document, click Yes." The unreadable content error is the reason why I went down the road of attempting to use write_buffer.
Any help would be appreciated.
Here is the code that I'm currently using:
require 'zip'
require 'zip/zipfilesystem'
module AdvisoryBoard
class ZipService
def initialize(input_dir, output_file)
#input_dir = input_dir
#output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(#input_dir) - %w[. ..]
path = ""
buffer = Zip::ZipOutputStream.write_buffer do |zipfile|
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(#input_dir, zipfile_path)
#file = nil
#data = nil
if !File.directory?(disk_file_path)
#file = File.open(disk_file_path, "r+b")
#data = #file.read
unless [#output_file, #input_dir].include?(e)
zipfile.put_next_entry(e)
zipfile.write #data
end
#file.close
end
end
zipfile.put_next_entry(#output_file)
zipfile.put_next_entry(#input_dir)
end
File.open(#output_file, "wb") { |f| f.write(buffer.string) }
end
end
end
I was able to get word documents to open without any warnings or corruption! Here's what I ended up doing:
require 'nokogiri'
require 'zip'
require 'zip/zipfilesystem'
class ZipService
# Initialize with the directory to zip and the location of the output archive.
def initialize(input_dir, output_file)
#input_dir = input_dir
#output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(#input_dir) - %w[. ..]
::Zip::File.open(#output_file, ::Zip::File::CREATE) do |zipfile|
write_entries entries, '', zipfile
end
end
private
# A helper method to make the recursion work.
def write_entries(entries, path, zipfile)
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(#input_dir, zipfile_path)
if File.directory? disk_file_path
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
else
put_into_archive(disk_file_path, zipfile, zipfile_path, e)
end
end
end
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
zipfile.mkdir zipfile_path
subdir = Dir.entries(disk_file_path) - %w[. ..]
write_entries subdir, zipfile_path, zipfile
end
def put_into_archive(disk_file_path, zipfile, zipfile_path, entry)
if File.extname(zipfile_path) == ".docx"
Zip::File.open(disk_file_path) do |zip|
doc = zip.read("word/document.xml")
xml = Nokogiri::XML.parse(doc)
zip.get_output_stream("word/document.xml") {|f| f.write(xml.to_s)}
end
zipfile.add(zipfile_path, disk_file_path)
else
zipfile.add(zipfile_path, disk_file_path)
end
end
end

Extracting ZIP files in a directory that doesn't exist

I want to extract one single content type file from a ZIP package into a directory that doesn't yet exist. My code so far:
require 'zip'
Dir.mkdir 'new_folder'
#I create the folder
def unzip_file (file_path, destination)
Zip::File.open(file_path) { |zip_file|
zip_file.glob('*.xml'){ |f| #I want to extract .XML files only
f_path = File.join(Preprocess, f.name)
FileUtils.mkdir_p(File.dirname(f_path))
puts "Extract file to %s" % f_path
zip_file.extract(f, f_path)
}
}
end
The folder gets successfully created, but no extraction is done at any directory. I suspect that there is something wrong within the working directory. Any help?
I believe you forgot to call your unzip method to begin with...
Nevertheless, this is how I would do this:
require 'zip'
def unzip_file (file_path, destination)
Zip::File.open(file_path) do |zip_file|
zip_file.each do |f| #I want to extract .XML files only
next unless File.extname(f.name) == '.xml'
FileUtils.mkdir_p(destination)
f_path = File.join(destination, File.basename(f.name))
puts "Extract file to %s" % f_path
zip_file.extract(f, f_path)
end
end
end
zip_file = 'random.zip' # change this to zip file's name (full path or even relative path to zip file)
out_dir = 'new_folder' # change this to the name of the output folder
unzip_file(zip_file, out_dir) # this runs the above method, supplying the zip_file and the output directory
EDIT
Adding an additional method called unzip_files that call unzip_file on all zipped files in a directory.
require 'zip'
def unzip_file (file_path, destination)
Zip::File.open(file_path) do |zip_file|
zip_file.each do |f| #I want to extract .XML files only
next unless File.extname(f.name) == '.xml'
FileUtils.mkdir_p(destination)
f_path = File.join(destination, File.basename(f.name))
puts "Extract file to %s" % f_path
zip_file.extract(f, f_path)
end
end
end
def unzip_files(directory, destination)
FileUtils.mkdir_p(destination)
zipped_files = File.join(directory, '*.zip')
Dir.glob(zipped_files).each do |zip_file|
file_name = File.basename(zip_file, '.zip') # this is the zipped file name
out_dir = File.join(destination, file_name)
unzip_file(zip_file, out_dir)
end
end
zipped_files_dir = 'zips' # this is the folder containing all the zip files
output_dir = 'output_dir' # this is the main output directory
unzip_files(zipped_files_dir, output_dir)

How to extract only one file type within a .ZIP file on another directory

Similar questions have been already asked before on this subject, but I'm unable to work it out. I have a batch of .ZIP files from which I only need to extract the .TXT files from all of them, and then move them to another location. I've tried this:
unzip test.zip '*.txt' #This would only be useful for extracting one single .ZIP file, not for each one.
And this:
require 'FileUtils'
require 'zip'
def unzip_file (file, destination)
Zip::File.open(file_path) { |zip_file|
zip_file.each { |f|
f_path=File.join("destination_path", f.name)
FileUtils.mkdir_p(File.dirname(f_path)) #Don't quite understand this line
zip_file.extract(f, f_path) #Extraction is done here, but where?
}
}
end
Neither option was successful, could you please suggest?
Your method has a parameter destination, but it is never used.
Your code
f_path=File.join("destination_path", f.name)
defines a folder named destination_path followed by the path of the file in the zip. Probably you want the content of the parameter destination.
With
FileUtils.mkdir_p(File.dirname(f_path))
you create the target path with all directories (a mkdir would only create one directory and if the parent directory does not exist you get an error).
In summary: Try this code:
require 'FileUtils'
require 'zip'
def unzip_file (file, destination)
Zip::File.open(file_path) { |zip_file|
zip_file.each { |f|
f_path=File.join(destination, f.name)
FileUtils.mkdir_p(File.dirname(f_path))
puts "Extract file to %s" % f_path
zip_file.extract(f, f_path)
}
}
end
After your comment:
To get only the txt-files inside the zip you can replace each with glob:
require 'zip' #Already loads FileUtils
#~ require 'FileUtils'
def unzip_file (file_path, destination)
Zip::File.open(file_path) { |zip_file|
zip_file.glob('*.txt'){ |f|
f_path=File.join(destination, f.name)
FileUtils.mkdir_p(File.dirname(f_path))
puts "Extract file to %s" % f_path
zip_file.extract(f, f_path)
}
}
end
Then you can call the method for each zip-file in Folder_A and define the destination Folder_B:
Dir['Folder_A/*.zip'].each{|zipfile|
unzip_file(zipfile, 'Folder_B')
}

Create zip archive without save archiving file to disk in Ruby

I tried to create zip archive without save archiving file to disk. So First I write method with save to disk:
begin
file = Zip::File.open("#{file_name}.zip", Zip::File::CREATE)
save_file file_name
file.add(file_name, file_name)
rescue IOError => e
puts "Error: #{e}"
ensure
file.close unless file.nil?
File.delete file_name
end
This work fine but before create save archiving file.
Second I tried to write this code, first create StringIO zip archive with file witch I need, second I cant save them to disk in bin mode:
string_io = Zip::OutputStream.write_buffer do |zos|
zos.put_next_entry(file_name)
zos.write dictionary.join(', ')
end
# Something wrong below
File.open("#{file_name}.zip", 'wb') do |file|
file.write string_io
file.close
end
What a do wrong? and how to do it right way?
Found!
string_io = Zip::OutputStream.write_buffer do |zos|
zos.put_next_entry(file_name)
zos.write dictionary.join(', ')
end
# Rewind
string_io.rewind
# Write simply to file in bin mode
IO.write("#{file_name}.zip", string_io.sysread)

Zipping an existing file with Rubyzip

I would like to use rubyzip to archive "zip" an existing file:
c:\textfile.txt
to
textfile.zip
I know how to add a stream to a text file:
require 'zip/zip'
Zip::ZipFile.open("mp.zip", Zip::ZipFile::CREATE) {
|zipfile|
zipfile.get_output_stream("text.txt") { |f| f.puts "Creating text file" }
}
but not how to add an existing file to a zip. Thanks for your help
This reads in the source file and writes it 1mb at a time to the zipfile.
I've been using something very similar in production for some time now.
require 'zip/zip'
Zip::ZipFile.open("mp.zip", Zip::ZipFile::CREATE) do |zipfile|
zipfile.get_output_stream("text.txt") do |out_file|
File.open("text.txt") do |in_file|
while blk = in_file.read(1024**2)
out_file << blk
end
end
end
end
Hope this answers your question.

Resources