summaryrefslogtreecommitdiffstats
path: root/src/gs-repos-dialog.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/gs-repos-dialog.c766
1 files changed, 766 insertions, 0 deletions
diff --git a/src/gs-repos-dialog.c b/src/gs-repos-dialog.c
new file mode 100644
index 0000000..81a1471
--- /dev/null
+++ b/src/gs-repos-dialog.c
@@ -0,0 +1,766 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013-2017 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com>
+ * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include "gs-repos-dialog.h"
+
+#include "gnome-software-private.h"
+#include "gs-common.h"
+#include "gs-os-release.h"
+#include "gs-repo-row.h"
+#include "gs-repos-section.h"
+#include "gs-utils.h"
+#include <glib/gi18n.h>
+
+struct _GsReposDialog
+{
+ AdwWindow parent_instance;
+ GSettings *settings;
+ GsFedoraThirdParty *third_party;
+ gboolean third_party_enabled;
+ GHashTable *third_party_repos; /* (nullable) (owned), mapping from owned repo ID ā†’ owned plugin name */
+ GHashTable *sections; /* gchar * ~> GsReposSection * */
+
+ GCancellable *cancellable;
+ GsPluginLoader *plugin_loader;
+ GtkWidget *status_empty;
+ GtkWidget *content_page;
+ GtkWidget *spinner;
+ GtkWidget *stack;
+};
+
+G_DEFINE_TYPE (GsReposDialog, gs_repos_dialog, ADW_TYPE_WINDOW)
+
+static void reload_third_party_repos (GsReposDialog *dialog);
+
+typedef struct {
+ GsReposDialog *dialog;
+ GsApp *repo;
+ GWeakRef row_weakref;
+ GsPluginManageRepositoryFlags operation;
+} InstallRemoveData;
+
+static void
+install_remove_data_free (InstallRemoveData *data)
+{
+ g_clear_object (&data->dialog);
+ g_clear_object (&data->repo);
+ g_weak_ref_clear (&data->row_weakref);
+ g_slice_free (InstallRemoveData, data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(InstallRemoveData, install_remove_data_free);
+
+static void
+repo_enabled_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ g_autoptr(InstallRemoveData) install_remove_data = (InstallRemoveData *) user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsRepoRow) row = NULL;
+ const gchar *operation_str;
+
+ operation_str = install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INSTALL ? "install" :
+ install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_REMOVE ? "remove" :
+ install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_ENABLE ? "enable" :
+ install_remove_data->operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE ? "disable" : NULL;
+ g_assert (operation_str != NULL);
+
+ row = g_weak_ref_get (&install_remove_data->row_weakref);
+ if (row)
+ gs_repo_row_unmark_busy (row);
+
+ if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_debug ("repo %s cancelled", operation_str);
+ return;
+ }
+
+ g_warning ("failed to %s repo: %s", operation_str, error->message);
+ return;
+ }
+
+ g_debug ("finished %s repo %s", operation_str, gs_app_get_id (install_remove_data->repo));
+}
+
+static void
+_enable_repo (InstallRemoveData *install_data)
+{
+ GsReposDialog *dialog = install_data->dialog;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ g_debug ("enabling repo %s", gs_app_get_id (install_data->repo));
+ plugin_job = gs_plugin_job_manage_repository_new (install_data->repo,
+ install_data->operation | GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ repo_enabled_cb,
+ install_data);
+}
+
+static void
+enable_repo_response_cb (GtkDialog *confirm_dialog,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(InstallRemoveData) install_data = (InstallRemoveData *) user_data;
+
+ /* unmap the dialog */
+ gtk_window_destroy (GTK_WINDOW (confirm_dialog));
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK) {
+ g_autoptr(GsRepoRow) row = g_weak_ref_get (&install_data->row_weakref);
+ if (row)
+ gs_repo_row_unmark_busy (row);
+ return;
+ }
+
+ _enable_repo (g_steal_pointer (&install_data));
+}
+
+static void
+enable_repo (GsReposDialog *dialog,
+ GsRepoRow *row,
+ GsApp *repo)
+{
+ g_autoptr(InstallRemoveData) install_data = NULL;
+
+ install_data = g_slice_new0 (InstallRemoveData);
+ install_data->operation = GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_ENABLE;
+ install_data->dialog = g_object_ref (dialog);
+ install_data->repo = g_object_ref (repo);
+ g_weak_ref_init (&install_data->row_weakref, row);
+
+ gs_repo_row_mark_busy (row);
+
+ /* user needs to confirm acceptance of an agreement */
+ if (gs_app_get_agreement (repo) != NULL) {
+ GtkWidget *confirm_dialog;
+ g_autofree gchar *message = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /* convert from AppStream markup */
+ message = as_markup_convert_simple (gs_app_get_agreement (repo), &error);
+ if (message == NULL) {
+ /* failed, so just try and show the original markup */
+ message = g_strdup (gs_app_get_agreement (repo));
+ g_warning ("Failed to process AppStream markup: %s",
+ error->message);
+ }
+
+ /* ask for confirmation */
+ confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: window title */
+ "%s", _("Enable Third-Party Software Repository?"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog),
+ "%s", message);
+
+ /* TRANSLATORS: button to accept the agreement */
+ gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("Enable"),
+ GTK_RESPONSE_OK);
+
+ /* handle this async */
+ g_signal_connect (confirm_dialog, "response",
+ G_CALLBACK (enable_repo_response_cb),
+ g_steal_pointer (&install_data));
+
+ gtk_window_set_modal (GTK_WINDOW (confirm_dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (confirm_dialog));
+ return;
+ }
+
+ /* no prompt required */
+ _enable_repo (g_steal_pointer (&install_data));
+}
+
+static void
+remove_repo_response_cb (GtkDialog *confirm_dialog,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(InstallRemoveData) remove_data = (InstallRemoveData *) user_data;
+ GsReposDialog *dialog = remove_data->dialog;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* unmap the dialog */
+ gtk_window_destroy (GTK_WINDOW (confirm_dialog));
+
+ /* not agreed */
+ if (response != GTK_RESPONSE_OK) {
+ g_autoptr(GsRepoRow) row = g_weak_ref_get (&remove_data->row_weakref);
+ if (row)
+ gs_repo_row_unmark_busy (row);
+ return;
+ }
+
+ g_debug ("removing repo %s", gs_app_get_id (remove_data->repo));
+ plugin_job = gs_plugin_job_manage_repository_new (remove_data->repo,
+ remove_data->operation |
+ GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_INTERACTIVE);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ repo_enabled_cb,
+ g_steal_pointer (&remove_data));
+}
+
+static void
+remove_confirm_repo (GsReposDialog *dialog,
+ GsRepoRow *row,
+ GsApp *repo,
+ GsPluginManageRepositoryFlags operation)
+{
+ InstallRemoveData *remove_data;
+ GtkWidget *confirm_dialog;
+ g_autofree gchar *message = NULL;
+ GtkWidget *button;
+ GtkStyleContext *context;
+
+ remove_data = g_slice_new0 (InstallRemoveData);
+ remove_data->operation = operation;
+ remove_data->dialog = g_object_ref (dialog);
+ remove_data->repo = g_object_ref (repo);
+ g_weak_ref_init (&remove_data->row_weakref, row);
+
+ /* TRANSLATORS: The '%s' is replaced with a repository name, like "Fedora Modular - x86_64" */
+ message = g_strdup_printf (_("Software that has been installed from ā€œ%sā€ will cease to receive updates."),
+ gs_app_get_name (repo));
+
+ /* ask for confirmation */
+ confirm_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_CANCEL,
+ "%s",
+ operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE ? _("Disable Repository?") : _("Remove Repository?"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (confirm_dialog),
+ "%s", message);
+
+ if (operation == GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE) {
+ /* TRANSLATORS: this is button text to disable a repo */
+ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Disable"), GTK_RESPONSE_OK);
+ } else {
+ /* TRANSLATORS: this is button text to remove a repo */
+ button = gtk_dialog_add_button (GTK_DIALOG (confirm_dialog), _("_Remove"), GTK_RESPONSE_OK);
+ }
+ context = gtk_widget_get_style_context (button);
+ gtk_style_context_add_class (context, "destructive-action");
+
+ /* handle this async */
+ g_signal_connect (confirm_dialog, "response",
+ G_CALLBACK (remove_repo_response_cb), remove_data);
+
+ gtk_window_set_modal (GTK_WINDOW (confirm_dialog), TRUE);
+ gtk_window_present (GTK_WINDOW (confirm_dialog));
+
+ gs_repo_row_mark_busy (row);
+}
+
+static void
+repo_section_switch_clicked_cb (GsReposSection *section,
+ GsRepoRow *row,
+ GsReposDialog *dialog)
+{
+ GsApp *repo;
+
+ repo = gs_repo_row_get_repo (row);
+
+ switch (gs_app_get_state (repo)) {
+ case GS_APP_STATE_AVAILABLE:
+ case GS_APP_STATE_AVAILABLE_LOCAL:
+ enable_repo (dialog, row, repo);
+ break;
+ case GS_APP_STATE_INSTALLED:
+ remove_confirm_repo (dialog, row, repo, GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_DISABLE);
+ break;
+ default:
+ g_warning ("repo %s button clicked in unexpected state %s",
+ gs_app_get_id (repo),
+ gs_app_state_to_string (gs_app_get_state (repo)));
+ break;
+ }
+}
+
+static void
+repo_section_remove_clicked_cb (GsReposSection *section,
+ GsRepoRow *row,
+ GsReposDialog *dialog)
+{
+ GsApp *repo = gs_repo_row_get_repo (row);
+ remove_confirm_repo (dialog, row, repo, GS_PLUGIN_MANAGE_REPOSITORY_FLAGS_REMOVE);
+}
+
+static void
+fedora_third_party_switch_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GsReposDialog) self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!gs_fedora_third_party_switch_finish (GS_FEDORA_THIRD_PARTY (source_object), result, &error)) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ g_warning ("Failed to switch 'fedora-third-party' config: %s", error->message);
+ }
+
+ /* Reload the state, because the user could dismiss the authentication prompt
+ or the repos could change their state. */
+ reload_third_party_repos (self);
+}
+
+static void
+fedora_third_party_repos_switch_notify_cb (GObject *object,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ GsReposDialog *self = user_data;
+
+ gs_fedora_third_party_switch (self->third_party,
+ gtk_switch_get_active (GTK_SWITCH (object)),
+ TRUE,
+ self->cancellable,
+ fedora_third_party_switch_done_cb,
+ g_object_ref (self));
+}
+
+static gboolean
+is_third_party_repo (GsReposDialog *dialog,
+ GsApp *repo)
+{
+ g_autoptr(GsPlugin) plugin = gs_app_dup_management_plugin (repo);
+ const gchar *plugin_name = (plugin != NULL) ? gs_plugin_get_name (plugin) : NULL;
+
+ return gs_app_get_scope (repo) == AS_COMPONENT_SCOPE_SYSTEM &&
+ gs_fedora_third_party_util_is_third_party_repo (dialog->third_party_repos,
+ gs_app_get_id (repo),
+ plugin_name);
+}
+
+static void
+add_repo (GsReposDialog *dialog,
+ GsApp *repo,
+ GSList **third_party_repos)
+{
+ GsAppState state;
+ GtkWidget *section;
+ g_autofree gchar *origin_ui = NULL;
+
+ state = gs_app_get_state (repo);
+ if (!(state == GS_APP_STATE_AVAILABLE ||
+ state == GS_APP_STATE_AVAILABLE_LOCAL ||
+ state == GS_APP_STATE_INSTALLED ||
+ state == GS_APP_STATE_INSTALLING ||
+ state == GS_APP_STATE_REMOVING)) {
+ g_warning ("repo %s in invalid state %s",
+ gs_app_get_id (repo),
+ gs_app_state_to_string (state));
+ return;
+ }
+
+ if (third_party_repos && is_third_party_repo (dialog, repo)) {
+ *third_party_repos = g_slist_prepend (*third_party_repos, repo);
+ return;
+ }
+
+ origin_ui = gs_app_dup_origin_ui (repo, TRUE);
+ if (!origin_ui)
+ origin_ui = gs_app_get_packaging_format (repo);
+ if (!origin_ui) {
+ g_autoptr(GsPlugin) plugin = gs_app_dup_management_plugin (repo);
+ origin_ui = (plugin != NULL) ? g_strdup (gs_plugin_get_name (plugin)) : NULL;
+ }
+
+ section = g_hash_table_lookup (dialog->sections, origin_ui);
+ if (section == NULL) {
+ section = gs_repos_section_new (FALSE);
+ adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (section),
+ origin_ui);
+ g_signal_connect_object (section, "remove-clicked",
+ G_CALLBACK (repo_section_remove_clicked_cb), dialog, 0);
+ g_signal_connect_object (section, "switch-clicked",
+ G_CALLBACK (repo_section_switch_clicked_cb), dialog, 0);
+ g_hash_table_insert (dialog->sections, g_steal_pointer (&origin_ui), section);
+ gtk_widget_show (section);
+ }
+
+ gs_repos_section_add_repo (GS_REPOS_SECTION (section), repo);
+}
+
+static gint
+repos_dialog_compare_sections_cb (gconstpointer aa,
+ gconstpointer bb)
+{
+ GsReposSection *section_a = (GsReposSection *) aa;
+ GsReposSection *section_b = (GsReposSection *) bb;
+ const gchar *section_sort_key_a;
+ const gchar *section_sort_key_b;
+ g_autofree gchar *title_sort_key_a = NULL;
+ g_autofree gchar *title_sort_key_b = NULL;
+ gint res;
+
+ section_sort_key_a = gs_repos_section_get_sort_key (section_a);
+ section_sort_key_b = gs_repos_section_get_sort_key (section_b);
+
+ res = g_strcmp0 (section_sort_key_a, section_sort_key_b);
+ if (res != 0)
+ return res;
+
+ title_sort_key_a = gs_utils_sort_key (adw_preferences_group_get_title (ADW_PREFERENCES_GROUP (section_a)));
+ title_sort_key_b = gs_utils_sort_key (adw_preferences_group_get_title (ADW_PREFERENCES_GROUP (section_b)));
+
+ return g_strcmp0 (title_sort_key_a, title_sort_key_b);
+}
+
+static void
+get_sources_cb (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GsReposDialog *dialog)
+{
+ GsApp *app;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsAppList) list = NULL;
+ g_autoptr(GSList) other_repos = NULL;
+ g_autoptr(GList) sections = NULL;
+ AdwPreferencesGroup *added_section;
+ GHashTableIter iter;
+
+ /* get the results */
+ list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
+ if (list == NULL) {
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_debug ("get sources cancelled");
+ return;
+ } else {
+ g_warning ("failed to get sources: %s", error->message);
+ }
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "empty");
+ return;
+ }
+
+ /* remove previous */
+ g_hash_table_iter_init (&iter, dialog->sections);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&added_section)) {
+ adw_preferences_page_remove (ADW_PREFERENCES_PAGE (dialog->content_page),
+ added_section);
+ g_hash_table_iter_remove (&iter);
+ }
+
+ /* stop the spinner */
+ gtk_spinner_stop (GTK_SPINNER (dialog->spinner));
+
+ /* no results */
+ if (gs_app_list_length (list) == 0) {
+ g_debug ("no sources to show");
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "empty");
+ return;
+ }
+
+ /* add each */
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "sources");
+ for (guint i = 0; i < gs_app_list_length (list); i++) {
+ app = gs_app_list_index (list, i);
+ add_repo (dialog, app, &other_repos);
+ }
+
+ sections = g_hash_table_get_values (dialog->sections);
+ sections = g_list_sort (sections, repos_dialog_compare_sections_cb);
+ for (GList *link = sections; link; link = g_list_next (link)) {
+ AdwPreferencesGroup *section = link->data;
+ adw_preferences_page_add (ADW_PREFERENCES_PAGE (dialog->content_page), section);
+ }
+
+ gtk_widget_set_visible (dialog->content_page, sections != NULL);
+
+ if (other_repos) {
+ GsReposSection *section;
+ GtkWidget *widget;
+ GtkWidget *row;
+ g_autofree gchar *anchor = NULL;
+ g_autofree gchar *hint = NULL;
+ g_autofree gchar *section_id = NULL;
+
+ widget = gtk_switch_new ();
+ gtk_widget_set_valign (widget, GTK_ALIGN_CENTER);
+ gtk_switch_set_active (GTK_SWITCH (widget), dialog->third_party_enabled);
+ g_signal_connect_object (widget, "notify::active",
+ G_CALLBACK (fedora_third_party_repos_switch_notify_cb), dialog, 0);
+ gtk_widget_show (widget);
+
+ row = adw_action_row_new ();
+#if ADW_CHECK_VERSION(1,2,0)
+ adw_preferences_row_set_use_markup (ADW_PREFERENCES_ROW (row), FALSE);
+#endif
+ adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("Enable New Repositories"));
+ adw_action_row_set_subtitle (ADW_ACTION_ROW (row), _("Turn on new repositories when they are added."));
+ adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), widget);
+ adw_action_row_add_suffix (ADW_ACTION_ROW (row), widget);
+ gtk_widget_show (row);
+
+ anchor = g_strdup_printf ("<a href=\"%s\">%s</a>",
+ "https://docs.fedoraproject.org/en-US/workstation-working-group/third-party-repos/",
+ /* TRANSLATORS: this is the clickable
+ * link on the third party repositories info bar */
+ _("more information"));
+ hint = g_strdup_printf (
+ /* TRANSLATORS: this is the third party repositories info bar. The '%s' is replaced
+ with a link consisting a text "more information", which constructs a sentence:
+ "Additional repositories from selected third parties - more information."*/
+ _("Additional repositories from selected third parties ā€” %s."),
+ anchor);
+
+ widget = adw_preferences_group_new ();
+ adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (widget),
+ _("Fedora Third Party Repositories"));
+
+ adw_preferences_group_set_description (ADW_PREFERENCES_GROUP (widget), hint);
+
+ gtk_widget_show (widget);
+ adw_preferences_group_add (ADW_PREFERENCES_GROUP (widget), row);
+ adw_preferences_page_add (ADW_PREFERENCES_PAGE (dialog->content_page),
+ ADW_PREFERENCES_GROUP (widget));
+
+ /* use something unique, not clashing with the other section names */
+ section_id = g_strdup_printf ("fedora-third-party::1::%p", widget);
+ g_hash_table_insert (dialog->sections, g_steal_pointer (&section_id), widget);
+
+ section = GS_REPOS_SECTION (gs_repos_section_new (TRUE));
+ gs_repos_section_set_sort_key (section, "900");
+ g_signal_connect_object (section, "switch-clicked",
+ G_CALLBACK (repo_section_switch_clicked_cb), dialog, 0);
+ gtk_widget_show (GTK_WIDGET (section));
+
+ for (GSList *link = other_repos; link; link = g_slist_next (link)) {
+ GsApp *repo = link->data;
+ gs_repos_section_add_repo (section, repo);
+ }
+
+ /* use something unique, not clashing with the other section names */
+ section_id = g_strdup_printf ("fedora-third-party::2::%p", section);
+ g_hash_table_insert (dialog->sections, g_steal_pointer (&section_id), section);
+
+ adw_preferences_page_add (ADW_PREFERENCES_PAGE (dialog->content_page),
+ ADW_PREFERENCES_GROUP (section));
+ }
+}
+
+static void
+reload_sources (GsReposDialog *dialog)
+{
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* get the list of non-core software repositories */
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES,
+ "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
+ "dedupe-flags", GS_APP_LIST_FILTER_FLAG_NONE,
+ NULL);
+ gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+ dialog->cancellable,
+ (GAsyncReadyCallback) get_sources_cb,
+ dialog);
+}
+
+static void
+fedora_third_party_list_repos_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GsReposDialog) self = user_data;
+ g_autoptr(GHashTable) repos = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (!gs_fedora_third_party_list_finish (GS_FEDORA_THIRD_PARTY (source_object), result, &repos, &error)) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ g_warning ("Failed to list 'fedora-third-party' repos: %s", error->message);
+ } else {
+ self->third_party_repos = g_steal_pointer (&repos);
+ }
+
+ reload_sources (self);
+}
+
+static void
+fedora_third_party_query_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GsFedoraThirdPartyState state = GS_FEDORA_THIRD_PARTY_STATE_UNKNOWN;
+ g_autoptr(GsReposDialog) self = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!gs_fedora_third_party_query_finish (GS_FEDORA_THIRD_PARTY (source_object), result, &state, &error)) {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+ g_warning ("Failed to query 'fedora-third-party': %s", error->message);
+ } else {
+ self->third_party_enabled = state == GS_FEDORA_THIRD_PARTY_STATE_ENABLED;
+ }
+
+ gs_fedora_third_party_list (self->third_party, self->cancellable,
+ fedora_third_party_list_repos_done_cb, g_object_ref (self));
+}
+
+static gboolean
+is_fedora (void)
+{
+ const gchar *id = NULL;
+ g_autoptr(GsOsRelease) os_release = NULL;
+
+ os_release = gs_os_release_new (NULL);
+ if (os_release == NULL)
+ return FALSE;
+
+ id = gs_os_release_get_id (os_release);
+ if (g_strcmp0 (id, "fedora") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+reload_third_party_repos (GsReposDialog *dialog)
+{
+ /* Fedora-specific functionality */
+ if (!is_fedora ()) {
+ reload_sources (dialog);
+ return;
+ }
+
+ gs_fedora_third_party_invalidate (dialog->third_party);
+
+ if (!gs_fedora_third_party_is_available (dialog->third_party)) {
+ reload_sources (dialog);
+ return;
+ }
+
+ g_clear_pointer (&dialog->third_party_repos, g_hash_table_unref);
+
+ gs_fedora_third_party_query (dialog->third_party, dialog->cancellable, fedora_third_party_query_done_cb, g_object_ref (dialog));
+}
+
+static gchar *
+get_os_name (void)
+{
+ gchar *name = NULL;
+ g_autoptr(GsOsRelease) os_release = NULL;
+
+ os_release = gs_os_release_new (NULL);
+ if (os_release != NULL)
+ name = g_strdup (gs_os_release_get_name (os_release));
+ if (name == NULL) {
+ /* TRANSLATORS: this is the fallback text we use if we can't
+ figure out the name of the operating system */
+ name = g_strdup (_("the operating system"));
+ }
+
+ return name;
+}
+
+static void
+reload_cb (GsPluginLoader *plugin_loader, GsReposDialog *dialog)
+{
+ reload_third_party_repos (dialog);
+}
+
+static void
+set_plugin_loader (GsReposDialog *dialog, GsPluginLoader *plugin_loader)
+{
+ dialog->plugin_loader = g_object_ref (plugin_loader);
+ g_signal_connect (dialog->plugin_loader, "reload",
+ G_CALLBACK (reload_cb), dialog);
+}
+
+static void
+gs_repos_dialog_dispose (GObject *object)
+{
+ GsReposDialog *dialog = GS_REPOS_DIALOG (object);
+
+ if (dialog->plugin_loader != NULL) {
+ g_signal_handlers_disconnect_by_func (dialog->plugin_loader, reload_cb, dialog);
+ g_clear_object (&dialog->plugin_loader);
+ }
+
+ g_cancellable_cancel (dialog->cancellable);
+ g_clear_pointer (&dialog->third_party_repos, g_hash_table_unref);
+ g_clear_pointer (&dialog->sections, g_hash_table_unref);
+ g_clear_object (&dialog->third_party);
+ g_clear_object (&dialog->cancellable);
+ g_clear_object (&dialog->settings);
+
+ G_OBJECT_CLASS (gs_repos_dialog_parent_class)->dispose (object);
+}
+
+static void
+gs_repos_dialog_init (GsReposDialog *dialog)
+{
+ g_autofree gchar *label_empty_text = NULL;
+ g_autofree gchar *os_name = NULL;
+ g_autoptr(GString) str = g_string_new (NULL);
+
+ gtk_widget_init_template (GTK_WIDGET (dialog));
+
+ dialog->third_party = gs_fedora_third_party_new ();
+ dialog->cancellable = g_cancellable_new ();
+ dialog->settings = g_settings_new ("org.gnome.software");
+ dialog->sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ os_name = get_os_name ();
+
+ /* TRANSLATORS: This is the description text displayed in the Software Repositories dialog.
+ %s gets replaced by the name of the actual distro, e.g. Fedora. */
+ label_empty_text = g_strdup_printf (_("These repositories supplement the default software provided by %s."),
+ os_name);
+ adw_status_page_set_description (ADW_STATUS_PAGE (dialog->status_empty), label_empty_text);
+}
+
+static void
+gs_repos_dialog_class_init (GsReposDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gs_repos_dialog_dispose;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-repos-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, status_empty);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, content_page);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, spinner);
+ gtk_widget_class_bind_template_child (widget_class, GsReposDialog, stack);
+
+ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL);
+}
+
+GtkWidget *
+gs_repos_dialog_new (GtkWindow *parent, GsPluginLoader *plugin_loader)
+{
+ GsReposDialog *dialog;
+
+ dialog = g_object_new (GS_TYPE_REPOS_DIALOG,
+ "transient-for", parent,
+ "modal", TRUE,
+ NULL);
+ set_plugin_loader (dialog, plugin_loader);
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "waiting");
+ gtk_spinner_start (GTK_SPINNER (dialog->spinner));
+ reload_third_party_repos (dialog);
+
+ return GTK_WIDGET (dialog);
+}