diff --git a/.gitignore b/.gitignore index ba3b2b3..b4345b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /gnome-software-*.tar.xz -/gnome-software-*-build diff --git a/0001-crash-with-broken-theme.patch b/0001-crash-with-broken-theme.patch new file mode 100644 index 0000000..fd37e80 --- /dev/null +++ b/0001-crash-with-broken-theme.patch @@ -0,0 +1,48 @@ +From 3a644c151f27f439c36170f0958fd21cf1cc54d0 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Thu, 3 Jun 2021 08:33:53 +0200 +Subject: [PATCH] gs-feature-tile: Do not abort when the theme is broken + +Just print a warning when the theme doesn't provide 'theme_fg_color' and +fallback to black color. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1228 +--- + src/gs-feature-tile.c | 14 +++++++++++--- + 1 file changed, 11 insertions(+), 3 deletions(-) + +diff --git a/src/gs-feature-tile.c b/src/gs-feature-tile.c +index 1c85083eb..158af1e56 100644 +--- a/src/gs-feature-tile.c ++++ b/src/gs-feature-tile.c +@@ -268,7 +268,6 @@ gs_feature_tile_refresh (GsAppTile *self) + if (key_colors != tile->key_colors_cache) { + g_autoptr(GArray) colors = NULL; + GdkRGBA fg_rgba; +- gboolean fg_rgba_valid; + GsHSBC fg_hsbc; + + /* Look up the foreground colour for the feature tile, +@@ -283,8 +282,17 @@ gs_feature_tile_refresh (GsAppTile *self) + * @min_abs_contrast contrast with the foreground, so + * that the text is legible. + */ +- fg_rgba_valid = gtk_style_context_lookup_color (context, "theme_fg_color", &fg_rgba); +- g_assert (fg_rgba_valid); ++ if (!gtk_style_context_lookup_color (context, "theme_fg_color", &fg_rgba)) { ++ static gboolean i_know = FALSE; ++ if (!i_know) { ++ i_know = TRUE; ++ g_warning ("The theme doesn't provide 'theme_fg_color', fallbacking to black"); ++ } ++ fg_rgba.red = 0.0; ++ fg_rgba.green = 0.0; ++ fg_rgba.blue = 0.0; ++ fg_rgba.alpha = 1.0; ++ } + + gtk_rgb_to_hsv (fg_rgba.red, fg_rgba.green, fg_rgba.blue, + &fg_hsbc.hue, &fg_hsbc.saturation, &fg_hsbc.brightness); +-- +GitLab + diff --git a/0001-dnf5-plugin.patch b/0001-dnf5-plugin.patch deleted file mode 100644 index 3b4b355..0000000 --- a/0001-dnf5-plugin.patch +++ /dev/null @@ -1,6770 +0,0 @@ -at commit 786bab5f1928920251ee9e93d51953ff9dd9faa4 -Date: Mon Sep 1 18:23:09 2025 +0200 - -diff --git a/meson_options.txt b/meson_options.txt -index cd49cf529..27fa15e8b 100644 ---- a/meson_options.txt -+++ b/meson_options.txt -@@ -3,6 +3,7 @@ option('installed_tests', type : 'boolean', value : false, description : 'enable - option('man', type : 'boolean', value : true, description : 'enable man pages') - option('packagekit', type : 'boolean', value : true, description : 'enable PackageKit support') - option('packagekit_autoremove', type : 'boolean', value : false, description : 'autoremove packages in PackageKit') -+option('dnf5', type : 'boolean', value : true, description : 'enable dnf5 support') - option('polkit', type : 'boolean', value : true, description : 'enable PolKit support') - option('eos_updater', type : 'boolean', value : false, description : 'enable eos-updater support') - option('fwupd', type : 'boolean', value : true, description : 'enable fwupd support') -diff --git a/plugins/dnf5/gs-dnf5-progress-helper.c b/plugins/dnf5/gs-dnf5-progress-helper.c -new file mode 100644 -index 000000000..cd8b861df ---- /dev/null -+++ b/plugins/dnf5/gs-dnf5-progress-helper.c -@@ -0,0 +1,552 @@ -+/* -*- 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+ -+ */ -+ -+#include -+ -+#include -+ -+#include "gs-dnf5-generated.h" -+ -+#include "gs-dnf5-progress-helper.h" -+ -+struct _GsDnf5ProgressHelper -+{ -+ GObject parent; -+ -+ GsDnf5RpmRpm *rpm_proxy; /* (owned) */ -+ GsDnf5Base *base_proxy; /* (owned) */ -+ GsApp *app; /* (owned) (nullable) */ -+ GsAppList *list; /* (owned) (nullable) */ -+ gchar *session_object_path; /* (owned) */ -+ GHashTable *ongoing_downloads; /* (owned); gchar * (download_id) ~> DownloadInfo * */ -+ GsAppState recover_download_state; -+ guint64 to_download_total; -+ guint64 downloaded_total; -+ -+ gulong download_add_new_id; -+ gulong download_progress_id; -+ gulong download_mirror_failure_id; -+ gulong download_end_id; -+ gulong transaction_before_begin_id; -+ gulong transaction_after_complete_id; -+ gulong transaction_elem_progress_id; -+ gulong transaction_action_progress_id; -+ gulong transaction_action_start_id; -+ gulong transaction_action_stop_id; -+ gulong transaction_script_error_id; -+ gulong transaction_script_start_id; -+ gulong transaction_script_stop_id; -+ gulong transaction_transaction_progress_id; -+ gulong transaction_transaction_start_id; -+ gulong transaction_transaction_stop_id; -+ gulong transaction_unpack_error_id; -+ gulong transaction_verify_progress_id; -+ gulong transaction_verify_start_id; -+ gulong transaction_verify_stop_id; -+}; -+ -+G_DEFINE_TYPE (GsDnf5ProgressHelper, gs_dnf5_progress_helper, G_TYPE_OBJECT) -+ -+static void -+gs_dnf5_helper_set_progress (GsDnf5ProgressHelper *self, -+ guint progress) -+{ -+ if (self->app != NULL) -+ gs_app_set_progress (self->app, progress); -+ if (self->list != NULL) -+ gs_app_list_override_progress (self->list, progress); -+} -+ -+static void -+gs_dnf5_update_download_progress (GsDnf5ProgressHelper *self) -+{ -+ if (self->to_download_total > 0) -+ gs_dnf5_helper_set_progress (self, 100 * self->downloaded_total / self->to_download_total); -+} -+ -+typedef struct { -+ guint64 downloaded; -+ guint64 total; -+} DownloadInfo; -+ -+static void -+gs_dnf5_download_add_new_cb (GsDnf5Base *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_download_id, -+ const gchar *arg_description, -+ gint64 arg_total_to_download, -+ GsDnf5ProgressHelper *self) -+{ -+ DownloadInfo *download_info; -+ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: download_id:'%s' description:'%s' total_to_download:%" G_GINT64_FORMAT, -+ G_STRFUNC, arg_download_id, arg_description, arg_total_to_download); -+ g_return_if_fail (arg_download_id != NULL); -+ -+ if (arg_total_to_download <= 0) -+ return; -+ -+ if (g_hash_table_size (self->ongoing_downloads) == 0) { -+ if (self->app != NULL) { -+ self->recover_download_state = gs_app_get_state (self->app); -+ gs_app_set_state (self->app, GS_APP_STATE_DOWNLOADING); -+ } -+ self->to_download_total = 0; -+ self->downloaded_total = 0; -+ } -+ -+ download_info = g_new0 (DownloadInfo, 1); -+ download_info->total = arg_total_to_download; -+ -+ g_hash_table_insert (self->ongoing_downloads, g_strdup (arg_download_id), download_info); -+ self->to_download_total += arg_total_to_download; -+ -+ if (self->app != NULL) -+ gs_app_set_size_download (self->app, GS_SIZE_TYPE_VALID, self->to_download_total); -+ gs_dnf5_update_download_progress (self); -+} -+ -+static void -+gs_dnf5_download_end_cb (GsDnf5Base *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_download_id, -+ guint arg_transfer_status, -+ const gchar *arg_message, -+ GsDnf5ProgressHelper *self) -+{ -+ DownloadInfo *download_info; -+ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: download_id:'%s' transfer_status:%u message:'%s'", -+ G_STRFUNC, arg_download_id, arg_transfer_status, arg_message); -+ g_return_if_fail (arg_download_id != NULL); -+ -+ download_info = g_hash_table_lookup (self->ongoing_downloads, arg_download_id); -+ if (download_info != NULL) { -+ self->downloaded_total -= download_info->downloaded; -+ self->downloaded_total += download_info->total; -+ -+ /* remove after being done with the `download_info`, because it frees it */ -+ g_hash_table_remove (self->ongoing_downloads, arg_download_id); -+ download_info = NULL; -+ -+ /* it's the last package to be downloaded */ -+ if (g_hash_table_size (self->ongoing_downloads) == 0) { -+ gs_dnf5_helper_set_progress (self, GS_APP_PROGRESS_UNKNOWN); -+ if (self->app != NULL) { -+ gs_app_set_state (self->app, self->recover_download_state); -+ gs_app_set_size_download (self->app, GS_SIZE_TYPE_UNKNOWN, 0); -+ } -+ } else { -+ gs_dnf5_update_download_progress (self); -+ } -+ } -+} -+ -+static void -+gs_dnf5_download_mirror_failure_cb (GsDnf5Base *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_download_id, -+ const gchar *arg_message, -+ const gchar *arg_url, -+ const gchar *arg_metadata, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: download_id:'%s' message:'%s' url:'%s' metadata:'%s'", -+ G_STRFUNC, arg_download_id, arg_message, arg_url, arg_metadata); -+} -+ -+static void -+gs_dnf5_download_progress_cb (GsDnf5Base *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_download_id, -+ gint64 arg_total_to_download, -+ gint64 arg_downloaded, -+ GsDnf5ProgressHelper *self) -+{ -+ DownloadInfo *download_info; -+ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: download_id:'%s' total_to_download:%" G_GINT64_FORMAT " downloaded:%" G_GINT64_FORMAT, -+ G_STRFUNC, arg_download_id, arg_total_to_download, arg_downloaded); -+ -+ download_info = g_hash_table_lookup (self->ongoing_downloads, arg_download_id); -+ if (download_info != NULL) { -+ self->downloaded_total += arg_downloaded - download_info->downloaded; -+ download_info->downloaded = arg_downloaded; -+ if (download_info->downloaded != (guint64) arg_total_to_download) { -+ self->to_download_total -= download_info->total; -+ download_info->total = arg_total_to_download; -+ self->to_download_total += download_info->total; -+ if (self->app != NULL) -+ gs_app_set_size_download (self->app, GS_SIZE_TYPE_VALID, self->to_download_total); -+ } -+ gs_dnf5_update_download_progress (self); -+ } -+} -+ -+static void -+gs_dnf5_transaction_before_begin_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ gs_dnf5_helper_set_progress (self, 0); -+ g_debug ("%s: total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_after_complete_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ gboolean arg_success, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: success:%d", G_STRFUNC, arg_success); -+ gs_dnf5_helper_set_progress (self, 100); -+} -+ -+static void -+gs_dnf5_transaction_elem_progress_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint64 arg_processed, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' progress:%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, G_STRFUNC, arg_nevra, arg_processed, arg_total); -+ if (arg_total != 0) -+ gs_dnf5_helper_set_progress (self, arg_processed * 100 / arg_total); -+} -+ -+static void -+gs_dnf5_transaction_action_progress_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint64 arg_processed, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' progress:%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, G_STRFUNC, arg_nevra, arg_processed, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_action_start_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint arg_action, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' action:%u total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_nevra, arg_action, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_action_stop_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_nevra, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_script_error_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint arg_scriptlet_type, -+ guint64 arg_return_code, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' scriptlet-type:%u return-code:%" G_GUINT64_FORMAT, G_STRFUNC, arg_nevra, arg_scriptlet_type, arg_return_code); -+} -+ -+static void -+gs_dnf5_transaction_script_start_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint arg_scriptlet_type, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' scriptlet-type:%u", G_STRFUNC, arg_nevra, arg_scriptlet_type); -+} -+ -+static void -+gs_dnf5_transaction_script_stop_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ guint arg_scriptlet_type, -+ guint64 arg_return_code, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s' scriptlet-type:%u return-code:%" G_GUINT64_FORMAT, G_STRFUNC, arg_nevra, arg_scriptlet_type, arg_return_code); -+} -+ -+static void -+gs_dnf5_transaction_transaction_progress_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_processed, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: progress:%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, G_STRFUNC, arg_processed, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_transaction_start_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_transaction_stop_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_unpack_error_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_nevra, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: nevra:'%s'", G_STRFUNC, arg_nevra); -+} -+ -+static void -+gs_dnf5_transaction_verify_progress_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_processed, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: progress:%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT, G_STRFUNC, arg_processed, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_verify_start_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_total); -+} -+ -+static void -+gs_dnf5_transaction_verify_stop_cb (GsDnf5RpmRpm *object, -+ const gchar *arg_session_object_path, -+ guint64 arg_total, -+ GsDnf5ProgressHelper *self) -+{ -+ if (g_strcmp0 (self->session_object_path, arg_session_object_path) != 0) -+ return; -+ g_debug ("%s: total:%" G_GUINT64_FORMAT, G_STRFUNC, arg_total); -+} -+ -+static void -+gs_dnf5_progress_helper_dispose (GObject *object) -+{ -+ GsDnf5ProgressHelper *self = GS_DNF5_PROGRESS_HELPER (object); -+ GObject *base_proxy = G_OBJECT (self->base_proxy); -+ GObject *rpm_proxy = G_OBJECT (self->rpm_proxy); -+ -+ #define clear_signal_handler(_proxy, _id) G_STMT_START { \ -+ if (_id) { \ -+ g_signal_handler_disconnect (_proxy, _id); \ -+ _id = 0; \ -+ } \ -+ } G_STMT_END -+ -+ clear_signal_handler (base_proxy, self->download_add_new_id); -+ clear_signal_handler (base_proxy, self->download_progress_id); -+ clear_signal_handler (base_proxy, self->download_mirror_failure_id); -+ clear_signal_handler (base_proxy, self->download_end_id); -+ -+ clear_signal_handler (rpm_proxy, self->transaction_before_begin_id); -+ clear_signal_handler (rpm_proxy, self->transaction_after_complete_id); -+ clear_signal_handler (rpm_proxy, self->transaction_elem_progress_id); -+ clear_signal_handler (rpm_proxy, self->transaction_action_progress_id); -+ clear_signal_handler (rpm_proxy, self->transaction_action_start_id); -+ clear_signal_handler (rpm_proxy, self->transaction_action_stop_id); -+ clear_signal_handler (rpm_proxy, self->transaction_script_error_id); -+ clear_signal_handler (rpm_proxy, self->transaction_script_start_id); -+ clear_signal_handler (rpm_proxy, self->transaction_script_stop_id); -+ clear_signal_handler (rpm_proxy, self->transaction_transaction_progress_id); -+ clear_signal_handler (rpm_proxy, self->transaction_transaction_start_id); -+ clear_signal_handler (rpm_proxy, self->transaction_transaction_stop_id); -+ clear_signal_handler (rpm_proxy, self->transaction_unpack_error_id); -+ clear_signal_handler (rpm_proxy, self->transaction_verify_progress_id); -+ clear_signal_handler (rpm_proxy, self->transaction_verify_start_id); -+ clear_signal_handler (rpm_proxy, self->transaction_verify_stop_id); -+ -+ #undef clear_signal_handler -+ -+ gs_dnf5_helper_set_progress (self, GS_APP_PROGRESS_UNKNOWN); -+ -+ G_OBJECT_CLASS (gs_dnf5_progress_helper_parent_class)->dispose (object); -+} -+ -+static void -+gs_dnf5_progress_helper_finalize (GObject *object) -+{ -+ GsDnf5ProgressHelper *self = GS_DNF5_PROGRESS_HELPER (object); -+ -+ g_clear_object (&self->base_proxy); -+ g_clear_object (&self->rpm_proxy); -+ g_clear_object (&self->app); -+ g_clear_object (&self->list); -+ g_clear_pointer (&self->session_object_path, g_free); -+ g_clear_pointer (&self->ongoing_downloads, g_hash_table_destroy); -+ -+ G_OBJECT_CLASS (gs_dnf5_progress_helper_parent_class)->finalize (object); -+} -+ -+static void -+gs_dnf5_progress_helper_class_init (GsDnf5ProgressHelperClass *klass) -+{ -+ GObjectClass *object_class = G_OBJECT_CLASS (klass); -+ -+ object_class->dispose = gs_dnf5_progress_helper_dispose; -+ object_class->finalize = gs_dnf5_progress_helper_finalize; -+} -+ -+static void -+gs_dnf5_progress_helper_init (GsDnf5ProgressHelper *self) -+{ -+ self->ongoing_downloads = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); -+} -+ -+GsDnf5ProgressHelper * -+gs_dnf5_progress_helper_new (GsDnf5Base *base_proxy, -+ GsDnf5RpmRpm *rpm_proxy, -+ GsApp *progress_app, -+ GsAppList *progress_list, -+ const gchar *session_object_path) -+{ -+ GsDnf5ProgressHelper *self; -+ -+ g_return_val_if_fail (GS_DNF5_IS_BASE (base_proxy), NULL); -+ g_return_val_if_fail (GS_DNF5_IS_RPM_RPM (rpm_proxy), NULL); -+ g_return_val_if_fail (GS_IS_APP (progress_app) || GS_IS_APP_LIST (progress_list), NULL); -+ g_return_val_if_fail (session_object_path != NULL, NULL); -+ -+ self = g_object_new (GS_TYPE_DNF5_PROGRESS_HELPER, NULL); -+ self->base_proxy = g_object_ref (base_proxy); -+ self->rpm_proxy = g_object_ref (rpm_proxy); -+ self->app = progress_app ? g_object_ref (progress_app) : NULL; -+ self->list = progress_list ? g_object_ref (progress_list) : NULL; -+ self->session_object_path = g_strdup (session_object_path); -+ -+ gs_dnf5_helper_set_progress (self, GS_APP_PROGRESS_UNKNOWN); -+ -+ self->download_add_new_id = -+ g_signal_connect_object (base_proxy, "download_add_new", -+ G_CALLBACK (gs_dnf5_download_add_new_cb), self, 0); -+ self->download_progress_id = -+ g_signal_connect_object (base_proxy, "download_progress", -+ G_CALLBACK (gs_dnf5_download_progress_cb), self, 0); -+ self->download_mirror_failure_id = -+ g_signal_connect_object (base_proxy, "download_mirror_failure", -+ G_CALLBACK (gs_dnf5_download_mirror_failure_cb), self, 0); -+ self->download_end_id = -+ g_signal_connect_object (base_proxy, "download_end", -+ G_CALLBACK (gs_dnf5_download_end_cb), self, 0); -+ -+ self->transaction_before_begin_id = -+ g_signal_connect_object (rpm_proxy, "transaction_before_begin", -+ G_CALLBACK (gs_dnf5_transaction_before_begin_cb), self, 0); -+ self->transaction_after_complete_id = -+ g_signal_connect_object (rpm_proxy, "transaction_after_complete", -+ G_CALLBACK (gs_dnf5_transaction_after_complete_cb), self, 0); -+ self->transaction_elem_progress_id = -+ g_signal_connect_object (rpm_proxy, "transaction_elem_progress", -+ G_CALLBACK (gs_dnf5_transaction_elem_progress_cb), self, 0); -+ self->transaction_action_progress_id = -+ g_signal_connect_object (rpm_proxy, "transaction_action_progress", -+ G_CALLBACK (gs_dnf5_transaction_action_progress_cb), self, 0); -+ self->transaction_action_start_id = -+ g_signal_connect_object (rpm_proxy, "transaction_action_start", -+ G_CALLBACK (gs_dnf5_transaction_action_start_cb), self, 0); -+ self->transaction_action_stop_id = -+ g_signal_connect_object (rpm_proxy, "transaction_action_stop", -+ G_CALLBACK (gs_dnf5_transaction_action_stop_cb), self, 0); -+ self->transaction_script_error_id = -+ g_signal_connect_object (rpm_proxy, "transaction_script_error", -+ G_CALLBACK (gs_dnf5_transaction_script_error_cb), self, 0); -+ self->transaction_script_start_id = -+ g_signal_connect_object (rpm_proxy, "transaction_script_start", -+ G_CALLBACK (gs_dnf5_transaction_script_start_cb), self, 0); -+ self->transaction_script_stop_id = -+ g_signal_connect_object (rpm_proxy, "transaction_script_stop", -+ G_CALLBACK (gs_dnf5_transaction_script_stop_cb), self, 0); -+ self->transaction_transaction_progress_id = -+ g_signal_connect_object (rpm_proxy, "transaction_transaction_progress", -+ G_CALLBACK (gs_dnf5_transaction_transaction_progress_cb), self, 0); -+ self->transaction_transaction_start_id = -+ g_signal_connect_object (rpm_proxy, "transaction_transaction_start", -+ G_CALLBACK (gs_dnf5_transaction_transaction_start_cb), self, 0); -+ self->transaction_transaction_stop_id = -+ g_signal_connect_object (rpm_proxy, "transaction_transaction_stop", -+ G_CALLBACK (gs_dnf5_transaction_transaction_stop_cb), self, 0); -+ self->transaction_unpack_error_id = -+ g_signal_connect_object (rpm_proxy, "transaction_unpack_error", -+ G_CALLBACK (gs_dnf5_transaction_unpack_error_cb), self, 0); -+ self->transaction_verify_progress_id = -+ g_signal_connect_object (rpm_proxy, "transaction_verify_progress", -+ G_CALLBACK (gs_dnf5_transaction_verify_progress_cb), self, 0); -+ self->transaction_verify_start_id = -+ g_signal_connect_object (rpm_proxy, "transaction_verify_start", -+ G_CALLBACK (gs_dnf5_transaction_verify_start_cb), self, 0); -+ self->transaction_verify_stop_id = -+ g_signal_connect_object (rpm_proxy, "transaction_verify_stop", -+ G_CALLBACK (gs_dnf5_transaction_verify_stop_cb), self, 0); -+ -+ return self; -+} -diff --git a/plugins/dnf5/gs-dnf5-progress-helper.h b/plugins/dnf5/gs-dnf5-progress-helper.h -new file mode 100644 -index 000000000..5d522468c ---- /dev/null -+++ b/plugins/dnf5/gs-dnf5-progress-helper.h -@@ -0,0 +1,31 @@ -+/* -*- 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+ -+ */ -+ -+#pragma once -+ -+#include -+#include -+ -+#include -+ -+#include "gs-dnf5-generated.h" -+ -+G_BEGIN_DECLS -+ -+#define GS_TYPE_DNF5_PROGRESS_HELPER (gs_dnf5_progress_helper_get_type ()) -+ -+G_DECLARE_FINAL_TYPE (GsDnf5ProgressHelper, gs_dnf5_progress_helper, GS, DNF5_PROGRESS_HELPER, GObject) -+ -+GsDnf5ProgressHelper * -+ gs_dnf5_progress_helper_new (GsDnf5Base *base_proxy, -+ GsDnf5RpmRpm *rpm_proxy, -+ GsApp *progress_app, -+ GsAppList *progress_list, -+ const gchar *session_object_path); -+ -+G_END_DECLS -diff --git a/plugins/dnf5/gs-plugin-dnf5.c b/plugins/dnf5/gs-plugin-dnf5.c -new file mode 100644 -index 000000000..82f3e19a8 ---- /dev/null -+++ b/plugins/dnf5/gs-plugin-dnf5.c -@@ -0,0 +1,4589 @@ -+/* -*- 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+ -+ */ -+ -+/* -+ * SECTION: dnf5 plugin. -+ * -+ * Talks to dnf5 to manage packages through dnf. -+ */ -+ -+#include -+ -+#include -+#include -+ -+#include "gs-app-private.h" -+ -+#include "gs-worker-thread.h" -+#include "gs-dnf5-generated.h" -+#include "gs-dnf5-rpm-generated.h" -+#include "gs-dnf5-progress-helper.h" -+ -+#include "gs-plugin-dnf5.h" -+ -+#include "../packagekit/gs-markdown.h" -+ -+#define GS_DNF5_RELEASEVER_DEFAULT NULL -+#define GS_DNF5_INTERFACE_RPM_DNF "org.rpm.dnf.v0" -+#define GS_DNF5_OBJECT_PATH_RPM_DNF "/org/rpm/dnf/v0" -+ -+#define GS_DNF5_ADVISORIES_KEY "gs-dnf5::advisories" -+#define GS_DNF5_CHANGELOGS_KEY "gs-dnf5::changelogs" -+ -+/* for how long a session can be left opened until it's auto-closed */ -+#define GS_SESSION_LIFETIME_SECS (5 * 60) -+ -+struct _GsPluginDnf5 -+{ -+ GsPlugin parent; -+ -+ GsWorkerThread *worker; /* (owned) */ -+ GDBusConnection *connection; -+ GsDnf5RpmTransaction *rpm_transaction_proxy; -+ guint rpm_transaction_watch_id; -+ gint calling_rpm; -+ -+ struct _SessionData { -+ GMutex mutex; -+ GCond cond; -+ guint autoclose_timer; -+ GsDnf5SessionManager *proxy; -+ gchar *object_path; -+ guint n_used; -+ gint needs_reset; -+ } session_data; -+ -+ struct _DependencySizesData { -+ GMutex mutex; -+ GPtrArray *apps; /* (element-type GsApp) */ -+ GCancellable *cancellable; /* non-NULL, when an op is running */ -+ } dependency_sizes_data; -+}; -+ -+G_DEFINE_TYPE (GsPluginDnf5, gs_plugin_dnf5, GS_TYPE_PLUGIN) -+ -+#define assert_in_worker(self) \ -+ g_assert (gs_worker_thread_is_in_worker_context (self->worker)) -+ -+static gint -+gs_dnf5_get_priority_for_interactivity (gboolean interactive) -+{ -+ return interactive ? G_PRIORITY_DEFAULT : G_PRIORITY_LOW; -+} -+ -+static void -+gs_dnf5_convert_error (GError **error) -+{ -+ gboolean not_authorized; -+ if (error == NULL || *error == NULL) -+ return; -+ -+ /* The message is not localized in the dnf5, thus this should work */ -+ not_authorized = strstr ((*error)->message, "GDBus.Error:org.rpm.dnf.v0.Error: Not authorized") != NULL; -+ -+ g_dbus_error_strip_remote_error (*error); -+ gs_utils_error_convert_gdbus (error); -+ if (not_authorized && g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)) { -+ (*error)->domain = GS_PLUGIN_ERROR; -+ (*error)->code = GS_PLUGIN_ERROR_AUTH_REQUIRED; -+ } else if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR) || ( -+ !gs_utils_error_convert_gdbus (error) && -+ !gs_utils_error_convert_gio (error))) { -+ (*error)->domain = GS_PLUGIN_ERROR; -+ (*error)->code = GS_PLUGIN_ERROR_FAILED; -+ } -+} -+ -+static void -+gs_dnf5_report_error (GsPluginDnf5 *self, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GsApp *app, -+ const GError *error, -+ gboolean interactive) -+{ -+ g_autoptr(GsPluginEvent) event = NULL; -+ -+ if (!event_callback) -+ return; -+ -+ event = gs_plugin_event_new ("error", error, -+ app ? "app" : NULL, app, -+ NULL); -+ if (interactive) -+ gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_INTERACTIVE); -+ gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING); -+ event_callback (GS_PLUGIN (self), event, event_user_data); -+} -+ -+/* The session reset is used to refresh information about the packages, -+ like after a package install/uninstall, it might not be noticed by -+ the daemon internal structures that the package is installed/uninstalled. */ -+static void -+gs_dnf5_mark_session_needs_reset (GsPluginDnf5 *self) -+{ -+ g_atomic_int_set (&self->session_data.needs_reset, 1); -+} -+ -+static void -+gs_dnf5_rpm_start_transaction_cb (GsDnf5RpmTransaction *proxy, -+ const gchar *dbcookie, -+ guint transaction_id, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = user_data; -+ -+ g_debug ("%s: %p: dbcookie:'%s' transaction_id:%u", G_STRFUNC, self, dbcookie, transaction_id); -+} -+ -+static void -+gs_dnf5_rpm_end_transaction_cb (GsDnf5RpmTransaction *proxy, -+ const gchar *dbcookie, -+ guint transaction_id, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = user_data; -+ -+ g_debug ("%s: %p: dbcookie:'%s' transaction_id:%u", G_STRFUNC, self, dbcookie, transaction_id); -+ -+ /* the transactions are for install/uninstall, thus -+ mark the connection to require a reset() */ -+ gs_dnf5_mark_session_needs_reset (self); -+ -+ /* also do not know what precisely changed, thus a heavy hammer */ -+ gs_plugin_reload (GS_PLUGIN (self)); -+} -+ -+static void -+gs_dnf5_got_rpm_transaction_proxy_cb (GObject *source_object, -+ GAsyncResult *result, -+ gpointer user_data) -+{ -+ g_autoptr(GsPluginDnf5) self = user_data; -+ g_autoptr(GError) local_error = NULL; -+ -+ self->rpm_transaction_proxy = gs_dnf5_rpm_transaction_proxy_new_finish (result, &local_error); -+ -+ if (self->rpm_transaction_proxy != NULL) { -+ g_debug ("Opened RPM Transaction D-Bus proxy"); -+ -+ g_signal_connect_object (self->rpm_transaction_proxy, "start-transaction", -+ G_CALLBACK (gs_dnf5_rpm_start_transaction_cb), self, 0); -+ g_signal_connect_object (self->rpm_transaction_proxy, "end-transaction", -+ G_CALLBACK (gs_dnf5_rpm_end_transaction_cb), self, 0); -+ } else { -+ g_debug ("Failed to open RPM Transaction proxy: %s", local_error ? local_error->message : "Unknown error"); -+ } -+} -+ -+static void -+gs_dnf5_rpm_transaction_appeared_cb (GDBusConnection *connection, -+ const gchar *name, -+ const gchar *name_owner, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = user_data; -+ -+ g_debug ("%s: name:'%s' owner:'%s' existing-proxy:%p calling_rpm:%d", -+ G_STRFUNC, name, name_owner, self->rpm_transaction_proxy, -+ g_atomic_int_get (&self->calling_rpm)); -+ -+ if (name_owner != NULL && *name_owner != '\0' && !g_atomic_int_get (&self->calling_rpm)) { -+ g_clear_object (&self->rpm_transaction_proxy); -+ gs_dnf5_rpm_transaction_proxy_new (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ "org.rpm.announce", -+ "/org/rpm/Transaction", -+ NULL, -+ gs_dnf5_got_rpm_transaction_proxy_cb, -+ g_object_ref (self)); -+ } -+} -+ -+static void -+gs_dnf5_rpm_transaction_vanished_cb (GDBusConnection *connection, -+ const gchar *name, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = user_data; -+ -+ g_debug ("%s: name:'%s' existing-proxy:%p", G_STRFUNC, name, self->rpm_transaction_proxy); -+ -+ g_clear_object (&self->rpm_transaction_proxy); -+} -+ -+static void -+gs_dnf5_bus_get_cb (GObject *source_object, -+ GAsyncResult *result, -+ gpointer user_data) -+{ -+ g_autoptr(GTask) task = user_data; -+ g_autoptr(GError) local_error = NULL; -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (g_task_get_source_object (task)); -+ -+ self->connection = g_bus_get_finish (result, &local_error); -+ gs_dnf5_convert_error (&local_error); -+ -+ if (self->connection != NULL) { -+ self->rpm_transaction_watch_id = g_bus_watch_name_on_connection (self->connection, -+ "org.rpm.announce", -+ G_BUS_NAME_WATCHER_FLAGS_NONE, -+ gs_dnf5_rpm_transaction_appeared_cb, -+ gs_dnf5_rpm_transaction_vanished_cb, -+ self, -+ NULL); -+ } -+ -+ if (self->connection != NULL) -+ g_task_return_boolean (task, TRUE); -+ else -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+} -+ -+static void -+gs_plugin_dnf5_setup_async (GsPlugin *plugin, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ g_autoptr(GTask) task = NULL; -+ -+ g_debug ("dnf5 setup"); -+ -+ /* Start up a worker thread to process all the plugin’s function calls. */ -+ self->worker = gs_worker_thread_new ("gs-plugin-dnf5"); -+ -+ task = g_task_new (plugin, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_setup_async); -+ -+ g_bus_get (G_BUS_TYPE_SYSTEM, cancellable, gs_dnf5_bus_get_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_setup_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+static void -+gs_dnf5_shutdown_cb (GObject *source_object, -+ GAsyncResult *result, -+ gpointer user_data) -+{ -+ g_autoptr(GTask) task = user_data; -+ GsPluginDnf5 *self = g_task_get_source_object (task); -+ g_autoptr(GsWorkerThread) worker = NULL; -+ g_autoptr(GError) local_error = NULL; -+ -+ worker = g_steal_pointer (&self->worker); -+ -+ if (!gs_worker_thread_shutdown_finish (worker, result, &local_error)) -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ else -+ g_task_return_boolean (task, TRUE); -+} -+ -+static void -+gs_plugin_dnf5_shutdown_async (GsPlugin *plugin, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ g_autoptr(GTask) task = NULL; -+ g_autoptr(GCancellable) dependency_sizes_cancellable = NULL; -+ -+ task = g_task_new (plugin, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_shutdown_async); -+ -+ /* stop any dependency sizes ongoing operation */ -+ g_mutex_lock (&self->dependency_sizes_data.mutex); -+ if (self->dependency_sizes_data.cancellable) -+ dependency_sizes_cancellable = g_object_ref (self->dependency_sizes_data.cancellable); -+ g_mutex_unlock (&self->dependency_sizes_data.mutex); -+ if (dependency_sizes_cancellable != NULL) -+ g_cancellable_cancel (dependency_sizes_cancellable); -+ -+ /* Stop the worker thread. */ -+ gs_worker_thread_shutdown_async (self->worker, cancellable, gs_dnf5_shutdown_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_shutdown_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+static void -+gs_dnf5_close_session_real (GsDnf5SessionManager *proxy, -+ const gchar *session_path) -+{ -+ g_autoptr(GError) local_error = NULL; -+ gboolean result = FALSE; -+ -+ /* Not passing operation's cancellable, because it can be cancelled by the user, -+ but the session needs to be closed. */ -+ if (gs_dnf5_session_manager_call_close_session_sync (proxy, session_path, &result, NULL, &local_error)) -+ g_warn_if_fail (result); -+ else -+ g_debug ("Failed to close session: %s", local_error->message); -+} -+ -+static gboolean -+gs_dnf5_autoclose_session_cb (gpointer user_data) -+{ -+ GsPluginDnf5 *self = user_data; -+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->session_data.mutex); -+ -+ self->session_data.autoclose_timer = 0; -+ if (self->session_data.n_used == 0 && self->session_data.proxy != NULL) { -+ gs_dnf5_close_session_real (self->session_data.proxy, self->session_data.object_path); -+ -+ g_clear_object (&self->session_data.proxy); -+ g_clear_pointer (&self->session_data.object_path, g_free); -+ } -+ -+ return G_SOURCE_REMOVE; -+} -+ -+static gboolean -+gs_dnf5_reset_session_sync (GsPluginDnf5 *self, -+ const gchar *session_path, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autoptr(GsDnf5Base) base_proxy = NULL; -+ g_autofree gchar *error_msg = NULL; -+ gboolean success = FALSE; -+ -+ base_proxy = gs_dnf5_base_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ if (base_proxy == NULL) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Base proxy: "); -+ return FALSE; -+ } -+ -+ if (!gs_dnf5_base_call_reset_sync (base_proxy, &success, &error_msg, cancellable, error)) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to call Base::reset(): "); -+ return FALSE; -+ } -+ -+ if (!success) { -+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to execute Base::reset(): %s", error_msg ? error_msg : "Unknown error"); -+ return FALSE; -+ } -+ -+ return TRUE; -+} -+ -+static gchar * /* (transfer full) */ -+gs_dnf5_open_session (GsPluginDnf5 *self, -+ const gchar *releasever, -+ GsDnf5SessionManager **out_proxy, /* (transfer full) */ -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->session_data.mutex); -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autofree gchar *session_path = NULL; -+ -+ if (releasever == GS_DNF5_RELEASEVER_DEFAULT) { -+ if (self->session_data.autoclose_timer) { -+ g_source_remove (self->session_data.autoclose_timer); -+ self->session_data.autoclose_timer = 0; -+ } -+ -+ if (self->session_data.proxy != NULL) { -+ GsDnf5SessionManager *proxy = self->session_data.proxy; -+ -+ while (g_atomic_int_get (&self->session_data.needs_reset) && -+ !g_cancellable_is_cancelled (cancellable)) { -+ g_autoptr(GError) local_error = NULL; -+ if (self->session_data.n_used > 0) { -+ g_debug ("Existing session needs reset, waiting for opened operations to finish"); -+ while (self->session_data.n_used && g_atomic_int_get (&self->session_data.needs_reset)) { -+ g_cond_wait_until (&self->session_data.cond, &self->session_data.mutex, -+ /* check whether was cancelled up to three times per second */ -+ g_get_monotonic_time () + (G_TIME_SPAN_SECOND / 3)); -+ -+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) -+ return NULL; -+ } -+ } -+ -+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) -+ return NULL; -+ -+ if (g_atomic_int_get (&self->session_data.needs_reset)) { -+ gboolean success; -+ -+ success = gs_dnf5_reset_session_sync (self, self->session_data.object_path, cancellable, &local_error); -+ g_atomic_int_set (&self->session_data.needs_reset, 0); -+ -+ /* let any waiter for session reset know the conditions changed */ -+ g_cond_broadcast (&self->session_data.cond); -+ -+ if (success) { -+ g_debug ("Successfully reset existing session"); -+ } else { -+ g_debug ("Failed to reset existing session: %s", local_error ? local_error->message : "Unknown error"); -+ break; -+ } -+ } else { -+ g_debug ("Wanted to reset the session, but someone else did that already, thus skipping the reset"); -+ break; -+ } -+ } -+ -+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) -+ return NULL; -+ -+ if (proxy == self->session_data.proxy) { -+ self->session_data.n_used++; -+ g_debug ("Using existing session"); -+ *out_proxy = g_object_ref (self->session_data.proxy); -+ return g_strdup (self->session_data.object_path); -+ } -+ } -+ } -+ -+ g_debug ("Creating new session"); -+ *out_proxy = gs_dnf5_session_manager_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_NONE, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ GS_DNF5_OBJECT_PATH_RPM_DNF, -+ cancellable, -+ error); -+ -+ if (*out_proxy == NULL) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to get Session Manager: "); -+ return NULL; -+ } -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ -+ if (releasever != GS_DNF5_RELEASEVER_DEFAULT) { -+ g_variant_builder_add (options_builder, "{sv}", "releasever", -+ g_variant_new_string (releasever)); -+ } else { -+ g_autoptr(GVariantBuilder) config = NULL; -+ -+ config = g_variant_builder_new (G_VARIANT_TYPE ("a{ss}")); -+ g_variant_builder_add (config, "{ss}", "optional_metadata_types", -+ "appstream"); -+ -+ g_variant_builder_add (options_builder, "{sv}", "config", -+ g_variant_builder_end (config)); -+ } -+ -+ if (!gs_dnf5_session_manager_call_open_session_sync (*out_proxy, -+ g_variant_builder_end (options_builder), -+ &session_path, -+ cancellable, -+ error)) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to open session: "); -+ return NULL; -+ } -+ -+ if (releasever == GS_DNF5_RELEASEVER_DEFAULT) { -+ g_assert (self->session_data.proxy == NULL); -+ self->session_data.proxy = g_object_ref (*out_proxy); -+ self->session_data.object_path = g_strdup (session_path); -+ self->session_data.n_used = 1; -+ } -+ -+ return g_steal_pointer (&session_path); -+} -+ -+static void -+gs_dnf5_close_session (GsPluginDnf5 *self, -+ GsDnf5SessionManager *proxy, -+ const gchar *session_path) -+{ -+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->session_data.mutex); -+ -+ if (self->session_data.proxy == proxy) { -+ g_assert (self->session_data.n_used > 0); -+ self->session_data.n_used--; -+ -+ /* let any waiter for session reset know the n_used changed */ -+ g_cond_broadcast (&self->session_data.cond); -+ -+ if (self->session_data.n_used == 0) { -+ g_assert (self->session_data.autoclose_timer == 0); -+ self->session_data.autoclose_timer = g_timeout_add_seconds (GS_SESSION_LIFETIME_SECS, gs_dnf5_autoclose_session_cb, self); -+ } -+ } else { -+ gs_dnf5_close_session_real (proxy, session_path); -+ } -+} -+ -+/* Returns whether to continue */ -+typedef gboolean (* GsDnf5ForeachFunc) (GsPluginDnf5 *self, -+ GVariant *value, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error); -+ -+static gboolean -+gs_dnf5_foreach_item (GsPluginDnf5 *self, -+ GVariant *array, -+ GsDnf5ForeachFunc func, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GVariantIter iter; -+ GVariant *child; -+ -+ g_variant_iter_init (&iter, array); -+ while (child = g_variant_iter_next_value (&iter), child != NULL) { -+ g_autoptr(GError) local_error = NULL; -+ gboolean done; -+ -+ done = !func (self, child, user_data, cancellable, &local_error); -+ g_variant_unref (child); -+ -+ if (local_error) { -+ g_propagate_error (error, g_steal_pointer (&local_error)); -+ return FALSE; -+ } -+ -+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) -+ return FALSE; -+ -+ if (done) -+ break; -+ } -+ -+ return TRUE; -+} -+ -+static void -+gs_dnf5_app_set_str (GVariantDict *dict, -+ const gchar *key, -+ GsApp *app, -+ void (*set_func) (GsApp *app, -+ const gchar *value)) -+{ -+ g_autoptr(GVariant) value = NULL; -+ -+ value = g_variant_dict_lookup_value (dict, key, G_VARIANT_TYPE_STRING); -+ if (value != NULL) -+ set_func (app, g_variant_get_string (value, NULL)); -+} -+ -+static void -+gs_dnf5_app_set_str2 (GVariantDict *dict, -+ const gchar *key, -+ GsApp *app, -+ void (*set_func) (GsApp *app, -+ GsAppQuality quality, -+ const gchar *value)) -+{ -+ g_autoptr(GVariant) value = NULL; -+ -+ value = g_variant_dict_lookup_value (dict, key, G_VARIANT_TYPE_STRING); -+ if (value != NULL) -+ set_func (app, GS_APP_QUALITY_NORMAL, g_variant_get_string (value, NULL)); -+} -+ -+static void -+gs_dnf5_app_set_size (GVariantDict *dict, -+ const gchar *key, -+ GsApp *app, -+ void (*set_func) (GsApp *app, -+ GsSizeType size_type, -+ guint64 size_bytes)) -+{ -+ g_autoptr(GVariant) value = NULL; -+ -+ value = g_variant_dict_lookup_value (dict, key, G_VARIANT_TYPE_UINT64); -+ if (value != NULL) -+ set_func (app, GS_SIZE_TYPE_VALID, g_variant_get_uint64 (value)); -+} -+ -+static gchar * -+gs_dnf5_dup_version_from_dict (GVariantDict *dict) -+{ -+ g_autoptr(GVariant) version = NULL; -+ -+ version = g_variant_dict_lookup_value (dict, "version", G_VARIANT_TYPE_STRING); -+ if (version != NULL) { -+ const gchar *version_str = g_variant_get_string (version, NULL); -+ if (version_str != NULL && *version_str != '\0') { -+ g_autoptr(GVariant) release = NULL; -+ const gchar *release_str = NULL; -+ release = g_variant_dict_lookup_value (dict, "release", G_VARIANT_TYPE_STRING); -+ if (release != NULL) -+ release_str = g_variant_get_string (release, NULL); -+ if (release_str != NULL && *release_str != '\0') { -+ g_autofree gchar *version_release_str = g_strconcat (version_str, "-", release_str, NULL); -+ return g_steal_pointer (&version_release_str); -+ } else { -+ return g_strdup (version_str); -+ } -+ } -+ } -+ -+ return NULL; -+} -+ -+static void -+gs_dnf5_app_set_version (GVariantDict *dict, -+ GsApp *app, -+ void (*set_func) (GsApp *app, -+ const gchar *value)) -+{ -+ g_autofree gchar *ver_str = gs_dnf5_dup_version_from_dict (dict); -+ if (ver_str != NULL) -+ set_func (app, ver_str); -+} -+ -+static void -+gs_plugin_dnf5_set_packaging_format (GsApp *app) -+{ -+ gs_app_set_metadata (app, "GnomeSoftware::PackagingFormat", "RPM"); -+ gs_app_set_metadata (app, "GnomeSoftware::PackagingBaseCssColor", "error_color"); -+} -+ -+static void -+gs_dnf5_update_app_state (GsApp *app, -+ GsAppState state, -+ GVariantDict *dict) -+{ -+ if (state == GS_APP_STATE_UNKNOWN) { -+ g_autoptr(GVariant) value = NULL; -+ value = g_variant_dict_lookup_value (dict, "is_installed", G_VARIANT_TYPE_BOOLEAN); -+ if (value == NULL) -+ return; -+ if (g_variant_get_boolean (value)) -+ state = GS_APP_STATE_INSTALLED; -+ else -+ state = GS_APP_STATE_AVAILABLE; -+ } -+ /* The state can be only installed, available or updatable */ -+ if (gs_app_get_state (app) == state || -+ gs_app_get_state (app) == GS_APP_STATE_UPDATABLE) -+ return; -+ if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) -+ gs_app_set_state (app, state); -+ else if (gs_app_get_state (app) != GS_APP_STATE_INSTALLED || -+ state == GS_APP_STATE_UPDATABLE) -+ gs_app_set_state (app, state); -+} -+ -+typedef enum { -+ GS_DNF5_READ_PACKAGE_FLAG_NONE = 0, -+ GS_DNF5_READ_PACKAGE_FLAG_CAN_CACHED = 1 << 0, -+ GS_DNF5_READ_PACKAGE_FLAG_CHANGELOGS = 1 << 1 -+} GsDnf5ReadPackageFlags; -+ -+static void -+gs_dnf5_update_app_changelogs (GsApp *app, -+ GsDnf5ReadPackageFlags flags, -+ GVariantDict *dict) -+{ -+ GVariantIter iter; -+ GVariant *child; -+ g_autoptr(GString) changes = NULL; -+ g_autoptr(GVariant) changelogs = NULL; -+ -+ changelogs = g_variant_dict_lookup_value (dict, "changelogs", NULL); -+ if (changelogs == NULL) -+ return; -+ -+ g_variant_iter_init (&iter, changelogs); -+ while (child = g_variant_iter_next_value (&iter), child != NULL) { -+ gint64 when = 0; -+ const gchar *whom = NULL; -+ const gchar *what = NULL; -+ -+ g_variant_get (child, "(x&s&s)", &when, &whom, &what); -+ -+ if (whom != NULL && what != NULL) { -+ g_autofree gchar *tmp = NULL; -+ const gchar *eml_start, *eml_end; -+ -+ /* Hide the email address in the changes */ -+ eml_start = strchr (whom, '<'); -+ eml_end = strrchr (whom, '>'); -+ if (eml_start != NULL && eml_start < eml_end) { -+ if (g_ascii_isspace (eml_end[1])) -+ eml_end++; -+ tmp = g_malloc0 (strlen (whom) + 1); -+ strncpy (tmp, whom, eml_start - whom); -+ strcat (tmp, eml_end + 1); -+ whom = tmp; -+ } -+ -+ if (changes == NULL) -+ changes = g_string_new (""); -+ else -+ g_string_append (changes, "\n\n"); -+ g_string_append (changes, whom); -+ g_string_append_c (changes, '\n'); -+ g_string_append (changes, what); -+ } -+ -+ g_variant_unref (child); -+ } -+ -+ if (changes != NULL) { -+ if ((flags & GS_DNF5_READ_PACKAGE_FLAG_CHANGELOGS) != 0) -+ gs_app_set_update_details_text (app, changes->str); -+ else -+ gs_app_set_metadata (app, GS_DNF5_CHANGELOGS_KEY, changes->str); -+ } -+} -+ -+static GsApp * /* (transfer full) */ -+gs_dnf5_read_package_from_dict (GsPluginDnf5 *self, -+ GVariantDict *dict, -+ GsDnf5ReadPackageFlags flags, -+ GsAppState set_state) -+{ -+ g_autoptr(GsApp) app = NULL; -+ g_autoptr(GVariant) value = NULL; -+ g_autofree gchar *name = NULL; -+ -+ if ((flags & GS_DNF5_READ_PACKAGE_FLAG_CAN_CACHED) != 0) { -+ value = g_variant_dict_lookup_value (dict, "name", G_VARIANT_TYPE_STRING); -+ if (value != NULL) { -+ const gchar *name_const = g_variant_get_string (value, NULL); -+ app = gs_plugin_cache_lookup (GS_PLUGIN (self), name_const); -+ if (app == NULL) -+ name = g_strdup (name_const); -+ g_clear_pointer (&value, g_variant_unref); -+ } -+ } -+ -+ if (app == NULL) { -+ app = gs_app_new (NULL); -+ gs_app_set_management_plugin (app, GS_PLUGIN (self)); -+ gs_app_set_metadata (app, "GnomeSoftware::Creator", gs_plugin_get_name (GS_PLUGIN (self))); -+ gs_plugin_dnf5_set_packaging_format (app); -+ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC); -+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE); -+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM); -+ -+ gs_dnf5_app_set_str (dict, "name", app, gs_app_add_source); -+ gs_dnf5_app_set_str (dict, "nevra", app, gs_app_add_source_id); -+ gs_dnf5_app_set_str2 (dict, "name", app, gs_app_set_name); -+ gs_dnf5_app_set_str2 (dict, "summary", app, gs_app_set_summary); -+ gs_dnf5_app_set_str2 (dict, "description", app, gs_app_set_description); -+ gs_dnf5_app_set_str2 (dict, "license", app, gs_app_set_license); -+ gs_dnf5_app_set_size (dict, "install_size", app, gs_app_set_size_installed); -+ gs_dnf5_app_set_size (dict, "download_size", app, gs_app_set_size_download); -+ -+ value = g_variant_dict_lookup_value (dict, "url", G_VARIANT_TYPE_STRING); -+ if (value != NULL) { -+ gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, g_variant_get_string (value, NULL)); -+ g_clear_pointer (&value, g_variant_unref); -+ } -+ -+ value = g_variant_dict_lookup_value (dict, "buildtime", G_VARIANT_TYPE_UINT64); -+ if (value != NULL) { -+ gs_app_set_install_date (app, (gint64) g_variant_get_uint64 (value)); -+ g_clear_pointer (&value, g_variant_unref); -+ } -+ -+ if ((flags & GS_DNF5_READ_PACKAGE_FLAG_CAN_CACHED) != 0) -+ gs_plugin_cache_add (GS_PLUGIN (self), name, app); -+ } -+ -+ gs_dnf5_update_app_changelogs (app, flags, dict); -+ gs_dnf5_update_app_state (app, set_state, dict); -+ -+ if (set_state == GS_APP_STATE_UPDATABLE) { -+ gs_dnf5_app_set_version (dict, app, gs_app_set_update_version); -+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT); -+ } else { -+ gs_dnf5_app_set_version (dict, app, gs_app_set_version); -+ } -+ -+ return g_steal_pointer (&app); -+} -+ -+typedef struct { -+ GsAppList *list; -+ GsAppState set_state; -+ GHashTable *nevra_to_app; /* (nullable): gchar *nevra ~> GsApp * */ -+ GHashTable *replaces; /* (nullable): GINT_TO_POINTER(pkg-id) ~> gchar *old_version */ -+ GHashTable *used_replaces; /* (nullable): GINT_TO_POINTER(pkg-id) ~> NULL */ -+ gboolean cover_unused_replaces; -+ gint64 timestamp; -+} ReadPackageData; -+ -+static gboolean -+gs_dnf5_read_package_cb (GsPluginDnf5 *self, -+ GVariant *package_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ ReadPackageData *rpd = user_data; -+ GsAppList *list = rpd->list; -+ GHashTable *nevra_to_app = rpd->nevra_to_app; -+ g_autoptr(GsApp) app = NULL; -+ g_autoptr(GVariantDict) dict = NULL; -+ -+ dict = g_variant_dict_new (package_array); -+ -+ app = gs_dnf5_read_package_from_dict (self, dict, GS_DNF5_READ_PACKAGE_FLAG_CAN_CACHED | GS_DNF5_READ_PACKAGE_FLAG_CHANGELOGS, rpd->set_state); -+ -+ if (nevra_to_app != NULL) { -+ g_autoptr(GVariant) value = NULL; -+ value = g_variant_dict_lookup_value (dict, "nevra", G_VARIANT_TYPE_STRING); -+ if (value != NULL) { -+ const gchar *nevra_const = g_variant_get_string (value, NULL); -+ if (nevra_const != NULL && *nevra_const != '\0') -+ g_hash_table_insert (nevra_to_app, g_strdup (nevra_const), g_object_ref (app)); -+ } -+ } -+ -+ gs_app_list_add (list, app); -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_read_repo_cb (GsPluginDnf5 *self, -+ GVariant *package_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GsAppList *list = user_data; -+ g_autoptr(GsApp) app = NULL; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) value = NULL; -+ const gchar *id; -+ -+ dict = g_variant_dict_new (package_array); -+ -+ value = g_variant_dict_lookup_value (dict, "id", G_VARIANT_TYPE_STRING); -+ if (value == NULL) -+ return TRUE; -+ -+ id = g_variant_get_string (value, NULL); -+ if (id == NULL || *id == '\0') -+ return TRUE; -+ -+ /* Skip sub-repos */ -+ if (g_str_has_suffix (id, "-source") || -+ g_str_has_suffix (id, "-debuginfo") || -+ g_str_has_suffix (id, "-testing")) -+ return TRUE; -+ -+ g_clear_pointer (&value, g_variant_unref); -+ id = NULL; -+ -+ app = gs_app_new (NULL); -+ gs_app_set_management_plugin (app, GS_PLUGIN (self)); -+ gs_app_set_kind (app, AS_COMPONENT_KIND_REPOSITORY); -+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE); -+ gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM); -+ gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE); -+ gs_app_set_metadata (app, "GnomeSoftware::Creator", gs_plugin_get_name (GS_PLUGIN (self))); -+ gs_plugin_dnf5_set_packaging_format (app); -+ gs_app_set_metadata (app, "GnomeSoftware::SortKey", "300"); -+ gs_app_set_origin_ui (app, _("Packages")); -+ -+ gs_dnf5_app_set_str (dict, "id", app, gs_app_set_id); -+ gs_dnf5_app_set_str2 (dict, "name", app, gs_app_set_name); -+ gs_dnf5_app_set_str2 (dict, "name", app, gs_app_set_description); -+ -+ value = g_variant_dict_lookup_value (dict, "enabled", G_VARIANT_TYPE_BOOLEAN); -+ if (value != NULL) { -+ gs_app_set_state (app, g_variant_get_boolean (value) ? -+ GS_APP_STATE_INSTALLED : GS_APP_STATE_AVAILABLE); -+ g_clear_pointer (&value, g_variant_unref); -+ } else { -+ gs_app_set_state (app, GS_APP_STATE_INSTALLED); -+ } -+ -+ gs_app_list_add (list, app); -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_refine_from_advisory_packages_cb (GsPluginDnf5 *self, -+ GVariant *array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GHashTable *nevra_to_app = user_data; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) var_nevra = NULL; -+ -+ dict = g_variant_dict_new (array); -+ -+ var_nevra = g_variant_dict_lookup_value (dict, "nevra", G_VARIANT_TYPE_STRING); -+ if (var_nevra != NULL) { -+ const gchar *nevra = g_variant_get_string (var_nevra, NULL); -+ if (nevra != NULL) { -+ GsApp *app; -+ -+ app = g_hash_table_lookup (nevra_to_app, nevra); -+ if (app != NULL) -+ gs_app_set_update_urgency (app, AS_URGENCY_KIND_CRITICAL); -+ } -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_refine_from_advisory_collections_cb (GsPluginDnf5 *self, -+ GVariant *array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GHashTable *nevra_to_app = user_data; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) packages = NULL; -+ gboolean res = TRUE; -+ -+ dict = g_variant_dict_new (array); -+ -+ packages = g_variant_dict_lookup_value (dict, "packages", G_VARIANT_TYPE_ARRAY); -+ if (packages != NULL) -+ res = gs_dnf5_foreach_item (self, packages, gs_dnf5_refine_from_advisory_packages_cb, nevra_to_app, cancellable, error); -+ -+ return res; -+} -+ -+static gboolean -+gs_dnf5_refine_from_advisory_cb (GsPluginDnf5 *self, -+ GVariant *array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GHashTable *nevra_to_app = user_data; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) collections = NULL; -+ g_autoptr(GVariant) severity = NULL; -+ gboolean res = TRUE; -+ -+ dict = g_variant_dict_new (array); -+ -+ /* ensure only advisories with severity 'critical' are traversed */ -+ severity = g_variant_dict_lookup_value (dict, "severity", G_VARIANT_TYPE_STRING); -+ if (severity == NULL || g_variant_get_string (severity, NULL) == NULL || -+ g_ascii_strcasecmp (g_variant_get_string (severity, NULL), "critical") != 0) -+ return TRUE; -+ -+ collections = g_variant_dict_lookup_value (dict, "collections", G_VARIANT_TYPE_ARRAY); -+ if (collections != NULL) -+ res = gs_dnf5_foreach_item (self, collections, gs_dnf5_refine_from_advisory_collections_cb, nevra_to_app, cancellable, error); -+ -+ return res; -+} -+ -+static GVariant * -+gs_dnf5_dup_package_attrs (void) -+{ -+ g_autoptr(GVariantBuilder) attrs_builder = NULL; -+ -+ attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (attrs_builder, "s", "nevra"); -+ g_variant_builder_add (attrs_builder, "s", "name"); -+ g_variant_builder_add (attrs_builder, "s", "epoch"); -+ g_variant_builder_add (attrs_builder, "s", "version"); -+ g_variant_builder_add (attrs_builder, "s", "release"); -+ g_variant_builder_add (attrs_builder, "s", "arch"); -+ g_variant_builder_add (attrs_builder, "s", "repo_id"); -+ g_variant_builder_add (attrs_builder, "s", "install_size"); -+ g_variant_builder_add (attrs_builder, "s", "download_size"); -+ g_variant_builder_add (attrs_builder, "s", "is_installed"); -+ g_variant_builder_add (attrs_builder, "s", "summary"); -+ g_variant_builder_add (attrs_builder, "s", "url"); -+ g_variant_builder_add (attrs_builder, "s", "license"); -+ g_variant_builder_add (attrs_builder, "s", "description"); -+ g_variant_builder_add (attrs_builder, "s", "buildtime"); -+ g_variant_builder_add (attrs_builder, "s", "changelogs"); -+ -+ return g_variant_builder_end (attrs_builder); -+} -+ -+static void -+gs_dnf5_gather_async_result_cb (GObject *source_object, -+ GAsyncResult *result, -+ gpointer user_data) -+{ -+ GAsyncResult **out_result = user_data; -+ *out_result = g_object_ref (result); -+} -+ -+static void -+gs_dnf5_confirm_key_response_cb (GObject *source_object, -+ GAsyncResult *result, -+ gpointer user_data) -+{ -+ g_autoptr(GError) local_error = NULL; -+ -+ if (!gs_dnf5_rpm_repo_call_confirm_key_finish (GS_DNF5_RPM_REPO (source_object), result, &local_error)) -+ g_debug ("Failed to confirm key: %s", local_error->message); -+} -+ -+typedef struct { -+ gchar *title; -+ gchar *msg; -+ gchar *details; -+ gchar *accept_label; -+ gchar *key_id; -+ gchar *key_filename; -+ gchar *session_path; -+ GCancellable *cancellable; /* (owned) */ -+ GsPluginDnf5 *self; /* (owned) */ -+ GWeakRef repo_proxy_weakref; /* GsDnf5RpmRepo */ -+ gboolean interactive; -+ gboolean confirmed_by_repo; -+} QuestionData; -+ -+static void -+question_data_free (QuestionData *data) -+{ -+ if (data != NULL) { -+ g_free (data->title); -+ g_free (data->msg); -+ g_free (data->details); -+ g_free (data->accept_label); -+ g_free (data->key_id); -+ g_free (data->key_filename); -+ g_free (data->session_path); -+ g_clear_object (&data->cancellable); -+ g_clear_object (&data->self); -+ g_weak_ref_clear (&data->repo_proxy_weakref); -+ g_free (data); -+ } -+} -+ -+static gboolean -+gs_dnf5_ask_question_idle_cb (gpointer user_data) -+{ -+ QuestionData *data = user_data; -+ g_autoptr(GsDnf5RpmRepo) repo_proxy = NULL; -+ -+ repo_proxy = g_weak_ref_get (&data->repo_proxy_weakref); -+ if (repo_proxy != NULL) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ gboolean confirmed; -+ -+ /* auto-accept new keys, just as PacakgeKit plugin used to do */ -+ confirmed = data->confirmed_by_repo; -+ if (!confirmed) -+ confirmed = gs_plugin_ask_untrusted (GS_PLUGIN (data->self), data->title, data->msg, data->details, data->accept_label); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (data->interactive)); -+ -+ gs_dnf5_rpm_repo_call_confirm_key_with_options (repo_proxy, -+ data->key_id, -+ confirmed, -+ g_variant_builder_end (options_builder), -+ data->cancellable, -+ gs_dnf5_confirm_key_response_cb, -+ NULL); -+ } -+ -+ return G_SOURCE_REMOVE; -+} -+ -+static gboolean -+gs_dnf5_read_package_repo_ids_cb (GsPluginDnf5 *self, -+ GVariant *package_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GPtrArray **out_repo_ids = user_data; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) value = NULL; -+ -+ dict = g_variant_dict_new (package_array); -+ -+ value = g_variant_dict_lookup_value (dict, "from_repo_id", G_VARIANT_TYPE_STRING); -+ if (value != NULL) { -+ const gchar *repo_id_const = g_variant_get_string (value, NULL); -+ if (repo_id_const != NULL && *repo_id_const != '\0') { -+ if (*out_repo_ids == NULL) -+ *out_repo_ids = g_ptr_array_new_with_free_func (g_free); -+ g_ptr_array_add (*out_repo_ids, g_strdup (repo_id_const)); -+ } -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_read_repo_gpg_info_cb (GsPluginDnf5 *self, -+ GVariant *package_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ guint *out_n_good = user_data; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) id_value = NULL; -+ g_autoptr(GVariant) gpgcheck_value = NULL; -+ -+ dict = g_variant_dict_new (package_array); -+ -+ id_value = g_variant_dict_lookup_value (dict, "id", G_VARIANT_TYPE_STRING); -+ gpgcheck_value = g_variant_dict_lookup_value (dict, "gpgcheck", G_VARIANT_TYPE_BOOLEAN); -+ -+ if (id_value != NULL && gpgcheck_value != NULL) { -+ gboolean gpgcheck = g_variant_get_boolean (gpgcheck_value); -+ -+ if (gpgcheck) -+ *out_n_good = (*out_n_good) + 1; -+ } -+ -+ return TRUE; -+} -+ -+static gpointer -+gs_dnf5_check_key_source_thread (gpointer user_data) -+{ -+ QuestionData *data = user_data; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ g_autoptr(GPtrArray) repo_ids = NULL; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success; -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (data->self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ data->session_path, -+ data->cancellable, -+ &local_error); -+ success = rpm_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Rpm proxy: "); -+ } -+ -+ /* try to get whether the key comes from any package */ -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariantBuilder) package_attrs_builder = NULL; -+ g_autoptr(GVariantBuilder) whatprovides_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ package_attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (package_attrs_builder, "s", "from_repo_id"); -+ -+ whatprovides_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (whatprovides_builder, "s", data->key_filename); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ g_variant_builder_end (package_attrs_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "whatprovides", -+ g_variant_builder_end (whatprovides_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "scope", -+ g_variant_new_string ("installed")); -+ -+ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ data->cancellable, -+ &local_error); -+ -+ if (success) { -+ success = gs_dnf5_foreach_item (data->self, result, gs_dnf5_read_package_repo_ids_cb, &repo_ids, data->cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call rpm::list: "); -+ } -+ } -+ -+ if (success && repo_ids != NULL) { -+ g_autoptr(GVariantBuilder) patterns_builder = NULL; -+ guint n_verify = 0; -+ -+ for (guint i = 0; i < repo_ids->len; i++) { -+ const gchar *repo_id = g_ptr_array_index (repo_ids, i); -+ /* those are special repos, like `@commandline` */ -+ if (*repo_id != '@') { -+ if (patterns_builder == NULL) -+ patterns_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (patterns_builder, "s", repo_id); -+ n_verify++; -+ } -+ } -+ -+ if (patterns_builder != NULL) { -+ g_autoptr(GsDnf5RpmRepo) repo_proxy = NULL; -+ -+ repo_proxy = g_weak_ref_get (&data->repo_proxy_weakref); -+ if (repo_proxy != NULL) { -+ g_autoptr(GVariantBuilder) repo_attrs_builder = NULL; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ repo_attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (repo_attrs_builder, "s", "id"); -+ g_variant_builder_add (repo_attrs_builder, "s", "gpgcheck"); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "patterns", -+ g_variant_builder_end (patterns_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "repo_attrs", -+ g_variant_builder_end (repo_attrs_builder)); -+ -+ success = gs_dnf5_rpm_repo_call_list_sync (repo_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ data->cancellable, -+ &local_error); -+ -+ if (success) { -+ guint n_good = 0; -+ -+ success = gs_dnf5_foreach_item (data->self, result, gs_dnf5_read_repo_gpg_info_cb, &n_good, data->cancellable, &local_error); -+ -+ data->confirmed_by_repo = success && n_verify == n_good; -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call repo::list: "); -+ } -+ } else { -+ success = FALSE; -+ g_set_error_literal (&local_error, G_IO_ERROR, G_IO_ERROR_FAILED, "Repo proxy vanished"); -+ } -+ } -+ } -+ -+ g_idle_add_full (G_PRIORITY_HIGH_IDLE, gs_dnf5_ask_question_idle_cb, data, (GDestroyNotify) question_data_free); -+ -+ return NULL; -+} -+ -+typedef struct { -+ GsPluginDnf5 *self; /* (not owned) */ -+ const gchar *session_path; /* (not owned) */ -+ GsDnf5RpmRepo *repo_proxy; /* (not owned) */ -+ GCancellable *cancellable; /* (not owned) */ -+ gboolean interactive; -+} RepoKeyImportData; -+ -+static void -+gs_dnf5_repo_key_import_request_cb (GsDnf5Base *object, -+ const gchar *arg_session_object_path, -+ const gchar *arg_key_id, -+ const gchar *const *arg_user_ids, -+ const gchar *arg_key_fingerprint, -+ const gchar *arg_key_url, -+ gint64 arg_timestamp, -+ RepoKeyImportData *repo_key_import_data) -+{ -+ QuestionData *data; -+ g_autoptr(GString) user_ids = NULL; -+ g_autofree gchar *key_filename = NULL; -+ g_autoptr(GString) str_fingerprint = NULL; -+ GString *details = NULL; -+ const gchar *fingerprint; -+ const gchar *from; -+ -+ if (g_strcmp0 (repo_key_import_data->session_path, arg_session_object_path) != 0) -+ return; -+ -+ user_ids = g_string_new (NULL); -+ for (guint i = 0; arg_user_ids != NULL && arg_user_ids[i] != NULL; i++) { -+ if (i > 0) -+ g_string_append (user_ids, ", "); -+ g_string_append_c (user_ids, '"'); -+ g_string_append (user_ids, arg_user_ids[i]); -+ g_string_append_c (user_ids, '"'); -+ } -+ -+ g_debug ("%s: key_id:'%s' user_ids:[%s] key_fingerprint:'%s' key_url:'%s' timestamp:%" G_GINT64_FORMAT, -+ G_STRFUNC, arg_key_id, user_ids->str, arg_key_fingerprint, arg_key_url, arg_timestamp); -+ -+ if (g_ascii_strncasecmp (arg_key_url, "file:", 5) == 0) -+ key_filename = g_filename_from_uri (arg_key_url, NULL, NULL); -+ -+ if ((strlen (arg_key_fingerprint) % 4) == 0) { -+ str_fingerprint = g_string_new (arg_key_fingerprint); -+ for (gint i = str_fingerprint->len - 4; i > 0; i -= 4) { -+ g_string_insert_c (str_fingerprint, i, ' '); -+ } -+ } -+ -+ details = g_string_new (""); -+ -+ from = key_filename != NULL ? key_filename : arg_key_url; -+ fingerprint = str_fingerprint != NULL ? str_fingerprint->str : arg_key_fingerprint; -+ -+ #define add_nonempty_line(_format, _value) G_STMT_START { \ -+ if ((_value) != NULL && *(_value) != '\0') { \ -+ g_string_append_printf (details, _format, _value); \ -+ g_string_append_c (details, '\n'); \ -+ } \ -+ } G_STMT_END -+ -+ /* Translators: the '%s' is replaced with the key user name */ -+ add_nonempty_line (_("Key user: %s"), user_ids->str); -+ /* Translators: the '%s' is replaced with the key fingerprint, a few hex digits */ -+ add_nonempty_line (_("Fingerprint: %s"), fingerprint); -+ /* Translators: the '%s' is replaced with the local path or a URI to the key */ -+ add_nonempty_line (_("From: %s"), from); -+ -+ #undef add_nonempty_line -+ -+ data = g_new0 (QuestionData, 1); -+ data->title = g_strdup (_("Import Key")); -+ /* Translators: the '%s' is replaced with the key ID, usually a few hex digits */ -+ data->msg = g_strdup_printf (_("Do you want to import key %s?"), arg_key_id); -+ data->details = g_string_free (details, FALSE); -+ data->accept_label = g_strdup (_("_Import Key")); -+ data->key_id = g_strdup (arg_key_id); -+ data->key_filename = g_steal_pointer (&key_filename); -+ data->session_path = g_strdup (repo_key_import_data->session_path); -+ data->self = g_object_ref (repo_key_import_data->self); -+ data->interactive = repo_key_import_data->interactive; -+ data->confirmed_by_repo = FALSE; -+ g_set_object (&data->cancellable, repo_key_import_data->cancellable); -+ g_weak_ref_init (&data->repo_proxy_weakref, repo_key_import_data->repo_proxy); -+ -+ if (data->key_filename != NULL) -+ g_thread_unref (g_thread_new ("gs-dnf5-check-key-source_thread", gs_dnf5_check_key_source_thread, data)); -+ else -+ g_idle_add_full (G_PRIORITY_HIGH_IDLE, gs_dnf5_ask_question_idle_cb, data, (GDestroyNotify) question_data_free); -+} -+ -+static void -+gs_dnf5_goal_cancelled_cb (GObject *source_object, -+ GAsyncResult *result, -+ gpointer user_data) -+{ -+ g_autoptr(GCancellable) run_cancellable = user_data; -+ g_autoptr(GError) local_error = NULL; -+ g_autofree gchar *error_message = NULL; -+ gboolean succeeded = FALSE; -+ -+ if (gs_dnf5_goal_call_cancel_finish (GS_DNF5_GOAL (source_object), &succeeded, &error_message, result, &local_error)) { -+ if (succeeded) { -+ g_debug ("%s: Transaction cancelled by the user", G_STRFUNC); -+ g_cancellable_cancel (run_cancellable); -+ } else { -+ g_debug ("%s: Cannot cancel transaction: %s", G_STRFUNC, error_message); -+ } -+ } else if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { -+ g_debug ("%s: Failed to cancel transaction: %s", G_STRFUNC, local_error->message); -+ } -+} -+ -+static gboolean -+gs_dnf5_reset_transaction_sync (GsPluginDnf5 *self, -+ const gchar *session_path, -+ GsDnf5RpmRpm *rpm_proxy, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autoptr(GsDnf5Goal) goal_proxy = NULL; -+ -+ goal_proxy = gs_dnf5_goal_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ if (goal_proxy == NULL) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Goal proxy: "); -+ return FALSE; -+ } -+ -+ return gs_dnf5_goal_call_reset_sync (goal_proxy, cancellable, error); -+} -+ -+/* this is needed to receive D-Bus signals about progress in the worker thread */ -+static gboolean -+gs_dnf5_goal_call_do_transaction_sync_wrapper (GsDnf5Goal *proxy, -+ GVariant *arg_options, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autoptr(GMainContext) main_context = g_main_context_ref_thread_default (); -+ g_autoptr(GAsyncResult) async_result = NULL; -+ gs_dnf5_goal_call_do_transaction (proxy, arg_options, cancellable, gs_dnf5_gather_async_result_cb, &async_result); -+ while (!async_result) -+ g_main_context_iteration (main_context, TRUE); -+ return gs_dnf5_goal_call_do_transaction_finish (proxy, async_result, error); -+} -+ -+typedef struct { -+ GsDnf5Goal *goal_proxy; -+ GCancellable *run_cancellable; -+} TransactionData; -+ -+static void -+gs_dnf5_transaction_cancelled_cb (GCancellable *parent_cancellable, -+ TransactionData *transaction_data) -+{ -+ gs_dnf5_goal_call_cancel (transaction_data->goal_proxy, transaction_data->run_cancellable, -+ gs_dnf5_goal_cancelled_cb, g_object_ref (transaction_data->run_cancellable)); -+} -+ -+typedef enum { -+ GS_DNF5_TRANSACTION_FLAG_NONE = 0, -+ GS_DNF5_TRANSACTION_FLAG_OFFLINE = 1 << 0, -+ GS_DNF5_TRANSACTION_FLAG_RESOLVE_ONLY = 1 << 1, -+ GS_DNF5_TRANSACTION_FLAG_DISALLOW_ERASING = 1 << 2, /* negative flag, to not have it everywhere */ -+ GS_DNF5_TRANSACTION_FLAG_INTERACTIVE = 1 << 3, -+ GS_DNF5_TRANSACTION_FLAG_DOWNLOAD_ONLY = 1 << 4 -+} GsDnf5TransactionFlags; -+ -+static gboolean -+gs_dnf5_run_transaction (GsPluginDnf5 *self, -+ const gchar *session_path, -+ GsDnf5RpmRpm *rpm_proxy, -+ GsApp *progress_app, -+ GsAppList *progress_list, -+ GsDnf5TransactionFlags flags, -+ GVariant **out_transaction, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autoptr(GsDnf5Base) base_proxy = NULL; -+ g_autoptr(GsDnf5Goal) goal_proxy = NULL; -+ g_autoptr(GsDnf5RpmRepo) repo_proxy = NULL; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) transaction = NULL; -+ g_autoptr(GsDnf5ProgressHelper) helper = NULL; -+ g_autoptr(GCancellable) run_cancellable = NULL; -+ RepoKeyImportData repo_key_import_data; -+ TransactionData transaction_data; -+ gulong repo_key_import_id; -+ gulong cancellable_id = 0; -+ guint result = 0; -+ gboolean interactive = (flags & GS_DNF5_TRANSACTION_FLAG_INTERACTIVE) != 0; -+ gboolean success; -+ -+ base_proxy = gs_dnf5_base_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ if (base_proxy == NULL) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Base proxy: "); -+ return FALSE; -+ } -+ -+ repo_proxy = gs_dnf5_rpm_repo_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ if (repo_proxy == NULL) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Repo proxy: "); -+ return FALSE; -+ } -+ -+ goal_proxy = gs_dnf5_goal_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ if (goal_proxy == NULL) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Goal proxy: "); -+ return FALSE; -+ } -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (goal_proxy), G_MAXINT); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ -+ g_variant_builder_add (options_builder, "{sv}", "allow_erasing", -+ g_variant_new_boolean ((flags & GS_DNF5_TRANSACTION_FLAG_DISALLOW_ERASING) == 0)); -+ -+ if (progress_app != NULL || progress_list != NULL) -+ helper = gs_dnf5_progress_helper_new (base_proxy, rpm_proxy, progress_app, progress_list, session_path); -+ -+ repo_key_import_data.self = self; -+ repo_key_import_data.session_path = session_path; -+ repo_key_import_data.repo_proxy = repo_proxy; -+ repo_key_import_data.interactive = interactive; -+ repo_key_import_data.cancellable = cancellable; -+ repo_key_import_id = g_signal_connect (base_proxy, "repo_key_import_request", -+ G_CALLBACK (gs_dnf5_repo_key_import_request_cb), &repo_key_import_data); -+ -+ if (!gs_dnf5_goal_call_resolve_sync (goal_proxy, -+ g_variant_builder_end (options_builder), -+ &transaction, -+ &result, -+ cancellable, -+ error)) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to resolve transaction: "); -+ g_signal_handler_disconnect (base_proxy, repo_key_import_id); -+ return FALSE; -+ } -+ -+ if (out_transaction != NULL && transaction != NULL) -+ *out_transaction = g_variant_ref_sink (transaction); -+ -+ /* 0 ... all fine, 1 ... there are warnings, 2 ... failed */ -+ if (result == 2) { -+ g_auto(GStrv) problems = NULL; -+ -+ if (gs_dnf5_goal_call_get_transaction_problems_string_sync (goal_proxy, &problems, cancellable, error) && -+ problems != NULL) { -+ /* extra space at the end, but no harm, it reuses translated string */ -+ GString *msg = g_string_new ("Failed to resolve transaction: "); -+ for (guint i = 0; problems[i] != NULL; i++) { -+ g_string_append_c (msg, '\n'); -+ g_string_append (msg, problems[i]); -+ } -+ g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, msg->str); -+ } else { -+ g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, -+ _("Unknown error")); -+ g_prefix_error (error, "Failed to resolve transaction: "); -+ } -+ g_signal_handler_disconnect (base_proxy, repo_key_import_id); -+ return FALSE; -+ } -+ -+ g_clear_pointer (&options_builder, g_variant_builder_unref); -+ -+ if ((flags & GS_DNF5_TRANSACTION_FLAG_RESOLVE_ONLY) != 0) { -+ g_signal_handler_disconnect (base_proxy, repo_key_import_id); -+ return TRUE; -+ } -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ if ((flags & GS_DNF5_TRANSACTION_FLAG_OFFLINE) != 0) { -+ g_variant_builder_add (options_builder, "{sv}", "offline", -+ g_variant_new_boolean (TRUE)); -+ } -+ if ((flags & GS_DNF5_TRANSACTION_FLAG_DOWNLOAD_ONLY) != 0) { -+ g_variant_builder_add (options_builder, "{sv}", "downloadonly", -+ g_variant_new_boolean (TRUE)); -+ } -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (interactive)); -+ -+ run_cancellable = g_cancellable_new (); -+ if (cancellable != NULL) { -+ transaction_data.goal_proxy = goal_proxy; -+ transaction_data.run_cancellable = run_cancellable; -+ -+ cancellable_id = g_cancellable_connect (cancellable, G_CALLBACK (gs_dnf5_transaction_cancelled_cb), -+ &transaction_data, NULL); -+ } -+ -+ success = gs_dnf5_goal_call_do_transaction_sync_wrapper (goal_proxy, -+ g_variant_builder_end (options_builder), -+ run_cancellable, -+ error); -+ -+ g_cancellable_disconnect (cancellable, cancellable_id); -+ g_signal_handler_disconnect (base_proxy, repo_key_import_id); -+ -+ /* in case there is a pending async call result for the cancel() invocation */ -+ g_cancellable_cancel (run_cancellable); -+ -+ if (!success) { -+ g_auto(GStrv) problems = NULL; -+ -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to run transaction: "); -+ -+ if (error != NULL && *error != NULL && -+ gs_dnf5_goal_call_get_transaction_problems_string_sync (goal_proxy, &problems, cancellable, NULL) && -+ problems != NULL) { -+ GString *msg = g_string_new ((*error)->message); -+ for (guint i = 0; problems[i] != NULL; i++) { -+ g_string_append_c (msg, '\n'); -+ g_string_append (msg, problems[i]); -+ } -+ g_clear_error (error); -+ g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, msg->str); -+ } -+ } -+ -+ return success; -+} -+ -+enum OpKindType { OP_KIND_UNKNOWN, OP_KIND_INSTALL, OP_KIND_UPGRADE, OP_KIND_DOWNGRADE, OP_KIND_REINSTALL, OP_KIND_REMOVE, OP_KIND_REPLACED }; -+ -+static enum OpKindType -+gs_dnf5_get_package_op_kind (GVariant *item_array) -+{ -+ struct _op_kinds { -+ const gchar *name; -+ enum OpKindType kind; -+ } op_kinds[] = { -+ { "Install", OP_KIND_INSTALL }, -+ { "Upgrade", OP_KIND_UPGRADE }, -+ { "Downgrade", OP_KIND_DOWNGRADE }, -+ { "Reinstall", OP_KIND_REINSTALL }, -+ { "Remove", OP_KIND_REMOVE }, -+ { "Replaced", OP_KIND_REPLACED } -+ }; -+ const gchar *str = NULL; -+ -+ g_variant_get_child (item_array, 0, "&s", &str); -+ if (str == NULL || g_ascii_strcasecmp (str, "package") != 0) -+ return OP_KIND_UNKNOWN; -+ -+ g_variant_get_child (item_array, 1, "&s", &str); -+ if (str == NULL) -+ return OP_KIND_UNKNOWN; -+ -+ for (guint i = 0; i < G_N_ELEMENTS (op_kinds); i++) { -+ if (g_ascii_strcasecmp (str, op_kinds[i].name) == 0) { -+ return op_kinds[i].kind; -+ } -+ } -+ -+ return OP_KIND_UNKNOWN; -+} -+ -+static gboolean -+gs_dnf5_gather_update_replaces_cb (GsPluginDnf5 *self, -+ GVariant *item_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ ReadPackageData *rpd = user_data; -+ enum OpKindType op_kind = gs_dnf5_get_package_op_kind (item_array); -+ -+ if (op_kind == OP_KIND_REPLACED && rpd->replaces) { -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) value = NULL; -+ -+ value = g_variant_get_child_value (item_array, 4); -+ dict = g_variant_dict_new (value); -+ g_clear_pointer (&value, g_variant_unref); -+ -+ value = g_variant_dict_lookup_value (dict, "id", G_VARIANT_TYPE_INT32); -+ if (value != NULL) { -+ gint32 id = g_variant_get_int32 (value); -+ gchar *version = gs_dnf5_dup_version_from_dict (dict); -+ if (version != NULL) -+ g_hash_table_insert (rpd->replaces, GINT_TO_POINTER (id), version); -+ } -+ } -+ -+ return TRUE; -+} -+ -+typedef struct _UpdateReplacesData { -+ GsApp *app; -+ GHashTable *replaces; -+ GHashTable *used_replaces; -+} UpdateReplacesData; -+ -+static gboolean -+gs_dnf5_gather_update_replaced_pkgs_cb (GsPluginDnf5 *self, -+ GVariant *item_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ UpdateReplacesData *upd = user_data; -+ gint32 replaces_id = g_variant_get_int32 (item_array); -+ const gchar *old_version = g_hash_table_lookup (upd->replaces, GINT_TO_POINTER (replaces_id)); -+ if (old_version != NULL) { -+ gs_app_set_version (upd->app, old_version); -+ g_hash_table_add (upd->used_replaces, GINT_TO_POINTER (replaces_id)); -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_gather_updates_from_transaction_cb (GsPluginDnf5 *self, -+ GVariant *item_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ ReadPackageData *rpd = user_data; -+ enum OpKindType op_kind = gs_dnf5_get_package_op_kind (item_array); -+ const gchar *str; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) value = NULL; -+ g_autoptr(GsApp) upd_app = NULL; -+ g_autoptr(GString) nevra = NULL; -+ GsApp *known_app; -+ -+ if (op_kind == OP_KIND_UNKNOWN || (!rpd->cover_unused_replaces && op_kind == OP_KIND_REPLACED) || -+ (rpd->cover_unused_replaces && op_kind != OP_KIND_REPLACED)) -+ return TRUE; -+ -+ value = g_variant_get_child_value (item_array, 4); -+ dict = g_variant_dict_new (value); -+ g_clear_pointer (&value, g_variant_unref); -+ -+ if (rpd->cover_unused_replaces) { -+ value = g_variant_dict_lookup_value (dict, "id", G_VARIANT_TYPE_INT32); -+ if (value != NULL) { -+ gint32 id = g_variant_get_int32 (value); -+ if (g_hash_table_contains (rpd->used_replaces, GINT_TO_POINTER (id))) -+ return TRUE; -+ } else { -+ return TRUE; -+ } -+ -+ g_clear_pointer (&value, g_variant_unref); -+ } -+ -+ value = g_variant_dict_lookup_value (dict, "name", G_VARIANT_TYPE_STRING); -+ if (value == NULL) -+ return TRUE; -+ str = g_variant_get_string (value, NULL); -+ if (str == NULL) -+ return TRUE; -+ -+ nevra = g_string_new (str); -+ -+ g_clear_pointer (&value, g_variant_unref); -+ value = g_variant_dict_lookup_value (dict, "evr", G_VARIANT_TYPE_STRING); -+ if (value == NULL) -+ return TRUE; -+ str = g_variant_get_string (value, NULL); -+ if (str == NULL) -+ return TRUE; -+ -+ g_string_append_c (nevra, '-'); -+ g_string_append (nevra, str); -+ -+ g_clear_pointer (&value, g_variant_unref); -+ value = g_variant_dict_lookup_value (dict, "arch", G_VARIANT_TYPE_STRING); -+ if (value == NULL) -+ return TRUE; -+ str = g_variant_get_string (value, NULL); -+ if (str == NULL) -+ return TRUE; -+ -+ g_string_append_c (nevra, '.'); -+ g_string_append (nevra, str); -+ -+ known_app = g_hash_table_lookup (rpd->nevra_to_app, nevra->str); -+ if (known_app != NULL) { -+ upd_app = g_object_ref (known_app); -+ if (gs_app_get_version (upd_app) == NULL) -+ gs_dnf5_app_set_version (dict, upd_app, gs_app_set_version); -+ else -+ gs_dnf5_app_set_version (dict, upd_app, gs_app_set_update_version); -+ -+ if (rpd->replaces != NULL) { -+ g_autoptr(GVariantDict) dict2 = NULL; -+ -+ g_clear_pointer (&value, g_variant_unref); -+ value = g_variant_get_child_value (item_array, 3); -+ dict2 = g_variant_dict_new (value); -+ g_clear_pointer (&value, g_variant_unref); -+ value = g_variant_dict_lookup_value (dict2, "replaces", G_VARIANT_TYPE_ARRAY); -+ if (value != NULL) { -+ UpdateReplacesData upd = { 0, }; -+ -+ upd.app = upd_app; -+ upd.replaces = rpd->replaces; -+ upd.used_replaces = rpd->used_replaces; -+ -+ if (!gs_dnf5_foreach_item (self, value, gs_dnf5_gather_update_replaced_pkgs_cb, &upd, cancellable, error)) -+ return FALSE; -+ } -+ } -+ } else { -+ upd_app = gs_app_new (NULL); -+ gs_app_set_management_plugin (upd_app, GS_PLUGIN (self)); -+ gs_app_set_metadata (upd_app, "GnomeSoftware::Creator", gs_plugin_get_name (GS_PLUGIN (self))); -+ gs_plugin_dnf5_set_packaging_format (upd_app); -+ gs_app_set_kind (upd_app, AS_COMPONENT_KIND_GENERIC); -+ gs_app_set_bundle_kind (upd_app, AS_BUNDLE_KIND_PACKAGE); -+ gs_app_set_scope (upd_app, AS_COMPONENT_SCOPE_SYSTEM); -+ gs_app_add_quirk (upd_app, GS_APP_QUIRK_HIDE_EVERYWHERE); -+ gs_app_add_quirk (upd_app, GS_APP_QUIRK_NEEDS_REBOOT); -+ -+ gs_dnf5_app_set_str (dict, "name", upd_app, gs_app_add_source); -+ gs_dnf5_app_set_str2 (dict, "name", upd_app, gs_app_set_name); -+ gs_dnf5_app_set_version (dict, upd_app, gs_app_set_version); -+ gs_app_add_source_id (upd_app, nevra->str); -+ -+ gs_app_list_add (rpd->list, upd_app); -+ g_hash_table_insert (rpd->nevra_to_app, g_string_free (g_steal_pointer (&nevra), FALSE), g_object_ref (upd_app)); -+ } -+ -+ gs_dnf5_app_set_size (dict, "install_size", upd_app, gs_app_set_size_installed); -+ gs_dnf5_app_set_size (dict, "download_size", upd_app, gs_app_set_size_download); -+ -+ switch (op_kind) { -+ case OP_KIND_INSTALL: -+ gs_app_set_state (upd_app, GS_APP_STATE_AVAILABLE); -+ break; -+ case OP_KIND_UPGRADE: -+ case OP_KIND_DOWNGRADE: -+ case OP_KIND_REINSTALL: -+ gs_app_set_state (upd_app, GS_APP_STATE_UPDATABLE); -+ break; -+ case OP_KIND_REMOVE: -+ case OP_KIND_REPLACED: -+ gs_app_set_state (upd_app, GS_APP_STATE_UNAVAILABLE); -+ gs_app_set_size_download (upd_app, GS_SIZE_TYPE_VALID, 0); -+ break; -+ case OP_KIND_UNKNOWN: -+ default: -+ g_warn_if_reached (); -+ break; -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_read_history_packages_cb (GsPluginDnf5 *self, -+ GVariant *array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ ReadPackageData *rpd = user_data; -+ g_autoptr(GsApp) app = NULL; -+ g_autoptr(GVariantDict) dict = NULL; -+ -+ dict = g_variant_dict_new (array); -+ -+ app = gs_dnf5_read_package_from_dict (self, dict, GS_DNF5_READ_PACKAGE_FLAG_NONE, rpd->set_state); -+ if (app != NULL) { -+ g_autoptr(GVariant) value = NULL; -+ value = g_variant_dict_lookup_value (dict, "original_evr", G_VARIANT_TYPE_STRING); -+ if (value != NULL) { -+ const gchar *str = g_variant_get_string (value, NULL); -+ if (str != NULL) { -+ const gchar *delim = strchr (str, ':'); -+ /* skip the epoch, if set, because it's skipped in the current version too */ -+ gs_app_set_version (app, delim ? delim + 1 : str); -+ } -+ } -+ -+ g_clear_pointer (&value, g_variant_unref); -+ value = g_variant_dict_lookup_value (dict, "advisories", G_VARIANT_TYPE_ARRAY); -+ if (value != NULL) { -+ gs_app_set_metadata_variant (app, GS_DNF5_ADVISORIES_KEY, value); -+ } -+ -+ /* do not know the exact change date/time, it uses the build time and the one from the settings as a fallback */ -+ if (!gs_app_get_install_date (app)) -+ gs_app_set_install_date (app, rpd->timestamp); -+ gs_app_list_add (rpd->list, app); -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_read_history_items_cb (GsPluginDnf5 *self, -+ GVariant *array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ struct _known_changes { -+ const gchar *type_of_change; -+ GsAppState set_state; -+ } known_changes[] = { -+ { "installed", GS_APP_STATE_UPDATABLE }, -+ { "removed", GS_APP_STATE_UNAVAILABLE }, -+ { "upgraded", GS_APP_STATE_UPDATABLE }, -+ { "downgraded", GS_APP_STATE_UPDATABLE } -+ }; -+ ReadPackageData *rpd = user_data; -+ g_autoptr(GVariant) value = NULL; -+ const gchar *type_of_change; -+ guint ii; -+ -+ value = g_variant_get_child_value (array, 0); -+ if (value == NULL) -+ return TRUE; -+ -+ type_of_change = g_variant_get_string (value, NULL); -+ if (!type_of_change) -+ return TRUE; -+ -+ for (ii = 0; ii < G_N_ELEMENTS (known_changes); ii++) { -+ if (g_strcmp0 (type_of_change, known_changes[ii].type_of_change) == 0) { -+ g_autoptr(GVariant) packages = NULL; -+ packages = g_variant_get_child_value (array, 1); -+ if (packages != NULL) { -+ rpd->set_state = known_changes[ii].set_state; -+ gs_dnf5_foreach_item (self, packages, gs_dnf5_read_history_packages_cb, rpd, cancellable, error); -+ } -+ break; -+ } -+ } -+ -+ return TRUE; -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_list_apps_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsAppQueryTristate is_for_update = GS_APP_QUERY_TRISTATE_UNSET; -+ GsAppQueryTristate is_historical_update = GS_APP_QUERY_TRISTATE_UNSET; -+ const AsComponentKind *component_kinds = NULL; -+ GsPluginListAppsData *data = task_data; -+ const gchar * const *provides_files = NULL; -+ const gchar *provides_tag = NULL; -+ GsAppQueryProvidesType provides_type = GS_APP_QUERY_PROVIDES_UNKNOWN; -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsAppList) list = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success = TRUE; -+ -+ assert_in_worker (self); -+ -+ if (data->query != NULL) { -+ is_for_update = gs_app_query_get_is_for_update (data->query); -+ is_historical_update = gs_app_query_get_is_historical_update (data->query); -+ component_kinds = gs_app_query_get_component_kinds (data->query); -+ provides_files = gs_app_query_get_provides_files (data->query); -+ provides_type = gs_app_query_get_provides (data->query, &provides_tag); -+ } -+ -+ if ((is_for_update == GS_APP_QUERY_TRISTATE_UNSET && -+ is_historical_update == GS_APP_QUERY_TRISTATE_UNSET && -+ component_kinds == NULL && -+ provides_tag == NULL && -+ provides_files == NULL) || -+ is_for_update == GS_APP_QUERY_TRISTATE_FALSE || -+ is_historical_update == GS_APP_QUERY_TRISTATE_FALSE || -+ (component_kinds != NULL && !gs_component_kind_array_contains (component_kinds, AS_COMPONENT_KIND_REPOSITORY)) || -+ gs_app_query_get_n_properties_set (data->query) != 1) { -+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, -+ "Unsupported query"); -+ return; -+ } -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ return; -+ } -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = rpm_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Rpm proxy: "); -+ } -+ -+ if (success) { -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ list = gs_app_list_new (); -+ -+ if (is_for_update == GS_APP_QUERY_TRISTATE_TRUE) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ gs_dnf5_dup_package_attrs ()); -+ g_variant_builder_add (options_builder, "{sv}", "with_provides", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_filenames", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_src", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "scope", -+ g_variant_new_string ("upgrades")); -+ -+ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); -+ -+ if (success) { -+ g_autoptr(GHashTable) nevra_to_app = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); -+ g_autoptr(GHashTable) replaces = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); -+ g_autoptr(GHashTable) used_replaces = g_hash_table_new (g_direct_hash, g_direct_equal); -+ ReadPackageData rpd = { 0, }; -+ rpd.list = list; -+ rpd.set_state = GS_APP_STATE_UPDATABLE; -+ rpd.nevra_to_app = nevra_to_app; -+ rpd.replaces = replaces; -+ rpd.used_replaces = used_replaces; -+ -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_package_cb, &rpd, cancellable, &local_error); -+ -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder2 = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_autoptr(GVariant) transaction = NULL; -+ const gchar * const packages[2] = { NULL, NULL }; -+ -+ success = gs_dnf5_reset_transaction_sync (self, session_path, rpm_proxy, cancellable, &local_error); -+ -+ success = success && -+ gs_dnf5_rpm_rpm_call_upgrade_sync (rpm_proxy, -+ packages, -+ g_variant_builder_end (options_builder2), -+ cancellable, -+ &local_error); -+ success = success && -+ gs_dnf5_run_transaction (self, session_path, rpm_proxy, NULL, NULL, -+ GS_DNF5_TRANSACTION_FLAG_RESOLVE_ONLY, -+ &transaction, cancellable, &local_error); -+ if (success && transaction != NULL) { -+ success = gs_dnf5_foreach_item (self, transaction, -+ gs_dnf5_gather_update_replaces_cb, &rpd, -+ cancellable, &local_error); -+ rpd.cover_unused_replaces = FALSE; -+ success = success && -+ gs_dnf5_foreach_item (self, transaction, -+ gs_dnf5_gather_updates_from_transaction_cb, &rpd, -+ cancellable, &local_error); -+ rpd.cover_unused_replaces = TRUE; -+ success = success && -+ gs_dnf5_foreach_item (self, transaction, -+ gs_dnf5_gather_updates_from_transaction_cb, &rpd, -+ cancellable, &local_error); -+ } -+ } -+ -+ if (success && g_hash_table_size (nevra_to_app) > 0) { -+ g_autoptr(GsDnf5Advisory) advisory_proxy = NULL; -+ g_autoptr(GError) local_error2 = NULL; -+ gboolean adv_success; -+ -+ advisory_proxy = gs_dnf5_advisory_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error2); -+ adv_success = advisory_proxy != NULL; -+ -+ if (adv_success) { -+ g_autoptr(GVariantBuilder) adv_options_builder = NULL; -+ g_autoptr(GVariantBuilder) attrs_builder = NULL; -+ g_autoptr(GVariantBuilder) severities_builder = NULL; -+ g_autoptr(GVariant) adv_result = NULL; -+ -+ attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (attrs_builder, "s", "severity"); -+ g_variant_builder_add (attrs_builder, "s", "collections"); -+ -+ severities_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (severities_builder, "s", "critical"); -+ -+ adv_options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (adv_options_builder, "{sv}", "advisory_attrs", -+ g_variant_builder_end (attrs_builder)); -+ g_variant_builder_add (adv_options_builder, "{sv}", "availability", -+ g_variant_new_string ("updates")); -+ g_variant_builder_add (adv_options_builder, "{sv}", "severities", -+ g_variant_builder_end (severities_builder)); -+ -+ adv_success = gs_dnf5_advisory_call_list_sync (advisory_proxy, -+ g_variant_builder_end (adv_options_builder), -+ &adv_result, -+ cancellable, -+ &local_error2); -+ if (adv_success) { -+ adv_success = gs_dnf5_foreach_item (self, adv_result, -+ gs_dnf5_refine_from_advisory_cb, nevra_to_app, -+ cancellable, &local_error2); -+ } else { -+ g_debug ("Failed to call Advisory::list: %s", local_error2->message); -+ } -+ } else { -+ g_debug ("Failed to create Advisory proxy: %s", local_error2->message); -+ } -+ } -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call Rpm::list: "); -+ } -+ } else if (is_historical_update == GS_APP_QUERY_TRISTATE_TRUE) { -+ g_autoptr(GSettings) settings = NULL; -+ gint64 timestamp; -+ -+ /* use the option the GUI part uses */ -+ settings = g_settings_new ("org.gnome.software"); -+ timestamp = (gint64) g_settings_get_uint64 (settings, "packagekit-historical-updates-timestamp"); -+ -+ if (timestamp > 0) { -+ g_autoptr(GsDnf5History) history_proxy = NULL; -+ -+ history_proxy = gs_dnf5_history_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = history_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create History proxy: "); -+ } -+ -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "since", -+ g_variant_new_int64 (timestamp)); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ gs_dnf5_dup_package_attrs ()); -+ -+ success = gs_dnf5_history_call_recent_changes_sync (history_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); -+ -+ if (success) { -+ ReadPackageData rpd = { 0, }; -+ -+ rpd.list = list; -+ rpd.set_state = GS_APP_STATE_UNKNOWN; -+ rpd.nevra_to_app = NULL; -+ rpd.timestamp = timestamp; -+ -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_history_items_cb, &rpd, cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call recent_changes: "); -+ } -+ } -+ } -+ } else if (gs_component_kind_array_contains (component_kinds, AS_COMPONENT_KIND_REPOSITORY)) { -+ g_autoptr(GsDnf5RpmRepo) repo_proxy = NULL; -+ -+ repo_proxy = gs_dnf5_rpm_repo_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = repo_proxy != NULL; -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariantBuilder) attrs_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (repo_proxy), G_MAXINT); -+ -+ attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (attrs_builder, "s", "id"); -+ g_variant_builder_add (attrs_builder, "s", "name"); -+ g_variant_builder_add (attrs_builder, "s", "enabled"); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "repo_attrs", -+ g_variant_builder_end (attrs_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "enable_disable", -+ g_variant_new_string ("all")); -+ -+ success = gs_dnf5_rpm_repo_call_list_sync (repo_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); -+ -+ if (success) { -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_repo_cb, list, cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call list: "); -+ } -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Rpm repo proxy: "); -+ } -+ } else if (provides_files != NULL) { -+ g_autoptr(GVariantBuilder) patterns_builder = NULL; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ patterns_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ for (guint i = 0; provides_files[i] != NULL; i++) { -+ g_variant_builder_add (patterns_builder, "s", provides_files[i]); -+ } -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "scope", -+ g_variant_new_string ("all")); -+ g_variant_builder_add (options_builder, "{sv}", "with_nevra", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_provides", -+ g_variant_new_boolean (TRUE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_filenames", -+ g_variant_new_boolean (TRUE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_src", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ gs_dnf5_dup_package_attrs ()); -+ g_variant_builder_add (options_builder, "{sv}", "patterns", -+ g_variant_builder_end (patterns_builder)); -+ -+ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); -+ -+ if (success) { -+ ReadPackageData rpd = { 0, }; -+ rpd.list = list; -+ rpd.set_state = GS_APP_STATE_UNKNOWN; -+ rpd.nevra_to_app = NULL; -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_package_cb, &rpd, cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call list: "); -+ } -+ } else if (provides_tag != NULL) { -+ g_autoptr(GVariantBuilder) patterns_builder = NULL; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ g_autofree gchar *pattern1 = NULL, *pattern2 = NULL; -+ -+ patterns_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ -+ switch (provides_type) { -+ case GS_APP_QUERY_PROVIDES_PACKAGE_NAME: -+ g_variant_builder_add (patterns_builder, "s", provides_tag); -+ break; -+ case GS_APP_QUERY_PROVIDES_GSTREAMER: -+ pattern1 = g_strdup_printf ("gstreamer0.10(%s)", provides_tag); -+ pattern2 = g_strdup_printf ("gstreamer1(%s)", provides_tag); -+ break; -+ case GS_APP_QUERY_PROVIDES_FONT: -+ pattern1 = g_strdup_printf ("font(%s)", provides_tag); -+ break; -+ case GS_APP_QUERY_PROVIDES_MIME_HANDLER: -+ pattern1 = g_strdup_printf ("mimehandler(%s)", provides_tag); -+ break; -+ case GS_APP_QUERY_PROVIDES_PS_DRIVER: -+ pattern1 = g_strdup_printf ("postscriptdriver(%s)", provides_tag); -+ break; -+ case GS_APP_QUERY_PROVIDES_PLASMA: -+ pattern1 = g_strdup_printf ("plasma4(%s)", provides_tag); -+ pattern2 = g_strdup_printf ("plasma5(%s)", provides_tag); -+ break; -+ case GS_APP_QUERY_PROVIDES_UNKNOWN: -+ default: -+ g_assert_not_reached (); -+ } -+ if (pattern1 != NULL) -+ g_variant_builder_add (patterns_builder, "s", pattern1); -+ if (pattern2 != NULL) -+ g_variant_builder_add (patterns_builder, "s", pattern2); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "scope", -+ g_variant_new_string ("all")); -+ g_variant_builder_add (options_builder, "{sv}", "with_nevra", -+ g_variant_new_boolean (provides_type == GS_APP_QUERY_PROVIDES_PACKAGE_NAME)); -+ g_variant_builder_add (options_builder, "{sv}", "with_provides", -+ g_variant_new_boolean (provides_type != GS_APP_QUERY_PROVIDES_PACKAGE_NAME)); -+ g_variant_builder_add (options_builder, "{sv}", "with_filenames", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_src", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ gs_dnf5_dup_package_attrs ()); -+ g_variant_builder_add (options_builder, "{sv}", "patterns", -+ g_variant_builder_end (patterns_builder)); -+ -+ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); -+ -+ if (success) { -+ ReadPackageData rpd = { 0, }; -+ rpd.list = list; -+ rpd.set_state = GS_APP_STATE_UNKNOWN; -+ rpd.nevra_to_app = NULL; -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_package_cb, &rpd, cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call list: "); -+ } -+ } else { -+ g_assert_not_reached (); -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ if (success) { -+ g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_list_apps_async (GsPlugin *plugin, -+ GsAppQuery *query, -+ GsPluginListAppsFlags flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_LIST_APPS_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_list_apps_data_new_task (plugin, query, flags, event_callback, event_user_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_list_apps_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_list_apps_thread_cb, g_steal_pointer (&task)); -+} -+ -+static GsAppList * -+gs_plugin_dnf5_list_apps_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_pointer (G_TASK (result), error); -+} -+ -+static gboolean -+gs_dnf5_gather_dependency_sizes_cb (GsPluginDnf5 *self, -+ GVariant *item_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GsApp *main_app = user_data; -+ const gchar *str; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) value = NULL; -+ g_autoptr(GsApp) dep_app = NULL; -+ -+ g_variant_get_child (item_array, 0, "&s", &str); -+ if (str == NULL || g_ascii_strcasecmp (str, "package") != 0) -+ return TRUE; -+ -+ g_variant_get_child (item_array, 2, "&s", &str); -+ if (str == NULL || g_ascii_strcasecmp (str, "dependency") != 0) -+ return TRUE; -+ -+ value = g_variant_get_child_value (item_array, 4); -+ dict = g_variant_dict_new (value); -+ g_clear_pointer (&value, g_variant_unref); -+ -+ value = g_variant_dict_lookup_value (dict, "name", G_VARIANT_TYPE_STRING); -+ if (value == NULL) -+ return TRUE; -+ str = g_variant_get_string (value, NULL); -+ if (str == NULL) -+ return TRUE; -+ /* the app itself is in the list too */ -+ if (g_strcmp0 (gs_app_get_default_source (main_app), str) == 0) -+ return TRUE; -+ -+ /* always create a new app, no cached can be used here */ -+ dep_app = gs_app_new (NULL); -+ gs_app_set_management_plugin (dep_app, GS_PLUGIN (self)); -+ gs_app_set_metadata (dep_app, "GnomeSoftware::Creator", gs_plugin_get_name (GS_PLUGIN (self))); -+ gs_plugin_dnf5_set_packaging_format (dep_app); -+ gs_app_set_kind (dep_app, AS_COMPONENT_KIND_GENERIC); -+ gs_app_set_bundle_kind (dep_app, AS_BUNDLE_KIND_PACKAGE); -+ gs_app_set_scope (dep_app, AS_COMPONENT_SCOPE_SYSTEM); -+ gs_app_add_quirk (dep_app, GS_APP_QUIRK_HIDE_EVERYWHERE); -+ -+ gs_dnf5_app_set_str (dict, "name", dep_app, gs_app_add_source); -+ gs_dnf5_app_set_str (dict, "nevra", dep_app, gs_app_add_source_id); -+ gs_dnf5_app_set_str2 (dict, "name", dep_app, gs_app_set_name); -+ gs_dnf5_app_set_str2 (dict, "summary", dep_app, gs_app_set_summary); -+ gs_dnf5_app_set_str2 (dict, "description", dep_app, gs_app_set_description); -+ gs_dnf5_app_set_str2 (dict, "license", dep_app, gs_app_set_license); -+ gs_dnf5_app_set_size (dict, "install_size", dep_app, gs_app_set_size_installed); -+ gs_dnf5_app_set_size (dict, "download_size", dep_app, gs_app_set_size_download); -+ -+ gs_dnf5_update_app_state (dep_app, GS_APP_STATE_UNKNOWN, dict); -+ -+ gs_app_add_related (main_app, dep_app); -+ -+ return TRUE; -+} -+ -+static gpointer -+gs_dnf5_dependency_sizes_thread (gpointer thread_data) -+{ -+ g_autofree GWeakRef *weakref = thread_data; -+ g_autoptr(GsPluginDnf5) self = g_weak_ref_get (weakref); -+ g_autoptr(GCancellable) cancellable = NULL; -+ g_autoptr(GError) local_error = NULL; -+ guint index = 0; -+ -+ if (self == NULL) -+ return NULL; -+ -+ g_mutex_lock (&self->dependency_sizes_data.mutex); -+ -+ if (self->dependency_sizes_data.cancellable != NULL) -+ cancellable = g_object_ref (self->dependency_sizes_data.cancellable); -+ -+ while (self->dependency_sizes_data.apps != NULL && index < self->dependency_sizes_data.apps->len && -+ !g_cancellable_is_cancelled (self->dependency_sizes_data.cancellable)) { -+ GsApp *app = g_ptr_array_index (self->dependency_sizes_data.apps, index); -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ gboolean success; -+ -+ index++; -+ -+ if (gs_app_is_installed (app) || -+ gs_app_list_length (gs_app_get_related (app)) > 0) -+ continue; -+ -+ g_mutex_unlock (&self->dependency_sizes_data.mutex); -+ -+ /* need to use temporary session, because the goals are piled up and cannot be cancelled yet */ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); -+ -+ success = session_path != NULL; -+ if (!success) -+ gs_dnf5_convert_error (&local_error); -+ -+ if (success) { -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = rpm_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Rpm proxy: "); -+ } -+ } -+ -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_autoptr(GVariant) transaction = NULL; -+ const gchar *package_name = gs_app_get_default_source (app); -+ const gchar *packages[2] = { NULL, NULL }; -+ -+ packages[0] = package_name; -+ -+ success = gs_dnf5_reset_transaction_sync (self, session_path, rpm_proxy, cancellable, &local_error); -+ -+ success = success && -+ gs_dnf5_rpm_rpm_call_install_sync (rpm_proxy, -+ (const gchar * const *) packages, -+ g_variant_builder_end (options_builder), -+ cancellable, -+ &local_error); -+ success = success && -+ gs_dnf5_run_transaction (self, session_path, rpm_proxy, NULL, NULL, -+ GS_DNF5_TRANSACTION_FLAG_RESOLVE_ONLY, -+ &transaction, cancellable, &local_error); -+ if (success && transaction != NULL) -+ success = gs_dnf5_foreach_item (self, transaction, gs_dnf5_gather_dependency_sizes_cb, app, cancellable, &local_error); -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ g_mutex_lock (&self->dependency_sizes_data.mutex); -+ -+ if (!success) -+ break; -+ } -+ -+ g_clear_pointer (&self->dependency_sizes_data.apps, g_ptr_array_unref); -+ g_clear_object (&self->dependency_sizes_data.cancellable); -+ g_mutex_unlock (&self->dependency_sizes_data.mutex); -+ -+ if (local_error != NULL) -+ g_debug ("Failed to get app dependency sizes: %s", local_error->message); -+ -+ return NULL; -+} -+ -+static gboolean -+gs_dnf5_get_dependency_sizes_sync (GsPluginDnf5 *self, -+ GHashTable *apps, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->session_data.mutex); -+ GHashTableIter iter; -+ gpointer value = NULL; -+ gboolean need_thread = FALSE; -+ -+ g_hash_table_iter_init (&iter, apps); -+ while (g_hash_table_iter_next (&iter, NULL, &value)) { -+ GsApp *app = value; -+ GsAppList *related = gs_app_get_related (app); -+ -+ if (gs_app_has_quirk (app, GS_APP_QUIRK_HIDE_EVERYWHERE) || -+ gs_app_get_state (app) == GS_APP_STATE_AVAILABLE_LOCAL || -+ gs_app_is_installed (app) || -+ gs_app_list_length (related) > 0) -+ continue; -+ -+ /* getting list of the dependencies to be installed can take a long time, thus run it -+ in a new thread, to not block the refine job */ -+ if (self->dependency_sizes_data.apps == NULL) { -+ need_thread = TRUE; -+ self->dependency_sizes_data.apps = g_ptr_array_new_with_free_func (g_object_unref); -+ } -+ -+ if (!g_ptr_array_find (self->dependency_sizes_data.apps, app, NULL)) -+ g_ptr_array_add (self->dependency_sizes_data.apps, g_object_ref (app)); -+ } -+ -+ if (need_thread) { -+ GWeakRef *weakref = g_new0 (GWeakRef, 1); -+ -+ g_assert (self->dependency_sizes_data.cancellable == NULL); -+ self->dependency_sizes_data.cancellable = g_cancellable_new (); -+ -+ g_weak_ref_set (weakref, self); -+ -+ g_thread_unref (g_thread_new ("gs-dnf5-dependency-sizes", gs_dnf5_dependency_sizes_thread, g_steal_pointer (&weakref))); -+ } -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_refine_cb (GsPluginDnf5 *self, -+ GVariant *package_array, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GHashTable *apps = user_data; -+ const gchar *name; -+ GsApp *app; -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) value = NULL; -+ -+ dict = g_variant_dict_new (package_array); -+ -+ value = g_variant_dict_lookup_value (dict, "name", G_VARIANT_TYPE_STRING); -+ if (value == NULL) -+ return TRUE; -+ name = g_variant_get_string (value, NULL); -+ if (name == NULL) -+ return TRUE; -+ app = g_hash_table_lookup (apps, name); -+ if (app == NULL) -+ return TRUE; -+ -+ if (gs_app_has_management_plugin (app, NULL) && -+ gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE && -+ gs_app_get_scope (app) == AS_COMPONENT_SCOPE_SYSTEM) { -+ gs_app_set_management_plugin (app, GS_PLUGIN (self)); -+ gs_plugin_dnf5_set_packaging_format (app); -+ } -+ -+ gs_dnf5_app_set_version (dict, app, gs_app_set_version); -+ gs_dnf5_app_set_size (dict, "install_size", app, gs_app_set_size_installed); -+ gs_dnf5_app_set_size (dict, "download_size", app, gs_app_set_size_download); -+ -+ gs_dnf5_update_app_state (app, GS_APP_STATE_UNKNOWN, dict); -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_add_advisories_cb (GsPluginDnf5 *self, -+ GVariant *value, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GHashTable *advisories = user_data; /* gchar *name ~> gchar *description */ -+ g_autofree gchar *str = NULL; -+ -+ str = g_variant_dup_string (value, NULL); -+ if (str != NULL && *str != '\0') -+ g_hash_table_insert (advisories, g_steal_pointer (&str), NULL); -+ -+ return TRUE; -+} -+ -+static gboolean -+gs_dnf5_gather_update_description_for_advisory_cb (GsPluginDnf5 *self, -+ GVariant *value, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GHashTable *advisories = user_data; /* gchar *name ~> gchar *description */ -+ g_autoptr(GVariantDict) dict = NULL; -+ g_autoptr(GVariant) name = NULL; -+ g_autoptr(GVariant) description = NULL; -+ const gchar *name_str, *description_str; -+ -+ dict = g_variant_dict_new (value); -+ -+ name = g_variant_dict_lookup_value (dict, "name", G_VARIANT_TYPE_STRING); -+ if (name == NULL) -+ return TRUE; -+ -+ description = g_variant_dict_lookup_value (dict, "description", G_VARIANT_TYPE_STRING); -+ if (description == NULL) -+ return TRUE; -+ -+ name_str = g_variant_get_string (name, NULL); -+ description_str = g_variant_get_string (description, NULL); -+ -+ if (name_str != NULL && description_str != NULL && *description_str != '\0') -+ g_hash_table_replace (advisories, g_strdup (name_str), g_strdup (description_str)); -+ -+ return TRUE; -+} -+ -+typedef struct _AdvisoryUpdateDescriptionData { -+ GHashTable *advisories; /* gchar *name ~> gchar *description */ -+ GString *description; -+} AdvisoryUpdateDescriptionData; -+ -+static gboolean -+gs_dnf5_refine_update_description_from_advisory_cb (GsPluginDnf5 *self, -+ GVariant *value, -+ gpointer user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ AdvisoryUpdateDescriptionData *adv_data = user_data; -+ const gchar *name = NULL; -+ -+ name = g_variant_get_string (value, NULL); -+ if (name != NULL && *name != '\0') { -+ const gchar *description = g_hash_table_lookup (adv_data->advisories, name); -+ if (description != NULL && *description != '\0') { -+ if (adv_data->description == NULL) { -+ adv_data->description = g_string_new (description); -+ } else { -+ g_string_append (adv_data->description, "\n\n"); -+ g_string_append (adv_data->description, description); -+ } -+ } -+ } -+ -+ return TRUE; -+} -+ -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_refine_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPlugin *plugin = GS_PLUGIN (self); -+ GsPluginRefineData *data = task_data; -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GHashTable) apps = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success = TRUE; -+ -+ assert_in_worker (self); -+ -+ apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); -+ for (guint i = 0; i < gs_app_list_length (data->list); i++) { -+ GsApp *app = gs_app_list_index (data->list, i); -+ if (!gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD) && ( -+ gs_app_get_state (app) == GS_APP_STATE_UNKNOWN || -+ (data->require_flags & GS_PLUGIN_REFINE_REQUIRE_FLAGS_UPDATE_DETAILS) != 0 || -+ ((data->require_flags & GS_PLUGIN_REFINE_REQUIRE_FLAGS_SIZE_DATA) != 0 && -+ gs_app_list_length (gs_app_get_related (app)) == 0))) { -+ if (gs_app_get_default_source (app) != NULL && -+ ((gs_app_has_management_plugin (app, NULL) && -+ gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE && -+ gs_app_get_scope (app) == AS_COMPONENT_SCOPE_SYSTEM) || -+ gs_app_has_management_plugin (app, plugin))) -+ g_hash_table_insert (apps, g_strdup (gs_app_get_default_source (app)), g_object_ref (app)); -+ } -+ } -+ -+ if (g_hash_table_size (apps) == 0) { -+ g_task_return_boolean (task, TRUE); -+ return; -+ } -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ return; -+ } -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = rpm_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Rpm proxy: "); -+ } -+ -+ if (success && (data->require_flags & GS_PLUGIN_REFINE_REQUIRE_FLAGS_UPDATE_DETAILS) != 0) { -+ g_autoptr(GHashTable) advisories = NULL; /* gchar *name ~> gchar *description */ -+ GHashTableIter iter; -+ gpointer value = NULL; -+ -+ data->require_flags = data->require_flags & (~GS_PLUGIN_REFINE_REQUIRE_FLAGS_UPDATE_DETAILS); -+ -+ advisories = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); -+ -+ g_hash_table_iter_init (&iter, apps); -+ while (success && g_hash_table_iter_next (&iter, NULL, &value)) { -+ GsApp *app = value; -+ GVariant *advisories_array; -+ -+ advisories_array = gs_app_get_metadata_variant (app, GS_DNF5_ADVISORIES_KEY); -+ if (advisories_array != NULL) -+ success = gs_dnf5_foreach_item (self, advisories_array, gs_dnf5_add_advisories_cb, advisories, cancellable, &local_error); -+ } -+ -+ if (success && g_hash_table_size (advisories) > 0) { -+ g_autoptr(GsDnf5Advisory) advisory_proxy = NULL; -+ -+ advisory_proxy = gs_dnf5_advisory_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = advisory_proxy != NULL; -+ -+ if (success) { -+ g_autoptr(GVariantBuilder) adv_options_builder = NULL; -+ g_autoptr(GVariantBuilder) attrs_builder = NULL; -+ g_autoptr(GVariantBuilder) names_builder = NULL; -+ g_autoptr(GVariant) adv_result = NULL; -+ gpointer key = NULL; -+ -+ attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (attrs_builder, "s", "name"); -+ g_variant_builder_add (attrs_builder, "s", "description"); -+ -+ names_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_hash_table_iter_init (&iter, advisories); -+ while (g_hash_table_iter_next (&iter, &key, NULL)) { -+ const gchar *name = key; -+ g_variant_builder_add (names_builder, "s", name); -+ } -+ -+ adv_options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (adv_options_builder, "{sv}", "advisory_attrs", -+ g_variant_builder_end (attrs_builder)); -+ g_variant_builder_add (adv_options_builder, "{sv}", "names", -+ g_variant_builder_end (names_builder)); -+ g_variant_builder_add (adv_options_builder, "{sv}", "availability", -+ g_variant_new_string ("all")); -+ -+ success = gs_dnf5_advisory_call_list_sync (advisory_proxy, -+ g_variant_builder_end (adv_options_builder), -+ &adv_result, -+ cancellable, -+ &local_error); -+ if (success) { -+ success = gs_dnf5_foreach_item (self, adv_result, -+ gs_dnf5_gather_update_description_for_advisory_cb, advisories, -+ cancellable, &local_error); -+ if (success) { -+ g_hash_table_iter_init (&iter, apps); -+ while (success && g_hash_table_iter_next (&iter, NULL, &value)) { -+ GsApp *app = value; -+ GVariant *advisories_array; -+ -+ advisories_array = gs_app_get_metadata_variant (app, GS_DNF5_ADVISORIES_KEY); -+ if (advisories_array != NULL) { -+ AdvisoryUpdateDescriptionData adv_data = { NULL, }; -+ adv_data.advisories = advisories; -+ adv_data.description = NULL; -+ -+ success = gs_dnf5_foreach_item (self, advisories_array, -+ gs_dnf5_refine_update_description_from_advisory_cb, -+ &adv_data, cancellable, &local_error); -+ -+ if (adv_data.description) { -+ g_autoptr(GsMarkdown) markdown = gs_markdown_new (GS_MARKDOWN_OUTPUT_PANGO); -+ g_autofree gchar *markup = gs_markdown_parse (markdown, adv_data.description->str); -+ if (markup != NULL && *markup != '\0') -+ gs_app_set_update_details_markup (app, markup); -+ else -+ gs_app_set_update_details_text (app, adv_data.description->str); -+ g_string_free (adv_data.description, TRUE); -+ } -+ } -+ } -+ -+ if (!success) -+ g_debug ("Failed to refine update description from advisory: %s", local_error->message); -+ } else { -+ g_debug ("Failed to gather update description for advisory: %s", local_error->message); -+ } -+ } else { -+ g_debug ("Failed to call Advisory::list: %s", local_error->message); -+ } -+ } else { -+ g_debug ("Failed to create Advisory proxy: %s", local_error->message); -+ } -+ } -+ -+ /* fallback to changelogs where advisory was not known */ -+ g_hash_table_iter_init (&iter, apps); -+ while (success && g_hash_table_iter_next (&iter, NULL, &value)) { -+ GsApp *app = value; -+ if (!gs_app_get_update_details_markup (app)) { -+ const gchar *changelogs = gs_app_get_metadata_item (app, GS_DNF5_CHANGELOGS_KEY); -+ if (changelogs != NULL && *changelogs != '\0') { -+ gs_app_set_update_details_text (app, changelogs); -+ gs_app_set_metadata (app, GS_DNF5_CHANGELOGS_KEY, NULL); -+ } -+ } -+ } -+ } -+ -+ if (success && data->require_flags != 0) { -+ g_autoptr(GVariantBuilder) attrs_builder = NULL; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariantBuilder) patterns_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ GHashTableIter iter; -+ gpointer key = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ patterns_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_hash_table_iter_init (&iter, apps); -+ while (g_hash_table_iter_next (&iter, &key, NULL)) { -+ const gchar *name = key; -+ g_variant_builder_add (patterns_builder, "s", name); -+ } -+ -+ attrs_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (attrs_builder, "s", "name"); -+ g_variant_builder_add (attrs_builder, "s", "version"); -+ g_variant_builder_add (attrs_builder, "s", "install_size"); -+ g_variant_builder_add (attrs_builder, "s", "download_size"); -+ g_variant_builder_add (attrs_builder, "s", "is_installed"); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "patterns", -+ g_variant_builder_end (patterns_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ g_variant_builder_end (attrs_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "with_nevra", -+ g_variant_new_boolean (TRUE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_provides", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_filenames", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_src", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "scope", -+ g_variant_new_string ("all")); -+ g_variant_builder_add (options_builder, "{sv}", "latest_limit", -+ g_variant_new_int32 (1)); -+ -+ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ &local_error); -+ -+ if (success) { -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_refine_cb, apps, cancellable, &local_error); -+ -+ if (success && -+ (data->require_flags & GS_PLUGIN_REFINE_REQUIRE_FLAGS_SIZE_DATA) != 0) { -+ /* do not panic when cannot get dependency sizes for the apps */ -+ if (!gs_dnf5_get_dependency_sizes_sync (self, apps, cancellable, &local_error)) { -+ g_debug ("Failed to get dependency sizes: %s", local_error->message); -+ g_clear_error (&local_error); -+ } -+ } -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call list: "); -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ if (success) { -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_refine_async (GsPlugin *plugin, -+ GsAppList *list, -+ GsPluginRefineFlags job_flags, -+ GsPluginRefineRequireFlags require_flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (job_flags & GS_PLUGIN_REFINE_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_refine_data_new_task (plugin, list, job_flags, require_flags, -+ event_callback, event_user_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_refine_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_refine_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_refine_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_refresh_metadata_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginRefreshMetadataData *data = task_data; -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5Base) base_proxy = NULL; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success = TRUE; -+ -+ assert_in_worker (self); -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ return; -+ } -+ -+ base_proxy = gs_dnf5_base_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = base_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Base proxy: "); -+ } -+ -+ if (success && data->cache_age_secs < 60) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autofree gchar *op_error_msg = NULL; -+ gboolean op_success = FALSE; -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean ((data->flags & GS_PLUGIN_REFRESH_METADATA_FLAGS_INTERACTIVE) != 0)); -+ -+ success = gs_dnf5_base_call_clean_with_options_sync (base_proxy, -+ "expire-cache", -+ g_variant_builder_end (options_builder), -+ &op_success, -+ &op_error_msg, -+ cancellable, -+ &local_error); -+ if (!success) -+ g_debug ("Failed to call expire-cache: %s", local_error->message); -+ else if (!op_success) -+ g_debug ("Failed to expire-cache: %s", op_error_msg); -+ -+ /* ignore expire-cache error, only log about it */ -+ g_clear_error (&local_error); -+ -+ /* reset the session, to take the expiration into the effect */ -+ success = gs_dnf5_reset_session_sync (self, session_path, cancellable, &local_error); -+ } -+ -+ if (success) { -+ gboolean result = FALSE; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (base_proxy), G_MAXINT); -+ -+ success = gs_dnf5_base_call_read_all_repos_sync (base_proxy, &result, cancellable, &local_error); -+ -+ /* 'result' is ignored here, due to no error provided */ -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call read-all-repos: "); -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ if (success) { -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_refresh_metadata_async (GsPlugin *plugin, -+ guint64 cache_age_secs, -+ GsPluginRefreshMetadataFlags flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_REFRESH_METADATA_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = g_task_new (self, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_refresh_metadata_async); -+ g_task_set_task_data (task, -+ gs_plugin_refresh_metadata_data_new (cache_age_secs, flags, event_callback, event_user_data), -+ (GDestroyNotify) gs_plugin_refresh_metadata_data_free); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_refresh_metadata_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_refresh_metadata_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+void -+gs_plugin_adopt_app (GsPlugin *plugin, -+ GsApp *app) -+{ -+ if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE && -+ gs_app_get_scope (app) == AS_COMPONENT_SCOPE_SYSTEM) { -+ gs_app_set_management_plugin (app, plugin); -+ gs_plugin_dnf5_set_packaging_format (app); -+ return; -+ } else if (gs_app_get_kind (app) == AS_COMPONENT_KIND_OPERATING_SYSTEM) { -+ gs_app_set_management_plugin (app, plugin); -+ } -+} -+ -+static gboolean -+gs_dnf5_install_update (GsPlugin *plugin, -+ GsAppList *list, -+ gboolean is_install, -+ gboolean interactive, -+ gboolean can_apply, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GPtrArray) packages = NULL; -+ g_autoptr(GsAppList) used_apps = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ g_autoptr(GsApp) progress_app = NULL; -+ GsAppList *progress_list = is_install ? NULL : list; -+ gboolean success; -+ -+ /* queue for install if installation needs the network */ -+ if (!gs_plugin_get_network_available (plugin)) { -+ if (is_install) { -+ for (guint i = 0; i < gs_app_list_length (list); i++) { -+ GsApp *app = gs_app_list_index (list, i); -+ if (gs_app_has_management_plugin (app, plugin)) { -+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) { -+ g_warn_if_reached (); -+ continue; -+ } -+ -+ gs_app_set_state (app, GS_APP_STATE_QUEUED_FOR_INSTALL); -+ } -+ } -+ } -+ return TRUE; -+ } -+ -+ packages = g_ptr_array_new_with_free_func (g_free); -+ used_apps = gs_app_list_new (); -+ -+ for (guint i = 0; i < gs_app_list_length (list); i++) { -+ GsApp *app = gs_app_list_index (list, i); -+ if (gs_app_has_management_plugin (app, plugin)) { -+ const gchar *source = gs_app_get_default_source (app); -+ if (source != NULL) { -+ GFile *local_file = gs_app_get_local_file (app); -+ if (local_file != NULL) -+ g_ptr_array_add (packages, g_file_get_uri (local_file)); -+ else -+ g_ptr_array_add (packages, g_strdup (source)); -+ gs_app_list_add (used_apps, app); -+ if (progress_list == NULL && progress_app == NULL) -+ progress_app = g_object_ref (app); -+ } -+ } else if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_PROXY)) { -+ GsAppList *related = gs_app_get_related (app); -+ gboolean is_os_update = gs_app_get_special_kind (app) == GS_APP_SPECIAL_KIND_OS_UPDATE; -+ for (guint j = 0; j < gs_app_list_length (related); j++) { -+ GsApp *rel_app = gs_app_list_index (related, j); -+ /* add only updatable packages into the list when it's an OS Update, otherwise -+ dnf5 claims "Packages for argument '$pkgname' available, but not installed." */ -+ if (gs_app_has_management_plugin (rel_app, plugin) && (!is_install || !is_os_update || -+ gs_app_get_state (rel_app) == GS_APP_STATE_UPDATABLE)) { -+ const gchar *source = gs_app_get_default_source (rel_app); -+ if (source != NULL) { -+ GFile *local_file = gs_app_get_local_file (rel_app); -+ if (local_file != NULL) -+ g_ptr_array_add (packages, g_file_get_uri (local_file)); -+ else -+ g_ptr_array_add (packages, g_strdup (source)); -+ gs_app_list_add (used_apps, rel_app); -+ } -+ } -+ } -+ if (progress_list == NULL && progress_app == NULL) -+ progress_app = g_object_ref (app); -+ } -+ } -+ -+ if (packages->len == 0) { -+ g_set_error_literal (error, -+ GS_PLUGIN_ERROR, -+ GS_PLUGIN_ERROR_NOT_SUPPORTED, -+ "installing not available"); -+ return FALSE; -+ } -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (error); -+ return FALSE; -+ } -+ -+ for (guint i = 0; i < gs_app_list_length (used_apps); i++) { -+ GsApp *app = gs_app_list_index (used_apps, i); -+ g_autoptr(GsAppList) addons = NULL; -+ -+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) { -+ g_warn_if_reached (); -+ continue; -+ } -+ -+ if (is_install) { -+ gs_app_set_state (app, GS_APP_STATE_INSTALLING); -+ addons = gs_app_dup_addons (app); -+ } -+ for (guint j = 0; addons != NULL && j < gs_app_list_length (addons); j++) { -+ GsApp *addon = gs_app_list_index (addons, j); -+ if (gs_app_get_to_be_installed (addon)) { -+ const gchar *addon_source = gs_app_get_default_source (app); -+ if (addon_source != NULL) { -+ g_ptr_array_add (packages, g_strdup (addon_source)); -+ if (is_install) -+ gs_app_set_state (addon, GS_APP_STATE_INSTALLING); -+ } -+ } -+ } -+ } -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ success = rpm_proxy != NULL; -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ GsDnf5TransactionFlags flags = (interactive ? GS_DNF5_TRANSACTION_FLAG_INTERACTIVE : GS_DNF5_TRANSACTION_FLAG_NONE) | -+ (!can_apply ? GS_DNF5_TRANSACTION_FLAG_DOWNLOAD_ONLY : GS_DNF5_TRANSACTION_FLAG_NONE); -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (interactive)); -+ -+ /* NULL-terminate the array, to be like strv */ -+ g_ptr_array_add (packages, NULL); -+ -+ success = gs_dnf5_reset_transaction_sync (self, session_path, rpm_proxy, cancellable, error); -+ -+ if (success && is_install) { -+ success = gs_dnf5_rpm_rpm_call_install_sync (rpm_proxy, -+ (const gchar * const *) packages->pdata, -+ g_variant_builder_end (options_builder), -+ cancellable, -+ error); -+ } else if (success) { -+ /* ignore package list for update, it's an offline action, which should update -+ everything possible, not only a specific package */ -+ const gchar * const all_packages[2] = { NULL, NULL }; -+ success = gs_dnf5_rpm_rpm_call_upgrade_sync (rpm_proxy, -+ all_packages, -+ g_variant_builder_end (options_builder), -+ cancellable, -+ error); -+ } -+ success = success && gs_dnf5_run_transaction (self, session_path, rpm_proxy, progress_app, progress_list, -+ flags | (is_install ? 0 : GS_DNF5_TRANSACTION_FLAG_OFFLINE), -+ NULL, cancellable, error); -+ if (success && !is_install && can_apply) { -+ /* ensure the update is finished with the 'reboot' action */ -+ g_autoptr(GsDnf5Offline) offline_proxy = NULL; -+ -+ offline_proxy = gs_dnf5_offline_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ success = offline_proxy != NULL; -+ if (success) { -+ g_autofree gchar *op_error_msg = NULL; -+ gboolean op_success = FALSE; -+ -+ g_clear_pointer (&options_builder, g_variant_builder_unref); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (interactive)); -+ -+ success = gs_dnf5_offline_call_set_finish_action_with_options_sync (offline_proxy, -+ "reboot", -+ g_variant_builder_end (options_builder), -+ &op_success, -+ &op_error_msg, -+ cancellable, -+ error); -+ if (success && !op_success) { -+ success = FALSE; -+ if (op_error_msg != NULL) { -+ g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, op_error_msg); -+ g_prefix_error_literal (error, "Failed to set finish action: "); -+ } -+ } else if (!success) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to set finish action: "); -+ } else { -+ g_autoptr(GSettings) settings = g_settings_new ("org.gnome.software"); -+ /* to check anything installed/updated after today */ -+ g_settings_set (settings, "packagekit-historical-updates-timestamp", "t", g_get_real_time () / G_USEC_PER_SEC); -+ } -+ } else { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Offline proxy: "); -+ } -+ } -+ } else { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Rpm proxy: "); -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ if (success) { -+ gs_dnf5_mark_session_needs_reset (self); -+ } else { -+ gs_dnf5_convert_error (error); -+ -+ if (error != NULL && *error != NULL) { -+ GsApp *app = NULL; -+ if (gs_app_list_length (list) == 1) -+ app = gs_app_list_index (list, 0); -+ -+ gs_dnf5_report_error (self, event_callback, event_user_data, app, *error, interactive); -+ } -+ } -+ -+ for (guint i = 0; i < gs_app_list_length (used_apps); i++) { -+ GsApp *app = gs_app_list_index (used_apps, i); -+ g_autoptr(GsAppList) addons = NULL; -+ -+ if (gs_app_get_state (app) == GS_APP_STATE_INSTALLING) { -+ if (success) { -+ gs_app_set_state (app, GS_APP_STATE_INSTALLED); -+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0); -+ } else { -+ gs_app_set_state_recover (app); -+ } -+ } else if (success) { -+ /* apps prepared for offline update are downloaded */ -+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0); -+ } -+ -+ if (is_install) -+ addons = gs_app_dup_addons (app); -+ for (guint j = 0; addons != NULL && j < gs_app_list_length (addons); j++) { -+ GsApp *addon = gs_app_list_index (addons, j); -+ if (gs_app_get_state (addon) == GS_APP_STATE_INSTALLING) { -+ if (success) { -+ gs_app_set_state (addon, GS_APP_STATE_INSTALLED); -+ gs_app_set_size_download (addon, GS_SIZE_TYPE_VALID, 0); -+ } else { -+ gs_app_set_state_recover (addon); -+ } -+ } else if (success) { -+ /* apps prepared for offline update are downloaded */ -+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0); -+ } -+ } -+ } -+ -+ return success; -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_install_apps_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPlugin *plugin = GS_PLUGIN (self); -+ GsPluginInstallAppsData *data = task_data; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success; -+ -+ assert_in_worker (self); -+ -+ g_atomic_int_inc (&self->calling_rpm); -+ -+ success = gs_dnf5_install_update (plugin, data->apps, TRUE, -+ (data->flags & GS_PLUGIN_INSTALL_APPS_FLAGS_INTERACTIVE) != 0, -+ (data->flags & GS_PLUGIN_INSTALL_APPS_FLAGS_NO_APPLY) == 0, -+ data->event_callback, data->event_user_data, -+ cancellable, &local_error); -+ -+ g_atomic_int_dec_and_test (&self->calling_rpm); -+ -+ if (success) -+ g_task_return_boolean (task, TRUE); -+ else -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+} -+ -+static void -+gs_plugin_dnf5_install_apps_async (GsPlugin *plugin, -+ GsAppList *apps, -+ GsPluginInstallAppsFlags flags, -+ GsPluginProgressCallback progress_callback, -+ gpointer progress_user_data, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GsPluginAppNeedsUserActionCallback app_needs_user_action_callback, -+ gpointer app_needs_user_action_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_INSTALL_APPS_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_install_apps_data_new_task (plugin, apps, flags, progress_callback, progress_user_data, -+ event_callback, event_user_data, -+ app_needs_user_action_callback, app_needs_user_action_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_install_apps_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_install_apps_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_install_apps_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_uninstall_apps_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPlugin *plugin = GS_PLUGIN (self); -+ GsPluginUninstallAppsData *data = task_data; -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GPtrArray) touched_apps = NULL; -+ g_autoptr(GPtrArray) packages = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ g_autoptr(GsApp) progress_app = NULL; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success; -+ -+ assert_in_worker (self); -+ -+ touched_apps = g_ptr_array_new_with_free_func (g_object_unref); -+ packages = g_ptr_array_new_with_free_func (g_free); -+ -+ for (guint i = 0; i < gs_app_list_length (data->apps); i++) { -+ g_autoptr(GsAppList) addons = NULL; -+ GsApp *app = gs_app_list_index (data->apps, i); -+ const gchar *source; -+ if (!gs_app_has_management_plugin (app, plugin)) -+ continue; -+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) { -+ g_warn_if_reached (); -+ continue; -+ } -+ source = gs_app_get_default_source (app); -+ if (source == NULL) { -+ g_task_return_new_error (task, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED, -+ "remove not available"); -+ return; -+ } -+ g_ptr_array_add (packages, g_strdup (source)); -+ g_ptr_array_add (touched_apps, g_object_ref (app)); -+ gs_app_set_state (app, GS_APP_STATE_REMOVING); -+ -+ if (progress_app == NULL) -+ progress_app = g_object_ref (app); -+ -+ addons = gs_app_dup_addons (app); -+ for (guint j = 0; addons != NULL && j < gs_app_list_length (addons); j++) { -+ GsApp *addon = gs_app_list_index (addons, j); -+ if (gs_app_get_state (addon) == GS_APP_STATE_INSTALLED) { -+ const gchar *addon_source = gs_app_get_default_source (addon); -+ if (addon_source != NULL) { -+ g_ptr_array_add (packages, g_strdup (addon_source)); -+ g_ptr_array_add (touched_apps, g_object_ref (addon)); -+ gs_app_set_state (addon, GS_APP_STATE_REMOVING); -+ } -+ } -+ } -+ } -+ -+ if (packages->len == 0) { -+ g_task_return_boolean (task, TRUE); -+ return; -+ } -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ return; -+ } -+ -+ g_atomic_int_inc (&self->calling_rpm); -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = rpm_proxy != NULL; -+ if (success) { -+ GsDnf5TransactionFlags flags = (data->flags & GS_PLUGIN_UNINSTALL_APPS_FLAGS_INTERACTIVE) != 0 ? -+ GS_DNF5_TRANSACTION_FLAG_INTERACTIVE : GS_DNF5_TRANSACTION_FLAG_NONE; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "strict", -+ g_variant_new_boolean (FALSE)); -+ -+ /* NULL-terminate the array, to be like strv */ -+ g_ptr_array_add (packages, NULL); -+ -+ success = gs_dnf5_reset_transaction_sync (self, session_path, rpm_proxy, cancellable, &local_error); -+ -+ success = success && -+ gs_dnf5_rpm_rpm_call_remove_sync (rpm_proxy, -+ (const gchar * const *) packages->pdata, -+ g_variant_builder_end (options_builder), -+ cancellable, -+ &local_error); -+ success = success && gs_dnf5_run_transaction (self, session_path, rpm_proxy, progress_app, NULL, flags, NULL, cancellable, &local_error); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Rpm proxy: "); -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ g_atomic_int_dec_and_test (&self->calling_rpm); -+ -+ if (success) { -+ gs_dnf5_mark_session_needs_reset (self); -+ } else { -+ GsApp *app = NULL; -+ if (gs_app_list_length (data->apps) == 1) -+ app = gs_app_list_index (data->apps, 0); -+ -+ gs_dnf5_convert_error (&local_error); -+ gs_dnf5_report_error (self, data->event_callback, data->event_user_data, app, -+ local_error, (data->flags & GS_PLUGIN_UNINSTALL_APPS_FLAGS_INTERACTIVE) != 0); -+ } -+ -+ for (guint i = 0; i < touched_apps->len; i++) { -+ GsApp *app = g_ptr_array_index (touched_apps, i); -+ if (gs_app_get_state (app) == GS_APP_STATE_REMOVING) { -+ if (success) -+ gs_app_set_state (app, GS_APP_STATE_UNKNOWN); -+ else -+ gs_app_set_state_recover (app); -+ } -+ } -+ -+ if (success) -+ g_task_return_boolean (task, TRUE); -+ else -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+} -+ -+static void -+gs_plugin_dnf5_uninstall_apps_async (GsPlugin *plugin, -+ GsAppList *apps, -+ GsPluginUninstallAppsFlags flags, -+ GsPluginProgressCallback progress_callback, -+ gpointer progress_user_data, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GsPluginAppNeedsUserActionCallback app_needs_user_action_callback, -+ gpointer app_needs_user_action_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_UNINSTALL_APPS_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_uninstall_apps_data_new_task (plugin, apps, flags, progress_callback, progress_user_data, -+ event_callback, event_user_data, -+ app_needs_user_action_callback, app_needs_user_action_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_uninstall_apps_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_uninstall_apps_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_uninstall_apps_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_update_apps_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPlugin *plugin = GS_PLUGIN (self); -+ GsPluginUpdateAppsData *data = task_data; -+ g_autoptr(GError) local_error = NULL; -+ gboolean success; -+ -+ assert_in_worker (self); -+ -+ g_atomic_int_inc (&self->calling_rpm); -+ -+ success = gs_dnf5_install_update (plugin, data->apps, FALSE, -+ (data->flags & GS_PLUGIN_UPDATE_APPS_FLAGS_INTERACTIVE) != 0, -+ (data->flags & GS_PLUGIN_UPDATE_APPS_FLAGS_NO_APPLY) == 0, -+ data->event_callback, data->event_user_data, -+ cancellable, &local_error); -+ -+ g_atomic_int_dec_and_test (&self->calling_rpm); -+ -+ if (success) -+ g_task_return_boolean (task, TRUE); -+ else -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+} -+ -+static void -+gs_plugin_dnf5_update_apps_async (GsPlugin *plugin, -+ GsAppList *apps, -+ GsPluginUpdateAppsFlags flags, -+ GsPluginProgressCallback progress_callback, -+ gpointer progress_user_data, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GsPluginAppNeedsUserActionCallback app_needs_user_action_callback, -+ gpointer app_needs_user_action_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_UPDATE_APPS_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_update_apps_data_new_task (plugin, apps, flags, progress_callback, progress_user_data, -+ event_callback, event_user_data, -+ app_needs_user_action_callback, app_needs_user_action_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_update_apps_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_update_apps_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_update_apps_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+static gboolean -+gs_dnf5_manage_repository_sync (GsPluginDnf5 *self, -+ GsApp *repository, -+ gboolean enable, -+ gboolean interactive, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRepo) repo_proxy = NULL; -+ gboolean success; -+ -+ if (!gs_app_has_management_plugin (repository, GS_PLUGIN (self))) -+ return TRUE; -+ -+ if (gs_app_get_kind (repository) != AS_COMPONENT_KIND_REPOSITORY) { -+ g_set_error (error, -+ GS_PLUGIN_ERROR, -+ GS_PLUGIN_ERROR_NOT_SUPPORTED, -+ "wrong app kind (%s) passed to manage_repository", -+ as_component_kind_to_string (gs_app_get_kind (repository))); -+ return FALSE; -+ } -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (error); -+ return FALSE; -+ } -+ -+ repo_proxy = gs_dnf5_rpm_repo_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ success = repo_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Rpm proxy: "); -+ } -+ -+ if (success) { -+ const gchar *ids[2] = { NULL, NULL }; -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (repo_proxy), G_MAXINT); -+ -+ ids[0] = gs_app_get_id (repository); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (interactive)); -+ -+ if (enable) -+ success = gs_dnf5_rpm_repo_call_enable_with_options_sync (repo_proxy, ids, g_variant_builder_end (options_builder), cancellable, error); -+ else -+ success = gs_dnf5_rpm_repo_call_disable_with_options_sync (repo_proxy, ids, g_variant_builder_end (options_builder), cancellable, error); -+ -+ if (success) { -+ if (enable) -+ gs_app_set_state (repository, GS_APP_STATE_INSTALLED); -+ else -+ gs_app_set_state (repository, GS_APP_STATE_AVAILABLE); -+ } else { -+ gs_dnf5_convert_error (error); -+ g_prefix_error (error, "Failed to call %s: ", enable ? "enable" : "disable"); -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ return success; -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_enable_repository_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginManageRepositoryData *data = task_data; -+ g_autoptr(GError) local_error = NULL; -+ -+ assert_in_worker (self); -+ -+ if (gs_dnf5_manage_repository_sync (self, data->repository, TRUE, -+ (data->flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE) != 0, -+ cancellable, &local_error)) { -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_enable_repository_async (GsPlugin *plugin, -+ GsApp *repository, -+ GsPluginManageRepositoryFlags flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_manage_repository_data_new_task (plugin, repository, flags, event_callback, event_user_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_enable_repository_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_enable_repository_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_enable_repository_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_disable_repository_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginManageRepositoryData *data = task_data; -+ g_autoptr(GError) local_error = NULL; -+ -+ assert_in_worker (self); -+ -+ if (gs_dnf5_manage_repository_sync (self, data->repository, FALSE, -+ (data->flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE) != 0, -+ cancellable, &local_error)) { -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_disable_repository_async (GsPlugin *plugin, -+ GsApp *repository, -+ GsPluginManageRepositoryFlags flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE) != 0; -+ g_autoptr(GTask) task = NULL; -+ -+ task = gs_plugin_manage_repository_data_new_task (plugin, repository, flags, event_callback, event_user_data, -+ cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_disable_repository_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_disable_repository_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_disable_repository_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+static gboolean -+gs_dnf5_pick_rpm_desktop_file_cb (GsPlugin *plugin, -+ GsApp *app, -+ const gchar *filename, -+ GKeyFile *key_file, -+ gpointer user_data) -+{ -+ return strstr (filename, "/snapd/") == NULL && -+ strstr (filename, "/snap/") == NULL && -+ strstr (filename, "/flatpak/") == NULL && -+ g_key_file_has_group (key_file, "Desktop Entry") && -+ !g_key_file_has_key (key_file, "Desktop Entry", "X-Flatpak", NULL) && -+ !g_key_file_has_key (key_file, "Desktop Entry", "X-SnapInstanceName", NULL); -+} -+ -+static void -+gs_plugin_dnf5_launch_async (GsPlugin *plugin, -+ GsApp *app, -+ GsPluginLaunchFlags flags, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ gs_plugin_app_launch_filtered_async (plugin, app, flags, -+ gs_dnf5_pick_rpm_desktop_file_cb, NULL, -+ cancellable, -+ callback, user_data); -+} -+ -+static gboolean -+gs_plugin_dnf5_launch_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return gs_plugin_app_launch_filtered_finish (plugin, result, error); -+} -+ -+static gboolean -+gs_dnf5_file_to_app_sync (GsPluginDnf5 *self, -+ GsAppList *list, -+ GFile *file, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autofree gchar *content_type = NULL; -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ gboolean success = TRUE; -+ const gchar *mimetypes[] = { -+ "application/x-app-package", -+ "application/x-deb", -+ "application/vnd.debian.binary-package", -+ "application/x-redhat-package-manager", -+ "application/x-rpm", -+ NULL }; -+ -+ /* does this match any of the mimetypes we support */ -+ content_type = gs_utils_get_content_type (file, cancellable, error); -+ if (content_type == NULL) -+ return FALSE; -+ if (!g_strv_contains (mimetypes, content_type)) -+ return TRUE; -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (error); -+ return FALSE; -+ } -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ success = rpm_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Rpm proxy: "); -+ } -+ -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariantBuilder) patterns_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ g_autofree gchar *file_uri = NULL; -+ -+ file_uri = g_file_get_path (file); -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ patterns_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); -+ g_variant_builder_add (patterns_builder, "s", file_uri); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "patterns", -+ g_variant_builder_end (patterns_builder)); -+ g_variant_builder_add (options_builder, "{sv}", "package_attrs", -+ gs_dnf5_dup_package_attrs ()); -+ g_variant_builder_add (options_builder, "{sv}", "with_provides", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_filenames", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "with_src", -+ g_variant_new_boolean (FALSE)); -+ g_variant_builder_add (options_builder, "{sv}", "scope", -+ g_variant_new_string ("all")); -+ -+ success = gs_dnf5_rpm_rpm_call_list_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ &result, -+ cancellable, -+ error); -+ -+ if (success) { -+ g_autofree gchar *basename = g_file_get_basename (file); -+ ReadPackageData rpd = { 0, }; -+ rpd.list = list; -+ rpd.set_state = GS_APP_STATE_AVAILABLE_LOCAL; -+ rpd.nevra_to_app = NULL; -+ success = gs_dnf5_foreach_item (self, result, gs_dnf5_read_package_cb, &rpd, cancellable, error); -+ -+ if (success) { -+ for (guint i = 0; i < gs_app_list_length (list); i++) { -+ GsApp *app = gs_app_list_index (list, i); -+ /* it's already downloaded, because it's a local file */ -+ gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0); -+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE); -+ gs_app_set_local_file (app, file); -+ gs_app_set_metadata (app, "GnomeSoftware::packagename-value", basename); -+ } -+ } -+ } else { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to call list: "); -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ return success; -+} -+ -+static void -+gs_dnf5_file_to_app_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ g_autoptr(GsAppList) list = gs_app_list_new (); -+ g_autoptr(GError) local_error = NULL; -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginFileToAppData *data = task_data; -+ -+ if (gs_dnf5_file_to_app_sync (self, list, data->file, cancellable, &local_error)) { -+ g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); -+ } else if (local_error != NULL) { -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } else { -+ g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref); -+ } -+} -+ -+static void -+gs_plugin_dnf5_file_to_app_async (GsPlugin *plugin, -+ GFile *file, -+ GsPluginFileToAppFlags flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ g_autoptr(GTask) task = NULL; -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ gboolean interactive = (flags & GS_PLUGIN_FILE_TO_APP_FLAGS_INTERACTIVE) != 0; -+ -+ task = gs_plugin_file_to_app_data_new_task (plugin, file, flags, event_callback, event_user_data, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_file_to_app_async); -+ -+ /* Queue a job to get the apps. */ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_file_to_app_thread_cb, g_steal_pointer (&task)); -+} -+ -+static GsAppList * -+gs_plugin_dnf5_file_to_app_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_pointer (G_TASK (result), error); -+} -+ -+static gboolean -+gs_dnf5_system_upgrade_sync (GsPluginDnf5 *self, -+ const gchar *releasever, -+ GsApp *progress_app, -+ gboolean download_only, -+ gboolean interactive, -+ GCancellable *cancellable, -+ GError **error) -+{ -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsAppList) list = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5Offline) offline_proxy = NULL; -+ g_autoptr(GsDnf5RpmRpm) rpm_proxy = NULL; -+ gboolean success; -+ -+ session_path = gs_dnf5_open_session (self, releasever, &session_manager, cancellable, error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (error); -+ return FALSE; -+ } -+ -+ rpm_proxy = gs_dnf5_rpm_rpm_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ success = rpm_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Rpm proxy: "); -+ } -+ -+ if (success) { -+ offline_proxy = gs_dnf5_offline_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ error); -+ success = offline_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to create Offline proxy: "); -+ } -+ } -+ -+ if (success) { -+ gboolean pending = FALSE; -+ g_autoptr(GVariant) transaction_status = NULL; -+ -+ if (gs_dnf5_offline_call_get_status_sync (offline_proxy, &pending, &transaction_status, cancellable, error) && -+ pending && transaction_status != NULL) { -+ const gchar *target_releasever = NULL; -+ gboolean found = g_variant_lookup (transaction_status, "target_releasever", "&s", &target_releasever); -+ if (found && g_strcmp0 (target_releasever, releasever) == 0) { -+ g_debug ("There is prepared an upgrade for version '%s' already, skipping preparation", releasever); -+ gs_dnf5_close_session (self, session_manager, session_path); -+ return TRUE; -+ } else if (found && target_releasever != NULL) { -+ g_debug ("There is prepared an update for version '%s', but needs version '%s', redo preparation", target_releasever, releasever); -+ } -+ } -+ } -+ -+ if (success) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autoptr(GVariant) result = NULL; -+ -+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (rpm_proxy), G_MAXINT); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (interactive)); -+ -+ success = gs_dnf5_reset_transaction_sync (self, session_path, rpm_proxy, cancellable, error); -+ -+ success = success && -+ gs_dnf5_rpm_rpm_call_system_upgrade_sync (rpm_proxy, -+ g_variant_builder_end (options_builder), -+ cancellable, -+ error); -+ -+ if (success) { -+ GsDnf5TransactionFlags flags = interactive ? GS_DNF5_TRANSACTION_FLAG_INTERACTIVE : GS_DNF5_TRANSACTION_FLAG_NONE; -+ -+ success = gs_dnf5_run_transaction (self, session_path, rpm_proxy, progress_app, NULL, flags | GS_DNF5_TRANSACTION_FLAG_OFFLINE, NULL, cancellable, error); -+ -+ if (success && !download_only) { -+ g_autofree gchar *op_error_msg = NULL; -+ gboolean op_success = FALSE; -+ -+ g_clear_pointer (&options_builder, g_variant_builder_unref); -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean (interactive)); -+ -+ /* ensure the upgrade is finished with the 'reboot' action */ -+ success = gs_dnf5_offline_call_set_finish_action_with_options_sync (offline_proxy, "reboot", g_variant_builder_end (options_builder), -+ &op_success, &op_error_msg, cancellable, error); -+ if (success && !op_success) { -+ success = FALSE; -+ if (op_error_msg != NULL) { -+ g_set_error_literal (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, op_error_msg); -+ g_prefix_error_literal (error, "Failed to set finish action: "); -+ } -+ } else if (!success) { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to set finish action: "); -+ } -+ } -+ } else { -+ gs_dnf5_convert_error (error); -+ g_prefix_error_literal (error, "Failed to call system_upgrade: "); -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ return success; -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_download_upgrade_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginDownloadUpgradeData *data = task_data; -+ g_autoptr(GError) local_error = NULL; -+ -+ assert_in_worker (self); -+ -+ gs_app_set_state (data->app, GS_APP_STATE_DOWNLOADING); -+ -+ if (gs_dnf5_system_upgrade_sync (self, gs_app_get_version (data->app), data->app, TRUE, -+ (data->flags & GS_PLUGIN_DOWNLOAD_UPGRADE_FLAGS_INTERACTIVE) != 0, -+ cancellable, &local_error)) { -+ gs_app_set_state (data->app, GS_APP_STATE_UPDATABLE); -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_app_set_state (data->app, GS_APP_STATE_AVAILABLE); -+ gs_dnf5_convert_error (&local_error); -+ gs_dnf5_report_error (self, data->event_callback, data->event_user_data, data->app, -+ local_error, (data->flags & GS_PLUGIN_DOWNLOAD_UPGRADE_FLAGS_INTERACTIVE) != 0); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_download_upgrade_async (GsPlugin *plugin, -+ GsApp *app, -+ GsPluginDownloadUpgradeFlags flags, -+ GsPluginEventCallback event_callback, -+ void *event_user_data, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ g_autoptr(GTask) task = NULL; -+ gboolean interactive = (flags & GS_PLUGIN_DOWNLOAD_UPGRADE_FLAGS_INTERACTIVE) != 0; -+ -+ task = gs_plugin_download_upgrade_data_new_task (plugin, app, flags, event_callback, event_user_data, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_download_upgrade_async); -+ -+ /* only process this app if was created by this plugin */ -+ if (!gs_app_has_management_plugin (app, plugin)) { -+ g_task_return_boolean (task, TRUE); -+ return; -+ } -+ -+ /* check is distro-upgrade */ -+ if (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM) { -+ g_task_return_boolean (task, TRUE); -+ return; -+ } -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_download_upgrade_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_download_upgrade_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_trigger_upgrade_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginTriggerUpgradeData *data = task_data; -+ g_autoptr(GError) local_error = NULL; -+ -+ assert_in_worker (self); -+ -+ gs_app_set_state (data->app, GS_APP_STATE_PENDING_INSTALL); -+ -+ if (gs_dnf5_system_upgrade_sync (self, gs_app_get_version (data->app), data->app, FALSE, -+ (data->flags & GS_PLUGIN_TRIGGER_UPGRADE_FLAGS_INTERACTIVE) != 0, -+ cancellable, &local_error)) { -+ gs_app_set_state (data->app, GS_APP_STATE_UPDATABLE); -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_app_set_state (data->app, GS_APP_STATE_AVAILABLE); -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_trigger_upgrade_async (GsPlugin *plugin, -+ GsApp *app, -+ GsPluginTriggerUpgradeFlags flags, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ g_autoptr(GTask) task = NULL; -+ gboolean interactive = (flags & GS_PLUGIN_TRIGGER_UPGRADE_FLAGS_INTERACTIVE) != 0; -+ -+ task = gs_plugin_trigger_upgrade_data_new_task (plugin, app, flags, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_trigger_upgrade_async); -+ -+ /* only process this app if was created by this plugin */ -+ if (!gs_app_has_management_plugin (app, plugin)) { -+ g_task_return_boolean (task, TRUE); -+ return; -+ } -+ -+ /* check is distro-upgrade */ -+ if (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM) { -+ g_task_return_boolean (task, TRUE); -+ return; -+ } -+ -+ gs_app_set_state (app, GS_APP_STATE_PENDING_INSTALL); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_trigger_upgrade_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_trigger_upgrade_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+/* Run in @worker. */ -+static void -+gs_dnf5_cancel_offline_update_thread_cb (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (source_object); -+ GsPluginCancelOfflineUpdateData *data = task_data; -+ g_autofree gchar *session_path = NULL; -+ g_autoptr(GsAppList) list = NULL; -+ g_autoptr(GsDnf5SessionManager) session_manager = NULL; -+ g_autoptr(GsDnf5Offline) offline_proxy = NULL; -+ gboolean success; -+ g_autoptr(GError) local_error = NULL; -+ -+ assert_in_worker (self); -+ -+ session_path = gs_dnf5_open_session (self, GS_DNF5_RELEASEVER_DEFAULT, &session_manager, cancellable, &local_error); -+ if (session_path == NULL) { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ return; -+ } -+ -+ offline_proxy = gs_dnf5_offline_proxy_new_sync (self->connection, -+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, -+ GS_DNF5_INTERFACE_RPM_DNF, -+ session_path, -+ cancellable, -+ &local_error); -+ success = offline_proxy != NULL; -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to create Offline proxy: "); -+ } -+ -+ if (success) { -+ gboolean pending = FALSE; -+ g_autoptr(GVariant) transaction_status = NULL; -+ -+ if (gs_dnf5_offline_call_get_status_sync (offline_proxy, &pending, &transaction_status, cancellable, &local_error) && -+ pending && transaction_status != NULL) { -+ g_autoptr(GVariantBuilder) options_builder = NULL; -+ g_autofree gchar *op_error_msg = NULL; -+ gboolean op_success = FALSE; -+ -+ options_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); -+ g_variant_builder_add (options_builder, "{sv}", "interactive", -+ g_variant_new_boolean ((data->flags & GS_PLUGIN_CANCEL_OFFLINE_UPDATE_FLAGS_INTERACTIVE) != 0)); -+ -+ success = gs_dnf5_offline_call_cancel_with_options_sync (offline_proxy, g_variant_builder_end (options_builder), -+ &op_success, &op_error_msg, cancellable, &local_error); -+ if (!success) { -+ gs_dnf5_convert_error (&local_error); -+ g_prefix_error_literal (&local_error, "Failed to call OfflineProxy::cancel: "); -+ } else if (!op_success) { -+ success = FALSE; -+ if (op_error_msg != NULL) { -+ g_set_error_literal (&local_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, op_error_msg); -+ g_prefix_error_literal (&local_error, "Failed to call OfflineProxy::cancel op: "); -+ } else { -+ g_set_error_literal (&local_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "Failed to call OfflineProxy::cancel op"); -+ } -+ } -+ } -+ } -+ -+ gs_dnf5_close_session (self, session_manager, session_path); -+ -+ if (success) { -+ g_task_return_boolean (task, TRUE); -+ } else { -+ gs_dnf5_convert_error (&local_error); -+ g_task_return_error (task, g_steal_pointer (&local_error)); -+ } -+} -+ -+static void -+gs_plugin_dnf5_cancel_offline_update_async (GsPlugin *plugin, -+ GsPluginCancelOfflineUpdateFlags flags, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer user_data) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (plugin); -+ g_autoptr(GTask) task = NULL; -+ gboolean interactive = (flags & GS_PLUGIN_CANCEL_OFFLINE_UPDATE_FLAGS_INTERACTIVE) != 0; -+ -+ task = gs_plugin_cancel_offline_update_data_new_task (plugin, flags, cancellable, callback, user_data); -+ g_task_set_source_tag (task, gs_plugin_dnf5_cancel_offline_update_async); -+ -+ gs_worker_thread_queue (self->worker, gs_dnf5_get_priority_for_interactivity (interactive), -+ gs_dnf5_cancel_offline_update_thread_cb, g_steal_pointer (&task)); -+} -+ -+static gboolean -+gs_plugin_dnf5_cancel_offline_update_finish (GsPlugin *plugin, -+ GAsyncResult *result, -+ GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+static void -+gs_plugin_dnf5_dispose (GObject *object) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (object); -+ -+ if (self->rpm_transaction_watch_id != 0) { -+ g_bus_unwatch_name (self->rpm_transaction_watch_id); -+ self->rpm_transaction_watch_id = 0; -+ } -+ -+ g_clear_object (&self->worker); -+ g_clear_object (&self->connection); -+ g_clear_object (&self->rpm_transaction_proxy); -+ -+ g_mutex_lock (&self->session_data.mutex); -+ if (self->session_data.autoclose_timer) { -+ g_source_remove (self->session_data.autoclose_timer); -+ self->session_data.autoclose_timer = 0; -+ } -+ -+ if (self->session_data.proxy != NULL) { -+ gs_dnf5_close_session_real (self->session_data.proxy, self->session_data.object_path); -+ -+ g_clear_object (&self->session_data.proxy); -+ g_clear_pointer (&self->session_data.object_path, g_free); -+ } -+ g_mutex_unlock (&self->session_data.mutex); -+ -+ g_mutex_lock (&self->dependency_sizes_data.mutex); -+ g_clear_pointer (&self->dependency_sizes_data.apps, g_ptr_array_unref); -+ g_clear_object (&self->dependency_sizes_data.cancellable); -+ g_mutex_unlock (&self->dependency_sizes_data.mutex); -+ -+ G_OBJECT_CLASS (gs_plugin_dnf5_parent_class)->dispose (object); -+} -+ -+static void -+gs_plugin_dnf5_finalize (GObject *object) -+{ -+ GsPluginDnf5 *self = GS_PLUGIN_DNF5 (object); -+ -+ g_mutex_clear (&self->session_data.mutex); -+ g_cond_clear (&self->session_data.cond); -+ g_mutex_clear (&self->dependency_sizes_data.mutex); -+ -+ G_OBJECT_CLASS (gs_plugin_dnf5_parent_class)->finalize (object); -+} -+ -+static void -+gs_plugin_dnf5_class_init (GsPluginDnf5Class *klass) -+{ -+ GObjectClass *object_class = G_OBJECT_CLASS (klass); -+ GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass); -+ -+ object_class->dispose = gs_plugin_dnf5_dispose; -+ object_class->finalize = gs_plugin_dnf5_finalize; -+ -+ plugin_class->setup_async = gs_plugin_dnf5_setup_async; -+ plugin_class->setup_finish = gs_plugin_dnf5_setup_finish; -+ plugin_class->shutdown_async = gs_plugin_dnf5_shutdown_async; -+ plugin_class->shutdown_finish = gs_plugin_dnf5_shutdown_finish; -+ plugin_class->list_apps_async = gs_plugin_dnf5_list_apps_async; -+ plugin_class->list_apps_finish = gs_plugin_dnf5_list_apps_finish; -+ plugin_class->refine_async = gs_plugin_dnf5_refine_async; -+ plugin_class->refine_finish = gs_plugin_dnf5_refine_finish; -+ plugin_class->refresh_metadata_async = gs_plugin_dnf5_refresh_metadata_async; -+ plugin_class->refresh_metadata_finish = gs_plugin_dnf5_refresh_metadata_finish; -+ plugin_class->update_apps_async = gs_plugin_dnf5_update_apps_async; -+ plugin_class->update_apps_finish = gs_plugin_dnf5_update_apps_finish; -+ plugin_class->enable_repository_async = gs_plugin_dnf5_enable_repository_async; -+ plugin_class->enable_repository_finish = gs_plugin_dnf5_enable_repository_finish; -+ plugin_class->disable_repository_async = gs_plugin_dnf5_disable_repository_async; -+ plugin_class->disable_repository_finish = gs_plugin_dnf5_disable_repository_finish; -+ plugin_class->install_apps_async = gs_plugin_dnf5_install_apps_async; -+ plugin_class->install_apps_finish = gs_plugin_dnf5_install_apps_finish; -+ plugin_class->uninstall_apps_async = gs_plugin_dnf5_uninstall_apps_async; -+ plugin_class->uninstall_apps_finish = gs_plugin_dnf5_uninstall_apps_finish; -+ plugin_class->launch_async = gs_plugin_dnf5_launch_async; -+ plugin_class->launch_finish = gs_plugin_dnf5_launch_finish; -+ plugin_class->file_to_app_async = gs_plugin_dnf5_file_to_app_async; -+ plugin_class->file_to_app_finish = gs_plugin_dnf5_file_to_app_finish; -+ plugin_class->download_upgrade_async = gs_plugin_dnf5_download_upgrade_async; -+ plugin_class->download_upgrade_finish = gs_plugin_dnf5_download_upgrade_finish; -+ plugin_class->trigger_upgrade_async = gs_plugin_dnf5_trigger_upgrade_async; -+ plugin_class->trigger_upgrade_finish = gs_plugin_dnf5_trigger_upgrade_finish; -+ plugin_class->cancel_offline_update_async = gs_plugin_dnf5_cancel_offline_update_async; -+ plugin_class->cancel_offline_update_finish = gs_plugin_dnf5_cancel_offline_update_finish; -+} -+ -+static void -+gs_plugin_dnf5_init (GsPluginDnf5 *self) -+{ -+ GsPlugin *plugin = GS_PLUGIN (self); -+ -+ g_mutex_init (&self->session_data.mutex); -+ g_cond_init (&self->session_data.cond); -+ g_mutex_init (&self->dependency_sizes_data.mutex); -+ -+ /* getting app properties from appstream is quicker */ -+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream"); -+ -+ /* like appstream, we need the icon plugin to load cached icons into pixbufs */ -+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_BEFORE, "icons"); -+ -+ /* prioritize over packagekit */ -+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "packagekit"); -+} -+ -+GType -+gs_plugin_query_type (void) -+{ -+ return GS_TYPE_PLUGIN_DNF5; -+} -diff --git a/plugins/dnf5/gs-plugin-dnf5.h b/plugins/dnf5/gs-plugin-dnf5.h -new file mode 100644 -index 000000000..319d5843c ---- /dev/null -+++ b/plugins/dnf5/gs-plugin-dnf5.h -@@ -0,0 +1,20 @@ -+/* -*- 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+ -+ */ -+ -+#pragma once -+ -+#include -+#include -+ -+G_BEGIN_DECLS -+ -+#define GS_TYPE_PLUGIN_DNF5 (gs_plugin_dnf5_get_type ()) -+ -+G_DECLARE_FINAL_TYPE (GsPluginDnf5, gs_plugin_dnf5, GS, PLUGIN_DNF5, GsPlugin) -+ -+G_END_DECLS -diff --git a/plugins/dnf5/meson.build b/plugins/dnf5/meson.build -new file mode 100644 -index 000000000..12b2137e1 ---- /dev/null -+++ b/plugins/dnf5/meson.build -@@ -0,0 +1,42 @@ -+cargs = ['-DG_LOG_DOMAIN="GsDnf5"'] -+ -+dnf5_generated = gnome.gdbus_codegen( -+ 'gs-dnf5-generated', -+ sources: [ 'org.rpm.dnf.v0.Advisory.xml', -+ 'org.rpm.dnf.v0.Base.xml', -+ 'org.rpm.dnf.v0.comps.Group.xml', -+ 'org.rpm.dnf.v0.Goal.xml', -+ 'org.rpm.dnf.v0.History.xml', -+ 'org.rpm.dnf.v0.Offline.xml', -+ 'org.rpm.dnf.v0.rpm.Repo.xml', -+ 'org.rpm.dnf.v0.rpm.Rpm.xml', -+ 'org.rpm.dnf.v0.SessionManager.xml' -+ ], -+ interface_prefix : 'org.rpm.dnf.v0', -+ namespace : 'GsDnf5' -+) -+ -+dnf5_generated += gnome.gdbus_codegen( -+ 'gs-dnf5-rpm-generated', -+ sources: [ 'org.rpm.Transaction.xml' ], -+ interface_prefix : 'org.rpm', -+ namespace : 'GsDnf5Rpm' -+) -+ -+shared_module( -+ 'gs_plugin_dnf5', -+ dnf5_generated, -+ sources : [ -+ 'gs-dnf5-progress-helper.c', -+ 'gs-plugin-dnf5.c', -+ '../packagekit/gs-markdown.c', -+ ], -+ include_directories : [ -+ include_directories('../..'), -+ include_directories('../../lib'), -+ ], -+ install : true, -+ install_dir: plugin_dir, -+ c_args : cargs, -+ dependencies : [ plugin_libs ], -+) -diff --git a/plugins/dnf5/org.rpm.Transaction.xml b/plugins/dnf5/org.rpm.Transaction.xml -new file mode 100644 -index 000000000..7d0cbab48 ---- /dev/null -+++ b/plugins/dnf5/org.rpm.Transaction.xml -@@ -0,0 +1,26 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.Advisory.xml b/plugins/dnf5/org.rpm.dnf.v0.Advisory.xml -new file mode 100644 -index 000000000..6aecee9fa ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.Advisory.xml -@@ -0,0 +1,74 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.Base.xml b/plugins/dnf5/org.rpm.dnf.v0.Base.xml -new file mode 100644 -index 000000000..e596e60b9 ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.Base.xml -@@ -0,0 +1,176 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.Goal.xml b/plugins/dnf5/org.rpm.dnf.v0.Goal.xml -new file mode 100644 -index 000000000..097e9dc7e ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.Goal.xml -@@ -0,0 +1,122 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.History.xml b/plugins/dnf5/org.rpm.dnf.v0.History.xml -new file mode 100644 -index 000000000..5df5184b0 ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.History.xml -@@ -0,0 +1,84 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.Offline.xml b/plugins/dnf5/org.rpm.dnf.v0.Offline.xml -new file mode 100644 -index 000000000..8d84c22aa ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.Offline.xml -@@ -0,0 +1,151 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.SessionManager.xml b/plugins/dnf5/org.rpm.dnf.v0.SessionManager.xml -new file mode 100644 -index 000000000..a0facd692 ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.SessionManager.xml -@@ -0,0 +1,69 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.comps.Group.xml b/plugins/dnf5/org.rpm.dnf.v0.comps.Group.xml -new file mode 100644 -index 000000000..b12e0ac6d ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.comps.Group.xml -@@ -0,0 +1,61 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.rpm.Repo.xml b/plugins/dnf5/org.rpm.dnf.v0.rpm.Repo.xml -new file mode 100644 -index 000000000..50b2ba3bf ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.rpm.Repo.xml -@@ -0,0 +1,147 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/dnf5/org.rpm.dnf.v0.rpm.Rpm.xml b/plugins/dnf5/org.rpm.dnf.v0.rpm.Rpm.xml -new file mode 100644 -index 000000000..f24b14d39 ---- /dev/null -+++ b/plugins/dnf5/org.rpm.dnf.v0.rpm.Rpm.xml -@@ -0,0 +1,470 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/plugins/meson.build b/plugins/meson.build -index a108e47e5..a131a5cd3 100644 ---- a/plugins/meson.build -+++ b/plugins/meson.build -@@ -16,6 +16,9 @@ subdir('fedora-pkgdb-collections') - if get_option('dkms') - subdir('dkms') - endif -+if get_option('dnf5') -+ subdir('dnf5') -+endif - if get_option('eos_updater') - subdir('eos-updater') - endif -diff --git a/plugins/rpm-ostree/gs-plugin-rpm-ostree.c b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c -index 627a5143e..64451bccc 100644 ---- a/plugins/rpm-ostree/gs-plugin-rpm-ostree.c -+++ b/plugins/rpm-ostree/gs-plugin-rpm-ostree.c -@@ -168,6 +168,7 @@ gs_plugin_rpm_ostree_init (GsPluginRpmOstree *self) - * more sense to use a custom plugin instead of using PackageKit. - */ - gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_CONFLICTS, "packagekit"); -+ gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_CONFLICTS, "dnf5"); - - /* need pkgname */ - gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "appstream"); -diff --git a/po/POTFILES.in b/po/POTFILES.in -index 71aa486e2..d3e909dee 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -112,6 +112,7 @@ lib/gs-utils.c - src/org.gnome.Software.desktop.in - plugins/dkms/gs-plugin-dkms.c - plugins/core/gs-plugin-generic-updates.c -+plugins/dnf5/gs-plugin-dnf5.c - plugins/eos-updater/gs-plugin-eos-updater.c - plugins/epiphany/gs-plugin-epiphany.c - plugins/epiphany/org.gnome.Software.Plugin.Epiphany.metainfo.xml.in -diff --git a/src/meson.build b/src/meson.build -index f8b918539..7df81d130 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -245,7 +245,7 @@ if get_option('fwupd') - ) - endif - --if get_option('packagekit') or get_option('rpm_ostree') -+if get_option('packagekit') or get_option('rpm_ostree') or get_option('dnf5') - i18n.merge_file( - input: - configure_file( diff --git a/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch b/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch deleted file mode 100644 index 33140df..0000000 --- a/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch +++ /dev/null @@ -1,686 +0,0 @@ -From 0c79d9fb08a348a45a18f15427c945a63b44982e Mon Sep 17 00:00:00 2001 -Date: Mon, 24 Nov 2025 16:06:51 +0100 -Subject: [PATCH 1/3] flatpak: Fix XbSilo lifetime in - gs_flatpak_refine_wildcard() - -The function uses pre-cached data in the hash tables, consisting of XbNode-s -from an XbSilo. The nodes are referenced, but the silos the node belongs to -is not referenced (by the XbNode). - -This change keeps the XbSilo alive as long as it's needed for the nodes -in the pre-cached data. It also does not call gs_flatpak_ref_silo() multiple -times for the same GsFlatpak instance, which could invalidate the XbNode-s -in the pre-cached data when a change in the silo or in the installation -had been received while the function was still processing the apps. - -Related downstream bug https://bugzilla.redhat.com/show_bug.cgi?id=2416542 -Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/2900 ---- - plugins/flatpak/gs-flatpak.c | 18 ++++++++--- - plugins/flatpak/gs-flatpak.h | 4 +++ - plugins/flatpak/gs-plugin-flatpak.c | 49 ++++++++++++++++++++--------- - 3 files changed, 51 insertions(+), 20 deletions(-) - -diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c -index b409475c7..a40ab54fb 100644 ---- a/plugins/flatpak/gs-flatpak.c -+++ b/plugins/flatpak/gs-flatpak.c -@@ -3748,16 +3748,19 @@ gboolean - gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app, - GsAppList *list, GsPluginRefineRequireFlags require_flags, - gboolean interactive, -+ XbSilo **inout_silo, -+ gchar **inout_silo_filename, -+ GHashTable **inout_installed_by_desktopid, - GHashTable **inout_components_by_id, - GHashTable **inout_components_by_bundle, - GCancellable *cancellable, GError **error) - { - const gchar *id; - GPtrArray* components = NULL; -+ XbSilo *silo; -+ GHashTable *silo_installed_by_desktopid; -+ const gchar *silo_filename; - g_autoptr(GError) error_local = NULL; -- g_autoptr(XbSilo) silo = NULL; -- g_autoptr(GHashTable) silo_installed_by_desktopid = NULL; -- g_autofree gchar *silo_filename = NULL; - - GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL); - -@@ -3766,10 +3769,15 @@ gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app, - if (id == NULL) - return TRUE; - -- silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error); -- if (silo == NULL) -+ if (*inout_silo == NULL) -+ *inout_silo = gs_flatpak_ref_silo (self, interactive, inout_silo_filename, inout_installed_by_desktopid, cancellable, error); -+ if (*inout_silo == NULL) - return FALSE; - -+ silo = *inout_silo; -+ silo_filename = *inout_silo_filename; -+ silo_installed_by_desktopid = *inout_installed_by_desktopid; -+ - GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardQuerySilo, "Flatpak (query silo)", NULL); - - if (*inout_components_by_id != NULL) { -diff --git a/plugins/flatpak/gs-flatpak.h b/plugins/flatpak/gs-flatpak.h -index 66ff59967..34e1416b5 100644 ---- a/plugins/flatpak/gs-flatpak.h -+++ b/plugins/flatpak/gs-flatpak.h -@@ -11,6 +11,7 @@ - - #include - #include -+#include - - G_BEGIN_DECLS - -@@ -108,6 +109,9 @@ gboolean gs_flatpak_refine_wildcard (GsFlatpak *self, - GsAppList *list, - GsPluginRefineRequireFlags require_flags, - gboolean interactive, -+ XbSilo **inout_silo, -+ gchar **inout_silo_filename, -+ GHashTable **inout_installed_by_desktopid, - GHashTable **inout_components_by_id, - GHashTable **inout_components_by_bundle, - GCancellable *cancellable, -diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c -index 3d0503d46..f5d4a1d67 100644 ---- a/plugins/flatpak/gs-plugin-flatpak.c -+++ b/plugins/flatpak/gs-plugin-flatpak.c -@@ -667,12 +667,29 @@ gs_plugin_flatpak_refine_app (GsPluginFlatpak *self, - return gs_flatpak_refine_app (flatpak, app, require_flags, interactive, FALSE, event_callback, event_user_data, cancellable, error); - } - -+typedef struct { -+ XbSilo *silo; -+ gchar *silo_filename; -+ GHashTable *installed_by_desktopid; -+ GHashTable *components_by_id; -+ GHashTable *components_by_bundle; -+} RefineInstallationData; -+ - static void --unref_nonnull_hash_table (gpointer ptr) -+refine_installation_data_free (gpointer ptr) - { -- GHashTable *hash_table = ptr; -- if (hash_table != NULL) -- g_hash_table_unref (hash_table); -+ RefineInstallationData *data = ptr; -+ -+ if (data == NULL) -+ return; -+ -+ g_clear_pointer (&data->silo_filename, g_free); -+ g_clear_pointer (&data->installed_by_desktopid, g_hash_table_unref); -+ g_clear_pointer (&data->components_by_id, g_hash_table_unref); -+ g_clear_pointer (&data->components_by_bundle, g_hash_table_unref); -+ /* free the silo as the last, just in case, because the data from it is in the above hash tables */ -+ g_clear_object (&data->silo); -+ g_free (data); - } - - static gboolean -@@ -761,8 +778,7 @@ refine_thread_cb (GTask *task, - gboolean interactive = (data->job_flags & GS_PLUGIN_REFINE_FLAGS_INTERACTIVE) != 0; - GsPluginEventCallback event_callback = data->event_callback; - void *event_user_data = data->event_user_data; -- g_autoptr(GPtrArray) array_components_by_id = NULL; /* (element-type GHashTable) */ -- g_autoptr(GPtrArray) array_components_by_bundle = NULL; /* (element-type GHashTable) */ -+ g_autoptr(GPtrArray) installation_data = NULL; /* (element-type RefineInstallationData) */ - g_autoptr(GsAppList) app_list = NULL; - g_autoptr(GError) local_error = NULL; - -@@ -783,10 +799,9 @@ refine_thread_cb (GTask *task, - * (e.g. inserting an app in the list on every call results in - * an infinite loop) */ - app_list = gs_app_list_copy (list); -- array_components_by_id = g_ptr_array_new_full (self->installations->len, unref_nonnull_hash_table); -- g_ptr_array_set_size (array_components_by_id, self->installations->len); -- array_components_by_bundle = g_ptr_array_new_full (self->installations->len, unref_nonnull_hash_table); -- g_ptr_array_set_size (array_components_by_bundle, self->installations->len); -+ -+ installation_data = g_ptr_array_new_full (self->installations->len, refine_installation_data_free); -+ g_ptr_array_set_size (installation_data, self->installations->len); - - for (guint j = 0; j < gs_app_list_length (app_list); j++) { - GsApp *app = gs_app_list_index (app_list, j); -@@ -796,16 +811,20 @@ refine_thread_cb (GTask *task, - - for (guint i = 0; i < self->installations->len; i++) { - GsFlatpak *flatpak = g_ptr_array_index (self->installations, i); -- GHashTable *components_by_id = array_components_by_id->pdata[i]; -- GHashTable *components_by_bundle = array_components_by_bundle->pdata[i]; -+ RefineInstallationData *inst_data = g_ptr_array_index (installation_data, i); -+ -+ if (inst_data == NULL) { -+ inst_data = g_new0 (RefineInstallationData, 1); -+ installation_data->pdata[i] = inst_data; -+ } - -- if (!gs_flatpak_refine_wildcard (flatpak, app, list, require_flags, interactive, &components_by_id, &components_by_bundle, -+ if (!gs_flatpak_refine_wildcard (flatpak, app, list, require_flags, interactive, &inst_data->silo, -+ &inst_data->silo_filename, &inst_data->installed_by_desktopid, -+ &inst_data->components_by_id, &inst_data->components_by_bundle, - cancellable, &local_error)) { - g_task_return_error (task, g_steal_pointer (&local_error)); - return; - } -- array_components_by_id->pdata[i] = components_by_id; -- array_components_by_bundle->pdata[i] = components_by_bundle; - } - } - --- -GitLab - - -From 546c6ee17b5959d2a696a6cb346a1f8b94aba303 Mon Sep 17 00:00:00 2001 -Date: Wed, 7 Jan 2026 18:37:52 +0100 -Subject: [PATCH 2/3] flatpak: Simplify wildcard app lookup function - -By flipping the order of traversing app and installation the code -can be simplified to not have too many in/out arguments. - -Suggested by Philip Withnall ---- - plugins/flatpak/gs-flatpak.c | 251 ++++++++++++++-------------- - plugins/flatpak/gs-flatpak.h | 9 +- - plugins/flatpak/gs-plugin-flatpak.c | 61 +------ - 3 files changed, 130 insertions(+), 191 deletions(-) - -diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c -index a40ab54fb..782b0bb48 100644 ---- a/plugins/flatpak/gs-flatpak.c -+++ b/plugins/flatpak/gs-flatpak.c -@@ -3745,167 +3745,158 @@ gs_flatpak_refine_app (GsFlatpak *self, - } - - gboolean --gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app, -- GsAppList *list, GsPluginRefineRequireFlags require_flags, -- gboolean interactive, -- XbSilo **inout_silo, -- gchar **inout_silo_filename, -- GHashTable **inout_installed_by_desktopid, -- GHashTable **inout_components_by_id, -- GHashTable **inout_components_by_bundle, -- GCancellable *cancellable, GError **error) -+gs_flatpak_refine_wildcards (GsFlatpak *self, -+ GPtrArray *wildcard_apps, -+ GsAppList *list, -+ GsPluginRefineRequireFlags require_flags, -+ gboolean interactive, -+ GCancellable *cancellable, -+ GError **error) - { -- const gchar *id; -- GPtrArray* components = NULL; -- XbSilo *silo; -- GHashTable *silo_installed_by_desktopid; -- const gchar *silo_filename; - g_autoptr(GError) error_local = NULL; -+ g_autoptr(XbSilo) silo = NULL; -+ g_autoptr(GHashTable) silo_installed_by_desktopid = NULL; -+ g_autoptr(GHashTable) components_by_id = NULL; -+ g_autoptr(GHashTable) components_by_bundle = NULL; -+ g_autoptr(GPtrArray) components_with_id = NULL; -+ g_autoptr(GPtrArray) bundles = NULL; -+ g_autofree gchar *silo_filename = NULL; - - GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL); - -- /* not enough info to find */ -- id = gs_app_get_id (app); -- if (id == NULL) -- return TRUE; -- -- if (*inout_silo == NULL) -- *inout_silo = gs_flatpak_ref_silo (self, interactive, inout_silo_filename, inout_installed_by_desktopid, cancellable, error); -- if (*inout_silo == NULL) -+ silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error); -+ if (silo == NULL) - return FALSE; - -- silo = *inout_silo; -- silo_filename = *inout_silo_filename; -- silo_installed_by_desktopid = *inout_installed_by_desktopid; -- - GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardQuerySilo, "Flatpak (query silo)", NULL); - -- if (*inout_components_by_id != NULL) { -- components = g_hash_table_lookup (*inout_components_by_id, gs_app_get_id (app)); -- } else { -- g_autoptr(GPtrArray) components_with_id = NULL; -- *inout_components_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); -- components_with_id = xb_silo_query (silo, "components/component/id", 0, &error_local); -- if (components_with_id == NULL) { -- if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) -- return TRUE; -- g_propagate_error (error, g_steal_pointer (&error_local)); -- return FALSE; -+ components_by_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); -+ components_with_id = xb_silo_query (silo, "components/component/id", 0, &error_local); -+ if (components_with_id == NULL) { -+ if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) -+ return TRUE; -+ g_propagate_error (error, g_steal_pointer (&error_local)); -+ return FALSE; -+ } -+ -+ for (guint i = 0; i < components_with_id->len; i++) { -+ XbNode *node = g_ptr_array_index (components_with_id, i); -+ XbNode *comp_node = xb_node_get_parent (node); -+ const gchar *comp_id = xb_node_get_text (node); -+ GPtrArray *comps = g_hash_table_lookup (components_by_id, comp_id); -+ if (comps == NULL) { -+ comps = g_ptr_array_new_with_free_func (g_object_unref); -+ g_hash_table_insert (components_by_id, g_strdup (comp_id), comps); - } -- for (guint i = 0; i < components_with_id->len; i++) { -- XbNode *node = g_ptr_array_index (components_with_id, i); -- XbNode *comp_node = xb_node_get_parent (node); -- const gchar *comp_id = xb_node_get_text (node); -- GPtrArray *comps = g_hash_table_lookup (*inout_components_by_id, comp_id); -- if (comps == NULL) { -- comps = g_ptr_array_new_with_free_func (g_object_unref); -- g_hash_table_insert (*inout_components_by_id, g_strdup (comp_id), comps); -+ g_ptr_array_add (comps, comp_node); -+ } -+ -+ components_by_bundle = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); -+ bundles = xb_silo_query (silo, "/components/component/bundle[@type='flatpak']", 0, NULL); -+ for (guint b = 0; bundles != NULL && b < bundles->len; b++) { -+ XbNode *bundle_node = g_ptr_array_index (bundles, b); -+ g_autoptr(XbNode) component_node = xb_node_get_parent (bundle_node); -+ g_autoptr(XbNode) components_node = xb_node_get_parent (component_node); -+ const gchar *origin = xb_node_get_attr (components_node, "origin"); -+ if (origin != NULL) { -+ const gchar *bundle = xb_node_get_text (bundle_node); -+ if (bundle != NULL) { -+ g_autofree gchar *key = g_strconcat (origin, "\n", bundle, NULL); -+ g_hash_table_insert (components_by_bundle, g_steal_pointer (&key), g_steal_pointer (&component_node)); - } -- g_ptr_array_add (comps, comp_node); -- if (components == NULL && g_strcmp0 (id, comp_id) == 0) -- components = comps; - } - } - - GS_PROFILER_END_SCOPED (FlatpakRefineWildcardQuerySilo); - -- if (components == NULL) -- return TRUE; -- - gs_flatpak_ensure_remote_title (self, interactive, cancellable); - -- if (*inout_components_by_bundle == NULL) { -- g_autoptr(GPtrArray) bundles = NULL; -- -- *inout_components_by_bundle = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); -- bundles = xb_silo_query (silo, "/components/component/bundle[@type='flatpak']", 0, NULL); -- for (guint b = 0; bundles != NULL && b < bundles->len; b++) { -- XbNode *bundle_node = g_ptr_array_index (bundles, b); -- g_autoptr(XbNode) component_node = xb_node_get_parent (bundle_node); -- g_autoptr(XbNode) components_node = xb_node_get_parent (component_node); -- const gchar *origin = xb_node_get_attr (components_node, "origin"); -- if (origin != NULL) { -- const gchar *bundle = xb_node_get_text (bundle_node); -- if (bundle != NULL) { -- g_autofree gchar *key = g_strconcat (origin, "\n", bundle, NULL); -- g_hash_table_insert (*inout_components_by_bundle, g_steal_pointer (&key), g_steal_pointer (&component_node)); -- } -- } -- } -- } -+ for (guint j = 0; j < wildcard_apps->len; j++) { -+ GsApp *app = g_ptr_array_index (wildcard_apps, j); -+ GPtrArray *components = NULL; -+ const gchar *id; - -+ /* not enough info to find */ -+ id = gs_app_get_id (app); -+ if (id == NULL) -+ continue; - -- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardGenerateApps, "Flatpak (create app)", NULL); -- for (guint i = 0; i < components->len; i++) { -- XbNode *component = g_ptr_array_index (components, i); -- g_autoptr(GsApp) new = NULL; -+ components = g_hash_table_lookup (components_by_id, id); -+ if (components == NULL) -+ continue; - -- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardCreateAppstreamApp, "Flatpak (create Appstream app)", NULL); -- new = gs_appstream_create_app (self->plugin, silo, component, silo_filename ? silo_filename : "", -- self->scope, error); -- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardCreateAppstreamApp); -+ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardGenerateApps, "Flatpak (create app)", NULL); -+ for (guint i = 0; i < components->len; i++) { -+ XbNode *component = g_ptr_array_index (components, i); -+ g_autoptr(GsApp) new = NULL; - -- if (new == NULL) -- return FALSE; -+ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardCreateAppstreamApp, "Flatpak (create Appstream app)", NULL); -+ new = gs_appstream_create_app (self->plugin, silo, component, silo_filename ? silo_filename : "", -+ self->scope, error); -+ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardCreateAppstreamApp); - -- gs_flatpak_claim_app (self, new); -- -- /* The appstream plugin did not find the component in the plugin's cache, -- thus read the required info from the 'bundle' element. */ -- if (gs_flatpak_app_get_ref_name (new) == NULL || -- gs_flatpak_app_get_ref_arch (new) == NULL) { -- const gchar *xref_str = NULL; -- g_autoptr(XbNode) child = NULL; -- g_autoptr(XbNode) next = NULL; -- for (child = xb_node_get_child (component); child != NULL && xref_str == NULL; -- g_object_unref (child), child = g_steal_pointer (&next)) { -- next = xb_node_get_next (child); -- if (g_strcmp0 (xb_node_get_element (child), "bundle") == 0 && -- g_strcmp0 (xb_node_get_attr (child, "type"), "flatpak") == 0) { -- xref_str = xb_node_get_text (child); -- break; -+ if (new == NULL) -+ return FALSE; -+ -+ gs_flatpak_claim_app (self, new); -+ -+ /* The appstream plugin did not find the component in the plugin's cache, -+ thus read the required info from the 'bundle' element. */ -+ if (gs_flatpak_app_get_ref_name (new) == NULL || -+ gs_flatpak_app_get_ref_arch (new) == NULL) { -+ const gchar *xref_str = NULL; -+ g_autoptr(XbNode) child = NULL; -+ g_autoptr(XbNode) next = NULL; -+ for (child = xb_node_get_child (component); child != NULL && xref_str == NULL; -+ g_object_unref (child), child = g_steal_pointer (&next)) { -+ next = xb_node_get_next (child); -+ if (g_strcmp0 (xb_node_get_element (child), "bundle") == 0 && -+ g_strcmp0 (xb_node_get_attr (child, "type"), "flatpak") == 0) { -+ xref_str = xb_node_get_text (child); -+ break; -+ } - } -- } -- if (xref_str != NULL) { -- g_auto(GStrv) split = NULL; -- -- /* get the kind/name/arch/branch */ -- split = g_strsplit (xref_str, "/", -1); -- if (g_strv_length (split) == 4) { -- const gchar *comp_type = xb_node_get_attr (component, "type"); -- AsComponentKind kind = as_component_kind_from_string (comp_type); -- if (kind != AS_COMPONENT_KIND_UNKNOWN) -- gs_app_set_kind (new, kind); -- else if (g_ascii_strcasecmp (split[0], "app") == 0) -- gs_app_set_kind (new, AS_COMPONENT_KIND_DESKTOP_APP); -- else if (g_ascii_strcasecmp (split[0], "runtime") == 0) -- gs_flatpak_set_runtime_kind_from_id (new); -- gs_flatpak_app_set_ref_name (new, split[1]); -- gs_flatpak_app_set_ref_arch (new, split[2]); -- gs_app_set_branch (new, split[3]); -- gs_app_set_metadata (new, "GnomeSoftware::packagename-value", xref_str); -+ if (xref_str != NULL) { -+ g_auto(GStrv) split = NULL; -+ -+ /* get the kind/name/arch/branch */ -+ split = g_strsplit (xref_str, "/", -1); -+ if (g_strv_length (split) == 4) { -+ const gchar *comp_type = xb_node_get_attr (component, "type"); -+ AsComponentKind kind = as_component_kind_from_string (comp_type); -+ if (kind != AS_COMPONENT_KIND_UNKNOWN) -+ gs_app_set_kind (new, kind); -+ else if (g_ascii_strcasecmp (split[0], "app") == 0) -+ gs_app_set_kind (new, AS_COMPONENT_KIND_DESKTOP_APP); -+ else if (g_ascii_strcasecmp (split[0], "runtime") == 0) -+ gs_flatpak_set_runtime_kind_from_id (new); -+ gs_flatpak_app_set_ref_name (new, split[1]); -+ gs_flatpak_app_set_ref_arch (new, split[2]); -+ gs_app_set_branch (new, split[3]); -+ gs_app_set_metadata (new, "GnomeSoftware::packagename-value", xref_str); -+ } - } - } -- } - -- if (gs_flatpak_app_get_ref_name (new) == NULL || -- gs_flatpak_app_get_ref_arch (new) == NULL) { -- g_debug ("Failed to get ref info for '%s' from wildcard '%s', skipping it...", gs_app_get_id (new), id); -- } else { -- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardRefineNewApp, "Flatpak (refine new app)", NULL); -- if (!gs_flatpak_refine_app_internal (self, new, require_flags, interactive, FALSE, *inout_components_by_bundle, -- silo, silo_filename, silo_installed_by_desktopid, cancellable, error)) -- return FALSE; -- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardRefineNewApp); -+ if (gs_flatpak_app_get_ref_name (new) == NULL || -+ gs_flatpak_app_get_ref_arch (new) == NULL) { -+ g_debug ("Failed to get ref info for '%s' from wildcard '%s', skipping it...", gs_app_get_id (new), id); -+ } else { -+ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardRefineNewApp, "Flatpak (refine new app)", NULL); -+ if (!gs_flatpak_refine_app_internal (self, new, require_flags, interactive, FALSE, components_by_bundle, -+ silo, silo_filename, silo_installed_by_desktopid, cancellable, error)) -+ return FALSE; -+ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardRefineNewApp); - -- GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardSubsumeMetadata, "Flatpak (subsume metadata)", NULL); -- gs_app_subsume_metadata (new, app); -- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardSubsumeMetadata); -+ GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcardSubsumeMetadata, "Flatpak (subsume metadata)", NULL); -+ gs_app_subsume_metadata (new, app); -+ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardSubsumeMetadata); - -- gs_app_list_add (list, new); -+ gs_app_list_add (list, new); -+ } - } -+ GS_PROFILER_END_SCOPED (FlatpakRefineWildcardGenerateApps); - } -- GS_PROFILER_END_SCOPED (FlatpakRefineWildcardGenerateApps); - - GS_PROFILER_END_SCOPED (FlatpakRefineWildcard); - -diff --git a/plugins/flatpak/gs-flatpak.h b/plugins/flatpak/gs-flatpak.h -index 34e1416b5..1f7d35a8f 100644 ---- a/plugins/flatpak/gs-flatpak.h -+++ b/plugins/flatpak/gs-flatpak.h -@@ -104,16 +104,11 @@ gboolean gs_flatpak_refine_app_state (GsFlatpak *self, - void *event_user_data, - GCancellable *cancellable, - GError **error); --gboolean gs_flatpak_refine_wildcard (GsFlatpak *self, -- GsApp *app, -+gboolean gs_flatpak_refine_wildcards (GsFlatpak *self, -+ GPtrArray *wildcard_apps, - GsAppList *list, - GsPluginRefineRequireFlags require_flags, - gboolean interactive, -- XbSilo **inout_silo, -- gchar **inout_silo_filename, -- GHashTable **inout_installed_by_desktopid, -- GHashTable **inout_components_by_id, -- GHashTable **inout_components_by_bundle, - GCancellable *cancellable, - GError **error); - gboolean gs_flatpak_launch (GsFlatpak *self, -diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c -index f5d4a1d67..c06b1fb5f 100644 ---- a/plugins/flatpak/gs-plugin-flatpak.c -+++ b/plugins/flatpak/gs-plugin-flatpak.c -@@ -667,31 +667,6 @@ gs_plugin_flatpak_refine_app (GsPluginFlatpak *self, - return gs_flatpak_refine_app (flatpak, app, require_flags, interactive, FALSE, event_callback, event_user_data, cancellable, error); - } - --typedef struct { -- XbSilo *silo; -- gchar *silo_filename; -- GHashTable *installed_by_desktopid; -- GHashTable *components_by_id; -- GHashTable *components_by_bundle; --} RefineInstallationData; -- --static void --refine_installation_data_free (gpointer ptr) --{ -- RefineInstallationData *data = ptr; -- -- if (data == NULL) -- return; -- -- g_clear_pointer (&data->silo_filename, g_free); -- g_clear_pointer (&data->installed_by_desktopid, g_hash_table_unref); -- g_clear_pointer (&data->components_by_id, g_hash_table_unref); -- g_clear_pointer (&data->components_by_bundle, g_hash_table_unref); -- /* free the silo as the last, just in case, because the data from it is in the above hash tables */ -- g_clear_object (&data->silo); -- g_free (data); --} -- - static gboolean - refine_app (GsPluginFlatpak *self, - GsApp *app, -@@ -778,8 +753,7 @@ refine_thread_cb (GTask *task, - gboolean interactive = (data->job_flags & GS_PLUGIN_REFINE_FLAGS_INTERACTIVE) != 0; - GsPluginEventCallback event_callback = data->event_callback; - void *event_user_data = data->event_user_data; -- g_autoptr(GPtrArray) installation_data = NULL; /* (element-type RefineInstallationData) */ -- g_autoptr(GsAppList) app_list = NULL; -+ g_autoptr(GPtrArray) wildcard_apps = g_ptr_array_new_with_free_func (g_object_unref); /* (element-type GsApp) (owned) */ - g_autoptr(GError) local_error = NULL; - - assert_in_worker (self); -@@ -790,38 +764,17 @@ refine_thread_cb (GTask *task, - g_task_return_error (task, g_steal_pointer (&local_error)); - return; - } -- } -- -- /* Refine wildcards. -- * -- * Use a copy of the list for the loop because a function called -- * on the plugin may affect the list which can lead to problems -- * (e.g. inserting an app in the list on every call results in -- * an infinite loop) */ -- app_list = gs_app_list_copy (list); -- -- installation_data = g_ptr_array_new_full (self->installations->len, refine_installation_data_free); -- g_ptr_array_set_size (installation_data, self->installations->len); -- -- for (guint j = 0; j < gs_app_list_length (app_list); j++) { -- GsApp *app = gs_app_list_index (app_list, j); - -- if (!gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD)) -- continue; -+ if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD) && gs_app_get_id (app) != NULL) -+ g_ptr_array_add (wildcard_apps, g_object_ref (app)); -+ } - -+ /* Refine wildcards. */ -+ if (wildcard_apps->len > 0) { - for (guint i = 0; i < self->installations->len; i++) { - GsFlatpak *flatpak = g_ptr_array_index (self->installations, i); -- RefineInstallationData *inst_data = g_ptr_array_index (installation_data, i); -- -- if (inst_data == NULL) { -- inst_data = g_new0 (RefineInstallationData, 1); -- installation_data->pdata[i] = inst_data; -- } - -- if (!gs_flatpak_refine_wildcard (flatpak, app, list, require_flags, interactive, &inst_data->silo, -- &inst_data->silo_filename, &inst_data->installed_by_desktopid, -- &inst_data->components_by_id, &inst_data->components_by_bundle, -- cancellable, &local_error)) { -+ if (!gs_flatpak_refine_wildcards (flatpak, wildcard_apps, list, require_flags, interactive, cancellable, &local_error)) { - g_task_return_error (task, g_steal_pointer (&local_error)); - return; - } --- -GitLab - - -From 8472d717356a801e6c0171156738ea48480fa695 Mon Sep 17 00:00:00 2001 -Date: Wed, 7 Jan 2026 18:45:47 +0100 -Subject: [PATCH 3/3] flatpak: Hold silo lock when refining wildcard apps - -The refine can take a long time, thus hold the silo lock to not have -it invalidated from another thread. Object reference is not enough -for the XbSilo, it breaks as soon as the underlying file changes. ---- - plugins/flatpak/gs-flatpak.c | 16 +++++++++++----- - 1 file changed, 11 insertions(+), 5 deletions(-) - -diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c -index 782b0bb48..c5437125d 100644 ---- a/plugins/flatpak/gs-flatpak.c -+++ b/plugins/flatpak/gs-flatpak.c -@@ -59,7 +59,7 @@ struct _GsFlatpak { - AsComponentScope scope; - GsPlugin *plugin; - XbSilo *silo; -- GMutex silo_lock; -+ GRecMutex silo_lock; - gchar *silo_filename; - GHashTable *silo_installed_by_desktopid; - gint silo_change_stamp; -@@ -1187,11 +1187,11 @@ gs_flatpak_ref_silo (GsFlatpak *self, - g_autoptr(GFile) file = NULL; - g_autoptr(GPtrArray) xremotes = NULL; - g_autoptr(GPtrArray) desktop_paths = NULL; -- g_autoptr(GMutexLocker) locker = NULL; -+ g_autoptr(GRecMutexLocker) locker = NULL; - g_autoptr(XbBuilder) builder = NULL; - g_autoptr(GMainContext) old_thread_default = NULL; - -- locker = g_mutex_locker_new (&self->silo_lock); -+ locker = g_rec_mutex_locker_new (&self->silo_lock); - /* everything is okay */ - if (self->silo != NULL && xb_silo_is_valid (self->silo) && - g_atomic_int_get (&self->silo_change_stamp_current) == g_atomic_int_get (&self->silo_change_stamp)) { -@@ -3754,6 +3754,7 @@ gs_flatpak_refine_wildcards (GsFlatpak *self, - GError **error) - { - g_autoptr(GError) error_local = NULL; -+ g_autoptr(GRecMutexLocker) silo_locker = NULL; - g_autoptr(XbSilo) silo = NULL; - g_autoptr(GHashTable) silo_installed_by_desktopid = NULL; - g_autoptr(GHashTable) components_by_id = NULL; -@@ -3764,6 +3765,11 @@ gs_flatpak_refine_wildcards (GsFlatpak *self, - - GS_PROFILER_BEGIN_SCOPED (FlatpakRefineWildcard, "Flatpak (refine wildcard)", NULL); - -+ /* the refine can take a long time, thus hold the silo lock to not have -+ it invalidated from another thread; object reference is not enough -+ for the XbSilo, it breaks as soon as the underlying file changes */ -+ silo_locker = g_rec_mutex_locker_new (&self->silo_lock); -+ - silo = gs_flatpak_ref_silo (self, interactive, &silo_filename, &silo_installed_by_desktopid, cancellable, error); - if (silo == NULL) - return FALSE; -@@ -4810,7 +4816,7 @@ gs_flatpak_finalize (GObject *object) - g_object_unref (self->plugin); - g_hash_table_unref (self->broken_remotes); - g_mutex_clear (&self->broken_remotes_mutex); -- g_mutex_clear (&self->silo_lock); -+ g_rec_mutex_clear (&self->silo_lock); - g_hash_table_unref (self->app_silos); - g_mutex_clear (&self->app_silos_mutex); - g_clear_pointer (&self->remote_title, g_hash_table_unref); -@@ -4831,7 +4837,7 @@ gs_flatpak_init (GsFlatpak *self) - { - /* XbSilo needs external locking as we destroy the silo and build a new - * one when something changes */ -- g_mutex_init (&self->silo_lock); -+ g_rec_mutex_init (&self->silo_lock); - - g_mutex_init (&self->installed_refs_mutex); - self->installed_refs = NULL; --- -GitLab - diff --git a/0006-optional-repos-cannot-be-disabled.patch b/0006-optional-repos-cannot-be-disabled.patch new file mode 100644 index 0000000..b581147 --- /dev/null +++ b/0006-optional-repos-cannot-be-disabled.patch @@ -0,0 +1,498 @@ +From dca731ff0daf904911dd6815fb9a1b181329c887 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 11:00:20 +0200 +Subject: [PATCH 1/4] gs-repo-row: Use GS_APP_QUIRK_COMPULSORY to recognize + required repositories + +The GS_APP_QUIRK_PROVENANCE quirk does not mean it's also required repository, +thus use the GS_APP_QUIRK_COMPULSORY for repos, which cannot be disabled. +The GS_APP_QUIRK_PROVENANCE is used only for repositories, which cannot be removed. +--- + src/gs-repo-row.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/src/gs-repo-row.c b/src/gs-repo-row.c +index 87926092f..bbf67c194 100644 +--- a/src/gs-repo-row.c ++++ b/src/gs-repo-row.c +@@ -48,7 +48,8 @@ refresh_ui (GsRepoRow *row) + gboolean active = FALSE; + gboolean state_sensitive = FALSE; + gboolean busy = priv->busy_counter> 0; +- gboolean is_system_repo; ++ gboolean is_provenance; ++ gboolean is_compulsory; + + if (priv->repo == NULL) { + gtk_widget_set_sensitive (priv->disable_switch, FALSE); +@@ -87,11 +88,12 @@ refresh_ui (GsRepoRow *row) + break; + } + +- is_system_repo = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_PROVENANCE); ++ is_provenance = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_PROVENANCE); ++ is_compulsory = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_COMPULSORY); + + /* Disable for the system repos, if installed */ +- gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive || !is_system_repo || priv->always_allow_enable_disable)); +- gtk_widget_set_visible (priv->remove_button, priv->supports_remove && !is_system_repo); ++ gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive || !is_compulsory || priv->always_allow_enable_disable)); ++ gtk_widget_set_visible (priv->remove_button, priv->supports_remove && !is_provenance && !is_compulsory); + + /* Set only the 'state' to visually indicate the state is not saved yet */ + if (busy) +-- +GitLab + + +From 026218b9d3211de243dfc49eca8b8d46633882b0 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 11:03:31 +0200 +Subject: [PATCH 2/4] gs-plugin-provenance: Improve search speed in list of + repositories + +Use a GHashTable for bare repository names and a GPtrArray for those +with wildcards. This helps with speed, due to not traversing all +the repository names with the fnmatch() call. +--- + plugins/core/gs-plugin-provenance.c | 55 ++++++++++++++++++++--------- + 1 file changed, 38 insertions(+), 17 deletions(-) + +diff --git a/plugins/core/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c +index 97ff76798..a72c25a27 100644 +--- a/plugins/core/gs-plugin-provenance.c ++++ b/plugins/core/gs-plugin-provenance.c +@@ -19,7 +19,8 @@ + + struct GsPluginData { + GSettings *settings; +- gchar **sources; ++ GHashTable *repos; /* gchar *name ~> NULL */ ++ GPtrArray *wildcards; /* non-NULL, when have names with wildcards */ + }; + + static gchar ** +@@ -42,8 +43,24 @@ gs_plugin_provenance_settings_changed_cb (GSettings *settings, + { + GsPluginData *priv = gs_plugin_get_data (plugin); + if (g_strcmp0 (key, "official-repos") == 0) { +- g_strfreev (priv->sources); +- priv->sources = gs_plugin_provenance_get_sources (plugin); ++ /* The keys are stolen by the hash table, thus free only the array */ ++ g_autofree gchar **repos = NULL; ++ g_hash_table_remove_all (priv->repos); ++ g_clear_pointer (&priv->wildcards, g_ptr_array_unref); ++ repos = gs_plugin_provenance_get_sources (plugin); ++ for (guint ii = 0; repos && repos[ii]; ii++) { ++ if (strchr (repos[ii], '*') || ++ strchr (repos[ii], '?') || ++ strchr (repos[ii], '[')) { ++ if (priv->wildcards == NULL) ++ priv->wildcards = g_ptr_array_new_with_free_func (g_free); ++ g_ptr_array_add (priv->wildcards, g_steal_pointer (&(repos[ii]))); ++ } else { ++ g_hash_table_insert (priv->repos, g_steal_pointer (&(repos[ii])), NULL); ++ } ++ } ++ if (priv->wildcards != NULL) ++ g_ptr_array_add (priv->wildcards, NULL); + } + } + +@@ -52,9 +69,10 @@ gs_plugin_initialize (GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData)); + priv->settings = g_settings_new ("org.gnome.software"); ++ priv->repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_signal_connect (priv->settings, "changed", + G_CALLBACK (gs_plugin_provenance_settings_changed_cb), plugin); +- priv->sources = gs_plugin_provenance_get_sources (plugin); ++ gs_plugin_provenance_settings_changed_cb (priv->settings, "official-repos", plugin); + + /* after the package source is set */ + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dummy"); +@@ -66,7 +84,8 @@ void + gs_plugin_destroy (GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_get_data (plugin); +- g_strfreev (priv->sources); ++ g_hash_table_unref (priv->repos); ++ g_clear_pointer (&priv->wildcards, g_ptr_array_unref); + g_object_unref (priv->settings); + } + +@@ -74,12 +93,12 @@ static gboolean + refine_app (GsPlugin *plugin, + GsApp *app, + GsPluginRefineFlags flags, ++ GHashTable *repos, ++ GPtrArray *wildcards, + GCancellable *cancellable, + GError **error) + { +- GsPluginData *priv = gs_plugin_get_data (plugin); + const gchar *origin; +- gchar **sources; + + /* not required */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) +@@ -87,14 +106,10 @@ refine_app (GsPlugin *plugin, + if (gs_app_has_quirk (app, GS_APP_QUIRK_PROVENANCE)) + return TRUE; + +- /* nothing to search */ +- sources = priv->sources; +- if (sources == NULL || sources[0] == NULL) +- return TRUE; +- + /* simple case */ + origin = gs_app_get_origin (app); +- if (origin != NULL && gs_utils_strv_fnmatch (sources, origin)) { ++ if (origin != NULL && (g_hash_table_contains (repos, origin) || ++ (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin)))) { + gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); + return TRUE; + } +@@ -103,7 +118,8 @@ refine_app (GsPlugin *plugin, + * provenance quirk to the system-configured repositories (but not + * user-configured ones). */ + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY && +- gs_utils_strv_fnmatch (sources, gs_app_get_id (app))) { ++ (g_hash_table_contains (repos, gs_app_get_id (app)) || ++ (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, gs_app_get_id (app))))) { + if (gs_app_get_scope (app) != AS_COMPONENT_SCOPE_USER) + gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); + return TRUE; +@@ -118,7 +134,8 @@ refine_app (GsPlugin *plugin, + return TRUE; + if (g_str_has_prefix (origin + 1, "installed:")) + origin += 10; +- if (gs_utils_strv_fnmatch (sources, origin + 1)) { ++ if (g_hash_table_contains (repos, origin + 1) || ++ (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin + 1))) { + gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); + return TRUE; + } +@@ -133,17 +150,21 @@ gs_plugin_refine (GsPlugin *plugin, + GError **error) + { + GsPluginData *priv = gs_plugin_get_data (plugin); ++ g_autoptr(GHashTable) repos = NULL; ++ g_autoptr(GPtrArray) wildcards = NULL; + + /* nothing to do here */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) + return TRUE; ++ repos = g_hash_table_ref (priv->repos); ++ wildcards = priv->wildcards != NULL ? g_ptr_array_ref (priv->wildcards) : NULL; + /* nothing to search */ +- if (priv->sources == NULL || priv->sources[0] == NULL) ++ if (g_hash_table_size (repos) == 0) + return TRUE; + + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); +- if (!refine_app (plugin, app, flags, cancellable, error)) ++ if (!refine_app (plugin, app, flags, repos, wildcards, cancellable, error)) + return FALSE; + } + +-- +GitLab + + +From b5e3356aff5fcd257248f9bb697e272c879249ae Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 13:03:44 +0200 +Subject: [PATCH 3/4] settings: Add 'required-repos' key + +To be used to list repositories, which cannot be removed or disabled. +It's a complementary option for the 'official-repos' key. +--- + data/org.gnome.software.gschema.xml | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/data/org.gnome.software.gschema.xml b/data/org.gnome.software.gschema.xml +index db1c27ce4..0e5706b7c 100644 +--- a/data/org.gnome.software.gschema.xml ++++ b/data/org.gnome.software.gschema.xml +@@ -94,6 +94,10 @@ + [] + A list of official repositories that should not be considered 3rd party + ++ ++ [] ++ A list of required repositories that cannot be disabled or removed ++ + + [] + A list of official repositories that should be considered free software +-- +GitLab + + +From d6b8b206a596bb520a0b77066898b44a5ef18920 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 14:16:56 +0200 +Subject: [PATCH 4/4] gs-plugin-provenance: Handle also 'required-repos' key + +Let it handle also 'required-repos' settings key, beside the 'official-repos' +key, which are close enough to share the same code and memory. With this +done the repositories can be marked as compulsory, independently from the official +repositories. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1479 +--- + plugins/core/gs-plugin-provenance.c | 142 +++++++++++++++++++++------- + 1 file changed, 108 insertions(+), 34 deletions(-) + +diff --git a/plugins/core/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c +index a72c25a27..22f3c98e1 100644 +--- a/plugins/core/gs-plugin-provenance.c ++++ b/plugins/core/gs-plugin-provenance.c +@@ -14,26 +14,61 @@ + /* + * SECTION: + * Sets the package provenance to TRUE if installed by an official +- * software source. ++ * software source. Also sets compulsory quirk when a required repository. + */ + + struct GsPluginData { + GSettings *settings; +- GHashTable *repos; /* gchar *name ~> NULL */ +- GPtrArray *wildcards; /* non-NULL, when have names with wildcards */ ++ GHashTable *repos; /* gchar *name ~> guint flags */ ++ GPtrArray *provenance_wildcards; /* non-NULL, when have names with wildcards */ ++ GPtrArray *compulsory_wildcards; /* non-NULL, when have names with wildcards */ + }; + ++static GHashTable * ++gs_plugin_provenance_remove_by_flag (GHashTable *old_repos, ++ GsAppQuirk quirk) ++{ ++ GHashTable *new_repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ++ GHashTableIter iter; ++ gpointer key, value; ++ g_hash_table_iter_init (&iter, old_repos); ++ while (g_hash_table_iter_next (&iter, &key, &value)) { ++ guint flags = GPOINTER_TO_UINT (value); ++ flags = flags & (~quirk); ++ if (flags != 0) ++ g_hash_table_insert (new_repos, g_strdup (key), GUINT_TO_POINTER (flags)); ++ } ++ return new_repos; ++} ++ ++static void ++gs_plugin_provenance_add_quirks (GsApp *app, ++ guint quirks) ++{ ++ GsAppQuirk array[] = { ++ GS_APP_QUIRK_PROVENANCE, ++ GS_APP_QUIRK_COMPULSORY ++ }; ++ for (guint ii = 0; ii < G_N_ELEMENTS (array); ii++) { ++ if ((quirks & array[ii]) != 0) ++ gs_app_add_quirk (app, array[ii]); ++ } ++} ++ + static gchar ** +-gs_plugin_provenance_get_sources (GsPlugin *plugin) ++gs_plugin_provenance_get_sources (GsPlugin *plugin, ++ const gchar *key) + { + GsPluginData *priv = gs_plugin_get_data (plugin); + const gchar *tmp; + tmp = g_getenv ("GS_SELF_TEST_PROVENANCE_SOURCES"); + if (tmp != NULL) { ++ if (g_strcmp0 (key, "required-repos") == 0) ++ return NULL; + g_debug ("using custom provenance sources of %s", tmp); + return g_strsplit (tmp, ",", -1); + } +- return g_settings_get_strv (priv->settings, "official-repos"); ++ return g_settings_get_strv (priv->settings, key); + } + + static void +@@ -42,25 +77,43 @@ gs_plugin_provenance_settings_changed_cb (GSettings *settings, + GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_get_data (plugin); ++ GsAppQuirk quirk = GS_APP_QUIRK_NONE; ++ GPtrArray **pwildcards = NULL; ++ + if (g_strcmp0 (key, "official-repos") == 0) { ++ quirk = GS_APP_QUIRK_PROVENANCE; ++ pwildcards = &priv->provenance_wildcards; ++ } else if (g_strcmp0 (key, "required-repos") == 0) { ++ quirk = GS_APP_QUIRK_COMPULSORY; ++ pwildcards = &priv->compulsory_wildcards; ++ } ++ ++ if (quirk != GS_APP_QUIRK_NONE) { + /* The keys are stolen by the hash table, thus free only the array */ + g_autofree gchar **repos = NULL; +- g_hash_table_remove_all (priv->repos); +- g_clear_pointer (&priv->wildcards, g_ptr_array_unref); +- repos = gs_plugin_provenance_get_sources (plugin); ++ g_autoptr(GHashTable) old_repos = priv->repos; ++ g_autoptr(GPtrArray) old_wildcards = *pwildcards; ++ GHashTable *new_repos = gs_plugin_provenance_remove_by_flag (old_repos, quirk); ++ GPtrArray *new_wildcards = NULL; ++ repos = gs_plugin_provenance_get_sources (plugin, key); + for (guint ii = 0; repos && repos[ii]; ii++) { +- if (strchr (repos[ii], '*') || +- strchr (repos[ii], '?') || +- strchr (repos[ii], '[')) { +- if (priv->wildcards == NULL) +- priv->wildcards = g_ptr_array_new_with_free_func (g_free); +- g_ptr_array_add (priv->wildcards, g_steal_pointer (&(repos[ii]))); ++ gchar *repo = g_steal_pointer (&(repos[ii])); ++ if (strchr (repo, '*') || ++ strchr (repo, '?') || ++ strchr (repo, '[')) { ++ if (new_wildcards == NULL) ++ new_wildcards = g_ptr_array_new_with_free_func (g_free); ++ g_ptr_array_add (new_wildcards, repo); + } else { +- g_hash_table_insert (priv->repos, g_steal_pointer (&(repos[ii])), NULL); ++ g_hash_table_insert (new_repos, repo, ++ GUINT_TO_POINTER (quirk | ++ GPOINTER_TO_UINT (g_hash_table_lookup (new_repos, repo)))); + } + } +- if (priv->wildcards != NULL) +- g_ptr_array_add (priv->wildcards, NULL); ++ if (new_wildcards != NULL) ++ g_ptr_array_add (new_wildcards, NULL); ++ priv->repos = new_repos; ++ *pwildcards = new_wildcards; + } + } + +@@ -73,6 +126,7 @@ gs_plugin_initialize (GsPlugin *plugin) + g_signal_connect (priv->settings, "changed", + G_CALLBACK (gs_plugin_provenance_settings_changed_cb), plugin); + gs_plugin_provenance_settings_changed_cb (priv->settings, "official-repos", plugin); ++ gs_plugin_provenance_settings_changed_cb (priv->settings, "required-repos", plugin); + + /* after the package source is set */ + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dummy"); +@@ -85,20 +139,42 @@ gs_plugin_destroy (GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_get_data (plugin); + g_hash_table_unref (priv->repos); +- g_clear_pointer (&priv->wildcards, g_ptr_array_unref); ++ g_clear_pointer (&priv->provenance_wildcards, g_ptr_array_unref); ++ g_clear_pointer (&priv->compulsory_wildcards, g_ptr_array_unref); + g_object_unref (priv->settings); + } + ++static gboolean ++gs_plugin_provenance_find_repo_flags (GHashTable *repos, ++ GPtrArray *provenance_wildcards, ++ GPtrArray *compulsory_wildcards, ++ const gchar *repo, ++ guint *out_flags) ++{ ++ if (repo == NULL || *repo == '\0') ++ return FALSE; ++ *out_flags = GPOINTER_TO_UINT (g_hash_table_lookup (repos, repo)); ++ if (provenance_wildcards != NULL && ++ gs_utils_strv_fnmatch ((gchar **) provenance_wildcards->pdata, repo)) ++ *out_flags |= GS_APP_QUIRK_PROVENANCE; ++ if (compulsory_wildcards != NULL && ++ gs_utils_strv_fnmatch ((gchar **) compulsory_wildcards->pdata, repo)) ++ *out_flags |= GS_APP_QUIRK_COMPULSORY; ++ return *out_flags != 0; ++} ++ + static gboolean + refine_app (GsPlugin *plugin, + GsApp *app, + GsPluginRefineFlags flags, + GHashTable *repos, +- GPtrArray *wildcards, ++ GPtrArray *provenance_wildcards, ++ GPtrArray *compulsory_wildcards, + GCancellable *cancellable, + GError **error) + { + const gchar *origin; ++ guint quirks; + + /* not required */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) +@@ -108,9 +184,8 @@ refine_app (GsPlugin *plugin, + + /* simple case */ + origin = gs_app_get_origin (app); +- if (origin != NULL && (g_hash_table_contains (repos, origin) || +- (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin)))) { +- gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); ++ if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, origin, &quirks)) { ++ gs_plugin_provenance_add_quirks (app, quirks); + return TRUE; + } + +@@ -118,10 +193,9 @@ refine_app (GsPlugin *plugin, + * provenance quirk to the system-configured repositories (but not + * user-configured ones). */ + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY && +- (g_hash_table_contains (repos, gs_app_get_id (app)) || +- (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, gs_app_get_id (app))))) { ++ gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, gs_app_get_id (app), &quirks)) { + if (gs_app_get_scope (app) != AS_COMPONENT_SCOPE_USER) +- gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); ++ gs_plugin_provenance_add_quirks (app, quirks); + return TRUE; + } + +@@ -134,11 +208,9 @@ refine_app (GsPlugin *plugin, + return TRUE; + if (g_str_has_prefix (origin + 1, "installed:")) + origin += 10; +- if (g_hash_table_contains (repos, origin + 1) || +- (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin + 1))) { +- gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); +- return TRUE; +- } ++ if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, origin + 1, &quirks)) ++ gs_plugin_provenance_add_quirks (app, quirks); ++ + return TRUE; + } + +@@ -151,20 +223,22 @@ gs_plugin_refine (GsPlugin *plugin, + { + GsPluginData *priv = gs_plugin_get_data (plugin); + g_autoptr(GHashTable) repos = NULL; +- g_autoptr(GPtrArray) wildcards = NULL; ++ g_autoptr(GPtrArray) provenance_wildcards = NULL; ++ g_autoptr(GPtrArray) compulsory_wildcards = NULL; + + /* nothing to do here */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) + return TRUE; + repos = g_hash_table_ref (priv->repos); +- wildcards = priv->wildcards != NULL ? g_ptr_array_ref (priv->wildcards) : NULL; ++ provenance_wildcards = priv->provenance_wildcards != NULL ? g_ptr_array_ref (priv->provenance_wildcards) : NULL; ++ compulsory_wildcards = priv->compulsory_wildcards != NULL ? g_ptr_array_ref (priv->compulsory_wildcards) : NULL; + /* nothing to search */ +- if (g_hash_table_size (repos) == 0) ++ if (g_hash_table_size (repos) == 0 && provenance_wildcards == NULL && compulsory_wildcards == NULL) + return TRUE; + + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); +- if (!refine_app (plugin, app, flags, repos, wildcards, cancellable, error)) ++ if (!refine_app (plugin, app, flags, repos, provenance_wildcards, compulsory_wildcards, cancellable, error)) + return FALSE; + } + +-- +GitLab + diff --git a/0007-compulsory-only-for-repos.patch b/0007-compulsory-only-for-repos.patch new file mode 100644 index 0000000..fb266df --- /dev/null +++ b/0007-compulsory-only-for-repos.patch @@ -0,0 +1,42 @@ +From 895d1ca748f4f33a852853f5f07903fb549fb66f Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Mon, 11 Oct 2021 09:13:59 +0200 +Subject: [PATCH] gs-plugin-provenance: Set COMPULSORY quirk only on REPOSITORY + apps + +The compulsory quirk related to repositories, which cannot be removed, +not to the applications provided by those repositories, thus set that +quirk only on repositories, not on the apps from it. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1488 +--- + plugins/core/gs-plugin-provenance.c | 13 +++++-------- + 1 file changed, 5 insertions(+), 8 deletions(-) + +diff --git a/plugins/core/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c +index 22f3c98e..e44a55f0 100644 +--- a/plugins/core/gs-plugin-provenance.c ++++ b/plugins/core/gs-plugin-provenance.c +@@ -45,14 +45,11 @@ static void + gs_plugin_provenance_add_quirks (GsApp *app, + guint quirks) + { +- GsAppQuirk array[] = { +- GS_APP_QUIRK_PROVENANCE, +- GS_APP_QUIRK_COMPULSORY +- }; +- for (guint ii = 0; ii < G_N_ELEMENTS (array); ii++) { +- if ((quirks & array[ii]) != 0) +- gs_app_add_quirk (app, array[ii]); +- } ++ if ((quirks & GS_APP_QUIRK_PROVENANCE) != 0) ++ gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); ++ if ((quirks & GS_APP_QUIRK_COMPULSORY) != 0 && ++ gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) ++ gs_app_add_quirk (app, GS_APP_QUIRK_COMPULSORY); + } + + static gchar ** +-- +2.31.1 + diff --git a/0008-gs-removal-dialog-crrect-property-name.patch b/0008-gs-removal-dialog-crrect-property-name.patch new file mode 100644 index 0000000..47325af --- /dev/null +++ b/0008-gs-removal-dialog-crrect-property-name.patch @@ -0,0 +1,12 @@ +diff -up gnome-software-41.5/src/gs-removal-dialog.ui.4 gnome-software-41.5/src/gs-removal-dialog.ui +--- gnome-software-41.5/src/gs-removal-dialog.ui.4 2022-09-12 08:59:57.819169830 +0200 ++++ gnome-software-41.5/src/gs-removal-dialog.ui 2022-09-12 09:00:25.148201673 +0200 +@@ -20,7 +20,7 @@ + + +