Improve the wording of the message for G_UNAVAILABLE
[glib.git] / gio / gconverteroutputstream.c
blob5b1cbec7e805141c2c2886d4da7c27a81429b5b8
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>
23 #include "config.h"
25 #include <string.h>
27 #include "gconverteroutputstream.h"
28 #include "gsimpleasyncresult.h"
29 #include "gcancellable.h"
30 #include "gioenumtypes.h"
31 #include "gioerror.h"
32 #include "glibintl.h"
35 /**
36 * SECTION:gconverteroutputstream
37 * @short_description: Converter Output Stream
38 * @include: gio/gio.h
39 * @see_also: #GOutputStream, #GConverter
41 * Converter output stream implements #GOutputStream and allows
42 * conversion of data of various types during reading.
44 **/
46 #define INITIAL_BUFFER_SIZE 4096
48 typedef struct {
49 char *data;
50 gsize start;
51 gsize end;
52 gsize size;
53 } Buffer;
55 struct _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 */
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.
76 enum {
77 PROP_0,
78 PROP_CONVERTER
81 static void g_converter_output_stream_set_property (GObject *object,
82 guint prop_id,
83 const GValue *value,
84 GParamSpec *pspec);
85 static void g_converter_output_stream_get_property (GObject *object,
86 guint prop_id,
87 GValue *value,
88 GParamSpec *pspec);
89 static void g_converter_output_stream_finalize (GObject *object);
90 static gssize g_converter_output_stream_write (GOutputStream *stream,
91 const void *buffer,
92 gsize count,
93 GCancellable *cancellable,
94 GError **error);
95 static gboolean g_converter_output_stream_flush (GOutputStream *stream,
96 GCancellable *cancellable,
97 GError **error);
99 G_DEFINE_TYPE (GConverterOutputStream,
100 g_converter_output_stream,
101 G_TYPE_FILTER_OUTPUT_STREAM)
103 static void
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,
121 PROP_CONVERTER,
122 g_param_spec_object ("converter",
123 P_("Converter"),
124 P_("The converter object"),
125 G_TYPE_CONVERTER,
126 G_PARAM_READWRITE|
127 G_PARAM_CONSTRUCT_ONLY|
128 G_PARAM_STATIC_STRINGS));
132 static void
133 g_converter_output_stream_finalize (GObject *object)
135 GConverterOutputStreamPrivate *priv;
136 GConverterOutputStream *stream;
138 stream = G_CONVERTER_OUTPUT_STREAM (object);
139 priv = stream->priv;
141 g_free (priv->output_buffer.data);
142 g_free (priv->converted_buffer.data);
143 if (priv->converter)
144 g_object_unref (priv->converter);
146 G_OBJECT_CLASS (g_converter_output_stream_parent_class)->finalize (object);
149 static void
150 g_converter_output_stream_set_property (GObject *object,
151 guint prop_id,
152 const GValue *value,
153 GParamSpec *pspec)
155 GConverterOutputStream *cstream;
157 cstream = G_CONVERTER_OUTPUT_STREAM (object);
159 switch (prop_id)
161 case PROP_CONVERTER:
162 cstream->priv->converter = g_value_dup_object (value);
163 break;
165 default:
166 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
167 break;
172 static void
173 g_converter_output_stream_get_property (GObject *object,
174 guint prop_id,
175 GValue *value,
176 GParamSpec *pspec)
178 GConverterOutputStreamPrivate *priv;
179 GConverterOutputStream *cstream;
181 cstream = G_CONVERTER_OUTPUT_STREAM (object);
182 priv = cstream->priv;
184 switch (prop_id)
186 case PROP_CONVERTER:
187 g_value_set_object (value, priv->converter);
188 break;
190 default:
191 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
192 break;
196 static void
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.
213 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,
224 NULL);
226 return stream;
229 static gsize
230 buffer_data_size (Buffer *buffer)
232 return buffer->end - buffer->start;
235 static gsize
236 buffer_tailspace (Buffer *buffer)
238 return buffer->size - buffer->end;
241 static char *
242 buffer_data (Buffer *buffer)
244 return buffer->data + buffer->start;
247 static void
248 buffer_consumed (Buffer *buffer,
249 gsize count)
251 buffer->start += count;
252 if (buffer->start == buffer->end)
253 buffer->start = buffer->end = 0;
256 static void
257 compact_buffer (Buffer *buffer)
259 gsize in_buffer;
261 in_buffer = buffer_data_size (buffer);
262 memmove (buffer->data,
263 buffer->data + buffer->start,
264 in_buffer);
265 buffer->end -= buffer->start;
266 buffer->start = 0;
269 static void
270 grow_buffer (Buffer *buffer)
272 char *data;
273 gsize size, in_buffer;
275 if (buffer->size == 0)
276 size = INITIAL_BUFFER_SIZE;
277 else
278 size = buffer->size * 2;
280 data = g_malloc (size);
281 in_buffer = buffer_data_size (buffer);
283 memcpy (data,
284 buffer->data + buffer->start,
285 in_buffer);
286 g_free (buffer->data);
287 buffer->data = data;
288 buffer->end -= buffer->start;
289 buffer->start = 0;
290 buffer->size = size;
293 /* Ensures that the buffer can fit at_least_size bytes,
294 * *including* the current in-buffer data */
295 static void
296 buffer_ensure_space (Buffer *buffer,
297 gsize at_least_size)
299 gsize in_buffer, left_to_fill;
301 in_buffer = buffer_data_size (buffer);
303 if (in_buffer >= at_least_size)
304 return;
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 */
312 if (in_buffer < 256)
313 compact_buffer (buffer);
315 else if (buffer->size >= at_least_size)
317 /* We fit, but only if we compact */
318 compact_buffer (buffer);
320 else
322 /* Need to grow buffer */
323 while (buffer->size < at_least_size)
324 grow_buffer (buffer);
328 static void
329 buffer_append (Buffer *buffer,
330 const char *data,
331 gsize data_size)
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;
340 static gboolean
341 flush_buffer (GConverterOutputStream *stream,
342 Buffer *buffer,
343 GCancellable *cancellable,
344 GError **error)
346 GConverterOutputStreamPrivate *priv;
347 GOutputStream *base_stream;
348 gsize nwritten;
349 gsize available;
350 gboolean res;
352 priv = stream->priv;
354 base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
356 available = buffer_data_size (&priv->converted_buffer);
357 if (available > 0)
359 res = g_output_stream_write_all (base_stream,
360 buffer_data (&priv->converted_buffer),
361 available,
362 &nwritten,
363 cancellable,
364 error);
365 buffer_consumed (&priv->converted_buffer, nwritten);
366 return res;
368 return TRUE;
372 static gssize
373 g_converter_output_stream_write (GOutputStream *stream,
374 const void *buffer,
375 gsize count,
376 GCancellable *cancellable,
377 GError **error)
379 GConverterOutputStream *cstream;
380 GConverterOutputStreamPrivate *priv;
381 gssize retval;
382 GConverterResult res;
383 gsize bytes_read;
384 gsize bytes_written;
385 GError *my_error;
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
394 not possible */
395 if (!flush_buffer (cstream, &priv->converted_buffer, cancellable, error))
396 return -1;
398 if (priv->finished)
399 return 0;
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);
409 else
411 converting_from_buffer = FALSE;
412 to_convert = buffer;
413 to_convert_size = count;
416 /* Ensure we have *some* initial target space */
417 buffer_ensure_space (&priv->converted_buffer, to_convert_size);
419 converted_bytes = 0;
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 */
427 my_error = NULL;
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),
434 &bytes_read,
435 &bytes_written,
436 &my_error);
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;
446 else
448 /* No-space errors can be handled locally: */
449 if (g_error_matches (my_error,
450 G_IO_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);
459 continue;
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);
467 break;
470 if (g_error_matches (my_error,
471 G_IO_ERROR,
472 G_IO_ERROR_PARTIAL_INPUT))
474 /* Consume everything to buffer that we append to next time
475 we write */
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);
486 return -1;
490 if (converting_from_buffer)
492 buffer_consumed (&priv->output_buffer, converted_bytes);
493 retval = count;
495 else
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
501 write call. */
502 flush_buffer (cstream, &priv->converted_buffer, cancellable, NULL);
504 return retval;
507 static gboolean
508 g_converter_output_stream_flush (GOutputStream *stream,
509 GCancellable *cancellable,
510 GError **error)
512 GConverterOutputStream *cstream;
513 GConverterOutputStreamPrivate *priv;
514 GConverterResult res;
515 GError *my_error;
516 gboolean is_closing;
517 gboolean flushed;
518 gsize bytes_read;
519 gsize bytes_written;
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
527 not possible */
528 if (!flush_buffer (cstream, &priv->converted_buffer, cancellable, error))
529 return FALSE;
531 /* Ensure we have *some* initial target space */
532 buffer_ensure_space (&priv->converted_buffer, 1);
534 /* Convert whole buffer */
535 flushed = FALSE;
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 */
543 my_error = NULL;
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,
550 &bytes_read,
551 &bytes_written,
552 &my_error);
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;
561 if (!is_closing &&
562 res == G_CONVERTER_FLUSHED)
564 /* Should not have retured FLUSHED with input left */
565 g_assert (buffer_data_size (&priv->output_buffer) == 0);
566 flushed = TRUE;
569 else
571 /* No-space errors can be handled locally: */
572 if (g_error_matches (my_error,
573 G_IO_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);
582 continue;
585 /* Any other error, including PARTIAL_INPUT can't be fixed by now
586 and is an error */
587 g_propagate_error (error, my_error);
588 return FALSE;
592 /* Now write all converted data to base stream */
593 if (!flush_buffer (cstream, &priv->converted_buffer, cancellable, error))
594 return FALSE;
596 return TRUE;
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
607 * Since: 2.24
609 GConverter *
610 g_converter_output_stream_get_converter (GConverterOutputStream *converter_stream)
612 return converter_stream->priv->converter;