diff --git a/lib/gnome-software.h b/lib/gnome-software.h index 64323c10b88662ee87224b4e984a1519e555fb2f..6165fd076cbddfd411c807780f98d25f30f7bdd0 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-cmd.c b/lib/gs-cmd.c index c740b9c529746a089b679995214f716595bddd36..9a1a2480718395d78f09005fc108370d0c515563 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) { diff --git a/lib/gs-plugin-helpers.c b/lib/gs-plugin-helpers.c index b61b330c6c107961047ed3d4969e8f58f04b7bdd..8ef6f471ba735e7b87b116460c1ae14ea130a67a 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 579febbf204ecd45a1060d378c12c055c69def3e..00c80dbbc36a6b01c442434f68346ce80524ca0f 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 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 0000000000000000000000000000000000000000..b9c3fcd14b4992c405f2872fee932baf49a3a10c --- /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 0000000000000000000000000000000000000000..2bca7a891869596ec844627cfdcdbbd41bcd00f2 --- /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 d5d06c2d65b6dad3d7b84b6e5faca5951e0bf663..2e402dbea12124f07df47a73240e3127dc618e78 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); @@ -3406,6 +3290,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 @@ -3664,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-types.h b/lib/gs-plugin-types.h index 5223d2abfe2e0d1788f332b03d0c3640171f5003..a4d98d0d352fde947e40f72a5d8072d9524d2831 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-vfuncs.h b/lib/gs-plugin-vfuncs.h index 68c87176338e1449cd8728ae0bfd9ac0f099a2fa..da96b0f2b930b690b3cc0463986fa9692d9fc010 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 3ac3a4735b94962bd0352e024d4ae31de00b6885..78963979d9e0b7aa105a246c7452a28dc0c773a9 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-plugin.h b/lib/gs-plugin.h index 5f39a582041a5b2fab2d5dbef30604306d7fa981..eeec3a3052f611959e66bb667fb50b3a91da6d59 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]; }; diff --git a/lib/gs-self-test.c b/lib/gs-self-test.c index 09325971ff76156fd67a6e138ed198cc06d3b5ed..86efd90123e271665bdc9fa1a34a8a11752f5223 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 || diff --git a/lib/meson.build b/lib/meson.build index 2f30ba160ab75c7309ce064d556d8b01b17638f6..cf0de862395679cf195aae700e4b77a9447b1d30 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', diff --git a/plugins/core/gs-plugin-appstream.c b/plugins/core/gs-plugin-appstream.c index fa39f686f630c6708de7c098894cbf2d007041d3..1e270ffae3c0dae0a86ad797e2670c57f36eaf8e 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 04462b937e441aa1a6050288c370906dec5778bd..48e06662e5af1ed3bf9949e002b5693b7f2c896f 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/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c index 30e136a4cecbdd490b3106f4c644f2bc23a587a1..3b881c5be90106cc2e0c03348c7a89f2bdaabc97 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); diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c index 0bc58158bc4a3e8f36fc294e9ab249503d015fc7..c9fde1a43583f622cc08e146db34bb5ece0707fe 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 b7426f0d337d945a092df30c1c6dc109985a187d..d2d972fc63bf8b8ca8fec6e23ceef1adf9589957 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 6aa32d02cfdabb51d20c63b7e267afe8235c479e..3f021b193e3082d209c3d5dbd8577d9fb6d9f106 100644 --- a/plugins/snap/gs-plugin-snap.c +++ b/plugins/snap/gs-plugin-snap.c @@ -441,51 +441,127 @@ 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) +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; - gboolean interactive = gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE); + 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); + 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 +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; + + 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 */ - snaps = find_snaps (self, client, - SNAPD_FIND_FLAGS_SCOPE_WIDE | SNAPD_FIND_FLAGS_MATCH_COMMON_ID, - NULL, path, cancellable, NULL); + 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); + return; } - 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; + g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); +} + +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 @@ -2615,6 +2691,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 diff --git a/src/gs-details-page.c b/src/gs-details-page.c index 27bece6ee326f739317e6f842c8a8af5712e9f24..ecc051a167c56a74ca526f0be8735dd03fb82dbe 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,