1 /* GIO - GLib Input, Output and Streaming Library
3 * Copyright (C) 2009 Red Hat, Inc.
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 of the License, or (at your option) any later version.
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.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Alexander Larsson <alexl@redhat.com>
27 #include "gconverteroutputstream.h"
28 #include "gsimpleasyncresult.h"
29 #include "gcancellable.h"
30 #include "gioenumtypes.h"
36 * SECTION:gconverteroutputstream
37 * @short_description: Converter Output Stream
39 * @see_also: #GOutputStream, #GConverter
41 * Converter output stream implements #GOutputStream and allows
42 * conversion of data of various types during reading.
46 #define INITIAL_BUFFER_SIZE 4096
55 struct _GConverterOutputStreamPrivate
{
56 gboolean at_output_end
;
58 GConverter
*converter
;
59 Buffer output_buffer
; /* To be converted and written */
60 Buffer converted_buffer
; /* Already converted */
63 /* Buffering strategy:
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).
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.
81 static void g_converter_output_stream_set_property (GObject
*object
,
85 static void g_converter_output_stream_get_property (GObject
*object
,
89 static void g_converter_output_stream_finalize (GObject
*object
);
90 static gssize
g_converter_output_stream_write (GOutputStream
*stream
,
93 GCancellable
*cancellable
,
95 static gboolean
g_converter_output_stream_flush (GOutputStream
*stream
,
96 GCancellable
*cancellable
,
99 G_DEFINE_TYPE (GConverterOutputStream
,
100 g_converter_output_stream
,
101 G_TYPE_FILTER_OUTPUT_STREAM
)
104 g_converter_output_stream_class_init (GConverterOutputStreamClass
*klass
)
106 GObjectClass
*object_class
;
107 GOutputStreamClass
*istream_class
;
109 g_type_class_add_private (klass
, sizeof (GConverterOutputStreamPrivate
));
111 object_class
= G_OBJECT_CLASS (klass
);
112 object_class
->get_property
= g_converter_output_stream_get_property
;
113 object_class
->set_property
= g_converter_output_stream_set_property
;
114 object_class
->finalize
= g_converter_output_stream_finalize
;
116 istream_class
= G_OUTPUT_STREAM_CLASS (klass
);
117 istream_class
->write_fn
= g_converter_output_stream_write
;
118 istream_class
->flush
= g_converter_output_stream_flush
;
120 g_object_class_install_property (object_class
,
122 g_param_spec_object ("converter",
124 P_("The converter object"),
127 G_PARAM_CONSTRUCT_ONLY
|
128 G_PARAM_STATIC_STRINGS
));
133 g_converter_output_stream_finalize (GObject
*object
)
135 GConverterOutputStreamPrivate
*priv
;
136 GConverterOutputStream
*stream
;
138 stream
= G_CONVERTER_OUTPUT_STREAM (object
);
141 g_free (priv
->output_buffer
.data
);
142 g_free (priv
->converted_buffer
.data
);
144 g_object_unref (priv
->converter
);
146 G_OBJECT_CLASS (g_converter_output_stream_parent_class
)->finalize (object
);
150 g_converter_output_stream_set_property (GObject
*object
,
155 GConverterOutputStream
*cstream
;
157 cstream
= G_CONVERTER_OUTPUT_STREAM (object
);
162 cstream
->priv
->converter
= g_value_dup_object (value
);
166 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
173 g_converter_output_stream_get_property (GObject
*object
,
178 GConverterOutputStreamPrivate
*priv
;
179 GConverterOutputStream
*cstream
;
181 cstream
= G_CONVERTER_OUTPUT_STREAM (object
);
182 priv
= cstream
->priv
;
187 g_value_set_object (value
, priv
->converter
);
191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
197 g_converter_output_stream_init (GConverterOutputStream
*stream
)
199 stream
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (stream
,
200 G_TYPE_CONVERTER_OUTPUT_STREAM
,
201 GConverterOutputStreamPrivate
);
205 * g_converter_output_stream_new:
206 * @base_stream: a #GOutputStream
207 * @converter: a #GConverter
209 * Creates a new converter output stream for the @base_stream.
211 * Returns: a new #GOutputStream.
214 g_converter_output_stream_new (GOutputStream
*base_stream
,
215 GConverter
*converter
)
217 GOutputStream
*stream
;
219 g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream
), NULL
);
221 stream
= g_object_new (G_TYPE_CONVERTER_OUTPUT_STREAM
,
222 "base-stream", base_stream
,
223 "converter", converter
,
230 buffer_data_size (Buffer
*buffer
)
232 return buffer
->end
- buffer
->start
;
236 buffer_tailspace (Buffer
*buffer
)
238 return buffer
->size
- buffer
->end
;
242 buffer_data (Buffer
*buffer
)
244 return buffer
->data
+ buffer
->start
;
248 buffer_consumed (Buffer
*buffer
,
251 buffer
->start
+= count
;
252 if (buffer
->start
== buffer
->end
)
253 buffer
->start
= buffer
->end
= 0;
257 compact_buffer (Buffer
*buffer
)
261 in_buffer
= buffer_data_size (buffer
);
262 memmove (buffer
->data
,
263 buffer
->data
+ buffer
->start
,
265 buffer
->end
-= buffer
->start
;
270 grow_buffer (Buffer
*buffer
)
273 gsize size
, in_buffer
;
275 if (buffer
->size
== 0)
276 size
= INITIAL_BUFFER_SIZE
;
278 size
= buffer
->size
* 2;
280 data
= g_malloc (size
);
281 in_buffer
= buffer_data_size (buffer
);
284 buffer
->data
+ buffer
->start
,
286 g_free (buffer
->data
);
288 buffer
->end
-= buffer
->start
;
293 /* Ensures that the buffer can fit at_least_size bytes,
294 * *including* the current in-buffer data */
296 buffer_ensure_space (Buffer
*buffer
,
299 gsize in_buffer
, left_to_fill
;
301 in_buffer
= buffer_data_size (buffer
);
303 if (in_buffer
>= at_least_size
)
306 left_to_fill
= buffer_tailspace (buffer
);
308 if (in_buffer
+ left_to_fill
>= at_least_size
)
310 /* We fit in remaining space at end */
311 /* If the copy is small, compact now anyway so we can fill more */
313 compact_buffer (buffer
);
315 else if (buffer
->size
>= at_least_size
)
317 /* We fit, but only if we compact */
318 compact_buffer (buffer
);
322 /* Need to grow buffer */
323 while (buffer
->size
< at_least_size
)
324 grow_buffer (buffer
);
329 buffer_append (Buffer
*buffer
,
333 buffer_ensure_space (buffer
,
334 buffer_data_size (buffer
) + data_size
);
335 memcpy (buffer
->data
+ buffer
->end
, data
, data_size
);
336 buffer
->end
+= data_size
;
341 flush_buffer (GConverterOutputStream
*stream
,
343 GCancellable
*cancellable
,
346 GConverterOutputStreamPrivate
*priv
;
347 GOutputStream
*base_stream
;
354 base_stream
= G_FILTER_OUTPUT_STREAM (stream
)->base_stream
;
356 available
= buffer_data_size (&priv
->converted_buffer
);
359 res
= g_output_stream_write_all (base_stream
,
360 buffer_data (&priv
->converted_buffer
),
365 buffer_consumed (&priv
->converted_buffer
, nwritten
);
373 g_converter_output_stream_write (GOutputStream
*stream
,
376 GCancellable
*cancellable
,
379 GConverterOutputStream
*cstream
;
380 GConverterOutputStreamPrivate
*priv
;
382 GConverterResult res
;
386 const char *to_convert
;
387 gsize to_convert_size
, converted_bytes
;
388 gboolean converting_from_buffer
;
390 cstream
= G_CONVERTER_OUTPUT_STREAM (stream
);
391 priv
= cstream
->priv
;
393 /* Write out all available pre-converted data and fail if
395 if (!flush_buffer (cstream
, &priv
->converted_buffer
, cancellable
, error
))
401 /* Convert as much as possible */
402 if (buffer_data_size (&priv
->output_buffer
) > 0)
404 converting_from_buffer
= TRUE
;
405 buffer_append (&priv
->output_buffer
, buffer
, count
);
406 to_convert
= buffer_data (&priv
->output_buffer
);
407 to_convert_size
= buffer_data_size (&priv
->output_buffer
);
411 converting_from_buffer
= FALSE
;
413 to_convert_size
= count
;
416 /* Ensure we have *some* initial target space */
417 buffer_ensure_space (&priv
->converted_buffer
, to_convert_size
);
420 while (!priv
->finished
&& converted_bytes
< to_convert_size
)
422 /* Ensure we have *some* target space */
423 if (buffer_tailspace (&priv
->converted_buffer
) == 0)
424 grow_buffer (&priv
->converted_buffer
);
426 /* Try to convert to our buffer */
428 res
= g_converter_convert (priv
->converter
,
429 to_convert
+ converted_bytes
,
430 to_convert_size
- converted_bytes
,
431 buffer_data (&priv
->converted_buffer
) + buffer_data_size (&priv
->converted_buffer
),
432 buffer_tailspace (&priv
->converted_buffer
),
438 if (res
!= G_CONVERTER_ERROR
)
440 priv
->converted_buffer
.end
+= bytes_written
;
441 converted_bytes
+= bytes_read
;
443 if (res
== G_CONVERTER_FINISHED
)
444 priv
->finished
= TRUE
;
448 /* No-space errors can be handled locally: */
449 if (g_error_matches (my_error
,
451 G_IO_ERROR_NO_SPACE
))
453 /* Need more destination space, grow it
454 * Note: if we actually grow the buffer (as opposed to compacting it),
455 * this will double the size, not just add one byte. */
456 buffer_ensure_space (&priv
->converted_buffer
,
457 priv
->converted_buffer
.size
+ 1);
458 g_error_free (my_error
);
462 if (converted_bytes
> 0)
464 /* We got an conversion error, but we did convert some bytes before
465 that, so handle those before reporting the error */
466 g_error_free (my_error
);
470 if (g_error_matches (my_error
,
472 G_IO_ERROR_PARTIAL_INPUT
))
474 /* Consume everything to buffer that we append to next time
476 if (!converting_from_buffer
)
477 buffer_append (&priv
->output_buffer
, buffer
, count
);
478 /* in the converting_from_buffer case we already appended this */
480 g_error_free (my_error
);
481 return count
; /* consume everything */
484 /* Converted no data and got an normal error, return it */
485 g_propagate_error (error
, my_error
);
490 if (converting_from_buffer
)
492 buffer_consumed (&priv
->output_buffer
, converted_bytes
);
496 retval
= converted_bytes
;
498 /* We now successfully consumed retval bytes, so we can't return an error,
499 even if writing this to the base stream fails. If it does we'll just
500 stop early and report this error when we try again on the next
502 flush_buffer (cstream
, &priv
->converted_buffer
, cancellable
, NULL
);
508 g_converter_output_stream_flush (GOutputStream
*stream
,
509 GCancellable
*cancellable
,
512 GConverterOutputStream
*cstream
;
513 GConverterOutputStreamPrivate
*priv
;
514 GConverterResult res
;
521 cstream
= G_CONVERTER_OUTPUT_STREAM (stream
);
522 priv
= cstream
->priv
;
524 is_closing
= g_output_stream_is_closing (stream
);
526 /* Write out all available pre-converted data and fail if
528 if (!flush_buffer (cstream
, &priv
->converted_buffer
, cancellable
, error
))
531 /* Ensure we have *some* initial target space */
532 buffer_ensure_space (&priv
->converted_buffer
, 1);
534 /* Convert whole buffer */
536 while (!priv
->finished
&& !flushed
)
538 /* Ensure we have *some* target space */
539 if (buffer_tailspace (&priv
->converted_buffer
) == 0)
540 grow_buffer (&priv
->converted_buffer
);
542 /* Try to convert to our buffer */
544 res
= g_converter_convert (priv
->converter
,
545 buffer_data (&priv
->output_buffer
),
546 buffer_data_size (&priv
->output_buffer
),
547 buffer_data (&priv
->converted_buffer
) + buffer_data_size (&priv
->converted_buffer
),
548 buffer_tailspace (&priv
->converted_buffer
),
549 is_closing
? G_CONVERTER_INPUT_AT_END
: G_CONVERTER_FLUSH
,
554 if (res
!= G_CONVERTER_ERROR
)
556 priv
->converted_buffer
.end
+= bytes_written
;
557 buffer_consumed (&priv
->output_buffer
, bytes_read
);
559 if (res
== G_CONVERTER_FINISHED
)
560 priv
->finished
= TRUE
;
562 res
== G_CONVERTER_FLUSHED
)
564 /* Should not have retured FLUSHED with input left */
565 g_assert (buffer_data_size (&priv
->output_buffer
) == 0);
571 /* No-space errors can be handled locally: */
572 if (g_error_matches (my_error
,
574 G_IO_ERROR_NO_SPACE
))
576 /* Need more destination space, grow it
577 * Note: if we actually grow the buffer (as opposed to compacting it),
578 * this will double the size, not just add one byte. */
579 buffer_ensure_space (&priv
->converted_buffer
,
580 priv
->converted_buffer
.size
+ 1);
581 g_error_free (my_error
);
585 /* Any other error, including PARTIAL_INPUT can't be fixed by now
587 g_propagate_error (error
, my_error
);
592 /* Now write all converted data to base stream */
593 if (!flush_buffer (cstream
, &priv
->converted_buffer
, cancellable
, error
))
600 * g_converter_output_stream_get_converter:
601 * @converter_stream: a #GConverterOutputStream
603 * Gets the #GConverter that is used by @converter_stream.
605 * Returns: (transfer none): the converter of the converter output stream
610 g_converter_output_stream_get_converter (GConverterOutputStream
*converter_stream
)
612 return converter_stream
->priv
->converter
;