Add support for de/compression with zlib
authorMikko Rasa <tdb@tdb.fi>
Thu, 2 Aug 2012 23:46:35 +0000 (02:46 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 3 Aug 2012 00:04:16 +0000 (03:04 +0300)
.gitignore
Build
examples/z.cpp [new file with mode: 0644]
source/io/zlibcompressed.cpp [new file with mode: 0644]
source/io/zlibcompressed.h [new file with mode: 0644]

index cc34686fda3081f76924c7ffb61504859ef17a45..8a83f6847033fcf8b695deaad280d43b6cf59c3b 100644 (file)
@@ -10,3 +10,4 @@ temp
 /tests/test
 /tests/test.txt
 /transcode
+/z
diff --git a/Build b/Build
index e2107aadb5a8fed1fef6a8a99c57e327ec2378ef..51b3bc8276ea7f69e72fb8565797eea2811ba047 100644 (file)
--- 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 (file)
index 0000000..54f0eb7
--- /dev/null
@@ -0,0 +1,68 @@
+#include <msp/core/application.h>
+#include <msp/core/getopt.h>
+#include <msp/io/console.h>
+#include <msp/io/file.h>
+#include <msp/io/zlibcompressed.h>
+
+using namespace std;
+using namespace Msp;
+
+class Z: public RegisteredApplication<Z>
+{
+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<string> &args = getopt.get_args();
+       if(!args.empty())
+               input_file = new IO::File(args[0]);
+
+       input = (input_file ? static_cast<IO::Base *>(input_file) : static_cast<IO::Base *>(&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 (file)
index 0000000..ea5e803
--- /dev/null
@@ -0,0 +1,252 @@
+#ifdef WITH_ZLIB
+#include <zlib.h>
+#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(processed<size)
+       {
+               unsigned free_in = (in_buffer+buffer_size-priv->stream.next_in);
+               if(free_in<size && priv->stream.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<char *>(out_buffer), priv->stream.next_out-out_buffer);
+       if(len>0)
+       {
+               if(len<static_cast<unsigned>(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(processed<size)
+       {
+               if(priv->stream.next_out>out_buffer)
+               {
+                       // We have some pending output, give it out first
+                       unsigned len = min<unsigned>(priv->stream.next_out-out_buffer, size-processed);
+
+                       copy(out_buffer, out_buffer+len, data+processed);
+                       processed += len;
+
+                       if(len<static_cast<unsigned>(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<char *>(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 (file)
index 0000000..1ee4274
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef MSP_IO_ZLIBCOMPRESSED_H_
+#define MSP_IO_ZLIBCOMPRESSED_H_
+
+#include <stdexcept>
+#include <string>
+#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