Add a constructor to ZlibCompressed that takes a mode parameter
[libs/core.git] / source / io / zlibcompressed.cpp
1 #ifdef WITH_ZLIB
2 #include <zlib.h>
3 #endif
4 #include "zlibcompressed.h"
5
6 using namespace std;
7
8 namespace Msp {
9 namespace IO {
10
11 zlib_error::zlib_error(const std::string &w, int c):
12 #ifdef WITH_ZLIB
13         runtime_error(w+": "+zError(c)),
14 #else
15         runtime_error(w),
16 #endif
17         code_(c)
18 { }
19
20
21 struct ZlibCompressed::Private
22 {
23 #ifdef WITH_ZLIB
24         z_stream stream;
25 #endif
26
27         Private();
28 };
29
30 ZlibCompressed::Private::Private()
31 {
32 #ifdef WITH_ZLIB
33         stream.zalloc = 0;
34         stream.zfree = 0;
35         stream.opaque = 0;
36 #endif
37 }
38
39
40 ZlibCompressed::ZlibCompressed(Base &b, unsigned level):
41         below(b)
42 {
43         mode = below.get_mode()&M_RDWR;
44         if(mode!=M_READ && mode!=M_WRITE)
45                 throw invalid_access(mode);
46
47         init(level);
48 }
49
50 ZlibCompressed::ZlibCompressed(Base &b, Mode m, unsigned level):
51         below(b)
52 {
53         mode = m&below.get_mode()&M_RDWR;
54         if(mode!=M_READ && mode!=M_WRITE)
55                 throw invalid_access(m);
56
57         init(level);
58 }
59
60 void ZlibCompressed::init(unsigned level)
61 {
62 #ifdef WITH_ZLIB
63         buffer_size = 1024;
64         in_buffer = 0;
65         out_buffer = 0;
66         stream_end = false;
67         priv = new Private;
68
69         if(mode==M_WRITE)
70         {
71                 int ret = deflateInit(&priv->stream, level);
72                 if(ret!=Z_OK)
73                         throw zlib_error("deflateInit", ret);
74         }
75         else
76         {
77                 int ret = inflateInit(&priv->stream);
78                 if(ret!=Z_OK)
79                         throw zlib_error("inflateInit", ret);
80         }
81
82         in_buffer = new unsigned char[buffer_size];
83         out_buffer = new unsigned char[buffer_size];
84
85         priv->stream.next_in = in_buffer;
86         priv->stream.avail_in = 0;
87         priv->stream.next_out = out_buffer;
88         priv->stream.avail_out = buffer_size;
89
90         below.signal_flush_required.connect(sigc::mem_fun(this, &ZlibCompressed::flush));
91 #else
92         (void)level;
93         throw zlib_error("unsupported", -1);
94 #endif
95 }
96
97 ZlibCompressed::~ZlibCompressed()
98 {
99 #ifdef WITH_ZLIB
100         if(mode==M_WRITE)
101         {
102                 while(compress_data(Z_FINISH)) ;
103                 deflateEnd(&priv->stream);
104         }
105         else
106                 inflateEnd(&priv->stream);
107 #endif
108
109         delete[] in_buffer;
110         delete[] out_buffer;
111         delete priv;
112 }
113
114 void ZlibCompressed::flush()
115 {
116 #ifdef WITH_ZLIB
117         if(mode==M_WRITE)
118         {
119                 while(1)
120                 {
121                         if(!compress_data(Z_SYNC_FLUSH))
122                                 break;
123
124                         // The flush is done when all input data has been consumed
125                         if(!priv->stream.avail_in)
126                                 break;
127                 }
128         }
129 #endif
130 }
131
132 unsigned ZlibCompressed::do_write(const char *data, unsigned size)
133 {
134         check_access(M_WRITE);
135
136         unsigned processed = 0;
137 #ifdef WITH_ZLIB
138         while(processed<size)
139         {
140                 unsigned free_in = (in_buffer+buffer_size-priv->stream.next_in);
141                 if(free_in<size && priv->stream.next_in>in_buffer)
142                 {
143                         // Not all of the data fits in the buffer, so make some more room
144                         copy(priv->stream.next_in, priv->stream.next_in+priv->stream.avail_in, in_buffer);
145                         priv->stream.next_in = in_buffer;
146                         free_in = buffer_size-priv->stream.avail_in;
147                 }
148
149                 if(free_in)
150                 {
151                         // Copy as much data into the input buffer as possible
152                         unsigned len = min(free_in, size-processed);
153                         copy(data+processed, data+processed+len, priv->stream.next_in+priv->stream.avail_in);
154                         priv->stream.avail_in += len;
155                         processed += len;
156                 }
157
158                 bool stalled = false;
159                 while(priv->stream.avail_in && !stalled)
160                         stalled = !compress_data(Z_NO_FLUSH);
161                 if(stalled)
162                         break;
163         }
164 #else
165         (void)data;
166         (void)size;
167 #endif
168
169         return processed;
170 }
171
172 bool ZlibCompressed::compress_data(int flush_mode)
173 {
174 #ifdef WITH_ZLIB
175         bool can_deflate = ((priv->stream.avail_in || flush_mode) && priv->stream.avail_out);
176         bool finished = false;
177         if(can_deflate)
178         {
179                 int ret = deflate(&priv->stream, flush_mode);
180                 if(flush_mode==Z_FINISH && ret==Z_STREAM_END)
181                         finished = true;
182                 else if(ret!=Z_OK)
183                         throw zlib_error("deflate", ret);
184         }
185
186         // Write compressed data into the underlying object
187         unsigned len = 0;
188         if(priv->stream.next_out>out_buffer)
189                 len = below.write(reinterpret_cast<char *>(out_buffer), priv->stream.next_out-out_buffer);
190         if(len>0)
191         {
192                 if(len<static_cast<unsigned>(priv->stream.next_out-out_buffer))
193                         copy(out_buffer+len, priv->stream.next_out, out_buffer);
194                 priv->stream.avail_out += len;
195                 priv->stream.next_out -= len;
196         }
197         else if(!can_deflate)
198                 // We weren't able to do anything
199                 return false;
200
201         return !finished;
202 #else
203         (void)flush_mode;
204         return false;
205 #endif
206 }
207
208 unsigned ZlibCompressed::do_read(char *data, unsigned size)
209 {
210         check_access(M_READ);
211
212         unsigned processed = 0;
213 #ifdef WITH_ZLIB
214         while(processed<size)
215         {
216                 if(priv->stream.next_out>out_buffer)
217                 {
218                         // We have some pending output, give it out first
219                         unsigned len = min<unsigned>(priv->stream.next_out-out_buffer, size-processed);
220
221                         copy(out_buffer, out_buffer+len, data+processed);
222                         processed += len;
223
224                         if(len<static_cast<unsigned>(priv->stream.next_out-out_buffer))
225                                 copy(out_buffer+len, priv->stream.next_out, out_buffer);
226                         priv->stream.next_out -= len;
227                         priv->stream.avail_out += len;
228
229                         continue;
230                 }
231
232                 bool need_more_input = !priv->stream.avail_in;
233                 if(priv->stream.avail_in)
234                 {
235                         int ret = inflate(&priv->stream, Z_NO_FLUSH);
236                         if(ret==Z_STREAM_END)
237                                 stream_end = true;
238                         else if(ret!=Z_OK)
239                                 throw zlib_error("inflate", ret);
240                         need_more_input = (priv->stream.next_out==out_buffer);
241                 }
242
243                 if(need_more_input)
244                 {
245                         if(stream_end)
246                                 break;
247
248                         if(priv->stream.next_in>in_buffer)
249                                 copy(priv->stream.next_in, priv->stream.next_in+priv->stream.avail_in, in_buffer);
250                         priv->stream.next_in = in_buffer;
251
252                         unsigned len = below.read(reinterpret_cast<char *>(priv->stream.next_in), in_buffer+buffer_size-priv->stream.next_in);
253                         priv->stream.avail_in += len;
254                         if(!len && below.eof())
255                                 stream_end = true;
256                 }
257         }
258
259         if(size>0 && processed==0 && stream_end)
260                 set_eof();
261 #else
262         (void)data;
263         (void)size;
264 #endif
265
266         return processed;
267 }
268
269 } // namespace IO
270 } // namespace Msp