RubyでZipを使ってやる！サンプル集

# はじめに

rubyでzipを使おうとするとziprubyとrubyzipがググれると思います。
サンプルもたくさん出てきます。
しかし「やりたいのこれじゃないんだよ！」と皆さん思いませんでしたか？
ということで、rubyzipのSource読みつつ痒いところに手が届く事を目指して苦労した部分を纏めてみました。

## 目次

* rubyzipの理由
* 設定
* 圧縮
* 解凍
* パスワード付きZIPの扱い（業務アプリなら必須だよね！
* zipにファイル追加（業務アプリだとやりたいじゃん！
* 文字化け対策

## ziprubyではなくrubyzipの理由

ziprubyの更新が止まっているから。。。


## 設定

gemにrubyzipを指定

```rb
gem 'rubyzip', '1.1.7'
```

zipをrequire する

```rb
require 'zip'
```

## 圧縮

* 方式１:Zip::OutputStreamを使う

```rb
    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
```

* 方式２：Zip::File.open(${ファイル名}, Zip::File::CREATE)を使う

こっちのほうが実装は楽だがパスワードとかは使えない

```rb
  # 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
```

## 解凍

```rb
  # 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の扱い

### 圧縮

```rb
    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
```

### 解凍

```rb
  # 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にファイル追加

```rb
    # 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
```

## 文字化け対策

基本的にバイナリモード使ってください

```rb
File.open(path, 'rb'
File.open(zippath, 'wb')
```

これでもダメならエンコード指定してください

```rb
File.open(path, 'rb:utf-8'
File.open(zippath, 'wb:utf-8')
```