From 8ed14b63ab6249e9fc2a3a691cf8ffbf49166deb Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 3 Aug 2012 02:46:35 +0300 Subject: [PATCH] Add support for de/compression with zlib --- .gitignore | 1 + Build | 21 +++ examples/z.cpp | 68 ++++++++++ source/io/zlibcompressed.cpp | 252 +++++++++++++++++++++++++++++++++++ source/io/zlibcompressed.h | 68 ++++++++++ 5 files changed, 410 insertions(+) create mode 100644 examples/z.cpp create mode 100644 source/io/zlibcompressed.cpp create mode 100644 source/io/zlibcompressed.h diff --git a/.gitignore b/.gitignore index cc34686..8a83f68 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ temp /tests/test /tests/test.txt /transcode +/z diff --git a/Build b/Build index e2107aa..51b3bc8 100644 --- a/Build +++ b/Build @@ -19,6 +19,15 @@ package "mspcore" }; }; + feature "zlib" "Support compression with zlib" + { + default "yes"; + }; + if_feature "zlib" + { + require "zlib"; + }; + library "mspcore" { source "source/core"; @@ -71,6 +80,18 @@ package "mspcore" }; }; + if_feature "zlib" + { + program "z" + { + source "examples/z.cpp"; + build_info + { + library "mspcore"; + }; + }; + }; + source_tarball { source "License.txt"; diff --git a/examples/z.cpp b/examples/z.cpp new file mode 100644 index 0000000..54f0eb7 --- /dev/null +++ b/examples/z.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include + +using namespace std; +using namespace Msp; + +class Z: public RegisteredApplication +{ +private: + IO::ZlibCompressed *zlib; + IO::File *input_file; + IO::Base *input; + IO::Base *output; + bool decompress; + +public: + Z(int, char **); + ~Z(); + + virtual int main(); +}; + +Z::Z(int argc, char **argv): + input_file(0) +{ + GetOpt getopt; + getopt.add_option('d', "decompress", decompress, GetOpt::NO_ARG); + getopt(argc, argv); + + const vector &args = getopt.get_args(); + if(!args.empty()) + input_file = new IO::File(args[0]); + + input = (input_file ? static_cast(input_file) : static_cast(&IO::cin)); + output = &IO::cout; + if(decompress) + { + zlib = new IO::ZlibCompressed(*input); + input = zlib; + } + else + { + zlib = new IO::ZlibCompressed(*output); + output = zlib; + } +} + +Z::~Z() +{ + delete zlib; + delete input_file; +} + +int Z::main() +{ + char buffer[1024]; + while(1) + { + unsigned len = input->read(buffer, sizeof(buffer)); + if(!len) + break; + output->write(buffer, len); + } + return 0; +} diff --git a/source/io/zlibcompressed.cpp b/source/io/zlibcompressed.cpp new file mode 100644 index 0000000..ea5e803 --- /dev/null +++ b/source/io/zlibcompressed.cpp @@ -0,0 +1,252 @@ +#ifdef WITH_ZLIB +#include +#endif +#include "zlibcompressed.h" + +using namespace std; + +namespace Msp { +namespace IO { + +zlib_error::zlib_error(const std::string &w, int c): +#ifdef WITH_ZLIB + runtime_error(w+": "+zError(c)), +#else + runtime_error(w), +#endif + code_(c) +{ } + + +struct ZlibCompressed::Private +{ +#ifdef WITH_ZLIB + z_stream stream; +#endif + + Private(); +}; + +ZlibCompressed::Private::Private() +{ +#ifdef WITH_ZLIB + stream.zalloc = 0; + stream.zfree = 0; + stream.opaque = 0; +#endif +} + + +ZlibCompressed::ZlibCompressed(Base &b, unsigned level): + below(b), + buffer_size(1024), + in_buffer(0), + out_buffer(0), + priv(0) +{ +#ifdef WITH_ZLIB + mode = below.get_mode()&M_RDWR; + if(mode!=M_READ && mode!=M_WRITE) + throw invalid_access(mode); + + priv = new Private; + + if(mode==M_WRITE) + { + int ret = deflateInit(&priv->stream, level); + if(ret!=Z_OK) + throw zlib_error("deflateInit", ret); + } + else + { + int ret = inflateInit(&priv->stream); + if(ret!=Z_OK) + throw zlib_error("inflateInit", ret); + } + + in_buffer = new unsigned char[buffer_size]; + out_buffer = new unsigned char[buffer_size]; + + priv->stream.next_in = in_buffer; + priv->stream.avail_in = 0; + priv->stream.next_out = out_buffer; + priv->stream.avail_out = buffer_size; + + below.signal_flush_required.connect(sigc::mem_fun(this, &ZlibCompressed::flush)); +#else + (void)level; + throw zlib_error("unsupported", -1); +#endif +} + +ZlibCompressed::~ZlibCompressed() +{ +#ifdef WITH_ZLIB + if(mode==M_WRITE) + { + while(compress_data(Z_FINISH)) ; + deflateEnd(&priv->stream); + } + else + inflateEnd(&priv->stream); +#endif + + delete[] in_buffer; + delete[] out_buffer; + delete priv; +} + +void ZlibCompressed::flush() +{ +#ifdef WITH_ZLIB + if(mode==M_WRITE) + { + while(1) + { + if(!compress_data(Z_SYNC_FLUSH)) + break; + + // The flush is done when all input data has been consumed + if(!priv->stream.avail_in) + break; + } + } +#endif +} + +unsigned ZlibCompressed::do_write(const char *data, unsigned size) +{ + check_access(M_WRITE); + + unsigned processed = 0; +#ifdef WITH_ZLIB + while(processedstream.next_in); + if(free_instream.next_in>in_buffer) + { + // Not all of the data fits in the buffer, so make some more room + copy(priv->stream.next_in, priv->stream.next_in+priv->stream.avail_in, in_buffer); + priv->stream.next_in = in_buffer; + free_in = buffer_size-priv->stream.avail_in; + } + + if(free_in) + { + // Copy as much data into the input buffer as possible + unsigned len = min(free_in, size-processed); + copy(data+processed, data+processed+len, priv->stream.next_in+priv->stream.avail_in); + priv->stream.avail_in += len; + processed += len; + } + + bool stalled = false; + while(priv->stream.avail_in && !stalled) + stalled = !compress_data(Z_NO_FLUSH); + if(stalled) + break; + } +#else + (void)data; + (void)size; +#endif + + return processed; +} + +bool ZlibCompressed::compress_data(int flush_mode) +{ +#ifdef WITH_ZLIB + bool can_deflate = ((priv->stream.avail_in || flush_mode) && priv->stream.avail_out); + bool finished = false; + if(can_deflate) + { + int ret = deflate(&priv->stream, flush_mode); + if(flush_mode==Z_FINISH && ret==Z_STREAM_END) + finished = true; + else if(ret!=Z_OK) + throw zlib_error("deflate", ret); + } + + // Write compressed data into the underlying object + unsigned len = 0; + if(priv->stream.next_out>out_buffer) + len = below.write(reinterpret_cast(out_buffer), priv->stream.next_out-out_buffer); + if(len>0) + { + if(len(priv->stream.next_out-out_buffer)) + copy(out_buffer+len, priv->stream.next_out, out_buffer); + priv->stream.avail_out += len; + priv->stream.next_out -= len; + } + else if(!can_deflate) + // We weren't able to do anything + return false; + + return !finished; +#else + (void)flush_mode; + return false; +#endif +} + +unsigned ZlibCompressed::do_read(char *data, unsigned size) +{ + check_access(M_READ); + + unsigned processed = 0; +#ifdef WITH_ZLIB + while(processedstream.next_out>out_buffer) + { + // We have some pending output, give it out first + unsigned len = min(priv->stream.next_out-out_buffer, size-processed); + + copy(out_buffer, out_buffer+len, data+processed); + processed += len; + + if(len(priv->stream.next_out-out_buffer)) + copy(out_buffer+len, priv->stream.next_out, out_buffer); + priv->stream.next_out -= len; + priv->stream.avail_out += len; + + continue; + } + + bool need_more_input = !priv->stream.avail_in; + if(priv->stream.avail_in) + { + int ret = inflate(&priv->stream, Z_NO_FLUSH); + if(ret==Z_STREAM_END) + set_eof(); + else if(ret!=Z_OK) + throw zlib_error("inflate", ret); + need_more_input = (priv->stream.next_out==out_buffer); + } + + if(need_more_input) + { + if(eof_flag) + break; + + if(priv->stream.next_in>in_buffer) + copy(priv->stream.next_in, priv->stream.next_in+priv->stream.avail_in, in_buffer); + priv->stream.next_in = in_buffer; + + unsigned len = below.read(reinterpret_cast(priv->stream.next_in), in_buffer+buffer_size-priv->stream.next_in); + priv->stream.avail_in += len; + if(!len && below.eof()) + set_eof(); + } + } +#else + (void)data; + (void)size; +#endif + + return processed; +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/zlibcompressed.h b/source/io/zlibcompressed.h new file mode 100644 index 0000000..1ee4274 --- /dev/null +++ b/source/io/zlibcompressed.h @@ -0,0 +1,68 @@ +#ifndef MSP_IO_ZLIBCOMPRESSED_H_ +#define MSP_IO_ZLIBCOMPRESSED_H_ + +#include +#include +#include "base.h" + +namespace Msp { +namespace IO { + +class zlib_error: public std::runtime_error +{ +private: + int code_; + +public: + zlib_error(const std::string &, int); + ~zlib_error() throw() { } + + int code() const throw() { return code_; } +}; + +/** +Compresses or decompresses data with zlib. This class is a filter that +operates on top of another I/O object. + +To ensure proper termination of the compressed data stream, the ZlibCompressed +object must be destroyed before the underlying object is closed. +*/ +class ZlibCompressed: public Base +{ +private: + struct Private; + + Base &below; + unsigned buffer_size; + unsigned char *in_buffer; + unsigned char *out_buffer; + Private *priv; + +public: + /** Creates a zlib de/compression object. The underlying object must be + open for reading or writing, not both. The level parameter determines + compression quality, ranging from 1 (fastest) to 9 (best compression). */ + ZlibCompressed(Base &, unsigned level = 9); + + virtual ~ZlibCompressed(); + + void flush(); + +protected: + virtual unsigned do_write(const char *, unsigned); + +private: + /** Compresses data and writes it to the underlying object. Returns true if + progress was made, false otherwise. flush_mode can be any of zlib's flush + modes. If it is Z_FINISH, false is also returned when the stream has been + terminated. */ + bool compress_data(int flush_mode); + +public: + virtual unsigned do_read(char *, unsigned); +}; + +} // namespace IO +} // namespace Msp + +#endif -- 2.43.0