From: Dan Winship Date: Thu, 5 Apr 2012 13:19:17 +0000 (-0400) Subject: gio: add GBytes-based input/output stream methods X-Git-Tag: 2.33.2~27 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=800d6ff1115b27bf0e9cce818a0511048e2f1cc5;p=platform%2Fupstream%2Fglib.git gio: add GBytes-based input/output stream methods Using a caller-supplied buffer for g_input_stream_read() doesn't translate well to the semantics of many other languages, and using a non-refcounted buffer for read_async() and write_async() makes it impossible to manage the memory correctly currently in garbage-collected languages. Fix both of these issues by adding a new set of methods that work with GBytes objects rather than plain buffers. https://bugzilla.gnome.org/show_bug.cgi?id=671139 --- diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 7c5636d..5f4a7df 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -562,6 +562,9 @@ g_input_stream_is_closed g_input_stream_has_pending g_input_stream_set_pending g_input_stream_clear_pending +g_input_stream_read_bytes +g_input_stream_read_bytes_async +g_input_stream_read_bytes_finish GInputStreamClass G_INPUT_STREAM @@ -769,6 +772,9 @@ g_output_stream_is_closed g_output_stream_has_pending g_output_stream_set_pending g_output_stream_clear_pending +g_output_stream_write_bytes +g_ouuput_stream_write_bytes_async +g_output_stream_write_bytes_finish GOutputStreamClass G_OUTPUT_STREAM diff --git a/gio/ginputstream.c b/gio/ginputstream.c index 4f9f48e..8c61285 100644 --- a/gio/ginputstream.c +++ b/gio/ginputstream.c @@ -274,6 +274,65 @@ g_input_stream_read_all (GInputStream *stream, } /** + * g_input_stream_read_bytes: + * @stream: a #GInputStream. + * @count: maximum number of bytes that will be read from the stream. Common + * values include 4096 and 8192. + * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore. + * @error: location to store the error occurring, or %NULL to ignore + * + * Like g_input_stream_read(), this tries to read @count bytes from + * the stream in a blocking fashion. However, rather than reading into + * a user-supplied buffer, this will create a new #GBytes containing + * the data that was read. This may be easier to use from language + * bindings. + * + * If count is zero, returns a zero-length #GBytes and does nothing. A + * value of @count larger than %G_MAXSSIZE will cause a + * %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, a new #GBytes is returned. It is not an error if the + * size of this object is not the same as the requested size, as it + * can happen e.g. near the end of a file. A zero-length #GBytes is + * returned on end of file (or if @count is zero), but never + * otherwise. + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * On error %NULL is returned and @error is set accordingly. + * + * Return value: a new #GBytes, or %NULL on error + **/ +GBytes * +g_input_stream_read_bytes (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + guchar *buf; + gssize nread; + + buf = g_malloc (count); + nread = g_input_stream_read (stream, buf, count, cancellable, error); + if (nread == -1) + { + g_free (buf); + return NULL; + } + else if (nread == 0) + { + g_free (buf); + return g_bytes_new_static ("", 0); + } + else + return g_bytes_new_take (buf, nread); +} + +/** * g_input_stream_skip: * @stream: a #GInputStream. * @count: the number of bytes that will be skipped from the stream @@ -611,6 +670,121 @@ g_input_stream_read_finish (GInputStream *stream, return class->read_finish (stream, result, error); } +static void +read_bytes_callback (GObject *stream, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *simple = user_data; + guchar *buf = g_simple_async_result_get_op_res_gpointer (simple); + GError *error = NULL; + gssize nread; + GBytes *bytes = NULL; + + nread = g_input_stream_read_finish (G_INPUT_STREAM (stream), + result, &error); + if (nread == -1) + { + g_free (buf); + g_simple_async_result_take_error (simple, error); + } + else if (nread == 0) + { + g_free (buf); + bytes = g_bytes_new_static ("", 0); + } + else + bytes = g_bytes_new_take (buf, nread); + + if (bytes) + { + g_simple_async_result_set_op_res_gpointer (simple, bytes, + (GDestroyNotify)g_bytes_unref); + } + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +/** + * g_input_stream_read_bytes_async: + * @stream: A #GInputStream. + * @count: the number of bytes that will be read from the stream + * @io_priority: the I/O priority + * of the request. + * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore. + * @callback: (scope async): callback to call when the request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Request an asynchronous read of @count bytes from the stream into a + * new #GBytes. When the operation is finished @callback will be + * called. You can then call g_input_stream_read_bytes_finish() to get the + * result of the operation. + * + * During an async request no other sync and async calls are allowed + * on @stream, and will result in %G_IO_ERROR_PENDING errors. + * + * A value of @count larger than %G_MAXSSIZE will cause a + * %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the new #GBytes will be passed to the callback. It is + * not an error if this is smaller than the requested size, as it can + * happen e.g. near the end of a file, but generally we try to read as + * many bytes as requested. Zero is returned on end of file (or if + * @count is zero), but never otherwise. + * + * Any outstanding I/O request with higher priority (lower numerical + * value) will be executed before an outstanding request with lower + * priority. Default priority is %G_PRIORITY_DEFAULT. + **/ +void +g_input_stream_read_bytes_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + guchar *buf; + + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, user_data, + g_input_stream_read_bytes_async); + buf = g_malloc (count); + g_simple_async_result_set_op_res_gpointer (simple, buf, NULL); + + g_input_stream_read_async (stream, buf, count, + io_priority, cancellable, + read_bytes_callback, simple); +} + +/** + * g_input_stream_read_bytes_finish: + * @stream: a #GInputStream. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occurring, or %NULL to + * ignore. + * + * Finishes an asynchronous stream read-into-#GBytes operation. + * + * Returns: the newly-allocated #GBytes, or %NULL on error + **/ +GBytes * +g_input_stream_read_bytes_finish (GInputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL); + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (stream), g_input_stream_read_bytes_async), NULL); + + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + return g_bytes_ref (g_simple_async_result_get_op_res_gpointer (simple)); +} + /** * g_input_stream_skip_async: * @stream: A #GInputStream. diff --git a/gio/ginputstream.h b/gio/ginputstream.h index 8179529..e9b36f7 100644 --- a/gio/ginputstream.h +++ b/gio/ginputstream.h @@ -124,6 +124,11 @@ gboolean g_input_stream_read_all (GInputStream *stream, gsize *bytes_read, GCancellable *cancellable, GError **error); +GLIB_AVAILABLE_IN_2_34 +GBytes *g_input_stream_read_bytes (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error); gssize g_input_stream_skip (GInputStream *stream, gsize count, GCancellable *cancellable, @@ -141,6 +146,17 @@ void g_input_stream_read_async (GInputStream *stream, gssize g_input_stream_read_finish (GInputStream *stream, GAsyncResult *result, GError **error); +GLIB_AVAILABLE_IN_2_34 +void g_input_stream_read_bytes_async (GInputStream *stream, + gsize count, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_34 +GBytes *g_input_stream_read_bytes_finish (GInputStream *stream, + GAsyncResult *result, + GError **error); void g_input_stream_skip_async (GInputStream *stream, gsize count, int io_priority, diff --git a/gio/gio.symbols b/gio/gio.symbols index 34c954f..f0c4c27 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -499,6 +499,9 @@ g_input_stream_is_closed g_input_stream_has_pending g_input_stream_set_pending g_input_stream_clear_pending +g_input_stream_read_bytes +g_input_stream_read_bytes_async +g_input_stream_read_bytes_finish g_io_stream_get_type g_io_stream_get_input_stream g_io_stream_get_output_stream @@ -592,6 +595,9 @@ g_output_stream_is_closing g_output_stream_has_pending g_output_stream_set_pending g_output_stream_clear_pending +g_output_stream_write_bytes +g_ouuput_stream_write_bytes_async +g_output_stream_write_bytes_finish g_seekable_get_type g_seekable_tell g_seekable_can_seek diff --git a/gio/goutputstream.c b/gio/goutputstream.c index e238d8b..3ee4cf0 100644 --- a/gio/goutputstream.c +++ b/gio/goutputstream.c @@ -293,6 +293,53 @@ g_output_stream_write_all (GOutputStream *stream, } /** + * g_output_stream_write_bytes: + * @stream: a #GOutputStream. + * @bytes: the #GBytes to write + * @cancellable: (allow-none): optional cancellable object + * @error: location to store the error occurring, or %NULL to ignore + * + * Tries to write the data from @bytes into the stream. Will block + * during the operation. + * + * If @bytes is 0-length, returns 0 and does nothing. A #GBytes larger + * than %G_MAXSSIZE will cause a %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes written to the stream is returned. + * It is not an error if this is not the same as the requested size, as it + * can happen e.g. on a partial I/O error, or if there is not enough + * storage in the stream. All writes block until at least one byte + * is written or an error occurs; 0 is never returned (unless + * the size of @bytes is 0). + * + * If @cancellable is not %NULL, then the operation can be cancelled by + * triggering the cancellable object from another thread. If the operation + * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned. If an + * operation was partially finished when the operation was cancelled the + * partial result will be returned, without an error. + * + * On error -1 is returned and @error is set accordingly. + * + * Return value: Number of bytes written, or -1 on error + **/ +gssize +g_output_stream_write_bytes (GOutputStream *stream, + GBytes *bytes, + GCancellable *cancellable, + GError **error) +{ + gsize size; + gconstpointer data; + + data = g_bytes_get_data (bytes, &size); + + return g_output_stream_write (stream, + data, size, + cancellable, + error); +} + +/** * g_output_stream_flush: * @stream: a #GOutputStream. * @cancellable: (allow-none): optional cancellable object @@ -809,6 +856,116 @@ g_output_stream_write_finish (GOutputStream *stream, return class->write_finish (stream, result, error); } +static void +write_bytes_callback (GObject *stream, + GAsyncResult *result, + gpointer user_data) +{ + GSimpleAsyncResult *simple = user_data; + GError *error = NULL; + gssize nwrote; + + nwrote = g_output_stream_write_finish (G_OUTPUT_STREAM (stream), + result, &error); + if (nwrote == -1) + g_simple_async_result_take_error (simple, error); + else + g_simple_async_result_set_op_res_gssize (simple, nwrote); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +/** + * g_output_stream_write_bytes_async: + * @stream: A #GOutputStream. + * @bytes: The bytes to write + * @io_priority: the io priority of the request. + * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore. + * @callback: (scope async): callback to call when the request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Request an asynchronous write of the data in @bytes to the stream. + * When the operation is finished @callback will be called. You can + * then call g_output_stream_write_bytes_finish() to get the result of + * the operation. + * + * During an async request no other sync and async calls are allowed, + * and will result in %G_IO_ERROR_PENDING errors. + * + * A #GBytes larger than %G_MAXSSIZE will cause a + * %G_IO_ERROR_INVALID_ARGUMENT error. + * + * On success, the number of bytes written will be passed to the + * @callback. It is not an error if this is not the same as the + * requested size, as it can happen e.g. on a partial I/O error, + * but generally we try to write as many bytes as requested. + * + * You are guaranteed that this method will never fail with + * %G_IO_ERROR_WOULD_BLOCK - if @stream can't accept more data, the + * method will just wait until this changes. + * + * Any outstanding I/O request with higher priority (lower numerical + * value) will be executed before an outstanding request with lower + * priority. Default priority is %G_PRIORITY_DEFAULT. + * + * For the synchronous, blocking version of this function, see + * g_output_stream_write_bytes(). + **/ +void +g_output_stream_write_bytes_async (GOutputStream *stream, + GBytes *bytes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + gsize size; + gconstpointer data; + + data = g_bytes_get_data (bytes, &size); + + simple = g_simple_async_result_new (G_OBJECT (stream), + callback, user_data, + g_output_stream_write_bytes_async); + g_simple_async_result_set_op_res_gpointer (simple, g_bytes_ref (bytes), + (GDestroyNotify) g_bytes_unref); + + g_output_stream_write_async (stream, + data, size, + io_priority, + cancellable, + write_bytes_callback, + simple); +} + +/** + * g_output_stream_write_bytes_finish: + * @stream: a #GOutputStream. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occurring, or %NULL to + * ignore. + * + * Finishes a stream write-from-#GBytes operation. + * + * Returns: a #gssize containing the number of bytes written to the stream. + **/ +gssize +g_output_stream_write_bytes_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), -1); + g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (stream), g_output_stream_write_bytes_async), -1); + + simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return -1; + return g_simple_async_result_get_op_res_gssize (simple); +} + typedef struct { GInputStream *source; gpointer user_data; diff --git a/gio/goutputstream.h b/gio/goutputstream.h index 995d0dd..5e9850c 100644 --- a/gio/goutputstream.h +++ b/gio/goutputstream.h @@ -146,6 +146,11 @@ gboolean g_output_stream_write_all (GOutputStream *stream, gsize *bytes_written, GCancellable *cancellable, GError **error); +GLIB_AVAILABLE_IN_2_34 +gssize g_output_stream_write_bytes (GOutputStream *stream, + GBytes *bytes, + GCancellable *cancellable, + GError **error); gssize g_output_stream_splice (GOutputStream *stream, GInputStream *source, GOutputStreamSpliceFlags flags, @@ -167,6 +172,17 @@ void g_output_stream_write_async (GOutputStream *stream, gssize g_output_stream_write_finish (GOutputStream *stream, GAsyncResult *result, GError **error); +GLIB_AVAILABLE_IN_2_34 +void g_output_stream_write_bytes_async (GOutputStream *stream, + GBytes *bytes, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_34 +gssize g_output_stream_write_bytes_finish (GOutputStream *stream, + GAsyncResult *result, + GError **error); void g_output_stream_splice_async (GOutputStream *stream, GInputStream *source, GOutputStreamSpliceFlags flags, diff --git a/gio/tests/memory-input-stream.c b/gio/tests/memory-input-stream.c index 753dfe9..39079cb 100644 --- a/gio/tests/memory-input-stream.c +++ b/gio/tests/memory-input-stream.c @@ -194,6 +194,31 @@ test_truncate (void) g_object_unref (stream); } +static void +test_read_bytes (void) +{ + const char *data1 = "abcdefghijklmnopqrstuvwxyz"; + const char *data2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + GInputStream *stream; + GError *error = NULL; + GBytes *bytes; + gsize size; + gconstpointer data; + + stream = g_memory_input_stream_new (); + g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream), + data1, -1, NULL); + g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream), + data2, -1, NULL); + + bytes = g_input_stream_read_bytes (stream, 26, NULL, &error); + g_assert_no_error (error); + + data = g_bytes_get_data (bytes, &size); + g_assert_cmpint (size, ==, 26); + g_assert (strncmp (data, data1, 26) == 0); +} + int main (int argc, char *argv[]) @@ -205,6 +230,7 @@ main (int argc, g_test_add_func ("/memory-input-stream/async", test_async); g_test_add_func ("/memory-input-stream/seek", test_seek); g_test_add_func ("/memory-input-stream/truncate", test_truncate); + g_test_add_func ("/memory-input-stream/read-bytes", test_read_bytes); return g_test_run(); } diff --git a/gio/tests/memory-output-stream.c b/gio/tests/memory-output-stream.c index 6f4b881..ddaa75f 100644 --- a/gio/tests/memory-output-stream.c +++ b/gio/tests/memory-output-stream.c @@ -150,6 +150,32 @@ test_properties (void) } static void +test_write_bytes (void) +{ + GOutputStream *mo; + GBytes *bytes, *bytes2; + GError *error = NULL; + + mo = (GOutputStream*) g_object_new (G_TYPE_MEMORY_OUTPUT_STREAM, + "realloc-function", g_realloc, + "destroy-function", g_free, + NULL); + bytes = g_bytes_new_static ("hello world!", strlen ("hello world!") + 1); + g_output_stream_write_bytes (mo, bytes, NULL, &error); + g_assert_no_error (error); + + g_output_stream_close (mo, NULL, &error); + g_assert_no_error (error); + + bytes2 = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mo)); + g_object_unref (mo); + g_assert (g_bytes_equal (bytes, bytes2)); + + g_bytes_unref (bytes); + g_bytes_unref (bytes2); +} + +static void test_steal_as_bytes (void) { GOutputStream *mo; @@ -198,6 +224,7 @@ main (int argc, g_test_add_func ("/memory-output-stream/seek", test_seek); g_test_add_func ("/memory-output-stream/get-data-size", test_data_size); g_test_add_func ("/memory-output-stream/properties", test_properties); + g_test_add_func ("/memory-output-stream/write-bytes", test_write_bytes); g_test_add_func ("/memory-output-stream/steal_as_bytes", test_steal_as_bytes); return g_test_run();