diff options
author | Hyunjee Kim <hj0426.kim@samsung.com> | 2019-12-03 10:43:46 +0900 |
---|---|---|
committer | Hyunjee Kim <hj0426.kim@samsung.com> | 2019-12-03 10:43:46 +0900 |
commit | 7547eb76bf27ff88b990f39d1dc706d6ce605da1 (patch) | |
tree | 9c842bd0d9d13ed0c930c20bb00744877a31ad36 /gio | |
parent | 99d572044d3b94498607a05ad7aaccf372e8cbff (diff) | |
download | glib-7547eb76bf27ff88b990f39d1dc706d6ce605da1.tar.gz glib-7547eb76bf27ff88b990f39d1dc706d6ce605da1.tar.bz2 glib-7547eb76bf27ff88b990f39d1dc706d6ce605da1.zip |
Imported Upstream version 2.59.2
Diffstat (limited to 'gio')
-rw-r--r-- | gio/gappinfo.c | 324 | ||||
-rw-r--r-- | gio/gappinfo.h | 25 | ||||
-rw-r--r-- | gio/gdbusauth.c | 2 | ||||
-rw-r--r-- | gio/gdbusmessage.c | 7 | ||||
-rw-r--r-- | gio/gdesktopappinfo.c | 160 | ||||
-rw-r--r-- | gio/gdocumentportal.c | 82 | ||||
-rw-r--r-- | gio/gdocumentportal.h | 3 | ||||
-rw-r--r-- | gio/gfile.c | 125 | ||||
-rw-r--r-- | gio/gfile.h | 11 | ||||
-rw-r--r-- | gio/gio-tool-open.c | 120 | ||||
-rw-r--r-- | gio/gkeyfilesettingsbackend.c | 4 | ||||
-rw-r--r-- | gio/glocalfile.c | 5 | ||||
-rw-r--r-- | gio/gopenuriportal.c | 7 | ||||
-rw-r--r-- | gio/gsettings.c | 8 | ||||
-rw-r--r-- | gio/gsettingsbackend.c | 4 | ||||
-rw-r--r-- | gio/gsettingsschema.c | 2 | ||||
-rw-r--r-- | gio/gsocketclient.c | 74 | ||||
-rw-r--r-- | gio/gtrashportal.c | 123 | ||||
-rw-r--r-- | gio/gtrashportal.h | 31 | ||||
-rw-r--r-- | gio/meson.build | 6 | ||||
-rw-r--r-- | gio/org.freedesktop.portal.Trash.xml | 48 | ||||
-rw-r--r-- | gio/tests/gdbus-message.c | 72 | ||||
-rw-r--r-- | gio/tests/gsettings.c | 1 | ||||
-rw-r--r-- | gio/tests/gsocketclient-slow.c | 55 | ||||
-rw-r--r-- | gio/tests/trash.c | 42 |
25 files changed, 1061 insertions, 280 deletions
diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 47cd73366..84e667575 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -24,6 +24,7 @@ #include "gappinfoprivate.h" #include "gcontextspecificgroup.h" #include "gtask.h" +#include "gcancellable.h" #include "glibintl.h" #include <gioerror.h> @@ -662,6 +663,86 @@ g_app_info_launch_uris (GAppInfo *appinfo, return (* iface->launch_uris) (appinfo, uris, launch_context, error); } +/** + * g_app_info_launch_uris_async: + * @appinfo: a #GAppInfo + * @uris: (nullable) (element-type utf8): a #GList containing URIs to launch. + * @context: (nullable): a #GAppLaunchContext or %NULL + * @cancellable: (nullable): a #GCancellable + * @callback: (nullable): a #GAsyncReadyCallback to call when the request is done + * @user_data: (nullable): data to pass to @callback + * + * Async version of g_app_info_launch_uris(). + * + * The @callback is invoked immediately after the application launch, but it + * waits for activation in case of D-Bus–activated applications and also provides + * extended error information for sandboxed applications, see notes for + * g_app_info_launch_default_for_uri_async(). + * + * Since: 2.60 + **/ +void +g_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GAppInfoIface *iface; + + g_return_if_fail (G_IS_APP_INFO (appinfo)); + g_return_if_fail (context == NULL || G_IS_APP_LAUNCH_CONTEXT (context)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + iface = G_APP_INFO_GET_IFACE (appinfo); + if (iface->launch_uris_async == NULL) + { + GTask *task; + + task = g_task_new (appinfo, cancellable, callback, user_data); + g_task_set_source_tag (task, g_app_info_launch_uris_async); + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Operation not supported for the current backend."); + g_object_unref (task); + + return; + } + + (* iface->launch_uris_async) (appinfo, uris, context, cancellable, callback, user_data); +} + +/** + * g_app_info_launch_uris_finish: + * @appinfo: a #GAppInfo + * @result: a #GAsyncResult + * @error: (nullable): a #GError + * + * Finishes a g_app_info_launch_uris_async() operation. + * + * Returns: %TRUE on successful launch, %FALSE otherwise. + * + * Since: 2.60 + */ +gboolean +g_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error) +{ + GAppInfoIface *iface; + + g_return_val_if_fail (G_IS_APP_INFO (appinfo), FALSE); + + iface = G_APP_INFO_GET_IFACE (appinfo); + if (iface->launch_uris_finish == NULL) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Operation not supported for the current backend."); + return FALSE; + } + + return (* iface->launch_uris_finish) (appinfo, result, error); +} /** * g_app_info_should_show: @@ -684,15 +765,31 @@ g_app_info_should_show (GAppInfo *appinfo) return (* iface->should_show) (appinfo); } -static gboolean -launch_default_for_uri (const char *uri, - GAppLaunchContext *context, - GError **error) +/** + * g_app_info_launch_default_for_uri: + * @uri: the uri to show + * @context: (nullable): an optional #GAppLaunchContext + * @error: (nullable): return location for an error, or %NULL + * + * Utility function that launches the default application + * registered to handle the specified uri. Synchronous I/O + * is done on the uri to detect the type of the file if + * required. + * + * The D-Bus–activated applications don't have to be started if your application + * terminates too soon after this function. To prevent this, use + * g_app_info_launch_default_for_uri() instead. + * + * Returns: %TRUE on success, %FALSE on error. + **/ +gboolean +g_app_info_launch_default_for_uri (const char *uri, + GAppLaunchContext *launch_context, + GError **error) { char *uri_scheme; GAppInfo *app_info = NULL; - GList l; - gboolean res; + gboolean res = FALSE; /* g_file_query_default_handler() calls * g_app_info_get_default_for_uri_scheme() too, but we have to do it @@ -712,56 +809,148 @@ launch_default_for_uri (const char *uri, g_object_unref (file); } - if (app_info == NULL) - return FALSE; + if (app_info) + { + GList l; - l.data = (char *)uri; - l.next = l.prev = NULL; - res = g_app_info_launch_uris (app_info, &l, context, error); + l.data = (char *)uri; + l.next = l.prev = NULL; + res = g_app_info_launch_uris (app_info, &l, launch_context, error); + g_object_unref (app_info); + } - g_object_unref (app_info); +#ifdef G_OS_UNIX + if (!res && glib_should_use_portal ()) + { + const char *parent_window = NULL; + + /* Reset any error previously set by launch_default_for_uri */ + g_clear_error (error); + + if (launch_context && launch_context->priv->envp) + parent_window = g_environ_getenv (launch_context->priv->envp, "PARENT_WINDOW_ID"); + + return g_openuri_portal_open_uri (uri, parent_window, error); + } +#endif return res; } -/** - * g_app_info_launch_default_for_uri: - * @uri: the uri to show - * @context: (nullable): an optional #GAppLaunchContext - * @error: (nullable): return location for an error, or %NULL - * - * Utility function that launches the default application - * registered to handle the specified uri. Synchronous I/O - * is done on the uri to detect the type of the file if - * required. - * - * Returns: %TRUE on success, %FALSE on error. - **/ -gboolean -g_app_info_launch_default_for_uri (const char *uri, - GAppLaunchContext *launch_context, - GError **error) +typedef struct +{ + gchar *uri; + GAppLaunchContext *context; +} LaunchDefaultForUriData; + +static void +launch_default_for_uri_data_free (LaunchDefaultForUriData *data) +{ + g_free (data->uri); + g_clear_object (&data->context); + g_free (data); +} + +#ifdef G_OS_UNIX +static void +launch_default_for_uri_portal_open_uri_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) { - if (launch_default_for_uri (uri, launch_context, error)) - return TRUE; + GTask *task = G_TASK (user_data); + GError *error = NULL; + + if (g_openuri_portal_open_uri_finish (result, &error)) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); +} +#endif +static void +launch_default_for_uri_portal_open_uri (GTask *task, GError *error) +{ #ifdef G_OS_UNIX + LaunchDefaultForUriData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + if (glib_should_use_portal ()) { const char *parent_window = NULL; /* Reset any error previously set by launch_default_for_uri */ - g_clear_error (error); + g_error_free (error); - if (launch_context && launch_context->priv->envp) - parent_window = g_environ_getenv (launch_context->priv->envp, "PARENT_WINDOW_ID"); - - return g_openuri_portal_open_uri (uri, parent_window, error); + if (data->context && data->context->priv->envp) + parent_window = g_environ_getenv (data->context->priv->envp, + "PARENT_WINDOW_ID"); + g_openuri_portal_open_uri_async (data->uri, + parent_window, + cancellable, + launch_default_for_uri_portal_open_uri_cb, + g_steal_pointer (&task)); + return; } #endif - return FALSE; + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); +} + +static void +launch_default_for_uri_launch_uris_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GAppInfo *app_info = G_APP_INFO (object); + GTask *task = G_TASK (user_data); + GError *error = NULL; + + if (g_app_info_launch_uris_finish (app_info, result, &error)) + { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + } + else + launch_default_for_uri_portal_open_uri (g_steal_pointer (&task), g_steal_pointer (&error)); +} + +static void +launch_default_for_uri_launch_uris (GTask *task, + GAppInfo *app_info) +{ + GCancellable *cancellable = g_task_get_cancellable (task); + GList l; + LaunchDefaultForUriData *data = g_task_get_task_data (task); + + l.data = (char *)data->uri; + l.next = l.prev = NULL; + g_app_info_launch_uris_async (app_info, + &l, + data->context, + cancellable, + launch_default_for_uri_launch_uris_cb, + g_steal_pointer (&task)); + g_object_unref (app_info); +} + +static void +launch_default_for_uri_default_handler_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + GTask *task = G_TASK (user_data); + GAppInfo *app_info = NULL; + GError *error = NULL; + + app_info = g_file_query_default_handler_finish (file, result, &error); + if (app_info) + launch_default_for_uri_launch_uris (g_steal_pointer (&task), g_steal_pointer (&app_info)); + else + launch_default_for_uri_portal_open_uri (g_steal_pointer (&task), g_steal_pointer (&error)); } /** @@ -779,6 +968,10 @@ g_app_info_launch_default_for_uri (const char *uri, * sandboxed and the portal may present an application chooser * dialog to the user. * + * This is also useful if you want to be sure that the D-Bus–activated + * applications are really started before termination and if you are interested + * in receiving error information from their activation. + * * Since: 2.50 */ void @@ -788,32 +981,45 @@ g_app_info_launch_default_for_uri_async (const char *uri, GAsyncReadyCallback callback, gpointer user_data) { - gboolean res; - GError *error = NULL; GTask *task; + char *uri_scheme; + GAppInfo *app_info = NULL; + LaunchDefaultForUriData *data; - res = launch_default_for_uri (uri, context, &error); + g_return_if_fail (uri != NULL); -#ifdef G_OS_UNIX - if (!res && glib_should_use_portal ()) - { - const char *parent_window = NULL; + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, g_app_info_launch_default_for_uri_async); - if (context && context->priv->envp) - parent_window = g_environ_getenv (context->priv->envp, "PARENT_WINDOW_ID"); + data = g_new (LaunchDefaultForUriData, 1); + data->uri = g_strdup (uri); + data->context = (context != NULL) ? g_object_ref (context) : NULL; + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_default_for_uri_data_free); - g_openuri_portal_open_uri_async (uri, parent_window, cancellable, callback, user_data); - return; - } -#endif + /* g_file_query_default_handler_async() calls + * g_app_info_get_default_for_uri_scheme() too, but we have to do it + * here anyway in case GFile can't parse @uri correctly. + */ + uri_scheme = g_uri_parse_scheme (uri); + if (uri_scheme && uri_scheme[0] != '\0') + /* FIXME: The following still uses blocking calls. */ + app_info = g_app_info_get_default_for_uri_scheme (uri_scheme); + g_free (uri_scheme); - task = g_task_new (context, cancellable, callback, user_data); - if (!res) - g_task_return_error (task, error); - else - g_task_return_boolean (task, TRUE); + if (!app_info) + { + GFile *file; - g_object_unref (task); + file = g_file_new_for_uri (uri); + g_file_query_default_handler_async (file, + G_PRIORITY_DEFAULT, + cancellable, + launch_default_for_uri_default_handler_cb, + g_steal_pointer (&task)); + g_object_unref (file); + } + else + launch_default_for_uri_launch_uris (g_steal_pointer (&task), g_steal_pointer (&app_info)); } /** @@ -831,11 +1037,9 @@ gboolean g_app_info_launch_default_for_uri_finish (GAsyncResult *result, GError **error) { -#ifdef G_OS_UNIX - return g_openuri_portal_open_uri_finish (result, error); -#else + g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); + return g_task_propagate_boolean (G_TASK (result), error); -#endif } /** diff --git a/gio/gappinfo.h b/gio/gappinfo.h index 4889be923..d26d048a5 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -78,7 +78,9 @@ typedef struct _GAppLaunchContextPrivate GAppLaunchContextPrivate; * @get_display_name: Gets the display name for the #GAppInfo. Since 2.24 * @set_as_last_used_for_type: Sets the application as the last used. See g_app_info_set_as_last_used_for_type(). * @get_supported_types: Retrieves the list of content types that @app_info claims to support. - * + * @launch_uris_async: Asynchronously launches an application with a list of URIs. (Since: 2.60) + * @launch_uris_finish: Finishes an operation started with @launch_uris_async. (Since: 2.60) + * Application Information interface, for operating system portability. */ typedef struct _GAppInfoIface GAppInfoIface; @@ -131,6 +133,15 @@ struct _GAppInfoIface const char *content_type, GError **error); const char ** (* get_supported_types) (GAppInfo *appinfo); + void (* launch_uris_async) (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* launch_uris_finish) (GAppInfo *appinfo, + GAsyncResult *result, + GError **error); }; GLIB_AVAILABLE_IN_ALL @@ -173,6 +184,18 @@ gboolean g_app_info_launch_uris (GAppInfo *appin GList *uris, GAppLaunchContext *context, GError **error); +GLIB_AVAILABLE_IN_2_60 +void g_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_60 +gboolean g_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error); + GLIB_AVAILABLE_IN_ALL gboolean g_app_info_should_show (GAppInfo *appinfo); diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c index 1f8ea8057..752ec23fc 100644 --- a/gio/gdbusauth.c +++ b/gio/gdbusauth.c @@ -1272,9 +1272,9 @@ _g_dbus_auth_run_server (GDBusAuth *auth, &line_length, cancellable, error); - debug_print ("SERVER: WaitingForBegin, read '%s'", line); if (line == NULL) goto out; + debug_print ("SERVER: WaitingForBegin, read '%s'", line); if (g_strcmp0 (line, "BEGIN") == 0) { /* YAY, done! */ diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c index b9f32e921..3a1a1f9e9 100644 --- a/gio/gdbusmessage.c +++ b/gio/gdbusmessage.c @@ -1984,7 +1984,7 @@ g_dbus_message_bytes_needed (guchar *blob, "Unable to determine message blob length - given blob is malformed"); } - if (ret > (2<<27)) + if (ret > (1<<27)) { g_set_error (error, G_IO_ERROR, @@ -2731,7 +2731,6 @@ g_dbus_message_to_blob (GDBusMessage *message, if (message->body != NULL) { gchar *tupled_signature_str; - tupled_signature_str = g_strdup_printf ("(%s)", signature_str); if (signature == NULL) { g_set_error (error, @@ -2739,10 +2738,10 @@ g_dbus_message_to_blob (GDBusMessage *message, G_IO_ERROR_INVALID_ARGUMENT, _("Message body has signature “%s” but there is no signature header"), signature_str); - g_free (tupled_signature_str); goto out; } - else if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->body)) != 0) + tupled_signature_str = g_strdup_printf ("(%s)", signature_str); + if (g_strcmp0 (tupled_signature_str, g_variant_get_type_string (message->body)) != 0) { g_set_error (error, G_IO_ERROR, diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 9ebfce4f0..7d7425ea9 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -2870,7 +2870,10 @@ static void launch_uris_with_dbus (GDesktopAppInfo *info, GDBusConnection *session_bus, GList *uris, - GAppLaunchContext *launch_context) + GAppLaunchContext *launch_context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GVariantBuilder builder; gchar *object_path; @@ -2889,14 +2892,11 @@ launch_uris_with_dbus (GDesktopAppInfo *info, g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context)); - /* This is non-blocking API. Similar to launching via fork()/exec() - * we don't wait around to see if the program crashed during startup. - * This is what startup-notification's job is... - */ object_path = object_path_from_appid (info->app_id); g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application", uris ? "Open" : "Activate", g_variant_builder_end (&builder), - NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); + NULL, G_DBUS_CALL_FLAGS_NONE, -1, + cancellable, callback, user_data); g_free (object_path); } @@ -2904,7 +2904,10 @@ static gboolean g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, GDBusConnection *session_bus, GList *uris, - GAppLaunchContext *launch_context) + GAppLaunchContext *launch_context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GList *ruris = uris; char *app_id = NULL; @@ -2921,7 +2924,8 @@ g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info, } #endif - launch_uris_with_dbus (info, session_bus, ruris, launch_context); + launch_uris_with_dbus (info, session_bus, ruris, launch_context, + cancellable, callback, user_data); if (ruris != uris) g_list_free_full (ruris, g_free); @@ -2952,7 +2956,12 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); if (session_bus && info->app_id) - g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context); + /* This is non-blocking API. Similar to launching via fork()/exec() + * we don't wait around to see if the program crashed during startup. + * This is what startup-notification's job is... + */ + g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context, + NULL, NULL, NULL); else success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context, spawn_flags, user_setup, user_setup_data, @@ -2986,6 +2995,137 @@ g_desktop_app_info_launch_uris (GAppInfo *appinfo, error); } +typedef struct +{ + GAppInfo *appinfo; + GList *uris; + GAppLaunchContext *context; +} LaunchUrisData; + +static void +launch_uris_data_free (LaunchUrisData *data) +{ + g_clear_object (&data->context); + g_list_free_full (data->uris, g_free); + g_free (data); +} + +static void +launch_uris_with_dbus_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GError *error = NULL; + + g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + if (error != NULL) + { + g_dbus_error_strip_remote_error (error); + g_task_return_error (task, g_steal_pointer (&error)); + } + else + g_task_return_boolean (task, TRUE); + + g_object_unref (task); +} + +static void +launch_uris_flush_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + + g_dbus_connection_flush_finish (G_DBUS_CONNECTION (object), result, NULL); + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +launch_uris_bus_get_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + GDesktopAppInfo *info = G_DESKTOP_APP_INFO (g_task_get_source_object (task)); + LaunchUrisData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + GDBusConnection *session_bus; + GError *error = NULL; + + session_bus = g_bus_get_finish (result, NULL); + + if (session_bus && info->app_id) + { + /* FIXME: The g_document_portal_add_documents() function, which is called + * from the g_desktop_app_info_launch_uris_with_dbus() function, still + * uses blocking calls. + */ + g_desktop_app_info_launch_uris_with_dbus (info, session_bus, + data->uris, data->context, + cancellable, + launch_uris_with_dbus_cb, + g_steal_pointer (&task)); + } + else + { + /* FIXME: The D-Bus message from the notify_desktop_launch() function + * can be still lost even if flush is called later. See: + * https://gitlab.freedesktop.org/dbus/dbus/issues/72 + */ + g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, + data->uris, data->context, + _SPAWN_FLAGS_DEFAULT, NULL, + NULL, NULL, NULL, -1, -1, -1, + &error); + if (error != NULL) + { + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + } + else + g_dbus_connection_flush (session_bus, + cancellable, + launch_uris_flush_cb, + g_steal_pointer (&task)); + } + + g_clear_object (&session_bus); +} + +static void +g_desktop_app_info_launch_uris_async (GAppInfo *appinfo, + GList *uris, + GAppLaunchContext *context, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + LaunchUrisData *data; + + task = g_task_new (appinfo, cancellable, callback, user_data); + g_task_set_source_tag (task, g_desktop_app_info_launch_uris_async); + + data = g_new0 (LaunchUrisData, 1); + data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); + data->context = (context != NULL) ? g_object_ref (context) : NULL; + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free); + + g_bus_get (G_BUS_TYPE_SESSION, cancellable, launch_uris_bus_get_cb, task); +} + +static gboolean +g_desktop_app_info_launch_uris_finish (GAppInfo *appinfo, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + static gboolean g_desktop_app_info_supports_uris (GAppInfo *appinfo) { @@ -3876,6 +4016,8 @@ g_desktop_app_info_iface_init (GAppInfoIface *iface) iface->supports_uris = g_desktop_app_info_supports_uris; iface->supports_files = g_desktop_app_info_supports_files; iface->launch_uris = g_desktop_app_info_launch_uris; + iface->launch_uris_async = g_desktop_app_info_launch_uris_async; + iface->launch_uris_finish = g_desktop_app_info_launch_uris_finish; iface->should_show = g_desktop_app_info_should_show; iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type; iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension; diff --git a/gio/gdocumentportal.c b/gio/gdocumentportal.c index 56378b1e6..154cc74df 100644 --- a/gio/gdocumentportal.c +++ b/gio/gdocumentportal.c @@ -31,9 +31,6 @@ #include "gunixfdlist.h" #endif -#ifndef O_PATH -#define O_PATH 0 -#endif #ifndef O_CLOEXEC #define O_CLOEXEC 0 #else @@ -92,76 +89,6 @@ init_document_portal (void) return (documents != NULL && documents_mountpoint != NULL); } -char * -g_document_portal_add_document (GFile *file, - GError **error) -{ - char *doc_path, *basename; - char *doc_id = NULL; - char *doc_uri = NULL; - char *path = NULL; - GUnixFDList *fd_list = NULL; - int fd, fd_in, errsv; - gboolean ret; - - if (!init_document_portal ()) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, - "Document portal is not available"); - goto out; - } - - path = g_file_get_path (file); - fd = g_open (path, O_PATH | O_CLOEXEC); - errsv = errno; - - if (fd == -1) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), - "Failed to open %s", path); - goto out; - } - -#ifndef HAVE_O_CLOEXEC - fcntl (fd, F_SETFD, FD_CLOEXEC); -#endif - - fd_list = g_unix_fd_list_new (); - fd_in = g_unix_fd_list_append (fd_list, fd, error); - g_close (fd, NULL); - - if (fd_in == -1) - goto out; - - ret = gxdp_documents_call_add_sync (documents, - g_variant_new_handle (fd_in), - TRUE, - TRUE, - fd_list, - &doc_id, - NULL, - NULL, - error); - - if (!ret) - goto out; - - basename = g_path_get_basename (path); - doc_path = g_build_filename (documents_mountpoint, doc_id, basename, NULL); - g_free (basename); - - doc_uri = g_filename_to_uri (doc_path, NULL, NULL); - g_free (doc_path); - - out: - if (fd_list) - g_object_unref (fd_list); - g_free (path); - g_free (doc_id); - - return doc_uri; -} - /* Flags accepted by org.freedesktop.portal.Documents.AddFull */ enum { XDP_ADD_FLAGS_REUSE_EXISTING = (1 << 0), @@ -210,7 +137,14 @@ g_document_portal_add_documents (GList *uris, { int fd; - fd = g_open (path, O_CLOEXEC | O_PATH); + fd = g_open (path, O_CLOEXEC | O_RDWR); + if (fd == -1 && (errno == EACCES || errno == EISDIR)) + { + /* If we don't have write access, fall back to read-only, + * and stop requesting the write permission */ + fd = g_open (path, O_CLOEXEC | O_RDONLY); + permissions[1] = NULL; + } if (fd >= 0) { #ifndef HAVE_O_CLOEXEC diff --git a/gio/gdocumentportal.h b/gio/gdocumentportal.h index adb1b974e..82d32640e 100644 --- a/gio/gdocumentportal.h +++ b/gio/gdocumentportal.h @@ -23,9 +23,6 @@ G_BEGIN_DECLS -char * g_document_portal_add_document (GFile *file, - GError **error); - GList * g_document_portal_add_documents (GList *uris, const char *app_id, GError **error); diff --git a/gio/gfile.c b/gio/gfile.c index a5709a4cc..1cc69166a 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -6851,6 +6851,8 @@ g_file_query_default_handler (GFile *file, if (appinfo != NULL) return appinfo; } + else + g_free (uri_scheme); info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, @@ -6883,6 +6885,129 @@ g_file_query_default_handler (GFile *file, return NULL; } +static void +query_default_handler_query_info_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GFile *file = G_FILE (object); + GTask *task = G_TASK (user_data); + GError *error = NULL; + GFileInfo *info; + const char *content_type; + GAppInfo *appinfo = NULL; + + info = g_file_query_info_finish (file, result, &error); + if (info == NULL) + { + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + return; + } + + content_type = g_file_info_get_content_type (info); + if (content_type) + { + char *path; + + /* Don't use is_native(), as we want to support fuse paths if available */ + path = g_file_get_path (file); + + /* FIXME: The following still uses blocking calls. */ + appinfo = g_app_info_get_default_for_type (content_type, + path == NULL); + g_free (path); + } + + g_object_unref (info); + + if (appinfo != NULL) + g_task_return_pointer (task, g_steal_pointer (&appinfo), g_object_unref); + else + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("No application is registered as handling this file")); + g_object_unref (task); +} + +/** + * g_file_query_default_handler_async: + * @file: a #GFile to open + * @cancellable: optional #GCancellable object, %NULL to ignore + * @callback: (nullable): a #GAsyncReadyCallback to call when the request is done + * @user_data: (nullable): data to pass to @callback + * + * Async version of g_file_query_default_handler(). + * + * Since: 2.60 + */ +void +g_file_query_default_handler_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + char *uri_scheme; + + task = g_task_new (file, cancellable, callback, user_data); + g_task_set_source_tag (task, g_file_query_default_handler_async); + + uri_scheme = g_file_get_uri_scheme (file); + if (uri_scheme && uri_scheme[0] != '\0') + { + GAppInfo *appinfo; + + /* FIXME: The following still uses blocking calls. */ + appinfo = g_app_info_get_default_for_uri_scheme (uri_scheme); + g_free (uri_scheme); + + if (appinfo != NULL) + { + g_task_return_pointer (task, g_steal_pointer (&appinfo), g_object_unref); + g_object_unref (task); + return; + } + } + else + g_free (uri_scheme); + + g_file_query_info_async (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + 0, + io_priority, + cancellable, + query_default_handler_query_info_cb, + g_steal_pointer (&task)); +} + +/** + * g_file_query_default_handler_finish: + * @file: a #GFile to open + * @result: a #GAsyncResult + * @error: (nullable): a #GError + * + * Finishes a g_file_query_default_handler_async() operation. + * + * Returns: (transfer full): a #GAppInfo if the handle was found, + * %NULL if there were errors. + * When you are done with it, release it with g_object_unref() + * + * Since: 2.60 + */ +GAppInfo * +g_file_query_default_handler_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (g_task_is_valid (result, file), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + #define GET_CONTENT_BLOCK_SIZE 8192 /** diff --git a/gio/gfile.h b/gio/gfile.h index 4aff644c3..6e25b0de0 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -1183,6 +1183,17 @@ GLIB_AVAILABLE_IN_ALL GAppInfo *g_file_query_default_handler (GFile *file, GCancellable *cancellable, GError **error); +GLIB_AVAILABLE_IN_2_60 +void g_file_query_default_handler_async (GFile *file, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GLIB_AVAILABLE_IN_2_60 +GAppInfo *g_file_query_default_handler_finish (GFile *file, + GAsyncResult *result, + GError **error); + GLIB_AVAILABLE_IN_ALL gboolean g_file_load_contents (GFile *file, GCancellable *cancellable, diff --git a/gio/gio-tool-open.c b/gio/gio-tool-open.c index 73863c7c5..ac6764a97 100644 --- a/gio/gio-tool-open.c +++ b/gio/gio-tool-open.c @@ -29,73 +29,32 @@ #include "gio-tool.h" +static int n_outstanding = 0; +static gboolean success = TRUE; static const GOptionEntry entries[] = { { NULL } }; -#if defined(G_OS_UNIX) && !defined(HAVE_COCOA) -static gboolean -get_bus_name_and_path_from_uri (const char *uri, - char **bus_name_out, - char **object_path_out) +static void +launch_default_for_uri_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { - GAppInfo *app_info = NULL; - char *bus_name = NULL; - char *object_path = NULL; - char *uri_scheme; - const char *filename; - char *basename = NULL; - char *p; - gboolean got_name = FALSE; - - uri_scheme = g_uri_parse_scheme (uri); - if (uri_scheme && uri_scheme[0] != '\0') - app_info = g_app_info_get_default_for_uri_scheme (uri_scheme); - g_free (uri_scheme); - - if (app_info == NULL) - { - GFile *file; + GError *error = NULL; + gchar *uri = user_data; - file = g_file_new_for_uri (uri); - app_info = g_file_query_default_handler (file, NULL, NULL); - g_object_unref (file); + if (!g_app_info_launch_default_for_uri_finish (res, &error)) + { + print_error ("%s: %s", uri, error->message); + g_clear_error (&error); + success = FALSE; } - if (app_info == NULL || !G_IS_DESKTOP_APP_INFO (app_info) || - !g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (app_info), "DBusActivatable")) - goto out; - - filename = g_desktop_app_info_get_filename (G_DESKTOP_APP_INFO (app_info)); - if (filename == NULL) - goto out; - - basename = g_path_get_basename (filename); - if (!g_str_has_suffix (basename, ".desktop")) - goto out; - - basename[strlen (basename) - strlen (".desktop")] = '\0'; - if (!g_dbus_is_name (basename)) - goto out; - - bus_name = g_strdup (basename); - object_path = g_strdup_printf ("/%s", bus_name); - for (p = object_path; *p != '\0'; p++) - if (*p == '.') - *p = '/'; + n_outstanding--; - *bus_name_out = g_steal_pointer (&bus_name); - *object_path_out = g_steal_pointer (&object_path); - got_name = TRUE; - -out: - g_clear_object (&app_info); - g_clear_pointer (&basename, g_free); - - return got_name; + g_free (uri); } -#endif int handle_open (int argc, char *argv[], gboolean do_help) @@ -104,8 +63,6 @@ handle_open (int argc, char *argv[], gboolean do_help) gchar *param; GError *error = NULL; int i; - gboolean success; - gboolean res; g_set_prgname ("gio open"); @@ -143,7 +100,6 @@ handle_open (int argc, char *argv[], gboolean do_help) g_option_context_free (context); - success = TRUE; for (i = 1; i < argc; i++) { char *uri = NULL; @@ -162,47 +118,23 @@ handle_open (int argc, char *argv[], gboolean do_help) uri = g_file_get_uri (file); g_object_unref (file); } + else + uri = g_strdup (argv[i]); g_free (uri_scheme); - res = g_app_info_launch_default_for_uri (uri ? uri : argv[i], NULL, &error); - if (!res) - { - print_error ("%s: %s", uri ? uri : argv[i], error->message); - g_clear_error (&error); - success = FALSE; - } + g_app_info_launch_default_for_uri_async (uri, + NULL, + NULL, + launch_default_for_uri_cb, + g_strdup (uri)); -#if defined(G_OS_UNIX) && !defined(HAVE_COCOA) - /* FIXME: This chunk of madness is a workaround for a dbus-daemon bug. - * See https://bugzilla.gnome.org/show_bug.cgi?id=780296 - */ - if (res) - { - char *bus_name = NULL; - char *object_path = NULL; - - if (get_bus_name_and_path_from_uri (uri ? uri : argv[i], &bus_name, &object_path)) - { - GDBusConnection *connection; - connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); - - if (connection) - g_dbus_connection_call_sync (connection, - bus_name, - object_path, - "org.freedesktop.DBus.Peer", - "Ping", - NULL, NULL, - G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); - g_clear_object (&connection); - g_free (bus_name); - g_free (object_path); - } - } -#endif + n_outstanding++; g_free (uri); } + while (n_outstanding > 0) + g_main_context_iteration (NULL, TRUE); + return success ? 0 : 2; } diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index d5796b706..5ea632305 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -225,6 +225,10 @@ get_from_keyfile (GKeyfileSettingsBackend *kfsb, if (str) { return_value = g_variant_parse (type, str, NULL, NULL, NULL); + + /* As a special case, support values of type %G_VARIANT_TYPE_STRING + * not being quoted, since users keep forgetting to do it and then + * getting confused. */ if (return_value == NULL && g_variant_type_equal (type, G_VARIANT_TYPE_STRING) && str[0] != '\"') diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 064755981..62f30b561 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -66,6 +66,8 @@ #include "glibintl.h" #ifdef G_OS_UNIX #include "glib-unix.h" +#include "gportalsupport.h" +#include "gtrashportal.h" #endif #include "glib-private.h" @@ -1950,6 +1952,9 @@ g_local_file_trash (GFile *file, GVfs *vfs; int errsv; + if (glib_should_use_portal ()) + return g_trash_portal_trash_file (file, error); + if (g_lstat (local->filename, &file_stat) != 0) { errsv = errno; diff --git a/gio/gopenuriportal.c b/gio/gopenuriportal.c index 38d60bf68..b798d7cd1 100644 --- a/gio/gopenuriportal.c +++ b/gio/gopenuriportal.c @@ -31,9 +31,6 @@ #include "gunixfdlist.h" #endif -#ifndef O_PATH -#define O_PATH 0 -#endif #ifndef O_CLOEXEC #define O_CLOEXEC 0 #else @@ -107,7 +104,7 @@ g_openuri_portal_open_uri (const char *uri, path = g_file_get_path (file); - fd = g_open (path, O_PATH | O_CLOEXEC); + fd = g_open (path, O_RDONLY | O_CLOEXEC); errsv = errno; if (fd == -1) { @@ -318,7 +315,7 @@ g_openuri_portal_open_uri_async (const char *uri, g_object_set_data (G_OBJECT (task), "open-file", GINT_TO_POINTER (TRUE)); path = g_file_get_path (file); - fd = g_open (path, O_PATH | O_CLOEXEC); + fd = g_open (path, O_RDONLY | O_CLOEXEC); errsv = errno; if (fd == -1) { diff --git a/gio/gsettings.c b/gio/gsettings.c index 2934f2b4b..dd8f94485 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -1137,6 +1137,8 @@ g_settings_new_full (GSettingsSchema *schema, } /* Internal read/write utilities {{{1 */ + +/* @value will be sunk */ static gboolean g_settings_write_to_backend (GSettings *settings, GSettingsSchemaKey *key, @@ -1417,7 +1419,7 @@ g_settings_set_enum (GSettings *settings, return FALSE; } - success = g_settings_write_to_backend (settings, &skey, variant); + success = g_settings_write_to_backend (settings, &skey, g_steal_pointer (&variant)); g_settings_schema_key_clear (&skey); return success; @@ -1528,7 +1530,7 @@ g_settings_set_flags (GSettings *settings, return FALSE; } - success = g_settings_write_to_backend (settings, &skey, variant); + success = g_settings_write_to_backend (settings, &skey, g_steal_pointer (&variant)); g_settings_schema_key_clear (&skey); return success; @@ -1672,7 +1674,7 @@ g_settings_set (GSettings *settings, value = g_variant_new_va (format, NULL, &ap); va_end (ap); - return g_settings_set_value (settings, key, value); + return g_settings_set_value (settings, key, g_steal_pointer (&value)); } /** diff --git a/gio/gsettingsbackend.c b/gio/gsettingsbackend.c index edc4ff4d3..18026ae56 100644 --- a/gio/gsettingsbackend.c +++ b/gio/gsettingsbackend.c @@ -777,6 +777,8 @@ g_settings_backend_read_user_value (GSettingsBackend *backend, * to indicate that the affected keys have suddenly "changed back" to their * old values. * + * If @value has a floating reference, it will be sunk. + * * Returns: %TRUE if the write succeeded, %FALSE if the key was not writable */ gboolean @@ -1050,5 +1052,7 @@ g_settings_backend_sync_default (void) if (class->sync) class->sync (backend); + + g_object_unref (backend); } } diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index 38c9d78b9..60b3fe0b3 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -1472,6 +1472,7 @@ g_settings_schema_key_to_enum (GSettingsSchemaKey *key, return result; } +/* Returns a new floating #GVariant. */ GVariant * g_settings_schema_key_from_enum (GSettingsSchemaKey *key, gint value) @@ -1511,6 +1512,7 @@ g_settings_schema_key_to_flags (GSettingsSchemaKey *key, return result; } +/* Returns a new floating #GVariant. */ GVariant * g_settings_schema_key_from_flags (GSettingsSchemaKey *key, guint value) diff --git a/gio/gsocketclient.c b/gio/gsocketclient.c index 5c6513c3d..29a5e5598 100644 --- a/gio/gsocketclient.c +++ b/gio/gsocketclient.c @@ -1327,7 +1327,7 @@ g_socket_client_connect_to_uri (GSocketClient *client, typedef struct { - GTask *task; + GTask *task; /* unowned */ GSocketClient *client; GSocketConnectable *connectable; @@ -1345,6 +1345,7 @@ static void connection_attempt_unref (gpointer attempt); static void g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data) { + data->task = NULL; g_clear_object (&data->connectable); g_clear_object (&data->enumerator); g_clear_object (&data->proxy_addr); @@ -1444,13 +1445,19 @@ set_last_error (GSocketClientAsyncConnectData *data, } static void -enumerator_next_async (GSocketClientAsyncConnectData *data) +enumerator_next_async (GSocketClientAsyncConnectData *data, + gboolean add_task_ref) { /* We need to cleanup the state */ g_clear_object (&data->socket); g_clear_object (&data->proxy_addr); g_clear_object (&data->connection); + /* Each enumeration takes a ref. This arg just avoids repeated unrefs when + an enumeration starts another enumeration */ + if (add_task_ref) + g_object_ref (data->task); + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL); g_socket_address_enumerator_next_async (data->enumerator, g_task_get_cancellable (data->task), @@ -1478,7 +1485,7 @@ g_socket_client_tls_handshake_callback (GObject *object, else { g_object_unref (object); - enumerator_next_async (data); + enumerator_next_async (data, FALSE); } } @@ -1509,7 +1516,7 @@ g_socket_client_tls_handshake (GSocketClientAsyncConnectData *data) } else { - enumerator_next_async (data); + enumerator_next_async (data, FALSE); } } @@ -1530,13 +1537,24 @@ g_socket_client_proxy_connect_callback (GObject *object, } else { - enumerator_next_async (data); + enumerator_next_async (data, FALSE); return; } g_socket_client_tls_handshake (data); } +static gboolean +task_completed_or_cancelled (GTask *task) +{ + if (g_task_get_completed (task)) + return TRUE; + else if (g_task_return_error_if_cancelled (task)) + return TRUE; + else + return FALSE; +} + static void g_socket_client_connected_callback (GObject *source, GAsyncResult *result, @@ -1549,8 +1567,7 @@ g_socket_client_connected_callback (GObject *source, GProxy *proxy; const gchar *protocol; - /* data is NULL once the task is completed */ - if (data && g_task_return_error_if_cancelled (data->task)) + if (g_cancellable_is_cancelled (attempt->cancellable) || task_completed_or_cancelled (data->task)) { g_object_unref (data->task); connection_attempt_unref (attempt); @@ -1570,17 +1587,15 @@ g_socket_client_connected_callback (GObject *source, { clarify_connect_error (error, data->connectable, attempt->address); set_last_error (data, error); + connection_attempt_remove (attempt); + enumerator_next_async (data, FALSE); } else - g_clear_error (&error); - - if (data) { - connection_attempt_remove (attempt); - enumerator_next_async (data); + g_clear_error (&error); + g_object_unref (data->task); + connection_attempt_unref (attempt); } - else - connection_attempt_unref (attempt); return; } @@ -1592,7 +1607,6 @@ g_socket_client_connected_callback (GObject *source, { ConnectionAttempt *attempt_entry = l->data; g_cancellable_cancel (attempt_entry->cancellable); - attempt_entry->data = NULL; connection_attempt_unref (attempt_entry); } g_slist_free (data->connection_attempts); @@ -1625,7 +1639,7 @@ g_socket_client_connected_callback (GObject *source, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Proxying over a non-TCP connection is not supported.")); - enumerator_next_async (data); + enumerator_next_async (data, FALSE); } else if (g_hash_table_contains (data->client->priv->app_proxies, protocol)) { @@ -1652,7 +1666,7 @@ g_socket_client_connected_callback (GObject *source, _("Proxy protocol “%s” is not supported."), protocol); - enumerator_next_async (data); + enumerator_next_async (data, FALSE); } } @@ -1661,7 +1675,7 @@ on_connection_attempt_timeout (gpointer data) { ConnectionAttempt *attempt = data; - enumerator_next_async (attempt->data); + enumerator_next_async (attempt->data, TRUE); g_clear_pointer (&attempt->timeout_source, g_source_unref); return G_SOURCE_REMOVE; @@ -1687,7 +1701,7 @@ g_socket_client_enumerator_callback (GObject *object, ConnectionAttempt *attempt; GError *error = NULL; - if (g_task_return_error_if_cancelled (data->task)) + if (task_completed_or_cancelled (data->task)) { g_object_unref (data->task); return; @@ -1698,7 +1712,10 @@ g_socket_client_enumerator_callback (GObject *object, if (address == NULL) { if (data->connection_attempts) - return; + { + g_object_unref (data->task); + return; + } g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_COMPLETE, data->connectable, NULL); if (!error) @@ -1732,7 +1749,7 @@ g_socket_client_enumerator_callback (GObject *object, if (socket == NULL) { g_object_unref (address); - enumerator_next_async (data); + enumerator_next_async (data, FALSE); return; } @@ -1804,11 +1821,24 @@ g_socket_client_connect_async (GSocketClient *client, else data->enumerator = g_socket_connectable_enumerate (connectable); + /* The flow and ownership here isn't quite obvious: + - The task starts an async attempt to connect. + - Each attempt holds a single ref on task. + - Each attempt may create new attempts by timing out (not a failure) so + there are multiple attempts happening in parallel. + - Upon failure an attempt will start a new attempt that steals its ref + until there are no more attempts left and it drops its ref. + - Upon success it will cancel all other attempts and continue on + to the rest of the connection (tls, proxies, etc) which do not + happen in parallel and at the very end drop its ref. + - Upon cancellation an attempt drops its ref. + */ + data->task = g_task_new (client, cancellable, callback, user_data); g_task_set_source_tag (data->task, g_socket_client_connect_async); g_task_set_task_data (data->task, data, (GDestroyNotify)g_socket_client_async_connect_data_free); - enumerator_next_async (data); + enumerator_next_async (data, FALSE); } /** diff --git a/gio/gtrashportal.c b/gio/gtrashportal.c new file mode 100644 index 000000000..a1e82102b --- /dev/null +++ b/gio/gtrashportal.c @@ -0,0 +1,123 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2018, Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#include "gtrashportal.h" +#include "xdp-dbus.h" +#include "gstdio.h" + +#ifdef G_OS_UNIX +#include "gunixfdlist.h" +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#else +#define HAVE_O_CLOEXEC 1 +#endif + +static GXdpTrash * +ensure_trash_portal (void) +{ + static GXdpTrash *trash = NULL; + + if (g_once_init_enter (&trash)) + { + GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + GXdpTrash *proxy = NULL; + + if (connection != NULL) + { + proxy = gxdp_trash_proxy_new_sync (connection, 0, + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + NULL, NULL); + g_object_unref (connection); + } + + g_once_init_leave (&trash, proxy); + } + + return trash; +} + +gboolean +g_trash_portal_trash_file (GFile *file, + GError **error) +{ + char *path = NULL; + GUnixFDList *fd_list = NULL; + int fd, fd_in, errsv; + gboolean ret = FALSE; + GXdpTrash *proxy; + + proxy = ensure_trash_portal (); + if (proxy == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "Trash portal is not available"); + goto out; + } + + path = g_file_get_path (file); + + fd = g_open (path, O_RDWR | O_CLOEXEC); + if (fd == -1 && (errno == EACCES || errno == EISDIR)) + /* If we don't have write access, fall back to read-only */ + fd = g_open (path, O_CLOEXEC | O_RDONLY); + + errsv = errno; + + if (fd == -1) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + "Failed to open %s", path); + goto out; + } + +#ifndef HAVE_O_CLOEXEC + fcntl (fd, F_SETFD, FD_CLOEXEC); +#endif + + fd_list = g_unix_fd_list_new (); + fd_in = g_unix_fd_list_append (fd_list, fd, error); + g_close (fd, NULL); + + if (fd_in == -1) + goto out; + + ret = gxdp_trash_call_trash_file_sync (proxy, + g_variant_new_handle (fd_in), + fd_list, + NULL, + NULL, + NULL, + error); + + out: + g_clear_object (&fd_list); + g_free (path); + + return ret; +} diff --git a/gio/gtrashportal.h b/gio/gtrashportal.h new file mode 100644 index 000000000..a53de8a6f --- /dev/null +++ b/gio/gtrashportal.h @@ -0,0 +1,31 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright 2018 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __G_TRASH_PORTAL_H__ + +#include <glib.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +gboolean g_trash_portal_trash_file (GFile *file, + GError **error); + +G_END_DECLS + +#endif diff --git a/gio/meson.build b/gio/meson.build index 47aa0b48d..d7f4f3f31 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -238,7 +238,8 @@ subdir('gdbus-2.0/codegen') xdp_dbus_generated = custom_target('xdp-dbus', input : ['org.freedesktop.portal.Documents.xml', 'org.freedesktop.portal.OpenURI.xml', - 'org.freedesktop.portal.ProxyResolver.xml'], + 'org.freedesktop.portal.ProxyResolver.xml', + 'org.freedesktop.portal.Trash.xml'], output : ['xdp-dbus.h', 'xdp-dbus.c'], depend_files : gdbus_codegen_built_files, command : [python, gdbus_codegen, @@ -254,6 +255,8 @@ xdp_dbus_generated = custom_target('xdp-dbus', 'org.gtk.GDBus.C.UnixFD', 'true', '--annotate', 'org.freedesktop.portal.OpenURI.OpenFile()', 'org.gtk.GDBus.C.UnixFD', 'true', + '--annotate', 'org.freedesktop.portal.Trash.TrashFile()', + 'org.gtk.GDBus.C.UnixFD', 'true', '@INPUT@']) # Generate gdbus-generated.{c,h} @@ -392,6 +395,7 @@ if host_system != 'windows' 'gopenuriportal.c', 'gnetworkmonitorportal.c', 'gproxyresolverportal.c', + 'gtrashportal.c', 'gportalsupport.c', 'gportalnotificationbackend.c'), xdp_dbus_generated diff --git a/gio/org.freedesktop.portal.Trash.xml b/gio/org.freedesktop.portal.Trash.xml new file mode 100644 index 000000000..6142f3d2c --- /dev/null +++ b/gio/org.freedesktop.portal.Trash.xml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<!-- + Copyright (C) 2016 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. + + Author: Matthias Clasen <mclasen@redhat.com> +--> + +<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <!-- + org.freedesktop.portal.Trash: + @short_description: Portal for trashing files + + This simple interface lets sandboxed applications send files to + the trashcan. + + This documentation describes version 1 of this interface. + --> + <interface name="org.freedesktop.portal.Trash"> + <!-- + TrashFile: + @fd: file descriptor for the file to trash + @result: the result. 0 if trashing failed, 1 if trashing succeeded, other values may be returned in the future + + Sends a file to the trashcan. Applications are allowed to + trash a file if they can open it in r/w mode. + --> + <method name="TrashFile"> + <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/> + <arg type="h" name="fd" direction="in"/> + <arg type="u" name="result" direction="out"/> + </method> + + <property name="version" type="u" access="read"/> + </interface> +</node> diff --git a/gio/tests/gdbus-message.c b/gio/tests/gdbus-message.c index 278ccc474..5cb141d94 100644 --- a/gio/tests/gdbus-message.c +++ b/gio/tests/gdbus-message.c @@ -141,6 +141,74 @@ message_copy (void) /* ---------------------------------------------------------------------------------------------------- */ +/* Test g_dbus_message_bytes_needed() returns correct results for a variety of + * arbitrary binary inputs.*/ +static void +message_bytes_needed (void) +{ + const struct + { + const guint8 blob[16]; + gssize expected_bytes_needed; + } + vectors[] = + { + /* Little endian with header rounding */ + { { 'l', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 50, 0, 0, 0, /* body length */ + 1, 0, 0, 0, /* message serial */ + 7, 0, 0, 0 /* header length */}, 74 }, + /* Little endian without header rounding */ + { { 'l', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 50, 0, 0, 0, /* body length */ + 1, 0, 0, 0, /* message serial */ + 8, 0, 0, 0 /* header length */}, 74 }, + /* Big endian with header rounding */ + { { 'B', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 50, /* body length */ + 0, 0, 0, 1, /* message serial */ + 0, 0, 0, 7 /* header length */}, 74 }, + /* Big endian without header rounding */ + { { 'B', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 50, /* body length */ + 0, 0, 0, 1, /* message serial */ + 0, 0, 0, 8 /* header length */}, 74 }, + /* Invalid endianness */ + { { '!', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 50, /* body length */ + 0, 0, 0, 1, /* message serial */ + 0, 0, 0, 8 /* header length */}, -1 }, + /* Oversized */ + { { 'l', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 0x08, /* body length (128MiB) */ + 1, 0, 0, 0, /* message serial */ + 7, 0, 0, 0 /* header length */}, -1 }, + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (vectors); i++) + { + gssize bytes_needed; + GError *local_error = NULL; + + g_test_message ("Vector: %" G_GSIZE_FORMAT, i); + + bytes_needed = g_dbus_message_bytes_needed ((guchar *) vectors[i].blob, + G_N_ELEMENTS (vectors[i].blob), + &local_error); + + if (vectors[i].expected_bytes_needed < 0) + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + else + g_assert_no_error (local_error); + g_assert_cmpint (bytes_needed, ==, vectors[i].expected_bytes_needed); + + g_clear_error (&local_error); + } +} + +/* ---------------------------------------------------------------------------------------------------- */ + int main (int argc, char *argv[]) @@ -151,6 +219,8 @@ main (int argc, g_test_add_func ("/gdbus/message/lock", message_lock); g_test_add_func ("/gdbus/message/copy", message_copy); - return g_test_run(); + g_test_add_func ("/gdbus/message/bytes-needed", message_bytes_needed); + + return g_test_run (); } diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index fb19e5156..f254c3195 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -2442,6 +2442,7 @@ test_schema_source (void) g_settings_schema_unref (schema); g_settings_schema_source_unref (source); + g_object_unref (backend); } static void diff --git a/gio/tests/gsocketclient-slow.c b/gio/tests/gsocketclient-slow.c index bc7d02772..a78ea71d0 100644 --- a/gio/tests/gsocketclient-slow.c +++ b/gio/tests/gsocketclient-slow.c @@ -63,12 +63,65 @@ test_happy_eyeballs (void) g_object_unref (client); } +static void +on_connected_cancelled (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GSocketConnection *conn; + GError *error = NULL; + + conn = g_socket_client_connect_to_uri_finish (G_SOCKET_CLIENT (source_object), result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert_null (conn); + + g_error_free (error); + g_main_loop_quit (user_data); +} + +static int +on_timer (GCancellable *cancel) +{ + g_cancellable_cancel (cancel); + return G_SOURCE_REMOVE; +} + +static void +test_happy_eyeballs_cancel (void) +{ + GSocketClient *client; + GSocketService *service; + GError *error = NULL; + guint16 port; + GMainLoop *loop; + GCancellable *cancel; + + loop = g_main_loop_new (NULL, FALSE); + + service = g_socket_service_new (); + port = g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL, &error); + g_assert_no_error (error); + g_socket_service_start (service); + + client = g_socket_client_new (); + cancel = g_cancellable_new (); + g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop); + g_timeout_add (1, (GSourceFunc) on_timer, cancel); + g_main_loop_run (loop); + + g_main_loop_unref (loop); + g_object_unref (service); + g_object_unref (client); + g_object_unref (cancel); +} + int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); - g_test_add_func ("/socket-client/happy-eyeballs", test_happy_eyeballs); + g_test_add_func ("/socket-client/happy-eyeballs/slow", test_happy_eyeballs); + g_test_add_func ("/socket-client/happy-eyeballs/cancellation", test_happy_eyeballs_cancel); return g_test_run (); }
\ No newline at end of file diff --git a/gio/tests/trash.c b/gio/tests/trash.c index 1055585e5..4bf8fd0ef 100644 --- a/gio/tests/trash.c +++ b/gio/tests/trash.c @@ -107,14 +107,54 @@ test_trash_symlinks (void) g_test_bug ("1522"); - /* The test assumes that ~/.local always exists. */ target = g_build_filename (g_get_home_dir (), ".local", NULL); + + if (!g_file_test (target, G_FILE_TEST_IS_DIR)) + { + gchar *message; + + message = g_strdup_printf ("Directory '%s' does not exist", target); + g_test_skip (message); + g_free (message); + g_free (target); + return; + } + target_mount = g_unix_mount_for (target, NULL); + + if (target_mount == NULL) + { + gchar *message; + + message = g_strdup_printf ("Unable to determine mount point for %s", + target); + g_test_skip (message); + g_free (message); + g_free (target); + return; + } + g_assert_nonnull (target_mount); g_test_message ("Target: %s (mount: %s)", target, g_unix_mount_get_mount_path (target_mount)); tmp = g_dir_make_tmp ("test-trashXXXXXX", &error); + g_assert_no_error (error); + g_assert_nonnull (tmp); tmp_mount = g_unix_mount_for (tmp, NULL); + + if (tmp_mount == NULL) + { + gchar *message; + + message = g_strdup_printf ("Unable to determine mount point for %s", tmp); + g_test_skip (message); + g_free (message); + g_unix_mount_free (target_mount); + g_free (target); + g_free (tmp); + return; + } + g_assert_nonnull (tmp_mount); g_test_message ("Tmp: %s (mount: %s)", tmp, g_unix_mount_get_mount_path (tmp_mount)); |