1/* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2009 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Alexander Larsson <alexl@redhat.com>
19 */
20
21#include "config.h"
22
23#include <string.h>
24
25#include "gconverteroutputstream.h"
26#include "gpollableoutputstream.h"
27#include "gcancellable.h"
28#include "gioenumtypes.h"
29#include "gioerror.h"
30#include "glibintl.h"
31
32
33/**
34 * SECTION:gconverteroutputstream
35 * @short_description: Converter Output Stream
36 * @include: gio/gio.h
37 * @see_also: #GOutputStream, #GConverter
38 *
39 * Converter output stream implements #GOutputStream and allows
40 * conversion of data of various types during reading.
41 *
42 * As of GLib 2.34, #GConverterOutputStream implements
43 * #GPollableOutputStream.
44 **/
45
46#define INITIAL_BUFFER_SIZE 4096
47
48typedef struct {
49 char *data;
50 gsize start;
51 gsize end;
52 gsize size;
53} Buffer;
54
55struct _GConverterOutputStreamPrivate {
56 gboolean at_output_end;
57 gboolean finished;
58 GConverter *converter;
59 Buffer output_buffer; /* To be converted and written */
60 Buffer converted_buffer; /* Already converted */
61};
62
63/* Buffering strategy:
64 *
65 * Each time we write we must at least consume some input, or
66 * return an error. Thus we start with writing all already
67 * converted data and *then* we start converting (reporting
68 * an error at any point in this).
69 *
70 * Its possible that what the user wrote is not enough data
71 * for the converter, so we must then buffer it in output_buffer
72 * and ask for more data, but we want to avoid this as much as
73 * possible, converting directly from the users buffer.
74 */
75
76enum {
77 PROP_0,
78 PROP_CONVERTER
79};
80
81static void g_converter_output_stream_set_property (GObject *object,
82 guint prop_id,
83 const GValue *value,
84 GParamSpec *pspec);
85static void g_converter_output_stream_get_property (GObject *object,
86 guint prop_id,
87 GValue *value,
88 GParamSpec *pspec);
89static void g_converter_output_stream_finalize (GObject *object);
90static gssize g_converter_output_stream_write (GOutputStream *stream,
91 const void *buffer,
92 gsize count,
93 GCancellable *cancellable,
94 GError **error);
95static gboolean g_converter_output_stream_flush (GOutputStream *stream,
96 GCancellable *cancellable,
97 GError **error);
98
99static gboolean g_converter_output_stream_can_poll (GPollableOutputStream *stream);
100static gboolean g_converter_output_stream_is_writable (GPollableOutputStream *stream);
101static gssize g_converter_output_stream_write_nonblocking (GPollableOutputStream *stream,
102 const void *buffer,
103 gsize size,
104 GError **error);
105
106static GSource *g_converter_output_stream_create_source (GPollableOutputStream *stream,
107 GCancellable *cancellable);
108
109static void g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface);
110
111G_DEFINE_TYPE_WITH_CODE (GConverterOutputStream,
112 g_converter_output_stream,
113 G_TYPE_FILTER_OUTPUT_STREAM,
114 G_ADD_PRIVATE (GConverterOutputStream)
115 G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
116 g_converter_output_stream_pollable_iface_init))
117
118static void
119g_converter_output_stream_class_init (GConverterOutputStreamClass *klass)
120{
121 GObjectClass *object_class;
122 GOutputStreamClass *istream_class;
123
124 object_class = G_OBJECT_CLASS (klass);
125 object_class->get_property = g_converter_output_stream_get_property;
126 object_class->set_property = g_converter_output_stream_set_property;
127 object_class->finalize = g_converter_output_stream_finalize;
128
129 istream_class = G_OUTPUT_STREAM_CLASS (klass);
130 istream_class->write_fn = g_converter_output_stream_write;
131 istream_class->flush = g_converter_output_stream_flush;
132
133 g_object_class_install_property (oclass: object_class,
134 property_id: PROP_CONVERTER,
135 pspec: g_param_spec_object (name: "converter",
136 P_("Converter"),
137 P_("The converter object"),
138 G_TYPE_CONVERTER,
139 flags: G_PARAM_READWRITE|
140 G_PARAM_CONSTRUCT_ONLY|
141 G_PARAM_STATIC_STRINGS));
142
143}
144
145static void
146g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface)
147{
148 iface->can_poll = g_converter_output_stream_can_poll;
149 iface->is_writable = g_converter_output_stream_is_writable;
150 iface->write_nonblocking = g_converter_output_stream_write_nonblocking;
151 iface->create_source = g_converter_output_stream_create_source;
152}
153
154static void
155g_converter_output_stream_finalize (GObject *object)
156{
157 GConverterOutputStreamPrivate *priv;
158 GConverterOutputStream *stream;
159
160 stream = G_CONVERTER_OUTPUT_STREAM (object);
161 priv = stream->priv;
162
163 g_free (mem: priv->output_buffer.data);
164 g_free (mem: priv->converted_buffer.data);
165 if (priv->converter)
166 g_object_unref (object: priv->converter);
167
168 G_OBJECT_CLASS (g_converter_output_stream_parent_class)->finalize (object);
169}
170
171static void
172g_converter_output_stream_set_property (GObject *object,
173 guint prop_id,
174 const GValue *value,
175 GParamSpec *pspec)
176{
177 GConverterOutputStream *cstream;
178
179 cstream = G_CONVERTER_OUTPUT_STREAM (object);
180
181 switch (prop_id)
182 {
183 case PROP_CONVERTER:
184 cstream->priv->converter = g_value_dup_object (value);
185 break;
186
187 default:
188 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
189 break;
190 }
191
192}
193
194static void
195g_converter_output_stream_get_property (GObject *object,
196 guint prop_id,
197 GValue *value,
198 GParamSpec *pspec)
199{
200 GConverterOutputStreamPrivate *priv;
201 GConverterOutputStream *cstream;
202
203 cstream = G_CONVERTER_OUTPUT_STREAM (object);
204 priv = cstream->priv;
205
206 switch (prop_id)
207 {
208 case PROP_CONVERTER:
209 g_value_set_object (value, v_object: priv->converter);
210 break;
211
212 default:
213 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
214 break;
215 }
216}
217
218static void
219g_converter_output_stream_init (GConverterOutputStream *stream)
220{
221 stream->priv = g_converter_output_stream_get_instance_private (self: stream);
222}
223
224/**
225 * g_converter_output_stream_new:
226 * @base_stream: a #GOutputStream
227 * @converter: a #GConverter
228 *
229 * Creates a new converter output stream for the @base_stream.
230 *
231 * Returns: a new #GOutputStream.
232 **/
233GOutputStream *
234g_converter_output_stream_new (GOutputStream *base_stream,
235 GConverter *converter)
236{
237 GOutputStream *stream;
238
239 g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL);
240
241 stream = g_object_new (G_TYPE_CONVERTER_OUTPUT_STREAM,
242 first_property_name: "base-stream", base_stream,
243 "converter", converter,
244 NULL);
245
246 return stream;
247}
248
249static gsize
250buffer_data_size (Buffer *buffer)
251{
252 return buffer->end - buffer->start;
253}
254
255static gsize
256buffer_tailspace (Buffer *buffer)
257{
258 return buffer->size - buffer->end;
259}
260
261static char *
262buffer_data (Buffer *buffer)
263{
264 return buffer->data + buffer->start;
265}
266
267static void
268buffer_consumed (Buffer *buffer,
269 gsize count)
270{
271 buffer->start += count;
272 if (buffer->start == buffer->end)
273 buffer->start = buffer->end = 0;
274}
275
276static void
277compact_buffer (Buffer *buffer)
278{
279 gsize in_buffer;
280
281 in_buffer = buffer_data_size (buffer);
282 memmove (dest: buffer->data,
283 src: buffer->data + buffer->start,
284 n: in_buffer);
285 buffer->end -= buffer->start;
286 buffer->start = 0;
287}
288
289static void
290grow_buffer (Buffer *buffer)
291{
292 char *data;
293 gsize size, in_buffer;
294
295 if (buffer->size == 0)
296 size = INITIAL_BUFFER_SIZE;
297 else
298 size = buffer->size * 2;
299
300 data = g_malloc (n_bytes: size);
301 in_buffer = buffer_data_size (buffer);
302
303 if (in_buffer != 0)
304 memcpy (dest: data,
305 src: buffer->data + buffer->start,
306 n: in_buffer);
307
308 g_free (mem: buffer->data);
309 buffer->data = data;
310 buffer->end -= buffer->start;
311 buffer->start = 0;
312 buffer->size = size;
313}
314
315/* Ensures that the buffer can fit at_least_size bytes,
316 * *including* the current in-buffer data */
317static void
318buffer_ensure_space (Buffer *buffer,
319 gsize at_least_size)
320{
321 gsize in_buffer, left_to_fill;
322
323 in_buffer = buffer_data_size (buffer);
324
325 if (in_buffer >= at_least_size)
326 return;
327
328 left_to_fill = buffer_tailspace (buffer);
329
330 if (in_buffer + left_to_fill >= at_least_size)
331 {
332 /* We fit in remaining space at end */
333 /* If the copy is small, compact now anyway so we can fill more */
334 if (in_buffer < 256)
335 compact_buffer (buffer);
336 }
337 else if (buffer->size >= at_least_size)
338 {
339 /* We fit, but only if we compact */
340 compact_buffer (buffer);
341 }
342 else
343 {
344 /* Need to grow buffer */
345 while (buffer->size < at_least_size)
346 grow_buffer (buffer);
347 }
348}
349
350static void
351buffer_append (Buffer *buffer,
352 const char *data,
353 gsize data_size)
354{
355 buffer_ensure_space (buffer,
356 at_least_size: buffer_data_size (buffer) + data_size);
357 memcpy (dest: buffer->data + buffer->end, src: data, n: data_size);
358 buffer->end += data_size;
359}
360
361
362static gboolean
363flush_buffer (GConverterOutputStream *stream,
364 gboolean blocking,
365 GCancellable *cancellable,
366 GError **error)
367{
368 GConverterOutputStreamPrivate *priv;
369 GOutputStream *base_stream;
370 gsize nwritten;
371 gsize available;
372 gboolean res;
373
374 priv = stream->priv;
375
376 base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
377
378 available = buffer_data_size (buffer: &priv->converted_buffer);
379 if (available > 0)
380 {
381 res = g_pollable_stream_write_all (stream: base_stream,
382 buffer: buffer_data (buffer: &priv->converted_buffer),
383 count: available,
384 blocking,
385 bytes_written: &nwritten,
386 cancellable,
387 error);
388 buffer_consumed (buffer: &priv->converted_buffer, count: nwritten);
389 return res;
390 }
391 return TRUE;
392}
393
394
395static gssize
396write_internal (GOutputStream *stream,
397 const void *buffer,
398 gsize count,
399 gboolean blocking,
400 GCancellable *cancellable,
401 GError **error)
402{
403 GConverterOutputStream *cstream;
404 GConverterOutputStreamPrivate *priv;
405 gssize retval;
406 GConverterResult res;
407 gsize bytes_read;
408 gsize bytes_written;
409 GError *my_error;
410 const char *to_convert;
411 gsize to_convert_size, converted_bytes;
412 gboolean converting_from_buffer;
413
414 cstream = G_CONVERTER_OUTPUT_STREAM (stream);
415 priv = cstream->priv;
416
417 /* Write out all available pre-converted data and fail if
418 not possible */
419 if (!flush_buffer (stream: cstream, blocking, cancellable, error))
420 return -1;
421
422 if (priv->finished)
423 return 0;
424
425 /* Convert as much as possible */
426 if (buffer_data_size (buffer: &priv->output_buffer) > 0)
427 {
428 converting_from_buffer = TRUE;
429 buffer_append (buffer: &priv->output_buffer, data: buffer, data_size: count);
430 to_convert = buffer_data (buffer: &priv->output_buffer);
431 to_convert_size = buffer_data_size (buffer: &priv->output_buffer);
432 }
433 else
434 {
435 converting_from_buffer = FALSE;
436 to_convert = buffer;
437 to_convert_size = count;
438 }
439
440 /* Ensure we have *some* initial target space */
441 buffer_ensure_space (buffer: &priv->converted_buffer, at_least_size: to_convert_size);
442
443 converted_bytes = 0;
444 while (!priv->finished && converted_bytes < to_convert_size)
445 {
446 /* Ensure we have *some* target space */
447 if (buffer_tailspace (buffer: &priv->converted_buffer) == 0)
448 grow_buffer (buffer: &priv->converted_buffer);
449
450 /* Try to convert to our buffer */
451 my_error = NULL;
452 res = g_converter_convert (converter: priv->converter,
453 inbuf: to_convert + converted_bytes,
454 inbuf_size: to_convert_size - converted_bytes,
455 outbuf: buffer_data (buffer: &priv->converted_buffer) + buffer_data_size (buffer: &priv->converted_buffer),
456 outbuf_size: buffer_tailspace (buffer: &priv->converted_buffer),
457 flags: 0,
458 bytes_read: &bytes_read,
459 bytes_written: &bytes_written,
460 error: &my_error);
461
462 if (res != G_CONVERTER_ERROR)
463 {
464 priv->converted_buffer.end += bytes_written;
465 converted_bytes += bytes_read;
466
467 if (res == G_CONVERTER_FINISHED)
468 priv->finished = TRUE;
469 }
470 else
471 {
472 /* No-space errors can be handled locally: */
473 if (g_error_matches (error: my_error,
474 G_IO_ERROR,
475 code: G_IO_ERROR_NO_SPACE))
476 {
477 /* Need more destination space, grow it
478 * Note: if we actually grow the buffer (as opposed to compacting it),
479 * this will double the size, not just add one byte. */
480 buffer_ensure_space (buffer: &priv->converted_buffer,
481 at_least_size: priv->converted_buffer.size + 1);
482 g_error_free (error: my_error);
483 continue;
484 }
485
486 if (converted_bytes > 0)
487 {
488 /* We got a conversion error, but we did convert some bytes before
489 that, so handle those before reporting the error */
490 g_error_free (error: my_error);
491 break;
492 }
493
494 if (g_error_matches (error: my_error,
495 G_IO_ERROR,
496 code: G_IO_ERROR_PARTIAL_INPUT))
497 {
498 /* Consume everything to buffer that we append to next time
499 we write */
500 if (!converting_from_buffer)
501 buffer_append (buffer: &priv->output_buffer, data: buffer, data_size: count);
502 /* in the converting_from_buffer case we already appended this */
503
504 g_error_free (error: my_error);
505 return count; /* consume everything */
506 }
507
508 /* Converted no data and got a normal error, return it */
509 g_propagate_error (dest: error, src: my_error);
510 return -1;
511 }
512 }
513
514 if (converting_from_buffer)
515 {
516 buffer_consumed (buffer: &priv->output_buffer, count: converted_bytes);
517 retval = count;
518 }
519 else
520 retval = converted_bytes;
521
522 /* We now successfully consumed retval bytes, so we can't return an error,
523 even if writing this to the base stream fails. If it does we'll just
524 stop early and report this error when we try again on the next
525 write call. */
526 flush_buffer (stream: cstream, blocking, cancellable, NULL);
527
528 return retval;
529}
530
531static gssize
532g_converter_output_stream_write (GOutputStream *stream,
533 const void *buffer,
534 gsize count,
535 GCancellable *cancellable,
536 GError **error)
537{
538 return write_internal (stream, buffer, count, TRUE, cancellable, error);
539}
540
541static gboolean
542g_converter_output_stream_flush (GOutputStream *stream,
543 GCancellable *cancellable,
544 GError **error)
545{
546 GConverterOutputStream *cstream;
547 GConverterOutputStreamPrivate *priv;
548 GConverterResult res;
549 GError *my_error;
550 gboolean is_closing;
551 gboolean flushed;
552 gsize bytes_read;
553 gsize bytes_written;
554
555 cstream = G_CONVERTER_OUTPUT_STREAM (stream);
556 priv = cstream->priv;
557
558 is_closing = g_output_stream_is_closing (stream);
559
560 /* Write out all available pre-converted data and fail if
561 not possible */
562 if (!flush_buffer (stream: cstream, TRUE, cancellable, error))
563 return FALSE;
564
565 /* Ensure we have *some* initial target space */
566 buffer_ensure_space (buffer: &priv->converted_buffer, at_least_size: 1);
567
568 /* Convert whole buffer */
569 flushed = FALSE;
570 while (!priv->finished && !flushed)
571 {
572 /* Ensure we have *some* target space */
573 if (buffer_tailspace (buffer: &priv->converted_buffer) == 0)
574 grow_buffer (buffer: &priv->converted_buffer);
575
576 /* Try to convert to our buffer */
577 my_error = NULL;
578 res = g_converter_convert (converter: priv->converter,
579 inbuf: buffer_data (buffer: &priv->output_buffer),
580 inbuf_size: buffer_data_size (buffer: &priv->output_buffer),
581 outbuf: buffer_data (buffer: &priv->converted_buffer) + buffer_data_size (buffer: &priv->converted_buffer),
582 outbuf_size: buffer_tailspace (buffer: &priv->converted_buffer),
583 flags: is_closing ? G_CONVERTER_INPUT_AT_END : G_CONVERTER_FLUSH,
584 bytes_read: &bytes_read,
585 bytes_written: &bytes_written,
586 error: &my_error);
587
588 if (res != G_CONVERTER_ERROR)
589 {
590 priv->converted_buffer.end += bytes_written;
591 buffer_consumed (buffer: &priv->output_buffer, count: bytes_read);
592
593 if (res == G_CONVERTER_FINISHED)
594 priv->finished = TRUE;
595 if (!is_closing &&
596 res == G_CONVERTER_FLUSHED)
597 {
598 /* Should not have returned FLUSHED with input left */
599 g_assert (buffer_data_size (&priv->output_buffer) == 0);
600 flushed = TRUE;
601 }
602 }
603 else
604 {
605 /* No-space errors can be handled locally: */
606 if (g_error_matches (error: my_error,
607 G_IO_ERROR,
608 code: G_IO_ERROR_NO_SPACE))
609 {
610 /* Need more destination space, grow it
611 * Note: if we actually grow the buffer (as opposed to compacting it),
612 * this will double the size, not just add one byte. */
613 buffer_ensure_space (buffer: &priv->converted_buffer,
614 at_least_size: priv->converted_buffer.size + 1);
615 g_error_free (error: my_error);
616 continue;
617 }
618
619 /* Any other error, including PARTIAL_INPUT can't be fixed by now
620 and is an error */
621 g_propagate_error (dest: error, src: my_error);
622 return FALSE;
623 }
624 }
625
626 /* Now write all converted data to base stream */
627 if (!flush_buffer (stream: cstream, TRUE, cancellable, error))
628 return FALSE;
629
630 return TRUE;
631}
632
633static gboolean
634g_converter_output_stream_can_poll (GPollableOutputStream *stream)
635{
636 GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
637
638 return (G_IS_POLLABLE_OUTPUT_STREAM (base_stream) &&
639 g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (base_stream)));
640}
641
642static gboolean
643g_converter_output_stream_is_writable (GPollableOutputStream *stream)
644{
645 GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
646
647 return g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (base_stream));
648}
649
650static gssize
651g_converter_output_stream_write_nonblocking (GPollableOutputStream *stream,
652 const void *buffer,
653 gsize count,
654 GError **error)
655{
656 return write_internal (G_OUTPUT_STREAM (stream), buffer, count, FALSE,
657 NULL, error);
658}
659
660static GSource *
661g_converter_output_stream_create_source (GPollableOutputStream *stream,
662 GCancellable *cancellable)
663{
664 GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
665 GSource *base_source, *pollable_source;
666
667 base_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (base_stream), NULL);
668 pollable_source = g_pollable_source_new_full (pollable_stream: stream, child_source: base_source,
669 cancellable);
670 g_source_unref (source: base_source);
671
672 return pollable_source;
673}
674
675/**
676 * g_converter_output_stream_get_converter:
677 * @converter_stream: a #GConverterOutputStream
678 *
679 * Gets the #GConverter that is used by @converter_stream.
680 *
681 * Returns: (transfer none): the converter of the converter output stream
682 *
683 * Since: 2.24
684 */
685GConverter *
686g_converter_output_stream_get_converter (GConverterOutputStream *converter_stream)
687{
688 return converter_stream->priv->converter;
689}
690

source code of gtk/subprojects/glib/gio/gconverteroutputstream.c