From 2227918dfd0bd13d52191538016e88f1344cc196 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 12 Nov 2017 19:55:52 -0800 Subject: [PATCH] file: add g_file_load_bytes() This adds g_file_load_bytes() to make it more convenient to load the contents of a GFile as GBytes. It includes a special casing for gresources to increase the chances that the GBytes directly references the embedded data instead of copying to the heap. https://bugzilla.gnome.org/show_bug.cgi?id=790272 --- docs/reference/gio/gio-sections.txt | 3 + gio/gfile.c | 188 ++++++++++++++++++++++++++++ gio/gfile.h | 16 +++ 3 files changed, 207 insertions(+) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 6f1682b7b..e0472f7ca 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -184,6 +184,9 @@ g_file_mount_enclosing_volume_finish g_file_monitor_directory g_file_monitor_file g_file_monitor +g_file_load_bytes +g_file_load_bytes_async +g_file_load_bytes_finish g_file_load_contents g_file_load_contents_async g_file_load_contents_finish diff --git a/gio/gfile.c b/gio/gfile.c index 53ae05fb5..812b148b7 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -8092,3 +8092,191 @@ g_file_supports_thread_contexts (GFile *file) iface = G_FILE_GET_IFACE (file); return iface->supports_thread_contexts; } + +/** + * g_file_load_bytes: + * @file: a #GFile + * @cancellable: (nullable): a #GCancellable or %NULL + * @etag_out: (out) (nullable) (optional): a location to place the current + * entity tag for the file, or %NULL if the entity tag is not needed + * @error: a location for a #GError or %NULL + * + * Loads the contents of @file and returns it as #GBytes. + * + * If @file is a resource:// based URI, the resulting bytes will reference the + * embedded resource instead of a copy. Otherwise, this is equivalent to calling + * g_file_load_contents() and g_bytes_new_take(). + * + * For resources, @etag_out will be set to %NULL. + * + * The data contained in the resulting #GBytes is always zero-terminated, but + * this is not included in the #GBytes length. The resulting #GBytes should be + * freed with g_bytes_unref() when no longer in use. + * + * Returns: (transfer full): a #GBytes or %NULL and @error is set + * + * Since: 2.56 + */ +GBytes * +g_file_load_bytes (GFile *file, + GCancellable *cancellable, + gchar **etag_out, + GError **error) +{ + gchar *contents; + gsize len; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (etag_out != NULL) + *etag_out = NULL; + + if (g_file_has_uri_scheme (file, "resource")) + { + GBytes *bytes; + gchar *uri, *unescaped; + + uri = g_file_get_uri (file); + unescaped = g_uri_unescape_string (uri + strlen ("resource://"), NULL); + g_free (uri); + + bytes = g_resources_lookup_data (unescaped, G_RESOURCE_LOOKUP_FLAGS_NONE, error); + g_free (unescaped); + + return bytes; + } + + /* contents is guaranteed to be \0 terminated */ + if (g_file_load_contents (file, cancellable, &contents, &len, etag_out, error)) + return g_bytes_new_take (g_steal_pointer (&contents), len); + + return NULL; +} + +static void +g_file_load_bytes_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + GTask *task = user_data; + GError *error = NULL; + gchar *etag = NULL; + gchar *contents = NULL; + gsize len = 0; + + g_file_load_contents_finish (file, result, &contents, &len, &etag, &error); + g_task_set_task_data (task, g_steal_pointer (&etag), g_free); + + if (error != NULL) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_pointer (task, + g_bytes_new_take (g_steal_pointer (&contents), len), + (GDestroyNotify)g_bytes_unref); + + g_object_unref (task); +} + +/** + * g_file_load_bytes_async: + * @file: a #GFile + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: (scope async): a #GAsyncReadyCallback to call when the + * request is satisfied + * @user_data: (closure): the data to pass to callback function + * + * Asynchronously loads the contents of @file as #GBytes. + * + * If @file is a resource:// based URI, the resulting bytes will reference the + * embedded resource instead of a copy. Otherwise, this is equivalent to calling + * g_file_load_contents_async() and g_bytes_new_take(). + * + * @callback should call g_file_load_bytes_finish() to get the result of this + * asynchronous operation. + * + * See g_file_load_bytes() for more information. + * + * Since: 2.56 + */ +void +g_file_load_bytes_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *error = NULL; + GBytes *bytes; + GTask *task; + + g_return_if_fail (G_IS_FILE (file)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (file, cancellable, callback, user_data); + g_task_set_source_tag (task, g_file_load_bytes_async); + + if (!g_file_has_uri_scheme (file, "resource")) + { + g_file_load_contents_async (file, + cancellable, + g_file_load_bytes_cb, + g_steal_pointer (&task)); + return; + } + + bytes = g_file_load_bytes (file, cancellable, NULL, &error); + + if (bytes == NULL) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_pointer (task, + g_steal_pointer (&bytes), + (GDestroyNotify)g_bytes_unref); + + g_object_unref (task); +} + +/** + * g_file_load_bytes_finish: + * @file: a #GFile + * @result: a #GAsyncResult provided to the callback + * @etag_out: (out) (nullable) (optional): a location to place the current + * entity tag for the file, or %NULL if the entity tag is not needed + * @error: a location for a #GError, or %NULL + * + * Completes an asynchronous request to g_file_load_bytes_async(). + * + * For resources, @etag_out will be set to %NULL. + * + * The data contained in the resulting #GBytes is always zero-terminated, but + * this is not included in the #GBytes length. The resulting #GBytes should be + * freed with g_bytes_unref() when no longer in use. + * + * See g_file_load_bytes() for more information. + * + * Returns: (transfer full): a #GBytes or %NULL and @error is set + * + * Since: 2.56 + */ +GBytes * +g_file_load_bytes_finish (GFile *file, + GAsyncResult *result, + gchar **etag_out, + GError **error) +{ + GBytes *bytes; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + g_return_val_if_fail (g_task_is_valid (G_TASK (result), file), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + bytes = g_task_propagate_pointer (G_TASK (result), error); + + if (etag_out != NULL) + *etag_out = g_strdup (g_task_get_task_data (G_TASK (result))); + + return bytes; +} diff --git a/gio/gfile.h b/gio/gfile.h index b3a06e8dd..1717665e4 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -1251,6 +1251,22 @@ gboolean g_file_replace_contents_finish (GFile *file, GLIB_AVAILABLE_IN_ALL gboolean g_file_supports_thread_contexts (GFile *file); +GLIB_AVAILABLE_IN_2_56 +GBytes *g_file_load_bytes (GFile *file, + GCancellable *cancellable, + gchar **etag_out, + GError **error); +GLIB_AVAILABLE_IN_2_56 +void g_file_load_bytes_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_56 +GBytes *g_file_load_bytes_finish (GFile *file, + GAsyncResult *result, + gchar **etag_out, + GError **error); + G_END_DECLS #endif /* __G_FILE_H__ */ -- 2.22.0