rewind_entry
圧縮アルゴリズムの多くは圧縮がかからなかった場合に非圧縮で書き出すモードを持っています。
zlibのdeflateアルゴリズムなどは(アーカイバとしての機能がないことから当然)一定のブロック単位で圧縮/非圧縮の切り替えができます。
このため、メモリ上で入力を蓄えながら圧縮イメージをメモリ上に作成し、小さくなったほうを選んで出力するという方法を取ることができます。
一方、LHAでは圧縮/非圧縮の区別はファイル単位でしか行えません。
ファイルの内容を全てメモリに置く方法はサイズによっては厳しいですから、一旦ファイルに吐き出してみて、大きくなったらファイルポインタを戻して書き直すことになります。
最初に考えた方法はこうです。
- Sinkにtiny_restrictでファイルサイズ分の制限をつける
- tiny_restrictで範囲外にwrite()したら(つまりサイズをオーバーしたら)out_of_restriction例外を送出するようにする
- lzh_file_sink::write()がout_of_restrictionを送出したらサイズをオーバーしたものとして非圧縮で書き直す
これは大抵うまく動くのですが、ジェネリックプログラミングでは捕捉したout_of_restrictionがどこから送出されたものか容易に判断することはできません。(つまりSink自体が投げたものかもしれない)
そこで、Sinkへの書き込みの際は例外を投げず、フラグをセットするだけにしました。
template<class Sink> class lzh_restriction { public: typedef char char_type; struct category : boost::iostreams::output , boost::iostreams::device_tag {}; // overflowフラグはlenバイト書き込んだらをtrueにセットされる lzh_restriction(Sink& sink, bool& overflow, boost::iostreams::stream_offset len); // lenバイトまではsinkに普通に書き込む // lenバイトを超えた分は書いたフリをする std::streamsize write(const char_type* s, std::streamsize n); };
そして、lzh_file_sink側でフラグをチェックするようにします。
template<class Sink> class basic_lzh_file_sink_impl { public: std::streamsize write(const char* s, std::streamsize n) { std::streamsize amt = image_->write(s, n); if (overflow_) throw lha::give_up_compression(); if (amt != -1) { crc_.process_bytes(s, amt); pos_ += amt; } return amt; } };
一見、lzh_file_sink側で書き込みバイト数を数えれば済みそうですが、入力バイト数ではなく圧縮後のサイズを数える必要があるため、こうなっています。
使い方はこうなります。
io_ex::lha::header head; head.path = fs::path("hoge.txt", fs::native); // 予めファイルサイズを設定しておく // これを設定しない場合は例えサイズが増加しても例外は投げられない head.file_size = fs::file_size(head.path); lzh.create_entry(head); try { // 圧縮を試みる io::copy( io_ex::file_source( head.path.native_file_string(), std::ios_base::binary ), lzh ); } catch (const io_ex::lha::give_up_compression&) { // 失敗したら巻き戻しす(自動的に非圧縮モードになる) lzh.rewind_entry(); // 非圧縮で再度コピー io::copy( io_ex::file_source( head.path.native_file_string(), std::ios_base::binary ), lzh ); }
ファイルサイズの設定は必須ではありません。
この機能はデータを動的に生成して圧縮する場合に役に立つはずです。