From 1123be811884942757b1c82f918602d3a8683b0d Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 07:58:35 +0100 Subject: [PATCH 1/9] gs-plugin: Add virtual functions for url-to-app operation --- lib/gs-plugin-types.h | 14 ++++++++++++++ lib/gs-plugin.h | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h index 5223d2abf..a4d98d0d3 100644 --- a/lib/gs-plugin-types.h +++ b/lib/gs-plugin-types.h @@ -374,6 +374,20 @@ typedef enum { GS_PLUGIN_FILE_TO_APP_FLAGS_INTERACTIVE = 1 << 0, } GsPluginFileToAppFlags; +/** + * GsPluginUrlToAppFlags: + * @GS_PLUGIN_URL_TO_APP_FLAGS_NONE: No flags set. + * @GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE: User initiated the job. + * + * Flags for a url-to-app operation. + * + * Since: 47 + */ +typedef enum { + GS_PLUGIN_URL_TO_APP_FLAGS_NONE = 0, + GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE = 1 << 0, +} GsPluginUrlToAppFlags; + /** * GsPluginProgressCallback: * @plugin: the #GsPlugin reporting its progress diff --git a/lib/gs-plugin.h b/lib/gs-plugin.h index 5f39a5820..eeec3a305 100644 --- a/lib/gs-plugin.h +++ b/lib/gs-plugin.h @@ -121,6 +121,10 @@ G_DECLARE_DERIVABLE_TYPE (GsPlugin, gs_plugin, GS, PLUGIN, GObject) * @file_to_app_finish: (nullable): Finish method for * @file_to_app_async. Must be implemented if * @file_to_app_async is implemented. (Since: 47) + * @url_to_app_async: (nullable): Converts a URL to a #GsApp. (Since: 47) + * @url_to_app_finish: (nullable): Finish method for + * @url_to_app_async. Must be implemented if + * @url_to_app_async is implemented. (Since: 47) * * The class structure for a #GsPlugin. Virtual methods here should be * implemented by plugin implementations derived from #GsPlugin to provide their @@ -344,6 +348,16 @@ struct _GsPluginClass GAsyncResult *result, GError **error); + void (*url_to_app_async) (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GsAppList * (*url_to_app_finish) (GsPlugin *plugin, + GAsyncResult *result, + GError **error); + gpointer padding[23]; }; -- GitLab From 01dd8095ff3464c8cedc47c6c1e198c899c51a66 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 08:19:25 +0100 Subject: [PATCH 2/9] lib: Add GsPluginJobUrlToApp --- lib/gnome-software.h | 1 + lib/gs-plugin-job-url-to-app.c | 512 +++++++++++++++++++++++++++++++++ lib/gs-plugin-job-url-to-app.h | 28 ++ lib/gs-plugin-loader.c | 4 + lib/meson.build | 2 + 5 files changed, 547 insertions(+) create mode 100644 lib/gs-plugin-job-url-to-app.c create mode 100644 lib/gs-plugin-job-url-to-app.h diff --git a/lib/gnome-software.h b/lib/gnome-software.h index 64323c10b..6165fd076 100644 --- a/lib/gnome-software.h +++ b/lib/gnome-software.h @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/lib/gs-plugin-job-url-to-app.c b/lib/gs-plugin-job-url-to-app.c new file mode 100644 index 000000000..b9c3fcd14 --- /dev/null +++ b/lib/gs-plugin-job-url-to-app.c @@ -0,0 +1,512 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2024 Red Hat + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/** + * SECTION:gs-plugin-job-url-to-app + * @short_description: A plugin job on an app + * + * #GsPluginJobUrlToApp is a #GsPluginJob representing an operation to + * convert a URL into a #GsApp. + * + * This class is a wrapper around #GsPluginClass.url_to_app_async + * calling it for all loaded plugins, with #GsPluginJobRefine used to refine the + * results. + * + * Retrieve the resulting #GsAppList using + * gs_plugin_job_url_to_app_get_result_list(). + * + * Since: 47 + */ + +#include "config.h" + +#include +#include + +#include "gs-app.h" +#include "gs-app-list-private.h" +#include "gs-enums.h" +#include "gs-plugin-job.h" +#include "gs-plugin-job-file-to-app.h" +#include "gs-plugin-job-private.h" +#include "gs-plugin-job-refine.h" +#include "gs-plugin-job-url-to-app.h" +#include "gs-plugin-types.h" + +struct _GsPluginJobUrlToApp +{ + GsPluginJob parent; + + /* Input arguments. */ + gchar *url; /* (owned) (not nullable) */ + GsPluginUrlToAppFlags flags; + + /* In-progress data. */ + GError *saved_error; /* (owned) (nullable) */ + guint n_pending_ops; + gboolean did_refine; + gboolean did_file_to_app; + GsAppList *in_progress_list; /* (owned) (nullable) */ + + /* Results. */ + GsAppList *result_list; /* (owned) (nullable) */ +}; + +G_DEFINE_TYPE (GsPluginJobUrlToApp, gs_plugin_job_url_to_app, GS_TYPE_PLUGIN_JOB) + +typedef enum { + PROP_FLAGS = 1, + PROP_URL, +} GsPluginJobUrlToAppProperty; + +static GParamSpec *props[PROP_URL + 1] = { NULL, }; + +static void +gs_plugin_job_url_to_app_dispose (GObject *object) +{ + GsPluginJobUrlToApp *self = GS_PLUGIN_JOB_URL_TO_APP (object); + + g_assert (self->saved_error == NULL); + g_assert (self->n_pending_ops == 0); + + g_clear_pointer (&self->url, g_free); + g_clear_object (&self->result_list); + g_clear_object (&self->in_progress_list); + + G_OBJECT_CLASS (gs_plugin_job_url_to_app_parent_class)->dispose (object); +} + +static void +gs_plugin_job_url_to_app_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsPluginJobUrlToApp *self = GS_PLUGIN_JOB_URL_TO_APP (object); + + switch ((GsPluginJobUrlToAppProperty) prop_id) { + case PROP_FLAGS: + g_value_set_flags (value, self->flags); + break; + case PROP_URL: + g_value_set_string (value, self->url); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_plugin_job_url_to_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsPluginJobUrlToApp *self = GS_PLUGIN_JOB_URL_TO_APP (object); + + switch ((GsPluginJobUrlToAppProperty) prop_id) { + case PROP_FLAGS: + /* Construct only. */ + g_assert (self->flags == 0); + self->flags = g_value_get_flags (value); + g_object_notify_by_pspec (object, props[prop_id]); + break; + case PROP_URL: + /* Construct only. */ + g_assert (self->url == NULL); + self->url = g_value_dup_string (value); + g_assert (self->url != NULL); + g_object_notify_by_pspec (object, props[prop_id]); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void plugin_app_func_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void finish_op (GTask *task, + GsAppList *list, + GError *error); +static void file_to_app_job_finished_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void finish_file_to_app_op (GTask *task, + GsAppList *list, + GError *error); +static void refine_job_finished_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void finish_refine_op (GTask *task, + GsAppList *list, + GError *error); +static gboolean gs_plugin_job_url_to_app_is_valid_filter (GsApp *app, + gpointer user_data); + +static void +gs_plugin_job_url_to_app_run_async (GsPluginJob *job, + GsPluginLoader *plugin_loader, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsPluginJobUrlToApp *self = GS_PLUGIN_JOB_URL_TO_APP (job); + g_autoptr(GTask) task = NULL; + GPtrArray *plugins; /* (element-type GsPlugin) */ + gboolean anything_ran = FALSE; + g_autoptr(GError) local_error = NULL; + + task = g_task_new (job, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_job_url_to_app_run_async); + g_task_set_task_data (task, g_object_ref (plugin_loader), (GDestroyNotify) g_object_unref); + + /* run each plugin, keeping a counter of pending operations which is + * initialised to 1 until all the operations are started */ + self->n_pending_ops = 1; + plugins = gs_plugin_loader_get_plugins (plugin_loader); + + for (guint i = 0; i < plugins->len; i++) { + GsPlugin *plugin = g_ptr_array_index (plugins, i); + GsPluginClass *plugin_class = GS_PLUGIN_GET_CLASS (plugin); + + if (!gs_plugin_get_enabled (plugin)) + continue; + if (plugin_class->url_to_app_async == NULL) + continue; + + /* at least one plugin supports this vfunc */ + anything_ran = TRUE; + + /* Handle cancellation */ + if (g_cancellable_set_error_if_cancelled (cancellable, &local_error)) + break; + + /* run the plugin */ + self->n_pending_ops++; + plugin_class->url_to_app_async (plugin, self->url, self->flags, cancellable, plugin_app_func_cb, g_object_ref (task)); + } + + if (!anything_ran) + g_debug ("no plugin could handle url-to-app operation"); + + finish_op (task, NULL, g_steal_pointer (&local_error)); +} + +static void +plugin_app_func_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GsPlugin *plugin = GS_PLUGIN (source_object); + GsPluginClass *plugin_class = GS_PLUGIN_GET_CLASS (plugin); + g_autoptr(GTask) task = G_TASK (g_steal_pointer (&user_data)); + g_autoptr(GsAppList) list = NULL; + g_autoptr(GError) local_error = NULL; + + list = plugin_class->url_to_app_finish (plugin, result, &local_error); + gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED); + + g_assert (list != NULL || local_error != NULL); + + finish_op (task, list, g_steal_pointer (&local_error)); +} + +/* @error is (transfer full) if non-%NULL */ +static void +finish_op (GTask *task, + GsAppList *list, + GError *error) +{ + GsPluginJobUrlToApp *self = g_task_get_source_object (task); + GsPluginLoader *plugin_loader = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + g_autoptr(GError) error_owned = g_steal_pointer (&error); + g_autofree gchar *job_debug = NULL; + + if (error_owned != NULL && self->saved_error == NULL) + self->saved_error = g_steal_pointer (&error_owned); + else if (error_owned != NULL) + g_debug ("Additional error while url-to-app: %s", error_owned->message); + + g_assert (self->n_pending_ops > 0); + self->n_pending_ops--; + + if (list != NULL) { + if (self->in_progress_list == NULL) + self->in_progress_list = gs_app_list_new (); + gs_app_list_add_list (self->in_progress_list, list); + } + + if (self->n_pending_ops > 0) + return; + + /* Once all the url-to-app operations are complete, try file-to-app if + * they produced no results and the URI uses the `file` scheme. */ + if ((self->in_progress_list == NULL || gs_app_list_length (self->in_progress_list) == 0) && + g_ascii_strncasecmp (self->url, "file:", strlen ("file:")) == 0) { + g_autoptr(GFile) file = g_file_new_for_uri (self->url); + g_autoptr(GsPluginJob) file_to_app_job = NULL; + + file_to_app_job = gs_plugin_job_file_to_app_new (file, + (self->flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0 ? + GS_PLUGIN_FILE_TO_APP_FLAGS_INTERACTIVE : + GS_PLUGIN_FILE_TO_APP_FLAGS_NONE); + gs_plugin_loader_job_process_async (plugin_loader, file_to_app_job, cancellable, + file_to_app_job_finished_cb, g_object_ref (task)); + return; + } + + /* Fall through without calling file-to-app. */ + finish_file_to_app_op (task, self->in_progress_list, NULL); +} + +static void +file_to_app_job_finished_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (g_steal_pointer (&user_data)); + g_autoptr(GsAppList) list = NULL; + g_autoptr(GError) local_error = NULL; + + list = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (source_object), result, &local_error); + g_prefix_error_literal (&local_error, "Failed to file-to-app from file: URL:"); + + finish_file_to_app_op (task, list, g_steal_pointer (&local_error)); +} + +/* @error is (transfer full) if non-%NULL */ +static void +finish_file_to_app_op (GTask *task, + GsAppList *list, + GError *error) +{ + GsPluginJobUrlToApp *self = g_task_get_source_object (task); + GsPluginLoader *plugin_loader = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + g_autoptr(GError) error_owned = g_steal_pointer (&error); + + if (error_owned != NULL && self->saved_error == NULL) + self->saved_error = g_steal_pointer (&error_owned); + else if (error_owned != NULL) + g_debug ("Additional error while converting URL to app: %s", error_owned->message); + + g_set_object (&self->in_progress_list, list); + + /* Now refine the results. */ + if (self->in_progress_list != NULL) { + GsPluginRefineFlags refine_flags = gs_plugin_job_get_refine_flags (GS_PLUGIN_JOB (self)); + + if (refine_flags != GS_PLUGIN_REFINE_FLAGS_NONE) { + g_autoptr(GsPluginJob) refine_job = NULL; + + /* to not have filtered out repositories */ + refine_flags |= GS_PLUGIN_REFINE_FLAGS_DISABLE_FILTERING; + + refine_job = gs_plugin_job_refine_new (self->in_progress_list, refine_flags); + gs_plugin_loader_job_process_async (plugin_loader, refine_job, cancellable, + refine_job_finished_cb, g_object_ref (task)); + return; + } + } + + /* Fall through without refining. */ + finish_refine_op (task, self->in_progress_list, NULL); +} + +static void +refine_job_finished_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (g_steal_pointer (&user_data)); + g_autoptr(GsAppList) list = NULL; + g_autoptr(GError) local_error = NULL; + + list = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (source_object), result, &local_error); + g_prefix_error_literal (&local_error, "Failed to refine url-to-app apps:"); + + finish_refine_op (task, list, g_steal_pointer (&local_error)); +} + +/* @error is (transfer full) if non-%NULL */ +static void +finish_refine_op (GTask *task, + GsAppList *list, + GError *error) +{ + GsPluginJobUrlToApp *self = g_task_get_source_object (task); + g_autoptr(GError) error_owned = g_steal_pointer (&error); + g_autofree gchar *job_debug = NULL; + + if (error_owned != NULL && self->saved_error == NULL) + self->saved_error = g_steal_pointer (&error_owned); + else if (error_owned != NULL) + g_debug ("Additional error while converting URL to app: %s", error_owned->message); + + g_clear_object (&self->result_list); + self->result_list = (list != NULL) ? g_object_ref (list) : NULL; + + if (self->result_list != NULL) + gs_app_list_filter (self->result_list, gs_plugin_job_url_to_app_is_valid_filter, self); + + /* only allow one result */ + if (self->saved_error == NULL) { + if (self->result_list == NULL || + gs_app_list_length (self->result_list) == 0) { + g_autofree gchar *str = gs_plugin_job_to_string (GS_PLUGIN_JOB (self)); + g_set_error (&self->saved_error, + GS_PLUGIN_ERROR, + GS_PLUGIN_ERROR_NOT_SUPPORTED, + "no application was created for %s", str); + } else if (gs_app_list_length (self->result_list) > 1) { + g_autofree gchar *str = gs_plugin_job_to_string (GS_PLUGIN_JOB (self)); + g_debug ("expected one, but received %u apps for %s", gs_app_list_length (self->result_list), str); + } + + /* Ensure the right icon is set on all the apps. */ + if (self->result_list != NULL) { + for (guint i = 0; i < gs_app_list_length (self->result_list); i++) { + GsApp *app = gs_app_list_index (self->result_list, i); + if (!gs_app_has_icons (app)) { + g_autoptr(GIcon) ic = NULL; + const gchar *icon_name; + if (gs_app_has_quirk (app, GS_APP_QUIRK_HAS_SOURCE)) + icon_name = "x-package-repository"; + else + icon_name = "system-component-application"; + ic = g_themed_icon_new (icon_name); + gs_app_add_icon (app, ic); + } + } + } + } + + /* show elapsed time */ + job_debug = gs_plugin_job_to_string (GS_PLUGIN_JOB (self)); + g_debug ("%s", job_debug); + + if (self->saved_error != NULL) + g_task_return_error (task, g_steal_pointer (&self->saved_error)); + else + g_task_return_boolean (task, TRUE); + g_signal_emit_by_name (G_OBJECT (self), "completed"); +} + +static gboolean +gs_plugin_job_url_to_app_is_valid_filter (GsApp *app, + gpointer user_data) +{ + GsPluginJob *plugin_job = user_data; + + return gs_plugin_loader_app_is_valid (app, gs_plugin_job_get_refine_flags (plugin_job)); +} + +static gboolean +gs_plugin_job_url_to_app_run_finish (GsPluginJob *self, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +gs_plugin_job_url_to_app_class_init (GsPluginJobUrlToAppClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsPluginJobClass *job_class = GS_PLUGIN_JOB_CLASS (klass); + + object_class->dispose = gs_plugin_job_url_to_app_dispose; + object_class->get_property = gs_plugin_job_url_to_app_get_property; + object_class->set_property = gs_plugin_job_url_to_app_set_property; + + job_class->run_async = gs_plugin_job_url_to_app_run_async; + job_class->run_finish = gs_plugin_job_url_to_app_run_finish; + + /** + * GsPluginJobUrlToApp:url: (not nullable) + * + * A URL to convert to a #GsApp. + * + * Since: 47 + */ + props[PROP_URL] = + g_param_spec_string ("url", "URL", + "A URL to convert to a #GsApp.", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GsPluginJobUrlToApp:flags: + * + * Flags affecting how the operation runs. + * + * Since: 47 + */ + props[PROP_FLAGS] = + g_param_spec_flags ("flags", "Flags", + "Flags affecting how the operation runs.", + GS_TYPE_PLUGIN_URL_TO_APP_FLAGS, + GS_PLUGIN_URL_TO_APP_FLAGS_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props); +} + +static void +gs_plugin_job_url_to_app_init (GsPluginJobUrlToApp *self) +{ +} + +/** + * gs_plugin_job_url_to_app_new: + * @url: (not nullable): a URL to run the operation on + * @flags: flags affecting how the operation runs + * + * Create a new #GsPluginJobUrlToApp to convert the given @url. + * + * Returns: (transfer full): a new #GsPluginJobUrlToApp + * Since: 47 + */ +GsPluginJob * +gs_plugin_job_url_to_app_new (const gchar *url, + GsPluginUrlToAppFlags flags) +{ + g_return_val_if_fail (url != NULL && g_uri_is_valid (url, G_URI_FLAGS_NONE, NULL), NULL); + + return g_object_new (GS_TYPE_PLUGIN_JOB_URL_TO_APP, + "url", url, + "flags", flags, + NULL); +} + +/** + * gs_plugin_job_url_to_app_get_result_list: + * @self: a #GsPluginJobUrlToApp + * + * Get the list of apps converted from the given URL. + * + * If this is called before the job is complete, %NULL will be returned. + * + * Returns: (transfer none) (nullable): the job results, or %NULL on error + * or if called before the job has completed + * + * Since: 47 + */ +GsAppList * +gs_plugin_job_url_to_app_get_result_list (GsPluginJobUrlToApp *self) +{ + g_return_val_if_fail (GS_IS_PLUGIN_JOB_URL_TO_APP (self), NULL); + + return self->result_list; +} diff --git a/lib/gs-plugin-job-url-to-app.h b/lib/gs-plugin-job-url-to-app.h new file mode 100644 index 000000000..2bca7a891 --- /dev/null +++ b/lib/gs-plugin-job-url-to-app.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2024 Red Hat + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include + +#include "gs-plugin-job.h" + +G_BEGIN_DECLS + +#define GS_TYPE_PLUGIN_JOB_URL_TO_APP (gs_plugin_job_url_to_app_get_type ()) + +G_DECLARE_FINAL_TYPE (GsPluginJobUrlToApp, gs_plugin_job_url_to_app, GS, PLUGIN_JOB_URL_TO_APP, GsPluginJob) + +GsPluginJob *gs_plugin_job_url_to_app_new (const gchar *url, + GsPluginUrlToAppFlags flags); +GsAppList *gs_plugin_job_url_to_app_get_result_list + (GsPluginJobUrlToApp *self); + +G_END_DECLS diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c index d5d06c2d6..15ae8b78d 100644 --- a/lib/gs-plugin-loader.c +++ b/lib/gs-plugin-loader.c @@ -3406,6 +3406,10 @@ run_job_cb (GObject *source_object, GsAppList *list = gs_plugin_job_file_to_app_get_result_list (GS_PLUGIN_JOB_FILE_TO_APP (plugin_job)); g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref); return; + } else if (GS_IS_PLUGIN_JOB_URL_TO_APP (plugin_job)) { + GsAppList *list = gs_plugin_job_url_to_app_get_result_list (GS_PLUGIN_JOB_URL_TO_APP (plugin_job)); + g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref); + return; } else if (GS_IS_PLUGIN_JOB_REFRESH_METADATA (plugin_job)) { /* FIXME: For some reason, existing callers of refresh jobs * expect a #GsAppList instance back, even though it’s empty and diff --git a/lib/meson.build b/lib/meson.build index 2f30ba160..cf0de8623 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -40,6 +40,7 @@ libgnomesoftware_public_headers = [ 'gs-plugin-job-install-apps.h', 'gs-plugin-job-uninstall-apps.h', 'gs-plugin-job-update-apps.h', + 'gs-plugin-job-url-to-app.h', 'gs-plugin-loader.h', 'gs-plugin-loader-sync.h', 'gs-plugin-types.h', @@ -134,6 +135,7 @@ libgnomesoftware = library( 'gs-plugin-job-install-apps.c', 'gs-plugin-job-uninstall-apps.c', 'gs-plugin-job-update-apps.c', + 'gs-plugin-job-url-to-app.c', 'gs-plugin-loader.c', 'gs-plugin-loader-sync.c', 'gs-profiler.h', -- GitLab From 54086135acfbd0741d5bcf0516d7ae26a25b7d93 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 08:25:35 +0100 Subject: [PATCH 3/9] gs-plugin-helpers: Add helper for url-to-app job --- lib/gs-plugin-helpers.c | 67 +++++++++++++++++++++++++++++++++++++++++ lib/gs-plugin-helpers.h | 17 +++++++++++ 2 files changed, 84 insertions(+) diff --git a/lib/gs-plugin-helpers.c b/lib/gs-plugin-helpers.c index b61b330c6..8ef6f471b 100644 --- a/lib/gs-plugin-helpers.c +++ b/lib/gs-plugin-helpers.c @@ -941,3 +941,70 @@ gs_plugin_file_to_app_data_free (GsPluginFileToAppData *data) g_clear_object (&data->file); g_free (data); } + +/** + * gs_plugin_url_to_app_data_new: + * @url: (not nullable): a URL + * @flags: operation flags + * + * Common context data for a call to #GsPluginClass.url_to_app_async. + * + * Returns: (transfer full): context data structure + * Since: 47 + */ +GsPluginUrlToAppData * +gs_plugin_url_to_app_data_new (const gchar *url, + GsPluginUrlToAppFlags flags) +{ + g_autoptr(GsPluginUrlToAppData) data = g_new0 (GsPluginUrlToAppData, 1); + data->url = g_strdup (url); + data->flags = flags; + + return g_steal_pointer (&data); +} + +/** + * gs_plugin_url_to_app_data_new_task: + * @source_object: task source object + * @url: (not nullable): a URL + * @flags: operation flags + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: function to call once asynchronous operation is finished + * @user_data: data to pass to @callback + * + * Create a #GTask for a url-to-app operation with the given arguments. The task + * data will be set to a #GsPluginUrlToAppData containing the given context. + * + * This is essentially a combination of gs_plugin_url_to_app_data_new(), + * g_task_new() and g_task_set_task_data(). + * + * Returns: (transfer full): new #GTask with the given context data + * Since: 47 + */ +GTask * +gs_plugin_url_to_app_data_new_task (gpointer source_object, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = g_task_new (source_object, cancellable, callback, user_data); + g_task_set_task_data (task, gs_plugin_url_to_app_data_new (url, flags), (GDestroyNotify) gs_plugin_url_to_app_data_free); + return g_steal_pointer (&task); +} + +/** + * gs_plugin_url_to_app_data_free: + * @data: (transfer full): a #GsPluginUrlToAppData + * + * Free the given @data. + * + * Since: 47 + */ +void +gs_plugin_url_to_app_data_free (GsPluginUrlToAppData *data) +{ + g_free (data->url); + g_free (data); +} diff --git a/lib/gs-plugin-helpers.h b/lib/gs-plugin-helpers.h index 579febbf2..00c80dbbc 100644 --- a/lib/gs-plugin-helpers.h +++ b/lib/gs-plugin-helpers.h @@ -259,4 +259,21 @@ GTask * gs_plugin_file_to_app_data_new_task (gpointer source_object, void gs_plugin_file_to_app_data_free (GsPluginFileToAppData *data); G_DEFINE_AUTOPTR_CLEANUP_FUNC (GsPluginFileToAppData, gs_plugin_file_to_app_data_free) +typedef struct { + gchar *url; /* (owned) */ + GsPluginUrlToAppFlags flags; +} GsPluginUrlToAppData; + +GsPluginUrlToAppData * + gs_plugin_url_to_app_data_new (const gchar *url, + GsPluginUrlToAppFlags flags); +GTask * gs_plugin_url_to_app_data_new_task (gpointer source_object, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +void gs_plugin_url_to_app_data_free (GsPluginUrlToAppData *data); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GsPluginUrlToAppData, gs_plugin_url_to_app_data_free) + G_END_DECLS -- GitLab From 3d8868ac2c80bcb662b84197d9cd9275f8b13aa2 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 09:42:39 +0100 Subject: [PATCH 4/9] gs-cmd: Replace GS_PLUGIN_ACTION_URL_TO_APP job with url-to-app job --- lib/gs-cmd.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/gs-cmd.c b/lib/gs-cmd.c index c740b9c52..9a1a24807 100644 --- a/lib/gs-cmd.c +++ b/lib/gs-cmd.c @@ -523,12 +523,11 @@ main (int argc, char **argv) } } else if (argc == 3 && g_strcmp0 (argv[1], "url-to-app") == 0) { g_autoptr(GsPluginJob) plugin_job = NULL; - plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP, - "search", argv[2], - "refine-flags", self->refine_flags, - "max-results", self->max_results, - "interactive", self->interactive, - NULL); + plugin_job = gs_plugin_job_url_to_app_new (argv[2], + self->interactive ? GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE : + GS_PLUGIN_URL_TO_APP_FLAGS_NONE); + gs_plugin_job_set_refine_flags (plugin_job, self->refine_flags); + gs_plugin_job_set_max_results (plugin_job, self->max_results); app = gs_plugin_loader_job_process_app (self->plugin_loader, plugin_job, NULL, &error); if (app == NULL) { -- GitLab From 957bc069e050d3f7aa57e1c836652f82f4ba9959 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 08:28:31 +0100 Subject: [PATCH 5/9] gs-details-page: Replace GS_PLUGIN_ACTION_URL_TO_APP job with url-to-app job --- src/gs-details-page.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/gs-details-page.c b/src/gs-details-page.c index 27bece6ee..ecc051a16 100644 --- a/src/gs-details-page.c +++ b/src/gs-details-page.c @@ -2114,11 +2114,9 @@ gs_details_page_set_url (GsDetailsPage *self, const gchar *url) g_clear_object (&self->app_local_file); _set_app (self, NULL); self->origin_by_packaging_format = FALSE; - plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP, - "search", url, - "refine-flags", GS_DETAILS_PAGE_REFINE_FLAGS | - GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES, - NULL); + plugin_job = gs_plugin_job_url_to_app_new (url, GS_PLUGIN_URL_TO_APP_FLAGS_NONE); + gs_plugin_job_set_refine_flags (plugin_job, GS_DETAILS_PAGE_REFINE_FLAGS | + GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES); gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, self->cancellable, gs_details_page_url_to_app_cb, -- GitLab From 8456c0b542f34d2c30b70e4c73ddc64f110011f3 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 08:29:06 +0100 Subject: [PATCH 6/9] dummy: Replace GS_PLUGIN_ACTION_URL_TO_APP job with url-to-app job in tests --- plugins/dummy/gs-self-test.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c index 30e136a4c..3b881c5be 100644 --- a/plugins/dummy/gs-self-test.c +++ b/plugins/dummy/gs-self-test.c @@ -485,10 +485,8 @@ gs_plugins_dummy_url_to_app_func (GsPluginLoader *plugin_loader) g_autoptr(GsApp) app = NULL; g_autoptr(GsPluginJob) plugin_job = NULL; - plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP, - "search", "dummy://chiron.desktop", - "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON, - NULL); + plugin_job = gs_plugin_job_url_to_app_new ("dummy://chiron.desktop", GS_PLUGIN_URL_TO_APP_FLAGS_NONE); + gs_plugin_job_set_refine_flags (plugin_job, GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON); app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error); gs_test_flush_main_context (); g_assert_no_error (error); -- GitLab From 1d9595b9ff5a400b1f9ed439d4332b51112a7fcf Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 09:31:02 +0100 Subject: [PATCH 7/9] plugins: Implement url-to-app as an async operation --- plugins/core/gs-plugin-appstream.c | 61 +++++++-- plugins/dummy/gs-plugin-dummy.c | 55 +++++--- plugins/flatpak/gs-plugin-flatpak.c | 57 ++++++-- plugins/packagekit/gs-plugin-packagekit.c | 150 ++++++++++++++-------- plugins/snap/gs-plugin-snap.c | 57 +++++++- 5 files changed, 280 insertions(+), 100 deletions(-) diff --git a/plugins/core/gs-plugin-appstream.c b/plugins/core/gs-plugin-appstream.c index fa39f686f..1e270ffae 100644 --- a/plugins/core/gs-plugin-appstream.c +++ b/plugins/core/gs-plugin-appstream.c @@ -903,23 +903,62 @@ gs_plugin_appstream_shutdown_finish (GsPlugin *plugin, return g_task_propagate_boolean (G_TASK (result), error); } -gboolean -gs_plugin_url_to_app (GsPlugin *plugin, - GsAppList *list, - const gchar *url, - GCancellable *cancellable, - GError **error) +/* Run in @worker. */ +static void +url_to_app_thread_cb (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { - GsPluginAppstream *self = GS_PLUGIN_APPSTREAM (plugin); + GsPluginAppstream *self = GS_PLUGIN_APPSTREAM (source_object); + GsPluginUrlToAppData *data = task_data; + g_autoptr(GsAppList) list = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; + g_autoptr(GError) local_error = NULL; + + assert_in_worker (self); /* check silo is valid */ - if (!gs_plugin_appstream_check_silo (self, cancellable, error)) - return FALSE; + if (!gs_plugin_appstream_check_silo (self, cancellable, &local_error)) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } locker = g_rw_lock_reader_locker_new (&self->silo_lock); + list = gs_app_list_new (); + + if (gs_appstream_url_to_app (GS_PLUGIN (self), self->silo, list, data->url, cancellable, &local_error)) + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); + else + g_task_return_error (task, g_steal_pointer (&local_error)); +} + +static void +gs_plugin_appstream_url_to_app_async (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsPluginAppstream *self = GS_PLUGIN_APPSTREAM (plugin); + g_autoptr(GTask) task = NULL; + gboolean interactive = (flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0; + + task = gs_plugin_url_to_app_data_new_task (plugin, url, flags, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_appstream_url_to_app_async); - return gs_appstream_url_to_app (plugin, self->silo, list, url, cancellable, error); + /* Queue a job for the refine. */ + gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive), + url_to_app_thread_cb, g_steal_pointer (&task)); +} + +static GsAppList * +gs_plugin_appstream_url_to_app_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); } static void @@ -1625,6 +1664,8 @@ gs_plugin_appstream_class_init (GsPluginAppstreamClass *klass) plugin_class->refresh_metadata_finish = gs_plugin_appstream_refresh_metadata_finish; plugin_class->refine_categories_async = gs_plugin_appstream_refine_categories_async; plugin_class->refine_categories_finish = gs_plugin_appstream_refine_categories_finish; + plugin_class->url_to_app_async = gs_plugin_appstream_url_to_app_async; + plugin_class->url_to_app_finish = gs_plugin_appstream_url_to_app_finish; } GType diff --git a/plugins/dummy/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c index 04462b937..48e06662e 100644 --- a/plugins/dummy/gs-plugin-dummy.c +++ b/plugins/dummy/gs-plugin-dummy.c @@ -259,30 +259,45 @@ gs_plugin_dummy_poll_cb (gpointer user_data) return TRUE; } -gboolean -gs_plugin_url_to_app (GsPlugin *plugin, - GsAppList *list, - const gchar *url, - GCancellable *cancellable, - GError **error) + +static void +gs_plugin_dummy_url_to_app_async (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - g_autofree gchar *path = NULL; - g_autofree gchar *scheme = NULL; + g_autoptr(GTask) task = NULL; + g_autoptr(GsAppList) list = gs_app_list_new (); g_autoptr(GsApp) app = NULL; + g_autofree gchar *scheme = NULL; + + task = gs_plugin_url_to_app_data_new_task (plugin, url, flags, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_dummy_url_to_app_async); - /* not us */ + /* it's us */ scheme = gs_utils_get_url_scheme (url); - if (g_strcmp0 (scheme, "dummy") != 0) - return TRUE; + if (g_strcmp0 (scheme, "dummy") == 0) { + g_autofree gchar *path = NULL; + /* create app */ + path = gs_utils_get_url_path (url); + app = gs_app_new (path); + gs_app_set_management_plugin (app, plugin); + gs_app_set_metadata (app, "GnomeSoftware::Creator", + gs_plugin_get_name (plugin)); + gs_app_list_add (list, app); + } - /* create app */ - path = gs_utils_get_url_path (url); - app = gs_app_new (path); - gs_app_set_management_plugin (app, plugin); - gs_app_set_metadata (app, "GnomeSoftware::Creator", - gs_plugin_get_name (plugin)); - gs_app_list_add (list, app); - return TRUE; + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); +} + +static GsAppList * +gs_plugin_dummy_url_to_app_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); } static gboolean timeout_cb (gpointer user_data); @@ -1542,6 +1557,8 @@ gs_plugin_dummy_class_init (GsPluginDummyClass *klass) plugin_class->download_upgrade_finish = gs_plugin_dummy_download_upgrade_finish; plugin_class->trigger_upgrade_async = gs_plugin_dummy_trigger_upgrade_async; plugin_class->trigger_upgrade_finish = gs_plugin_dummy_trigger_upgrade_finish; + plugin_class->url_to_app_async = gs_plugin_dummy_url_to_app_async; + plugin_class->url_to_app_finish = gs_plugin_dummy_url_to_app_finish; } GType diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c index 0bc58158b..c9fde1a43 100644 --- a/plugins/flatpak/gs-plugin-flatpak.c +++ b/plugins/flatpak/gs-plugin-flatpak.c @@ -2601,22 +2601,55 @@ gs_plugin_flatpak_list_apps_finish (GsPlugin *plugin, return g_task_propagate_pointer (G_TASK (result), error); } -gboolean -gs_plugin_url_to_app (GsPlugin *plugin, - GsAppList *list, - const gchar *url, - GCancellable *cancellable, - GError **error) +static void +url_to_app_thread_cb (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { - GsPluginFlatpak *self = GS_PLUGIN_FLATPAK (plugin); - gboolean interactive = gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE); + g_autoptr(GsAppList) list = gs_app_list_new (); + g_autoptr(GError) local_error = NULL; + GsPluginFlatpak *self = GS_PLUGIN_FLATPAK (source_object); + GsPluginUrlToAppData *data = task_data; + gboolean interactive = (data->flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0; for (guint i = 0; i < self->installations->len; i++) { GsFlatpak *flatpak = g_ptr_array_index (self->installations, i); - if (!gs_flatpak_url_to_app (flatpak, list, url, interactive, cancellable, error)) - return FALSE; + if (!gs_flatpak_url_to_app (flatpak, list, data->url, interactive, cancellable, &local_error)) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } } - return TRUE; + + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); +} + +static void +gs_plugin_flatpak_url_to_app_async (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + GsPluginFlatpak *self = GS_PLUGIN_FLATPAK (plugin); + gboolean interactive = (flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0; + + task = gs_plugin_url_to_app_data_new_task (plugin, url, flags, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_flatpak_url_to_app_async); + + /* Queue a job to get the apps. */ + gs_worker_thread_queue (self->worker, get_priority_for_interactivity (interactive), + url_to_app_thread_cb, g_steal_pointer (&task)); +} + +static GsAppList * +gs_plugin_flatpak_url_to_app_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); } static void install_repository_thread_cb (GTask *task, @@ -2939,6 +2972,8 @@ gs_plugin_flatpak_class_init (GsPluginFlatpakClass *klass) plugin_class->launch_finish = gs_plugin_flatpak_launch_finish; plugin_class->file_to_app_async = gs_plugin_flatpak_file_to_app_async; plugin_class->file_to_app_finish = gs_plugin_flatpak_file_to_app_finish; + plugin_class->url_to_app_async = gs_plugin_flatpak_url_to_app_async; + plugin_class->url_to_app_finish = gs_plugin_flatpak_url_to_app_finish; } GType diff --git a/plugins/packagekit/gs-plugin-packagekit.c b/plugins/packagekit/gs-plugin-packagekit.c index b7426f0d3..d2d972fc6 100644 --- a/plugins/packagekit/gs-plugin-packagekit.c +++ b/plugins/packagekit/gs-plugin-packagekit.c @@ -4005,69 +4005,100 @@ gs_plugin_packagekit_file_to_app_finish (GsPlugin *plugin, return g_task_propagate_pointer (G_TASK (result), error); } -gboolean -gs_plugin_url_to_app (GsPlugin *plugin, - GsAppList *list, - const gchar *url, - GCancellable *cancellable, - GError **error) +static void gs_plugin_packagekit_url_to_app_resolved_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); + +static void +gs_plugin_packagekit_url_to_app_async (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - GsPluginPackagekit *self = GS_PLUGIN_PACKAGEKIT (plugin); - g_autofree gchar *scheme = NULL; - g_autofree gchar *path = NULL; - const gchar *id = NULL; - const gchar * const *id_like = NULL; g_auto(GStrv) package_ids = NULL; - g_autoptr(PkResults) results = NULL; - g_autoptr(GsApp) app = NULL; g_autoptr(GsOsRelease) os_release = NULL; - g_autoptr(GPtrArray) packages = NULL; - g_autoptr(GPtrArray) details = NULL; - g_autoptr(GsPackagekitHelper) helper = gs_packagekit_helper_new (plugin); - g_autoptr(PkClient) client_url_to_app = NULL; + g_autoptr(GsPackagekitHelper) helper = NULL; + g_autoptr(PkTask) task_resolve = NULL; + g_autoptr(GTask) task = NULL; + g_autoptr(GError) local_error = NULL; - path = gs_utils_get_url_path (url); + task = gs_plugin_url_to_app_data_new_task (plugin, url, flags, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_packagekit_url_to_app_async); /* only do this for apt:// on debian or debian-like distros */ - os_release = gs_os_release_new (error); + os_release = gs_os_release_new (&local_error); if (os_release == NULL) { - g_prefix_error (error, "failed to determine OS information:"); - return FALSE; - } else { + g_prefix_error_literal (&local_error, "Failed to determine OS information: "); + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } else { + const gchar *id = NULL; + const gchar * const *id_like = NULL; + g_autofree gchar *scheme = NULL; id = gs_os_release_get_id (os_release); id_like = gs_os_release_get_id_like (os_release); scheme = gs_utils_get_url_scheme (url); if (!(g_strcmp0 (scheme, "apt") == 0 && (g_strcmp0 (id, "debian") == 0 || - g_strv_contains (id_like, "debian")))) { - return TRUE; + (id_like != NULL && g_strv_contains (id_like, "debian"))))) { + g_task_return_pointer (task, gs_app_list_new (), g_object_unref); + return; } } - app = gs_app_new (NULL); - gs_plugin_packagekit_set_packaging_format (plugin, app); - gs_app_add_source (app, path); - gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC); - gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE); - package_ids = g_new0 (gchar *, 2); - package_ids[0] = g_strdup (path); + package_ids[0] = gs_utils_get_url_path (url); - client_url_to_app = pk_client_new (); - pk_client_set_interactive (client_url_to_app, gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE)); + task_resolve = gs_packagekit_task_new (plugin); + helper = gs_packagekit_helper_new (plugin); + gs_packagekit_task_setup (GS_PACKAGEKIT_TASK (task_resolve), GS_PACKAGEKIT_TASK_QUESTION_TYPE_NONE, + flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE); + gs_packagekit_task_take_helper (GS_PACKAGEKIT_TASK (task_resolve), helper); - results = pk_client_resolve (client_url_to_app, - pk_bitfield_from_enums (PK_FILTER_ENUM_NEWEST, PK_FILTER_ENUM_ARCH, -1), - package_ids, - cancellable, - gs_packagekit_helper_cb, helper, - error); + pk_client_resolve_async (PK_CLIENT (task_resolve), + pk_bitfield_from_enums (PK_FILTER_ENUM_NEWEST, PK_FILTER_ENUM_ARCH, -1), + package_ids, + cancellable, + gs_packagekit_helper_cb, g_steal_pointer (&helper), + gs_plugin_packagekit_url_to_app_resolved_cb, g_steal_pointer (&task)); +} - if (!gs_plugin_packagekit_results_valid (results, cancellable, error)) { - g_prefix_error (error, "failed to resolve package_ids: "); - return FALSE; +static void +gs_plugin_packagekit_url_to_app_resolved_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (g_steal_pointer (&user_data)); + GsPluginPackagekit *self = GS_PLUGIN_PACKAGEKIT (g_task_get_source_object (task)); + GsPluginUrlToAppData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + GsPlugin *plugin = GS_PLUGIN (self); + g_autofree gchar *path = NULL; + g_autoptr(PkResults) results = NULL; + g_autoptr(GsApp) app = NULL; + g_autoptr(GsAppList) list = NULL; + g_autoptr(GPtrArray) packages = NULL; + g_autoptr(GPtrArray) details = NULL; + g_autoptr(GError) local_error = NULL; + + results = pk_client_generic_finish (PK_CLIENT (source_object), result, &local_error); + if (local_error != NULL || !gs_plugin_packagekit_results_valid (results, cancellable, &local_error)) { + g_prefix_error (&local_error, "Failed to resolve package_ids: "); + gs_plugin_packagekit_error_convert (&local_error, cancellable); + g_task_return_error (task, g_steal_pointer (&local_error)); + return; } + path = gs_utils_get_url_path (data->url); + list = gs_app_list_new (); + app = gs_app_new (NULL); + gs_plugin_packagekit_set_packaging_format (plugin, app); + gs_app_add_source (app, path); + gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC); + gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE); + /* get results */ packages = pk_results_get_package_array (results); details = pk_results_get_details_array (results); @@ -4076,24 +4107,35 @@ gs_plugin_url_to_app (GsPlugin *plugin, g_autoptr(GHashTable) details_collection = NULL; g_autoptr(GHashTable) prepared_updates = NULL; - if (gs_app_get_local_file (app) != NULL) - return TRUE; - - details_collection = gs_plugin_packagekit_details_array_to_hash (details); + if (gs_app_get_local_file (app) == NULL) { + details_collection = gs_plugin_packagekit_details_array_to_hash (details); - g_mutex_lock (&self->prepared_updates_mutex); - prepared_updates = g_hash_table_ref (self->prepared_updates); - g_mutex_unlock (&self->prepared_updates_mutex); + g_mutex_lock (&self->prepared_updates_mutex); + prepared_updates = g_hash_table_ref (self->prepared_updates); + g_mutex_unlock (&self->prepared_updates_mutex); - gs_plugin_packagekit_resolve_packages_app (GS_PLUGIN (self), packages, app); - gs_plugin_packagekit_refine_details_app (plugin, details_collection, prepared_updates, app); + gs_plugin_packagekit_resolve_packages_app (plugin, packages, app); + gs_plugin_packagekit_refine_details_app (plugin, details_collection, prepared_updates, app); + } gs_app_list_add (list, app); } else { - g_warning ("no results returned"); + g_task_return_new_error (task, + GS_PLUGIN_ERROR, + GS_PLUGIN_ERROR_INVALID_FORMAT, + "No files for %s", data->url); + return; } - return TRUE; + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); +} + +static GsAppList * +gs_plugin_packagekit_url_to_app_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); } static gchar * @@ -5357,6 +5399,8 @@ gs_plugin_packagekit_class_init (GsPluginPackagekitClass *klass) plugin_class->launch_finish = gs_plugin_packagekit_launch_finish; plugin_class->file_to_app_async = gs_plugin_packagekit_file_to_app_async; plugin_class->file_to_app_finish = gs_plugin_packagekit_file_to_app_finish; + plugin_class->url_to_app_async = gs_plugin_packagekit_url_to_app_async; + plugin_class->url_to_app_finish = gs_plugin_packagekit_url_to_app_finish; } GType diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c index 6aa32d02c..958f60732 100644 --- a/plugins/snap/gs-plugin-snap.c +++ b/plugins/snap/gs-plugin-snap.c @@ -441,12 +441,13 @@ snap_to_app (GsPluginSnap *self, SnapdSnap *snap, const gchar *branch) return g_steal_pointer (&app); } -gboolean -gs_plugin_url_to_app (GsPlugin *plugin, - GsAppList *list, - const gchar *url, - GCancellable *cancellable, - GError **error) +static gboolean +gs_plugin_snap_url_to_app_sync (GsPlugin *plugin, + const gchar *url, + gboolean interactive, + GsAppList *list, + GCancellable *cancellable, + GError **error) { GsPluginSnap *self = GS_PLUGIN_SNAP (plugin); g_autoptr(SnapdClient) client = NULL; @@ -454,7 +455,6 @@ gs_plugin_url_to_app (GsPlugin *plugin, g_autofree gchar *path = NULL; g_autoptr(GPtrArray) snaps = NULL; g_autoptr(GsApp) app = NULL; - gboolean interactive = gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE); /* not us */ scheme = gs_utils_get_url_scheme (url); @@ -488,6 +488,47 @@ gs_plugin_url_to_app (GsPlugin *plugin, return TRUE; } +static void +gs_plugin_snap_url_to_app_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr(GsAppList) list = gs_app_list_new (); + g_autoptr(GError) local_error = NULL; + GsPlugin *plugin = GS_PLUGIN (source_object); + GsPluginUrlToAppData *data = task_data; + gboolean interactive = (data->flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0; + + if (gs_plugin_snap_url_to_app_sync (plugin, data->url, interactive, list, cancellable, &local_error)) + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); + else + g_task_return_error (task, g_steal_pointer (&local_error)); +} + +static void +gs_plugin_snap_url_to_app_async (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + task = gs_plugin_url_to_app_data_new_task (plugin, url, flags, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_snap_url_to_app_async); + g_task_run_in_thread (task, gs_plugin_snap_url_to_app_thread); +} + +static GsAppList * +gs_plugin_snap_url_to_app_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); +} + static void gs_plugin_snap_dispose (GObject *object) { @@ -2615,6 +2656,8 @@ gs_plugin_snap_class_init (GsPluginSnapClass *klass) plugin_class->update_apps_finish = gs_plugin_snap_update_apps_finish; plugin_class->launch_async = gs_plugin_snap_launch_async; plugin_class->launch_finish = gs_plugin_snap_launch_finish; + plugin_class->url_to_app_async = gs_plugin_snap_url_to_app_async; + plugin_class->url_to_app_finish = gs_plugin_snap_url_to_app_finish; } GType -- GitLab From a31b5d36bb1fe23b64a048abae69fcbb0e584ab0 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 2 Feb 2024 09:44:51 +0100 Subject: [PATCH 8/9] plugin: Remove definition of gs_plugin_url_to_app() function --- lib/gs-plugin-loader.c | 131 ----------------------------------------- lib/gs-plugin-vfuncs.h | 24 -------- lib/gs-plugin.c | 2 - lib/gs-self-test.c | 1 + 4 files changed, 1 insertion(+), 157 deletions(-) diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c index 15ae8b78d..2e402dbea 100644 --- a/lib/gs-plugin-loader.c +++ b/lib/gs-plugin-loader.c @@ -568,14 +568,6 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper, if (gs_plugin_job_get_interactive (helper->plugin_job)) gs_plugin_interactive_inc (plugin); switch (action) { - case GS_PLUGIN_ACTION_URL_TO_APP: - { - GsPluginUrlToAppFunc plugin_func = func; - ret = plugin_func (plugin, list, - gs_plugin_job_get_search (helper->plugin_job), - cancellable, &error_local); - } - break; case GS_PLUGIN_ACTION_GET_LANGPACKS: { GsPluginGetLangPacksFunc plugin_func = func; @@ -847,15 +839,6 @@ gs_plugin_loader_app_is_valid (GsApp *app, return TRUE; } -static gboolean -gs_plugin_loader_app_is_valid_filter (GsApp *app, - gpointer user_data) -{ - GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) user_data; - - return gs_plugin_loader_app_is_valid (app, gs_plugin_job_get_refine_flags (helper->plugin_job)); -} - gboolean gs_plugin_loader_app_is_compatible (GsPluginLoader *plugin_loader, GsApp *app) @@ -3151,24 +3134,6 @@ gs_plugin_loader_process_old_api_job_cb (gpointer task_data, gs_job_manager_remove_job (plugin_loader->job_manager, helper->plugin_job); return; } - - if (action == GS_PLUGIN_ACTION_URL_TO_APP) { - const gchar *search = gs_plugin_job_get_search (helper->plugin_job); - if (search && g_ascii_strncasecmp (search, "file://", 7) == 0 && ( - gs_plugin_job_get_list (helper->plugin_job) == NULL || - gs_app_list_length (gs_plugin_job_get_list (helper->plugin_job)) == 0)) { - g_autoptr(GError) local_error = NULL; - g_autoptr(GFile) file = NULL; - file = g_file_new_for_uri (search); - gs_plugin_job_set_action (helper->plugin_job, GS_PLUGIN_ACTION_FILE_TO_APP); - gs_plugin_job_set_file (helper->plugin_job, file); - helper->function_name = gs_plugin_action_to_function_name (GS_PLUGIN_ACTION_FILE_TO_APP); - if (!gs_plugin_loader_run_results (helper, cancellable, &local_error)) - g_debug ("Failed to convert file:// URI to app using file-to-app action: %s", local_error->message); - gs_plugin_job_set_action (helper->plugin_job, GS_PLUGIN_ACTION_URL_TO_APP); - gs_plugin_job_set_file (helper->plugin_job, NULL); - } - } } if (!helper->anything_ran && !GS_IS_PLUGIN_JOB_REFINE (helper->plugin_job)) { @@ -3214,87 +3179,6 @@ gs_plugin_loader_process_old_api_job_cb (gpointer task_data, g_debug ("no refine flags set for transaction"); } - /* check the local files have an icon set */ - switch (action) { - case GS_PLUGIN_ACTION_URL_TO_APP: { - g_autoptr(GsPluginJob) refine_job = NULL; - g_autoptr(GAsyncResult) refine_result = NULL; - g_autoptr(GsAppList) new_list = NULL; - - for (guint j = 0; j < gs_app_list_length (list); j++) { - GsApp *app = gs_app_list_index (list, j); - if (!gs_app_has_icons (app)) { - g_autoptr(GIcon) ic = NULL; - const gchar *icon_name; - if (gs_app_has_quirk (app, GS_APP_QUIRK_HAS_SOURCE)) - icon_name = "x-package-repository"; - else - icon_name = "system-component-application"; - ic = g_themed_icon_new (icon_name); - gs_app_add_icon (app, ic); - } - } - - refine_job = gs_plugin_job_refine_new (list, GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON | GS_PLUGIN_REFINE_FLAGS_DISABLE_FILTERING); - gs_plugin_loader_job_process_async (plugin_loader, refine_job, - cancellable, - async_result_cb, - &refine_result); - - /* FIXME: Make this sync until the enclosing function is - * refactored to be async. */ - while (refine_result == NULL) - g_main_context_iteration (g_main_context_get_thread_default (), TRUE); - - new_list = gs_plugin_loader_job_process_finish (plugin_loader, refine_result, &error); - if (new_list == NULL) { - gs_utils_error_convert_gio (&error); - g_task_return_error (task, g_steal_pointer (&error)); - gs_job_manager_remove_job (plugin_loader->job_manager, helper->plugin_job); - return; - } - - gs_plugin_loader_inherit_list_props (new_list, list); - - /* Update the app list in case the refine resolved any wildcards. */ - g_set_object (&list, new_list); - - break; - } - default: - break; - } - - /* filter package list */ - switch (action) { - case GS_PLUGIN_ACTION_URL_TO_APP: - gs_app_list_filter (list, gs_plugin_loader_app_is_valid_filter, helper); - break; - default: - break; - } - - /* only allow one result */ - if (action == GS_PLUGIN_ACTION_URL_TO_APP) { - if (gs_app_list_length (list) == 0) { - g_autofree gchar *str = gs_plugin_job_to_string (helper->plugin_job); - g_autoptr(GError) error_local = NULL; - g_set_error (&error_local, - GS_PLUGIN_ERROR, - GS_PLUGIN_ERROR_NOT_SUPPORTED, - "no application was created for %s", str); - if (!gs_plugin_job_get_propagate_error (helper->plugin_job)) - gs_plugin_loader_claim_job_error (plugin_loader, NULL, helper->plugin_job, error_local); - g_task_return_error (task, g_steal_pointer (&error_local)); - gs_job_manager_remove_job (plugin_loader->job_manager, helper->plugin_job); - return; - } - if (gs_app_list_length (list) > 1) { - g_autofree gchar *str = gs_plugin_job_to_string (helper->plugin_job); - g_debug ("more than one application was created for %s", str); - } - } - /* filter duplicates with priority, taking into account the source name * & version, so we combine available updates with the installed app */ dedupe_flags = gs_plugin_job_get_dedupe_flags (helper->plugin_job); @@ -3668,21 +3552,6 @@ job_process_cb (GTask *task) GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME); } - /* check required args */ - switch (action) { - case GS_PLUGIN_ACTION_URL_TO_APP: - if (gs_plugin_job_get_search (plugin_job) == NULL) { - g_task_return_new_error (task, - GS_PLUGIN_ERROR, - GS_PLUGIN_ERROR_NOT_SUPPORTED, - "no valid search terms"); - return; - } - break; - default: - break; - } - /* save helper */ helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job); g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free); diff --git a/lib/gs-plugin-vfuncs.h b/lib/gs-plugin-vfuncs.h index 68c871763..da96b0f2b 100644 --- a/lib/gs-plugin-vfuncs.h +++ b/lib/gs-plugin-vfuncs.h @@ -63,30 +63,6 @@ GType gs_plugin_query_type (void); void gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app); -/** - * gs_plugin_url_to_app: - * @plugin: a #GsPlugin - * @list: a #GsAppList - * @url: a #URL, e.g. "apt://gimp" - * @cancellable: a #GCancellable, or %NULL - * @error: a #GError, or %NULL - * - * Converts a URL to a #GsApp. It's expected that only one plugin will - * match the scheme of @url and that a single #GsApp will be in the returned - * list. If no plugins can handle the file, the list will be empty. - * - * For example, the apt plugin can turn apt://gimp into a application. - * - * Plugins are expected to add new apps using gs_app_list_add(). - * - * Returns: %TRUE for success or if not relevant - **/ -gboolean gs_plugin_url_to_app (GsPlugin *plugin, - GsAppList *list, - const gchar *url, - GCancellable *cancellable, - GError **error); - /** * gs_plugin_add_langpacks: * @plugin: a #GsPlugin diff --git a/lib/gs-plugin.c b/lib/gs-plugin.c index 3ac3a4735..78963979d 100644 --- a/lib/gs-plugin.c +++ b/lib/gs-plugin.c @@ -1543,8 +1543,6 @@ gs_plugin_error_to_string (GsPluginError error) const gchar * gs_plugin_action_to_function_name (GsPluginAction action) { - if (action == GS_PLUGIN_ACTION_URL_TO_APP) - return "gs_plugin_url_to_app"; if (action == GS_PLUGIN_ACTION_GET_LANGPACKS) return "gs_plugin_add_langpacks"; return NULL; diff --git a/lib/gs-self-test.c b/lib/gs-self-test.c index 09325971f..86efd9012 100644 --- a/lib/gs-self-test.c +++ b/lib/gs-self-test.c @@ -237,6 +237,7 @@ gs_plugin_func (void) if (i == GS_PLUGIN_ACTION_INSTALL || i == GS_PLUGIN_ACTION_LAUNCH || i == GS_PLUGIN_ACTION_FILE_TO_APP || + i == GS_PLUGIN_ACTION_URL_TO_APP || i == GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD || i == GS_PLUGIN_ACTION_INSTALL_REPO || i == GS_PLUGIN_ACTION_REMOVE_REPO || -- GitLab From 1ca057cbd6daef0f23b4edb4d6f30668511e373e Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 4 Jun 2024 23:41:50 +0100 Subject: [PATCH 9/9] snap: Refactor url-to-app operation to be fully async This avoids the overhead of spawning a thread, only to have it sit mostly idle while one or two IPC calls are made to the snap daemon. Signed-off-by: Philip Withnall Helps: #1472 --- plugins/snap/gs-plugin-snap.c | 143 +++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 54 deletions(-) diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c index 958f60732..3f021b193 100644 --- a/plugins/snap/gs-plugin-snap.c +++ b/plugins/snap/gs-plugin-snap.c @@ -441,84 +441,119 @@ snap_to_app (GsPluginSnap *self, SnapdSnap *snap, const gchar *branch) return g_steal_pointer (&app); } -static gboolean -gs_plugin_snap_url_to_app_sync (GsPlugin *plugin, - const gchar *url, - gboolean interactive, - GsAppList *list, - GCancellable *cancellable, - GError **error) +typedef struct { + char *url; /* (owned) (not nullable) */ + GsPluginUrlToAppFlags flags; + + gboolean tried_match_common_id; +} UrlToAppData; + +static void +url_to_app_data_free (UrlToAppData *data) +{ + g_free (data->url); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UrlToAppData, url_to_app_data_free) + +static void url_to_app_find_section_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); + +static void +gs_plugin_snap_url_to_app_async (GsPlugin *plugin, + const gchar *url, + GsPluginUrlToAppFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { GsPluginSnap *self = GS_PLUGIN_SNAP (plugin); + g_autoptr(GTask) task = NULL; + g_autoptr(UrlToAppData) data = NULL; + gboolean interactive = (flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0; g_autoptr(SnapdClient) client = NULL; g_autofree gchar *scheme = NULL; g_autofree gchar *path = NULL; - g_autoptr(GPtrArray) snaps = NULL; - g_autoptr(GsApp) app = NULL; + g_autoptr(GError) local_error = NULL; + + task = g_task_new (plugin, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_snap_url_to_app_async); + + data = g_new0 (UrlToAppData, 1); + data->url = g_strdup (url); + data->flags = flags; + + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) url_to_app_data_free); /* not us */ scheme = gs_utils_get_url_scheme (url); if (g_strcmp0 (scheme, "snap") != 0 && - g_strcmp0 (scheme, "appstream") != 0) - return TRUE; + g_strcmp0 (scheme, "appstream") != 0) { + g_task_return_pointer (task, gs_app_list_new (), g_object_unref); + return; + } /* Create client. */ - client = get_client (self, interactive, error); - if (client == NULL) - return FALSE; + client = get_client (self, interactive, &local_error); + if (client == NULL) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } /* create app */ path = gs_utils_get_url_path (url); - snaps = find_snaps (self, client, - SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_NAME, - NULL, path, cancellable, NULL); - if (snaps == NULL || snaps->len < 1) { - g_clear_pointer (&snaps, g_ptr_array_unref); - /* This works for the appstream:// URL-s */ - snaps = find_snaps (self, client, - SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_COMMON_ID, - NULL, path, cancellable, NULL); - } - if (snaps == NULL || snaps->len < 1) - return TRUE; - - app = snap_to_app (self, g_ptr_array_index (snaps, 0), NULL); - gs_app_list_add (list, app); - - return TRUE; + snapd_client_find_section_async (client, + SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_NAME, + NULL, path, cancellable, + url_to_app_find_section_cb, g_steal_pointer (&task)); } static void -gs_plugin_snap_url_to_app_thread (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +url_to_app_find_section_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { + SnapdClient *client = SNAPD_CLIENT (source_object); + g_autoptr(GTask) task = G_TASK (g_steal_pointer (&user_data)); + GsPluginSnap *self = g_task_get_source_object (task); + GCancellable *cancellable = g_task_get_cancellable (task); + UrlToAppData *data = g_task_get_task_data (task); + g_autoptr(GPtrArray) snaps = NULL; g_autoptr(GsAppList) list = gs_app_list_new (); + g_autoptr(GsApp) app = NULL; g_autoptr(GError) local_error = NULL; - GsPlugin *plugin = GS_PLUGIN (source_object); - GsPluginUrlToAppData *data = task_data; - gboolean interactive = (data->flags & GS_PLUGIN_URL_TO_APP_FLAGS_INTERACTIVE) != 0; - if (gs_plugin_snap_url_to_app_sync (plugin, data->url, interactive, list, cancellable, &local_error)) + snaps = snapd_client_find_section_finish (client, result, NULL, &local_error); + + if ((snaps == NULL || snaps->len < 1) && + !data->tried_match_common_id) { + g_autofree char *path = NULL; + + /* This works for the appstream:// URL-s */ + data->tried_match_common_id = TRUE; + + path = gs_utils_get_url_path (data->url); + snapd_client_find_section_async (client, + SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_COMMON_ID, + NULL, path, cancellable, + url_to_app_find_section_cb, g_steal_pointer (&task)); + return; + } + + if (snaps != NULL) + store_snap_cache_update (self, snaps, FALSE); + + if (snaps == NULL || snaps->len < 1) { g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); - else - g_task_return_error (task, g_steal_pointer (&local_error)); -} + return; + } -static void -gs_plugin_snap_url_to_app_async (GsPlugin *plugin, - const gchar *url, - GsPluginUrlToAppFlags flags, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; + app = snap_to_app (self, g_ptr_array_index (snaps, 0), NULL); + gs_app_list_add (list, app); - task = gs_plugin_url_to_app_data_new_task (plugin, url, flags, cancellable, callback, user_data); - g_task_set_source_tag (task, gs_plugin_snap_url_to_app_async); - g_task_run_in_thread (task, gs_plugin_snap_url_to_app_thread); + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); } static GsAppList * -- GitLab