diff --git a/.gitignore b/.gitignore index d4bab8a..ba3b2b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,110 +1,2 @@ -/gnome-software-0.1.tar.xz -/gnome-software-3.9.1.tar.xz -/gnome-software-3.9.2.tar.xz -/gnome-software-3.9.3.tar.xz -/fedora-20.xml.gz -/fedora-20-icons.tar.gz -/gnome-software-3.10.0.tar.xz -/gnome-software-3.11.1.tar.xz -/fedora-20-codecs.xml -/web-apps.xml -/gnome-software-3.11.2.tar.xz -/gnome-software-3.11.3.tar.xz -/gnome-software-3.11.4.tar.xz -/fedora-rawhide-icons.tar.gz -/fedora-rawhide.xml.gz -/gnome-software-3.11.5.tar.xz -/gnome-software-3.11.90.tar.xz -/gnome-software-3.11.91.tar.xz -/gnome-software-3.11.92.tar.xz -/gnome-software-3.12.0.tar.xz -/gnome-software-3.12.1.tar.xz -/gnome-software-3.13.1.tar.xz -/fedora-21-icons.tar.gz -/fedora-21.xml.gz -/gnome-software-3.13.2.tar.xz -/gnome-software-3.13.3-7491627.tar.xz -/gnome-software-3.13.3.tar.xz -/gnome-software-3.13.4.tar.xz -/gnome-software-3.13.5-5c89189.tar.xz -/gnome-software-3.13.90.tar.xz -/gnome-software-3.13.91.tar.xz -/gnome-software-3.13.92.tar.xz -/gnome-software-3.14.0.tar.xz -/gnome-software-3.14.1.tar.xz -/gnome-software-3.14.2.tar.xz -/gnome-software-3.15.2.tar.xz -/gnome-software-3.15.4.tar.xz -/gnome-software-3.15.90.tar.xz -/gnome-software-3.15.91.tar.xz -/gnome-software-3.15.92.tar.xz -/gnome-software-3.16.0.tar.xz -/gnome-software-3.16.1.tar.xz -/gnome-software-3.16.2.tar.xz -/gnome-software-3.17.1.tar.xz -/gnome-software-3.17.2.tar.xz -/gnome-software-3.17.3.tar.xz -/gnome-software-3.17.90.tar.xz -/gnome-software-3.17.91.tar.xz -/gnome-software-3.17.92.tar.xz -/gnome-software-3.18.0.tar.xz -/gnome-software-3.18.1.tar.xz -/gnome-software-3.18.2.tar.xz -/gnome-software-3.18.3.tar.xz -/gnome-software-3.19.4.tar.xz -/gnome-software-3.19.90.tar.xz -/gnome-software-3.19.91.tar.xz -/gnome-software-3.19.92.tar.xz -/gnome-software-3.20.0.tar.xz -/gnome-software-3.20.1.tar.xz -/gnome-software-3.20.2.tar.xz -/gnome-software-3.21.1.tar.xz -/gnome-software-3.21.2.tar.xz -/gnome-software-3.21.4.tar.xz -/gnome-software-3.21.90.tar.xz -/gnome-software-3.21.91.tar.xz -/gnome-software-3.21.92.tar.xz -/gnome-software-3.22.0.tar.xz -/gnome-software-3.22.1.tar.xz -/gnome-software-3.22.2.tar.xz -/gnome-software-3.23.2.tar.xz -/gnome-software-3.23.3.tar.xz -/gnome-software-3.23.90.tar.xz -/gnome-software-3.23.91.tar.xz -/gnome-software-3.23.92.tar.xz -/gnome-software-3.24.0.tar.xz -/gnome-software-3.24.1.tar.xz -/gnome-software-3.24.2.tar.xz -/gnome-software-3.24.3.tar.xz -/gnome-software-3.25.3.tar.xz -/gnome-software-3.25.4.tar.xz -/gnome-software-3.25.90.tar.xz -/gnome-software-3.25.91.tar.xz -/gnome-software-3.26.0.tar.xz -/gnome-software-3.26.1.tar.xz -/gnome-software-3.26.2.tar.xz -/gnome-software-3.27.2.tar.xz -/gnome-software-3.27.3.tar.xz -/gnome-software-3.27.4.tar.xz -/gnome-software-3.27.90.tar.xz -/gnome-software-3.27.92.tar.xz -/gnome-software-3.28.0.tar.xz -/gnome-software-3.28.1.tar.xz -/gnome-software-3.29.1.tar.xz -/gnome-software-3.29.92.tar.xz -/gnome-software-3.30.0.tar.xz -/gnome-software-3.30.1.tar.xz -/gnome-software-3.30.2.tar.xz -/gnome-software-3.31.1.tar.xz -/gnome-software-3.31.2.tar.xz -/gnome-software-3.31.90.tar.xz -/gnome-software-3.31.92.tar.xz -/gnome-software-3.32.0.tar.xz -/gnome-software-3.32.1.tar.xz -/gnome-software-3.32.2.tar.xz -/gnome-software-3.32.3.tar.xz -/gnome-software-3.32.4.tar.xz -/gnome-software-3.34.0.tar.xz -/gnome-software-3.34.1.tar.xz -/gnome-software-3.35.2.tar.xz -/gnome-software-3.35.91.tar.xz +/gnome-software-*.tar.xz +/gnome-software-*-build diff --git a/0001-dnf5-plugin.patch b/0001-dnf5-plugin.patch new file mode 100644 index 0000000..3b4b355 --- /dev/null +++ b/0001-dnf5-plugin.patch @@ -0,0 +1,6770 @@ +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 new file mode 100644 index 0000000..33140df --- /dev/null +++ b/0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch @@ -0,0 +1,686 @@ +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/dnf5-plugin/.gitignore b/dnf5-plugin/.gitignore new file mode 100644 index 0000000..b86f2cd --- /dev/null +++ b/dnf5-plugin/.gitignore @@ -0,0 +1 @@ +checkout-gs-dnf5 diff --git a/dnf5-plugin/update-patch.sh b/dnf5-plugin/update-patch.sh new file mode 100755 index 0000000..481ffcf --- /dev/null +++ b/dnf5-plugin/update-patch.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +if [ ! -d checkout-gs-dnf5 ]; then + git clone --branch main https://gitlab.gnome.org/mcrha/gnome-software.git checkout-gs-dnf5 && \ + cd checkout-gs-dnf5 && \ + git checkout -b wip/dnf5daemon origin/wip/dnf5daemon && \ + cd - >/dev/null + + if [ "$?" != "0" ]; then + echo "Failed to clone dnf5-plugin repository" 1>&2 + exit 1; + fi +fi + +PATCH_PATH=../0001-dnf5-plugin.patch + +cd checkout-gs-dnf5 && \ +echo "Updating gs-dnf5 git 'main' repository" && \ +git checkout main && \ +git pull --rebase && \ +echo "Updating gs-dnf5 git 'dnf5-pugin' repository" && \ +git checkout wip/dnf5daemon && \ +git pull --rebase && \ +echo -n "at " >../${PATCH_PATH} && \ +git log HEAD | head -n 5 | grep -E "commit|Date" >>../${PATCH_PATH} && \ +echo "" >>../${PATCH_PATH} && \ +git diff main >>../${PATCH_PATH} && \ +cd - >/dev/null + +if [ "$?" = "0" ]; then + echo "Patch '${PATCH_PATH}' updated" +else + echo "Failed to update patch '${PATCH_PATH}'" 1>&2 + exit 1 +fi diff --git a/gnome-software.spec b/gnome-software.spec index 827161b..c080ede 100644 --- a/gnome-software.spec +++ b/gnome-software.spec @@ -1,78 +1,133 @@ -%global glib2_version 2.61.1 -%global gtk3_version 3.22.4 -%global json_glib_version 1.2.0 -%global packagekit_version 1.1.1 -%global appstream_glib_version 0.7.15 -%global libsoup_version 2.52.0 -%global gsettings_desktop_schemas_version 3.12.0 -%global gnome_desktop_version 3.18.0 -%global fwupd_version 1.3.3 -%global flatpak_version 1.5.1 -%global libxmlb_version 0.1.7 +%global appstream_version 0.16.4 +%global flatpak_version 1.14.1 +%global fwupd_version 1.6.2 +%global glib2_version 2.76.0 +%global gtk4_version 4.16.0 +%global json_glib_version 1.6.0 +%global libadwaita_version 1.6.0 +%global libxmlb_version 0.3.4 +%global packagekit_version 1.2.5 +%global dnf5_version 5.2.16 + +# Disable WebApps for RHEL builds +%bcond webapps %[!0%{?rhel}] +# Disable parental control for RHEL builds +%bcond malcontent %[!0%{?rhel}] +# Disable rpm-ostree support for RHEL builds +%bcond rpmostree %[!0%{?rhel}] +# Disable DKMS/akmods support for RHEL builds +%bcond dkms %[!0%{?rhel}] + +%bcond packagekit 0 +%bcond dnf5 1 + +# this is not a library version +%define gs_plugin_version 23 + +%global tarball_version %%(echo %{version} | tr '~' '.') + +%global __provides_exclude_from ^%{_libdir}/%{name}/plugins-%{gs_plugin_version}/.*\\.so.*$ Name: gnome-software -Version: 3.35.91 +Version: 49.3 Release: 1%{?dist} Summary: A software center for GNOME -License: GPLv2+ -URL: https://wiki.gnome.org/Apps/Software -Source0: https://download.gnome.org/sources/gnome-software/3.35/%{name}-%{version}.tar.xz +License: GPL-2.0-or-later +URL: https://apps.gnome.org/Software +Source0: https://download.gnome.org/sources/gnome-software/49/%{name}-%{tarball_version}.tar.xz + +%if %{with dnf5} +# to update the patch enter the ./dnf5-plugin/ directory and run from +# it the ./update-patch.sh script +Patch: 0001-dnf5-plugin.patch +%endif + +# https://bugzilla.redhat.com/show_bug.cgi?id=2416542 +Patch: 0002-rhbug2416542-crash-under-gs_flatpak_refine_wildcard.patch + +# ostree and flatpak not on i686 for Fedora and RHEL 10 +# https://github.com/containers/composefs/pull/229#issuecomment-1838735764 +%if 0%{?fedora} || 0%{?rhel} >= 10 +ExcludeArch: %{ix86} +%endif -BuildRequires: gcc -BuildRequires: gettext -BuildRequires: libxslt BuildRequires: docbook-style-xsl BuildRequires: desktop-file-utils -BuildRequires: fwupd-devel >= %{fwupd_version} -BuildRequires: glib2-devel >= %{glib2_version} -BuildRequires: gnome-desktop3-devel -BuildRequires: gnome-online-accounts-devel -BuildRequires: gsettings-desktop-schemas-devel >= %{gsettings_desktop_schemas_version} -BuildRequires: gspell-devel -BuildRequires: gtk3-devel >= %{gtk3_version} +BuildRequires: gcc +BuildRequires: gettext +BuildRequires: git-core BuildRequires: gtk-doc -BuildRequires: json-glib-devel >= %{json_glib_version} -BuildRequires: libappstream-glib-devel >= %{appstream_glib_version} -BuildRequires: libdnf-devel -BuildRequires: libsoup-devel -BuildRequires: libxmlb-devel >= %{libxmlb_version} +BuildRequires: itstool +BuildRequires: libxslt BuildRequires: meson -BuildRequires: PackageKit-glib-devel >= %{packagekit_version} -BuildRequires: polkit-devel -BuildRequires: flatpak-devel >= %{flatpak_version} -BuildRequires: ostree-devel -BuildRequires: rpm-devel -BuildRequires: rpm-ostree-devel -BuildRequires: libgudev1-devel -BuildRequires: valgrind-devel +BuildRequires: pkgconfig(appstream) >= %{appstream_version} +BuildRequires: pkgconfig(flatpak) >= %{flatpak_version} +BuildRequires: pkgconfig(fwupd) >= %{fwupd_version} +BuildRequires: pkgconfig(gdk-pixbuf-2.0) +BuildRequires: pkgconfig(gio-unix-2.0) >= %{glib2_version} +BuildRequires: pkgconfig(glib-2.0) >= %{glib2_version} +BuildRequires: pkgconfig(gmodule-2.0) >= %{glib2_version} +BuildRequires: pkgconfig(gsettings-desktop-schemas) +BuildRequires: pkgconfig(gtk4) >= %{gtk4_version} +BuildRequires: pkgconfig(gudev-1.0) +BuildRequires: pkgconfig(json-glib-1.0) >= %{json_glib_version} +BuildRequires: pkgconfig(libadwaita-1) >= %{libadwaita_version} +BuildRequires: pkgconfig(libdnf) +BuildRequires: pkgconfig(libsoup-3.0) +%if %{with malcontent} +BuildRequires: pkgconfig(malcontent-0) +%endif +BuildRequires: pkgconfig(ostree-1) +%if %{with packagekit} +BuildRequires: pkgconfig(packagekit-glib2) >= %{packagekit_version} +%endif +BuildRequires: pkgconfig(polkit-gobject-1) +BuildRequires: pkgconfig(rpm) +%if %{with rpmostree} +BuildRequires: pkgconfig(rpm-ostree-1) +%endif +BuildRequires: pkgconfig(sysprof-capture-4) +BuildRequires: pkgconfig(xmlb) >= %{libxmlb_version} +BuildRequires: systemd Requires: appstream-data +Requires: appstream%{?_isa} >= %{appstream_version} +%if %{with webapps} +Requires: epiphany-runtime%{?_isa} +%endif +%if %{with dnf5} +Requires: dnf5daemon-server%{?_isa} >= %{dnf5_version} +Requires: dnf5daemon-server-polkit +Requires: libdnf5-plugin-appstream%{?_isa} +Requires: rpm-plugin-dbus-announce%{?_isa} +%endif Requires: flatpak%{?_isa} >= %{flatpak_version} Requires: flatpak-libs%{?_isa} >= %{flatpak_version} Requires: fwupd%{?_isa} >= %{fwupd_version} Requires: glib2%{?_isa} >= %{glib2_version} -Requires: gnome-desktop3%{?_isa} >= %{gnome_desktop_version} +%if !0%{?rhel} +Requires: gnome-app-list +%endif # gnome-menus is needed for app folder .directory entries Requires: gnome-menus%{?_isa} -Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas_version} -Requires: gtk3%{?_isa} >= %{gtk3_version} +Requires: gsettings-desktop-schemas%{?_isa} +Requires: gtk4 >= %{gtk4_version} Requires: json-glib%{?_isa} >= %{json_glib_version} Requires: iso-codes -Requires: libappstream-glib%{?_isa} >= %{appstream_glib_version} +Requires: libadwaita >= %{libadwaita_version} # librsvg2 is needed for gdk-pixbuf svg loader Requires: librsvg2%{?_isa} -Requires: libsoup%{?_isa} >= %{libsoup_version} Requires: libxmlb%{?_isa} >= %{libxmlb_version} +%if %{with packagekit} Recommends: PackageKit%{?_isa} >= %{packagekit_version} +%endif +Recommends: %{name}-fedora-langpacks Obsoletes: gnome-software-snap < 3.33.1 Obsoletes: gnome-software-editor < 3.35.1 -# this is not a library version -%define gs_plugin_version 13 - %description gnome-software is an application that makes it easy to add, remove and update software in the GNOME desktop. @@ -85,6 +140,15 @@ Requires: %{name}%{?_isa} = %{version}-%{release} These development files are for building gnome-software plugins outside the source tree. Most users do not need this subpackage installed. +%package fedora-langpacks +Summary: Contains fedora-langpacks plugin +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description fedora-langpacks +The fedora-langpacks plugin ensures langpacks packages are installed +for the current locale. + +%if %{with rpmostree} %package rpm-ostree Summary: rpm-ostree backend for gnome-software Requires: %{name}%{?_isa} = %{version}-%{release} @@ -96,26 +160,58 @@ gnome-software is an application that makes it easy to add, remove and update software in the GNOME desktop. This package includes the rpm-ostree backend. +%endif %prep -%autosetup -p1 +%autosetup -p1 -S gendiff -n %{name}-%{tarball_version} %build %meson \ -Dsnap=false \ +%if %{with malcontent} + -Dmalcontent=true \ +%else -Dmalcontent=false \ +%endif -Dgudev=true \ +%if %{with packagekit} -Dpackagekit=true \ + -Dpackagekit_autoremove=true \ +%else + -Dpackagekit=false \ +%endif +%if %{with dnf5} + -Ddnf5=true \ +%endif -Dexternal_appstream=false \ +%if %{with rpmostree} -Drpm_ostree=true \ +%else + -Drpm_ostree=false \ +%endif +%if %{with webapps} + -Dwebapps=true \ + -Dhardcoded_foss_webapps=true \ + -Dhardcoded_proprietary_webapps=false \ +%else + -Dwebapps=false \ + -Dhardcoded_foss_webapps=false \ + -Dhardcoded_proprietary_webapps=false \ +%endif +%if %{with dkms} + -Ddkms=true \ +%else + -Ddkms=false \ +%endif -Dtests=false %meson_build %install %meson_install -# remove unneeded dpkg plugin -rm %{buildroot}%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_dpkg.so +# remove unneeded dpkg and dummy plugins +rm %{buildroot}%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_dpkg.so +rm %{buildroot}%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_dummy.so # make the software center load faster desktop-file-edit %{buildroot}%{_datadir}/applications/org.gnome.Software.desktop \ @@ -124,7 +220,13 @@ desktop-file-edit %{buildroot}%{_datadir}/applications/org.gnome.Software.deskto # set up for Fedora cat >> %{buildroot}%{_datadir}/glib-2.0/schemas/org.gnome.software-fedora.gschema.override << FOE [org.gnome.software] -official-repos = [ 'anaconda', 'fedora', 'fedora-debuginfo', 'fedora-source', 'koji-override-0', 'koji-override-1', 'rawhide', 'rawhide-debuginfo', 'rawhide-source', 'updates', 'updates-debuginfo', 'updates-source', 'updates-testing', 'updates-testing-debuginfo', 'updates-testing-source', 'fedora-modular', 'fedora-modular-debuginfo', 'fedora-modular-source', 'rawhide-modular', 'rawhide-modular-debuginfo', 'rawhide-modular-source' ] +%if 0%{?rhel} +official-repos = [ 'rhel-%{?rhel}' ] +%else +official-repos = [ 'anaconda', 'fedora', 'fedora-debuginfo', 'fedora-source', 'koji-override-0', 'koji-override-1', 'rawhide', 'rawhide-debuginfo', 'rawhide-source', 'updates', 'updates-debuginfo', 'updates-source', 'updates-testing', 'updates-testing-debuginfo', 'updates-testing-source', 'fedora-modular', 'fedora-modular-debuginfo', 'fedora-modular-source', 'rawhide-modular', 'rawhide-modular-debuginfo', 'rawhide-modular-source', 'fedora-cisco-openh264', 'fedora-cisco-openh264-debuginfo' ] +required-repos = [ 'fedora', 'updates' ] +packaging-format-preference = [ 'flatpak:fedora-testing', 'flatpak:fedora', 'rpm' ] +%endif FOE %find_lang %name --with-gnome @@ -133,718 +235,98 @@ FOE desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop %files -f %{name}.lang -%doc AUTHORS README.md +%doc AUTHORS NEWS README.md %license COPYING %{_bindir}/gnome-software -%{_datadir}/applications/gnome-software-local-file.desktop +%{_datadir}/applications/gnome-software-local-file-flatpak.desktop +%{_datadir}/applications/gnome-software-local-file-fwupd.desktop +%if %{with packagekit} || %{with rpmostree} || %{with dnf5} +%{_datadir}/applications/gnome-software-local-file-packagekit.desktop +%endif %{_datadir}/applications/org.gnome.Software.desktop -%dir %{_datadir}/gnome-software -%{_datadir}/gnome-software/*.png -%{_mandir}/man1/gnome-software.1.gz +%{_datadir}/bash-completion/completions/gnome-software +%{_mandir}/man1/gnome-software.1* %{_datadir}/icons/hicolor/*/apps/org.gnome.Software.svg %{_datadir}/icons/hicolor/symbolic/apps/org.gnome.Software-symbolic.svg -%{_datadir}/icons/hicolor/scalable/status/software-installed-symbolic.svg -%{_datadir}/gnome-software/featured-*.svg -%{_datadir}/gnome-software/featured-*.jpg -%{_datadir}/metainfo/org.gnome.Software.appdata.xml +%{_datadir}/icons/hicolor/scalable/categories/system-component-addon.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-application.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-codecs.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-driver.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-firmware.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-input-sources.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-language.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-os-updates.svg +%{_datadir}/icons/hicolor/scalable/categories/system-component-runtime.svg +%{_datadir}/metainfo/org.gnome.Software.metainfo.xml +%if %{with webapps} +%{_datadir}/metainfo/org.gnome.Software.Plugin.Epiphany.metainfo.xml +%endif %{_datadir}/metainfo/org.gnome.Software.Plugin.Flatpak.metainfo.xml %{_datadir}/metainfo/org.gnome.Software.Plugin.Fwupd.metainfo.xml -%{_datadir}/metainfo/org.gnome.Software.Plugin.Odrs.metainfo.xml -%dir %{_libdir}/gs-plugins-%{gs_plugin_version} -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_appstream.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_desktop-categories.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_desktop-menu-path.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_dummy.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_fedora-langpacks.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_fedora-pkgdb-collections.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_flatpak.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_fwupd.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_generic-updates.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_hardcoded-blacklist.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_hardcoded-popular.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_icons.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_key-colors-metadata.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_key-colors.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_modalias.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_odrs.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_os-release.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-history.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-local.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-offline.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-proxy.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-refine-repos.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-refine.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-refresh.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-upgrade.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit-url-to-app.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_packagekit.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_provenance-license.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_provenance.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_repos.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_rewrite-resource.so -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_systemd-updates.so -%{_sysconfdir}/xdg/autostart/gnome-software-service.desktop -%{_datadir}/app-info/xmls/org.gnome.Software.Featured.xml +%dir %{_libdir}/gnome-software/plugins-%{gs_plugin_version} +%{_libdir}/gnome-software/libgnomesoftware.so.%{gs_plugin_version} +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_appstream.so +%if %{with dnf5} +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_dnf5.so +%endif +%if %{with webapps} +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_epiphany.so +%endif +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_fedora-pkgdb-collections.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_flatpak.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_fwupd.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_generic-updates.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_hardcoded-blocklist.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_icons.so +%if %{with malcontent} +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_malcontent.so +%endif +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_modalias.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_os-release.so +%if %{with packagekit} +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_packagekit.so +%endif +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_provenance-license.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_provenance.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_repos.so +%if %{with webapps} +%dir %{_datadir}/swcatalog +%dir %{_datadir}/swcatalog/xml +%{_datadir}/swcatalog/xml/gnome-pwa-list-foss.xml +%endif +%if %{with packagekit} %{_datadir}/dbus-1/services/org.freedesktop.PackageKit.service +%endif %{_datadir}/dbus-1/services/org.gnome.Software.service %{_datadir}/gnome-shell/search-providers/org.gnome.Software-search-provider.ini %{_datadir}/glib-2.0/schemas/org.gnome.software.gschema.xml %{_datadir}/glib-2.0/schemas/org.gnome.software-fedora.gschema.override %{_libexecdir}/gnome-software-cmd %{_libexecdir}/gnome-software-restarter +%{_userunitdir}/gnome-software.service +%if %{with dkms} +%{_datadir}/polkit-1/actions/org.gnome.software.dkms-helper.policy +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_dkms.so +%{_libexecdir}/gnome-software-dkms-helper +%endif + +%files fedora-langpacks +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_fedora-langpacks.so + +%if %{with rpmostree} %files rpm-ostree -%{_libdir}/gs-plugins-%{gs_plugin_version}/libgs_plugin_rpm-ostree.so +%{_libdir}/gnome-software/plugins-%{gs_plugin_version}/libgs_plugin_rpm-ostree.so +%endif %files devel %{_libdir}/pkgconfig/gnome-software.pc %dir %{_includedir}/gnome-software %{_includedir}/gnome-software/*.h -%{_datadir}/gtk-doc/html/gnome-software +%{_libdir}/gnome-software/libgnomesoftware.so +%dir %{_datadir}/gtk-doc +%dir %{_datadir}/gtk-doc/html +%{_datadir}/gtk-doc/html/gnome-software/ %changelog -* Wed Feb 19 2020 Richard Hughes - 3.35.91-1 -- Update to 3.35.91. - -* Tue Jan 28 2020 Fedora Release Engineering - 3.35.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild - -* Mon Nov 25 2019 Richard Hughes - 3.35.2-1 -- Update to 3.35.2. - -* Fri Oct 18 2019 Kalev Lember - 3.34.1-6 -- Backport patches to fix a crash in gs_flatpak_get_installation (#1762689) - -* Mon Oct 14 2019 Kalev Lember - 3.34.1-5 -- Update renamed appstream ids for GNOME 3.34 - -* Fri Oct 11 2019 Richard Hughes - 3.34.1-4 -- Backport a simpler to correct the installed applications -- Resolves #1759193 - -* Fri Oct 11 2019 Richard Hughes - 3.34.1-3 -- Backport a better patch to correct the installed applications -- Resolves #1759193 - -* Thu Oct 10 2019 Richard Hughes - 3.34.1-2 -- Backport a patch to correct the applications shown in the installed list -- Resolves #1759193 - -* Mon Oct 07 2019 Kalev Lember - 3.34.1-1 -- Update to 3.34.1 - -* Wed Sep 25 2019 Kalev Lember - 3.34.0-2 -- Fix third party repo enabling not working (#1749566) - -* Mon Sep 09 2019 Kalev Lember - 3.34.0-1 -- Update to 3.34.0 - -* Thu Jul 25 2019 Fedora Release Engineering - 3.32.4-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild - -* Thu Jul 11 2019 Kalev Lember - 3.32.4-1 -- Update to 3.32.4 - -* Thu Jul 11 2019 Richard Hughes - 3.32.3-5 -- Disable the snap plugin. Canonical upstream are not going to be installing - gnome-software in the next LTS, prefering instead to ship a "Snap Store" - rather than GNOME Software. -- Enabling the snap plugin also enables the Snap Store which violated the same - rules which prevented us installing Flathub by default. -- The existing plugin is barely maintained and I don't want to be the one - responsible when it breaks. - -* Thu Jun 13 2019 Kalev Lember - 3.32.3-4 -- Rebuild for accidental libflatpak ABI break - -* Mon Jun 10 22:13:19 CET 2019 Igor Gnatenko - 3.32.3-3 -- Rebuild for RPM 4.15 - -* Mon Jun 10 15:42:01 CET 2019 Igor Gnatenko - 3.32.3-2 -- Rebuild for RPM 4.15 - -* Fri May 24 2019 Kalev Lember - 3.32.3-1 -- Update to 3.32.3 - -* Tue May 07 2019 Kalev Lember - 3.32.2-1 -- Update to 3.32.2 - -* Fri May 03 2019 Kalev Lember - 3.32.1-4 -- Update a patch to final upstream version - -* Tue Apr 30 2019 Kalev Lember - 3.32.1-3 -- Backport a number of rpm-ostree fixes - -* Tue Apr 16 2019 Adam Williamson - 3.32.1-2 -- Rebuild with Meson fix for #1699099 - -* Mon Apr 15 2019 Kalev Lember - 3.32.1-1 -- Update to 3.32.1 - -* Fri Apr 05 2019 Neal Gompa - 3.32.0-6 -- Require snapd instead of the obsolete snapd-login-service for snap subpackage - -* Wed Apr 03 2019 Kalev Lember - 3.32.0-5 -- Switch to system libdnf - -* Fri Mar 29 2019 Kalev Lember - 3.32.0-4 -- Rebuild for new rpm-ostree - -* Fri Mar 15 2019 Kalev Lember - 3.32.0-3 -- Add nm-connection-editor.desktop to Utilities folder (#1686851) - -* Tue Mar 12 2019 Kalev Lember - 3.32.0-2 -- Backport a patch to add shadows to app icons - -* Mon Mar 11 2019 Kalev Lember - 3.32.0-1 -- Update to 3.32.0 - -* Tue Mar 05 2019 Kalev Lember - 3.31.92-1 -- Update to 3.31.92 - -* Thu Feb 28 2019 Kalev Lember - 3.31.90-4 -- Change PackageKit requires to recommends - -* Wed Feb 27 2019 Kalev Lember - 3.31.90-3 -- Remove unneeded dpkg plugin - -* Mon Feb 25 2019 Kalev Lember - 3.31.90-2 -- Split rpm-ostree backend to its own subpackage - -* Sun Feb 24 2019 Kalev Lember - 3.31.90-1 -- Update to 3.31.90 -- Add "anaconda" repo to official repos list (#1679693) - -* Thu Jan 31 2019 Fedora Release Engineering - 3.31.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild - -* Wed Jan 16 2019 Kalev Lember - 3.31.2-1 -- Update to 3.31.2 - -* Fri Dec 14 2018 Kalev Lember - 3.31.1-2 -- Fix offline update notifications to show up (#1659231) - -* Tue Oct 09 2018 Kalev Lember - 3.31.1-1 -- Update to 3.31.1 - -* Fri Oct 05 2018 Kalev Lember - 3.30.2-1 -- Update to 3.30.2 - -* Wed Sep 26 2018 Kalev Lember - 3.30.1-2 -- Add modular repos to official repos list - -* Tue Sep 25 2018 Kalev Lember - 3.30.1-1 -- Update to 3.30.1 - -* Thu Sep 06 2018 Kalev Lember - 3.30.0-1 -- Update to 3.30.0 - -* Tue Aug 28 2018 Richard Hughes - 3.29.92-1 -- Update to 3.29.92 - -* Fri Jul 13 2018 Fedora Release Engineering - 3.29.1-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild - -* Wed May 09 2018 Kalev Lember - 3.29.1-1 -- Update to 3.29.1 - -* Mon Apr 09 2018 Kalev Lember - 3.28.1-1 -- Update to 3.28.1 - -* Thu Mar 29 2018 Kalev Lember - 3.28.0-5 -- Fix empty OS Updates showing up -- Make rpm-ostree update triggering work - -* Thu Mar 15 2018 Kalev Lember - 3.28.0-4 -- Fix opening results from gnome-shell search provider - -* Wed Mar 14 2018 Kalev Lember - 3.28.0-3 -- Fix crash on initial run with no network (#1554986) - -* Tue Mar 13 2018 Kalev Lember - 3.28.0-2 -- Backport an upstream patch to fix shell extensions app ID - -* Mon Mar 12 2018 Kalev Lember - 3.28.0-1 -- Update to 3.28.0 - -* Sun Mar 11 2018 Kalev Lember - 3.27.92-3 -- Rebuilt for gspell 1.8 - -* Wed Mar 07 2018 Kalev Lember - 3.27.92-2 -- Move org.gnome.Software.Featured.xml from -editor to main package - -* Mon Mar 05 2018 Kalev Lember - 3.27.92-1 -- Update to 3.27.92 - -* Sun Mar 04 2018 Neal Gompa - 3.27.90-4 -- Drop obsolete snapd-login-service requirement for snap plugin subpackage - -* Mon Feb 19 2018 Adam Williamson - 3.27.90-3 -- Backport fix for RHBZ #1546893 from upstream git - -* Mon Feb 19 2018 Kalev Lember - 3.27.90-2 -- Re-enable rpm-ostree plugin - -* Thu Feb 15 2018 Kalev Lember - 3.27.90-1 -- Update to 3.27.90 -- Temporarily disable the rpm-ostree plugin - -* Tue Feb 13 2018 Björn Esser - 3.27.4-4 -- Rebuild against newer gnome-desktop3 package - -* Thu Feb 08 2018 Kalev Lember - 3.27.4-3 -- Add fedora-workstation-repositories to nonfree-sources schema defaults - -* Wed Feb 07 2018 Fedora Release Engineering - 3.27.4-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild - -* Mon Jan 08 2018 Kalev Lember - 3.27.4-1 -- Update to 3.27.4 -- Drop unused --without packagekit option - -* Fri Jan 05 2018 Igor Gnatenko - 3.27.3-2 -- Remove obsolete scriptlets - -* Sat Dec 16 2017 Kalev Lember - 3.27.3-1 -- Update to 3.27.3 - -* Mon Nov 13 2017 Kalev Lember - 3.27.2-1 -- Update to 3.27.2 - -* Thu Nov 09 2017 Kalev Lember - 3.26.2-1 -- Update to 3.26.2 -- Re-enable fwupd support - -* Tue Oct 31 2017 Kalev Lember - 3.26.1-5 -- Enable the rpm-ostree plugin - -* Wed Oct 25 2017 Kalev Lember - 3.26.1-4 -- Fix "too many results returned" error after distro upgrades (#1496489) - -* Tue Oct 10 2017 Kalev Lember - 3.26.1-3 -- Backport a flatpakref installation fix - -* Mon Oct 09 2017 Richard Hughes - 3.26.1-2 -- Disable fwupd support until we get a 3.27.1 tarball - -* Sun Oct 08 2017 Kalev Lember - 3.26.1-1 -- Update to 3.26.1 - -* Mon Sep 11 2017 Kalev Lember - 3.26.0-1 -- Update to 3.26.0 - -* Sun Aug 27 2017 Kalev Lember - 3.25.91-1 -- Update to 3.25.91 - -* Tue Aug 15 2017 Kalev Lember - 3.25.90-1 -- Update to 3.25.90 - -* Fri Aug 11 2017 Igor Gnatenko - 3.25.4-6 -- Rebuilt after RPM update (№ 3) - -* Thu Aug 10 2017 Igor Gnatenko - 3.25.4-5 -- Rebuilt for RPM soname bump - -* Thu Aug 10 2017 Igor Gnatenko - 3.25.4-4 -- Rebuilt for RPM soname bump - -* Wed Aug 02 2017 Fedora Release Engineering - 3.25.4-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild - -* Wed Jul 26 2017 Fedora Release Engineering - 3.25.4-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild - -* Fri Jul 21 2017 Kalev Lember - 3.25.4-1 -- Update to 3.25.4 - -* Tue Jul 18 2017 Kalev Lember - 3.25.3-6 -- Drop a meson workaround now that meson is fixed - -* Wed Jun 28 2017 Neal Gompa - 3.25.3-5 -- Actually properly enable snap subpackage after removing conditional - -* Wed Jun 28 2017 Neal Gompa - 3.25.3-4 -- Remove unnecessary arch-specific conditional for snap subpackage - -* Tue Jun 27 2017 Neal Gompa - 3.25.3-3 -- Ensure snap subpackage is installed if snapd is installed - -* Fri Jun 23 2017 Richard Hughes - 3.24.3-2 -- Enable the snap subpackage - -* Fri Jun 23 2017 Kalev Lember - 3.25.3-1 -- Update to 3.25.3 -- Switch to the meson build system -- Add an -editor subpackage with new banner editor - -* Mon May 15 2017 Richard Hughes - 3.24.3-1 -- Update to 3.23.3 -- Fix a common crash when installing flatpakrepo files -- Ensure we show the banner when upgrades are available - -* Tue May 09 2017 Kalev Lember - 3.24.2-1 -- Update to 3.24.2 - -* Tue Apr 25 2017 Adam Williamson - 3.24.1-2 -- Backport crasher fix from upstream (RHBZ #1444669 / BGO #781217) - -* Tue Apr 11 2017 Kalev Lember - 3.24.1-1 -- Update to 3.24.1 - -* Tue Mar 21 2017 Kalev Lember - 3.24.0-1 -- Update to 3.24.0 - -* Thu Mar 16 2017 Kalev Lember - 3.23.92-1 -- Update to 3.23.92 - -* Mon Feb 27 2017 Richard Hughes - 3.23.91-1 -- Update to 3.23.91 - -* Mon Feb 13 2017 Richard Hughes - 3.23.90-1 -- Update to 3.23.90 - -* Fri Feb 10 2017 Fedora Release Engineering - 3.23.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild - -* Thu Dec 15 2016 Richard Hughes - 3.23.3-1 -- Update to 3.23.3 - -* Wed Nov 23 2016 Kalev Lember - 3.23.2-1 -- Update to 3.23.2 - -* Tue Nov 08 2016 Kalev Lember - 3.22.2-1 -- Update to 3.22.2 - -* Wed Oct 12 2016 Kalev Lember - 3.22.1-1 -- Update to 3.22.1 - -* Mon Sep 19 2016 Kalev Lember - 3.22.0-1 -- Update to 3.22.0 - -* Wed Sep 14 2016 Kalev Lember - 3.21.92-1 -- Update to 3.21.92 -- Don't set group tags - -* Thu Sep 01 2016 Kalev Lember - 3.21.91-1 -- Update to 3.21.91 - -* Wed Aug 17 2016 Kalev Lember - 3.21.90-2 -- Rebuilt for fixed libappstream-glib headers - -* Wed Aug 17 2016 Kalev Lember - 3.21.90-1 -- Update to 3.21.90 -- Tighten -devel subpackage dependencies - -* Thu Jul 28 2016 Richard Hughes - 3.21.4-2 -- Allow building without PackageKit for the atomic workstation. - -* Mon Jul 18 2016 Richard Hughes - 3.21.4-1 -- Update to 3.21.4 - -* Thu May 26 2016 Kalev Lember - 3.21.2-2 -- Build with flatpak support - -* Mon May 23 2016 Richard Hughes - 3.21.2-1 -- Update to 3.21.2 - -* Tue May 10 2016 Kalev Lember - 3.21.1-2 -- Require PackageKit 1.1.1 for system upgrade support - -* Mon Apr 25 2016 Richard Hughes - 3.21.1-1 -- Update to 3.21.1 - -* Mon Apr 25 2016 Richard Hughes - 3.20.2-1 -- Update to 3.20.1 -- Allow popular and featured apps to match any plugin -- Do not make the ODRS plugin depend on xdg-app -- Fix many of the os-upgrade issues and implement the latest mockups -- Make all the plugins more threadsafe -- Return all update descriptions newer than the installed version -- Show some non-fatal error messages if installing fails -- Use a background PackageKit transaction when downloading upgrades - -* Wed Apr 13 2016 Kalev Lember - 3.20.1-1 -- Update to 3.20.1 - -* Fri Apr 01 2016 Richard Hughes - 3.20.1-2 -- Set the list of official sources -- Compile with xdg-app support - -* Tue Mar 22 2016 Kalev Lember - 3.20.0-1 -- Update to 3.20.0 - -* Mon Mar 14 2016 Richard Hughes - 3.19.92-1 -- Update to 3.19.92 - -* Thu Mar 03 2016 Kalev Lember - 3.19.91-2 -- Set minimum required json-glib version - -* Mon Feb 29 2016 Richard Hughes - 3.19.91-1 -- Update to 3.19.91 - -* Mon Feb 15 2016 Richard Hughes - 3.19.90-1 -- Update to 3.19.90 - -* Wed Feb 03 2016 Fedora Release Engineering - 3.19.4-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild - -* Fri Jan 15 2016 Richard Hughes - 3.19.4-1 -- Update to 3.19.4 - -* Thu Dec 03 2015 Kalev Lember - 3.18.3-2 -- Require librsvg2 for the gdk-pixbuf svg loader - -* Thu Nov 05 2015 Richard Hughes - 3.18.3-1 -- Update to 3.18.3 -- Use the correct user agent string when downloading firmware -- Fix a crash in the limba plugin -- Fix installing web applications - -* Mon Oct 26 2015 Kalev Lember - 3.18.2-2 -- Fix apps reappearing as installed a few seconds after removal (#1275163) - -* Thu Oct 15 2015 Kalev Lember - 3.18.2-1 -- Update to 3.18.2 - -* Tue Oct 13 2015 Kalev Lember - 3.18.1-1 -- Update to 3.18.1 - -* Wed Oct 07 2015 Kalev Lember - 3.18.0-2 -- Backport two crasher fixes from upstream - -* Mon Sep 21 2015 Kalev Lember - 3.18.0-1 -- Update to 3.18.0 - -* Tue Sep 15 2015 Kalev Lember - 3.17.92-2 -- Update dependency versions - -* Tue Sep 15 2015 Richard Hughes - 3.17.92-1 -- Update to 3.17.92 - -* Thu Sep 10 2015 Richard Hughes - 3.17.91-2 -- Fix firmware updates - -* Thu Sep 03 2015 Kalev Lember - 3.17.91-1 -- Update to 3.17.91 - -* Wed Aug 19 2015 Kalev Lember - 3.17.90-1 -- Update to 3.17.90 - -* Wed Aug 12 2015 Richard Hughes - 3.17.3-1 -- Update to 3.17.3 - -* Wed Jul 22 2015 David King - 3.17.2-3 -- Bump for new gnome-desktop3 - -* Wed Jun 17 2015 Fedora Release Engineering - 3.17.2-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild - -* Fri Jun 05 2015 Kalev Lember - 3.17.2-1 -- Update to 3.17.2 - -* Mon May 25 2015 Kalev Lember - 3.17.1-1 -- Update to 3.17.1 - -* Fri May 15 2015 Kalev Lember - 3.16.2-2 -- Fix a crash under Wayland (#1221968) - -* Mon May 11 2015 Kalev Lember - 3.16.2-1 -- Update to 3.16.2 - -* Tue Apr 14 2015 Kalev Lember - 3.16.1-1 -- Update to 3.16.1 - -* Mon Mar 23 2015 Kalev Lember - 3.16.0-1 -- Update to 3.16.0 - -* Mon Mar 16 2015 Kalev Lember - 3.15.92-1 -- Update to 3.15.92 -- Use license macro for the COPYING file -- Add a patch to adapt to gnome-terminal desktop file rename - -* Mon Mar 02 2015 Kalev Lember - 3.15.91-1 -- Update to 3.15.91 - -* Sat Feb 21 2015 Kalev Lember - 3.15.90-3 -- Export DisplayName property on the packagekit session service - -* Thu Feb 19 2015 Kalev Lember - 3.15.90-2 -- Backport a crash fix - -* Tue Feb 17 2015 Richard Hughes - 3.15.90-1 -- Update to 3.15.90 - -* Mon Jan 19 2015 Richard Hughes - 3.15.4-1 -- Update to 3.15.4 - -* Tue Nov 25 2014 Kalev Lember - 3.15.2-1 -- Update to 3.15.2 - -* Thu Nov 13 2014 Richard Hughes - 3.14.2-3 -- Fix non-Fedora build - -* Tue Nov 11 2014 Richard Hughes - 3.14.2-2 -- Backport a patch to fix compilation - -* Mon Nov 10 2014 Kalev Lember - 3.14.2-1 -- Update to 3.14.2 - -* Sat Nov 08 2014 Kalev Lember - 3.14.1-3 -- Update the list of system apps - -* Sat Nov 01 2014 David King - 3.14.1-2 -- Rebuild for new libappstream-glib (#1156494) - -* Mon Oct 13 2014 Kalev Lember - 3.14.1-1 -- Update to 3.14.1 - -* Thu Oct 09 2014 Kalev Lember - 3.14.0-2 -- Depend on gnome-menus for app folder directory entries - -* Mon Sep 22 2014 Kalev Lember - 3.14.0-1 -- Update to 3.14.0 - -* Wed Sep 17 2014 Kalev Lember - 3.13.92-2 -- Set minimum required dependency versions (#1136343) - -* Tue Sep 16 2014 Kalev Lember - 3.13.92-1 -- Update to 3.13.92 -- Replace gnome-system-log with gnome-logs in the system apps list - -* Tue Sep 02 2014 Kalev Lember - 3.13.91-1 -- Update to 3.13.91 - -* Tue Aug 19 2014 Richard Hughes - 3.13.90-1 -- Update to 3.13.90 - -* Sat Aug 16 2014 Fedora Release Engineering - 3.13.5-0.2.git5c89189 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild - -* Mon Aug 11 2014 Kalev Lember - 3.13.5-0.1.git5c89189 -- Update to 3.13.5 git snapshot -- Ship HighContrast icons - -* Sun Aug 03 2014 Kalev Lember - 3.13.4-2 -- Replace Epiphany with Firefox in the system apps list - -* Wed Jul 23 2014 Kalev Lember - 3.13.4-1 -- Update to 3.13.4 - -* Wed Jun 25 2014 Kalev Lember - 3.13.3-1 -- Update to 3.13.3 - -* Thu Jun 12 2014 Richard Hughes - 3.13.3-0.2.git7491627 -- Depend on the newly-created appstream-data package and stop shipping - the metadata here. - -* Sat Jun 07 2014 Kalev Lember - 3.13.3-0.1.git7491627 -- Update to 3.13.3 git snapshot - -* Wed May 28 2014 Richard Hughes - 3.13.2-2 -- Rebuild with new metadata. - -* Wed May 28 2014 Kalev Lember - 3.13.2-1 -- Update to 3.13.2 - -* Thu May 15 2014 Kalev Lember - 3.13.1-4 -- Depend on gsettings-desktop-schemas - -* Mon May 12 2014 Richard Hughes - 3.13.1-3 -- Update the metadata and use appstream-util to install the metadata. - -* Wed May 07 2014 Kalev Lember - 3.13.1-2 -- Drop gnome-icon-theme dependency - -* Mon Apr 28 2014 Richard Hughes - 3.13.1-1 -- Update to 3.13.1 - -* Fri Apr 11 2014 Kalev Lember - 3.12.1-2 -- Rebuild with new metadata. - -* Fri Apr 11 2014 Richard Hughes - 3.12.1-1 -- Update to 3.12.1 - -* Mon Mar 24 2014 Richard Hughes - 3.12.0-1 -- Update to 3.12.0 - -* Thu Mar 20 2014 Richard Hughes - 3.11.92-1 -- Update to 3.11.92 - -* Tue Mar 18 2014 Richard Hughes - 3.11.91-2 -- Rebuild with new metadata. - -* Sat Mar 08 2014 Richard Hughes - 3.11.91-1 -- Update to 3.11.91 - -* Tue Feb 18 2014 Richard Hughes - 3.11.90-1 -- Update to 3.11.90 - -* Mon Feb 03 2014 Richard Hughes - 3.11.5-2 -- Require epiphany-runtime rather than the full application - -* Mon Feb 03 2014 Richard Hughes - 3.11.5-1 -- Update to 3.11.5 - -* Thu Jan 30 2014 Richard Hughes - 3.11.4-3 -- Rebuild for libpackagekit-glib soname bump - -* Wed Jan 22 2014 Richard Hughes - 3.11.4-2 -- Rebuild with metadata that has the correct screenshot url. - -* Thu Jan 16 2014 Richard Hughes - 3.11.4-1 -- Update to 3.11.4 - -* Tue Dec 17 2013 Richard Hughes - 3.11.3-1 -- Update to 3.11.3 - -* Tue Nov 19 2013 Richard Hughes - 3.11.2-1 -- Update to 3.11.2 - -* Tue Oct 29 2013 Richard Hughes - 3.11.1-1 -- Update to 3.11.1 -- Add a gnome shell search provider -- Add a module to submit the user rating to the fedora-tagger web service -- Add support for 'missing' codecs that we know exist but we can't install -- Add support for epiphany web applications -- Handle offline installation sensibly -- Save the user rating if the user clicks the rating stars -- Show a modal error message if install or remove actions failed -- Show a star rating on the application details page -- Show font screenshots -- Show more detailed version numbers when required -- Show screenshots to each application - -* Wed Sep 25 2013 Richard Hughes 3.10.0-1 -- New upstream release. -- New metadata for fedora, updates and updates-testing -- Add a plugin to query the PackageKit prepared-update file directly -- Do not clear the offline-update trigger if rebooting succeeded -- Do not load incompatible projects when parsing AppStream data -- Lots of updated translations -- Show the window right away when starting - -* Fri Sep 13 2013 Richard Hughes 3.9.3-1 -- New upstream release. -- Lots of new and fixed UI and updated metadata for Fedora 20 - -* Tue Sep 03 2013 Richard Hughes 3.9.2-1 -- New upstream release. -- Allow stock items in the AppStream XML -- Extract the AppStream URL and description from the XML -- Only present the window when the overview is complete -- Return the subcategories sorted by name - -* Mon Sep 02 2013 Richard Hughes 3.9.1-1 -- New upstream release which is a technical preview for the alpha. - -* Sun Sep 01 2013 Richard Hughes 0.1-3 -- Use buildroot not RPM_BUILD_ROOT -- Own all gnome-software directories -- Drop gtk-update-icon-cache requires and the mime database functionality - -* Thu Aug 29 2013 Richard Hughes 0.1-2 -- Add call to desktop-file-validate and fix other review comments. - -* Wed Aug 28 2013 Richard Hughes 0.1-1 -- First release for Fedora package review - +%autochangelog diff --git a/rpminspect.yaml b/rpminspect.yaml new file mode 100644 index 0000000..ef70fcd --- /dev/null +++ b/rpminspect.yaml @@ -0,0 +1,7 @@ +--- +runpath: + allowed_paths: + - /usr/lib/gnome-software + - /usr/lib/rpm-ostree + - /usr/lib64/gnome-software + - /usr/lib64/rpm-ostree diff --git a/sources b/sources index 21ab9a7..c24e704 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (gnome-software-3.35.91.tar.xz) = 2329e3aafa896442ad61f76e72325632a98dc2913e2874e96bad91ea2b52f759730d729acd7d3ba93c07c3a89817c4e5c29133cc9082f6a54a675cb23f9f28ca +SHA512 (gnome-software-49.3.tar.xz) = 0414ea55ad3b83bcd50514985e3d2026207801c040934805bb26d162e88f0619a9d70aa6ba324a29acfab02573490b928c83594b54f57ec2a8ad00e6c127f657