はじめに
rubyでzipを使おうとするとziprubyとrubyzipがググれると思います。
サンプルもたくさん出てきます。
しかし「やりたいのこれじゃないんだよ!」と皆さん思いませんでしたか?
ということで、rubyzipのSource読みつつ痒いところに手が届く事を目指して苦労した部分を纏めてみました。
目次
- rubyzipの理由
- 設定
- 圧縮
- 解凍
- パスワード付きZIPの扱い(業務アプリなら必須だよね!
- zipにファイル追加(業務アプリだとやりたいじゃん!
- 文字化け対策
ziprubyではなくrubyzipの理由
ziprubyの更新が止まっているから。。。
設定
gemにrubyzipを指定
gem 'rubyzip', '1.1.7'
zipをrequire する
require 'zip'
圧縮
- 方式1:Zip::OutputStreamを使う
def compress(path, zippath)
# Stream開く
buffer = Zip::OutputStream.write_buffer(::StringIO.new('')) do |out|
out.put_next_entry(File.basename(path))
# ワンラインで書けたけど、ファイル名が長い?とエラーになるのでStreamを一旦変数に受ける・・・謎
file_buf = File.open(path) { |e| e.read }
out.write file_buf
end
# Stream書き出す
File.open(zippath) { |f| f.write(buffer.string) }
zippath
end
- 方式2:Zip::File.open(${ファイル名}, Zip::File::CREATE)を使う
こっちのほうが実装は楽だがパスワードとかは使えない
# Zip圧縮
def compress(path, zippath)
File.unlink zippath if File.file?(zippath)
Zip::File.open(zippath, Zip::File::CREATE) do |z_fp|
z_fp.add(File.basename(path), path)
end
outpath
end
解凍
# Zip解凍
def uncompress(path, outpath)
entrys = []
Dir.mkdir(outpath) unless Dir.exist?(outpath)
# 2つ目の引数はoffset
Zip::InputStream.open(path, 0) do |input|
# get_next_entryするとinputのoffset(ポインタ)が動く
while (entry = input.get_next_entry)
# 書き出し先を作る
save_path = File.join(outpath, entry.name)
File.open(save_path) do |wf|
# get_next_entryでポインタが動いているので、毎回input.readでOK
wf.puts(input.read)
end
entrys << save_path
end
end
# 解凍されたファイルたちを返却する
entrys
end
パスワード付きZIPの扱い
圧縮
def compress(path, zippath, password )
# パスワードのオブジェクト作る
encrypter = password.present? ? Zip::TraditionalEncrypter.new(password) : nil
# Stream開く&パスワードを渡す
# Zip::File.openはTraditionalEncrypterを受けるIFが(多分)ないので、使えない
buffer = Zip::OutputStream.write_buffer(::StringIO.new(''), encrypter) do |out|
out.put_next_entry(File.basename(path))
# ワンラインで書けたけど、ファイル名が長い?とエラーになるのでStreamを一旦変数に受ける・・・謎&Windowsだから?
file_buf = File.open(path) { |e| e.read }
out.write file_buf
end
# Stream書き出す
File.open(zippath) { |f| f.write(buffer.string) }
zippath
end
解凍
# Zip解凍
def uncompress(path, outpath, password )
entrys = []
Dir.mkdir(outpath) unless Dir.exist?(outpath)
# パスワードのオブジェクト作る
decrypter = password.present? ? Zip::TraditionalDecrypter.new(password) : nil
Zip::InputStream.open(path, 0, decrypter ) do |input|
# get_next_entryするとinputのoffset(ポインタ)が動く
while (entry = input.get_next_entry)
# 書き出し先を作る
save_path = File.join(outpath, entry.name)
File.open(save_path) do |wf|
# get_next_entryでポインタが動いているので、毎回input.readでOK
wf.puts(input.read)
end
entrys << save_path
end
end
# 解凍されたファイルたちを返却する
entrys
end
zipにファイル追加
# Zip圧縮(ファイル追加)
def modify(path, zippath, password)
# 追加するファイルの名前
addfile_name = File.basename(path)
# パスワードのオブジェクト作る
decrypter = nil
encrypter = nil
if password.present?
decrypter = Zip::TraditionalDecrypter.new(password)
encrypter = Zip::TraditionalEncrypter.new(password)
end
entrys = []
# 出力するZip用のStreamを開く
buffer = Zip::OutputStream.write_buffer(::StringIO.new(''), encrypter) do |out|
# ファイルを追加したいZipのStreamを開く
Zip::InputStream.open(zippath, 0, decrypter) do |input|
# inputの内容をoutにコピー
while (entry = input.get_next_entry)
out.put_next_entry(entry.name)
file_buf = input.read
out.write file_buf
entrys << entry.name
end
end
# inputのentryに追加するファイル名がなければ追加
unless entrys.include?(addfile_name)
out.put_next_entry(addfile_name)
file_buf = File.open(path, 'rb') { |e| e.read }
out.write file_buf
end
end
File.open(zippath, 'wb') { |f| f.write(buffer.string) }
zippath
end
文字化け対策
基本的にバイナリモード使ってください
File.open(path, 'rb'
File.open(zippath, 'wb')
これでもダメならエンコード指定してください
File.open(path, 'rb:utf-8'
File.open(zippath, 'wb:utf-8')