From 2f222a7ddff8afcc4c61716c628d496d0f4fe9bf Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Tue, 12 Oct 2021 08:18:24 +0200 Subject: [PATCH] Resolves: #2012699 (Backport changes from Fedora 35) --- 0002-correct-update-notifications.patch | 116 ++++ 0003-refresh-on-repository-change.patch | 233 +++++++ 0004-filtered-system-flathub.patch | 331 +++++++++ 0005-repos-dialog-can-show-apps.patch | 163 +++++ 0006-optional-repos-cannot-be-disabled.patch | 665 +++++++++++++++++++ 0007-compulsory-only-for-repos.patch | 42 ++ gnome-software.spec | 18 +- 7 files changed, 1567 insertions(+), 1 deletion(-) create mode 100644 0002-correct-update-notifications.patch create mode 100644 0003-refresh-on-repository-change.patch create mode 100644 0004-filtered-system-flathub.patch create mode 100644 0005-repos-dialog-can-show-apps.patch create mode 100644 0006-optional-repos-cannot-be-disabled.patch create mode 100644 0007-compulsory-only-for-repos.patch diff --git a/0002-correct-update-notifications.patch b/0002-correct-update-notifications.patch new file mode 100644 index 0000000..a8c8f08 --- /dev/null +++ b/0002-correct-update-notifications.patch @@ -0,0 +1,116 @@ +From 1b0c476d66f89332187da2894b06ec2d4b83fa2a Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Thu, 30 Sep 2021 16:28:11 +0200 +Subject: [PATCH 1/2] gs-update-monitor: Use wall-clock time for + one-notification-per-day check + +Instead of using the g_timeout_add_seconds(), which uses a monotonic time, +which may or may not increase when the machine is suspended, rather use +the wall-clock time, to avoid issues with machine suspend where the monotonic +time does not increase. +--- + src/gs-update-monitor.c | 31 +++++++++++++------------------ + 1 file changed, 13 insertions(+), 18 deletions(-) + +diff --git a/src/gs-update-monitor.c b/src/gs-update-monitor.c +index bde39fbbb..787c605a1 100644 +--- a/src/gs-update-monitor.c ++++ b/src/gs-update-monitor.c +@@ -44,7 +44,8 @@ struct _GsUpdateMonitor { + guint check_startup_id; /* 60s after startup */ + guint check_hourly_id; /* and then every hour */ + guint check_daily_id; /* every 3rd day */ +- guint notification_blocked_id; /* rate limit notifications */ ++ ++ gint64 last_notification_time_usec; /* to notify once per day only */ + }; + + G_DEFINE_TYPE (GsUpdateMonitor, gs_update_monitor, G_TYPE_OBJECT) +@@ -88,14 +89,6 @@ with_app_data_free (WithAppData *data) + + G_DEFINE_AUTOPTR_CLEANUP_FUNC(WithAppData, with_app_data_free); + +-static gboolean +-reenable_offline_update_notification (gpointer data) +-{ +- GsUpdateMonitor *monitor = data; +- monitor->notification_blocked_id = 0; +- return G_SOURCE_REMOVE; +-} +- + static void + check_updates_kind (GsAppList *apps, + gboolean *out_has_important, +@@ -265,16 +258,22 @@ notify_about_pending_updates (GsUpdateMonitor *monitor, + GsAppList *apps) + { + const gchar *title = NULL, *body = NULL; ++ gint64 time_diff_sec; + g_autoptr(GNotification) nn = NULL; + +- if (monitor->notification_blocked_id > 0) ++ time_diff_sec = (g_get_real_time () - monitor->last_notification_time_usec) / G_USEC_PER_SEC; ++ if (time_diff_sec < SECONDS_IN_A_DAY) { ++ g_debug ("Skipping update notification daily check, because made one only %" G_GINT64_FORMAT "s ago", ++ time_diff_sec); + return; ++ } + +- /* rate limit update notifications to once per day */ +- monitor->notification_blocked_id = g_timeout_add_seconds (24 * SECONDS_IN_AN_HOUR, reenable_offline_update_notification, monitor); +- +- if (!should_notify_about_pending_updates (monitor, apps, &title, &body)) ++ if (!should_notify_about_pending_updates (monitor, apps, &title, &body)) { ++ g_debug ("No update notification needed"); + return; ++ } ++ ++ monitor->last_notification_time_usec = g_get_real_time (); + + g_debug ("Notify about update: '%s'", title); + +@@ -1394,10 +1393,6 @@ gs_update_monitor_dispose (GObject *object) + g_source_remove (monitor->check_startup_id); + monitor->check_startup_id = 0; + } +- if (monitor->notification_blocked_id != 0) { +- g_source_remove (monitor->notification_blocked_id); +- monitor->notification_blocked_id = 0; +- } + if (monitor->cleanup_notifications_id != 0) { + g_source_remove (monitor->cleanup_notifications_id); + monitor->cleanup_notifications_id = 0; +-- +GitLab + + +From 2ff332826f841c4ea1d9458df81648868745ea41 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Thu, 30 Sep 2021 16:47:40 +0200 +Subject: [PATCH 2/2] gs-update-monitor: Correct last notification timestamp + reset + +Do not reset the notification timestamp after the list of updates +is received, that should be done when the notification had been shown. + +Reported downstream at: +https://bugzilla.redhat.com/show_bug.cgi?id=2009063 +--- + src/gs-update-monitor.c | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/src/gs-update-monitor.c b/src/gs-update-monitor.c +index 787c605a1..a8421fcc4 100644 +--- a/src/gs-update-monitor.c ++++ b/src/gs-update-monitor.c +@@ -613,7 +613,6 @@ get_updates_finished_cb (GObject *object, GAsyncResult *res, gpointer data) + notify_list = apps; + + notify_about_pending_updates (monitor, notify_list); +- reset_update_notification_timestamp (monitor); + } + } + +-- +GitLab + diff --git a/0003-refresh-on-repository-change.patch b/0003-refresh-on-repository-change.patch new file mode 100644 index 0000000..3af8ef8 --- /dev/null +++ b/0003-refresh-on-repository-change.patch @@ -0,0 +1,233 @@ +From 5c203ae1b07e2c31ca39c3d6a793534d45c49125 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 19:42:37 +0200 +Subject: [PATCH 1/5] gs-shell: Remove left-over function prototype declaration + +The function body does not exist. +--- + src/gs-shell.h | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/gs-shell.h b/src/gs-shell.h +index bb39fc928..46661db16 100644 +--- a/src/gs-shell.h ++++ b/src/gs-shell.h +@@ -43,8 +43,6 @@ typedef enum { + + GsShell *gs_shell_new (void); + void gs_shell_activate (GsShell *shell); +-void gs_shell_refresh (GsShell *shell, +- GCancellable *cancellable); + void gs_shell_change_mode (GsShell *shell, + GsShellMode mode, + gpointer data, +-- +GitLab + + +From ca49eb3974e220ff17262c6189e3cdf0a92746fe Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 19:45:10 +0200 +Subject: [PATCH 2/5] gs-application: Introduce gs_application_refresh() + +It can be used to call a non-interactive refresh call for the plugins. +--- + src/gs-application.c | 28 ++++++++++++++++++++++++++++ + src/gs-application.h | 3 ++- + 2 files changed, 30 insertions(+), 1 deletion(-) + +diff --git a/src/gs-application.c b/src/gs-application.c +index 90487df62..df982df19 100644 +--- a/src/gs-application.c ++++ b/src/gs-application.c +@@ -1371,3 +1371,31 @@ gs_application_emit_install_resources_done (GsApplication *application, + { + g_signal_emit (application, signals[INSTALL_RESOURCES_DONE], 0, ident, op_error, NULL); + } ++ ++static void ++gs_application_refresh_cb (GsPluginLoader *plugin_loader, ++ GAsyncResult *result, ++ GsApplication *self) ++{ ++ gboolean success; ++ g_autoptr(GError) error = NULL; ++ ++ success = gs_plugin_loader_job_action_finish (plugin_loader, result, &error); ++ if (!success && ++ !g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) ++ g_warning ("failed to refresh: %s", error->message); ++} ++ ++void ++gs_application_refresh (GsApplication *self) ++{ ++ g_autoptr(GsPluginJob) plugin_job = NULL; ++ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH, ++ "interactive", FALSE, ++ "age", (guint64) 1, ++ NULL); ++ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, ++ self->cancellable, ++ (GAsyncReadyCallback) gs_application_refresh_cb, ++ self); ++} +diff --git a/src/gs-application.h b/src/gs-application.h +index 40bad4d4c..92978e1f5 100644 +--- a/src/gs-application.h ++++ b/src/gs-application.h +@@ -23,4 +23,5 @@ gboolean gs_application_has_active_window (GsApplication *application); + void gs_application_emit_install_resources_done + (GsApplication *application, + const gchar *ident, +- const GError *op_error); +\ No newline at end of file ++ const GError *op_error); ++void gs_application_refresh (GsApplication *self); +-- +GitLab + + +From 2781d350a9a9ee1fd2078928f4e0933a34e05b9f Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 19:46:17 +0200 +Subject: [PATCH 3/5] gs-repos-dialog: Call refresh on repository setup change + +When a repository is enabled/disabled/removed, call also the refresh +on the plugins, thus the data from those repos are available for the user. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1486 +--- + src/gs-repos-dialog.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/src/gs-repos-dialog.c b/src/gs-repos-dialog.c +index 1d6a82f48..c986ad724 100644 +--- a/src/gs-repos-dialog.c ++++ b/src/gs-repos-dialog.c +@@ -13,6 +13,7 @@ + #include "gs-repos-dialog.h" + + #include "gnome-software-private.h" ++#include "gs-application.h" + #include "gs-common.h" + #include "gs-os-release.h" + #include "gs-repo-row.h" +@@ -35,6 +36,8 @@ struct _GsReposDialog + GtkWidget *content_page; + GtkWidget *spinner; + GtkWidget *stack; ++ ++ gboolean changed; + }; + + G_DEFINE_TYPE (GsReposDialog, gs_repos_dialog, HDY_TYPE_WINDOW) +@@ -115,6 +118,8 @@ repo_enabled_cb (GObject *source, + } + + g_debug ("finished %s repo %s", action_str, gs_app_get_id (install_remove_data->repo)); ++ ++ install_remove_data->dialog->changed = TRUE; + } + + static void +@@ -710,6 +715,17 @@ gs_repos_dialog_dispose (GObject *object) + g_clear_object (&dialog->cancellable); + g_clear_object (&dialog->settings); + ++ if (dialog->changed) { ++ GApplication *app; ++ ++ dialog->changed = FALSE; ++ g_debug ("Repository setup changed, calling refresh..."); ++ ++ app = g_application_get_default (); ++ if (app) ++ gs_application_refresh (GS_APPLICATION (app)); ++ } ++ + G_OBJECT_CLASS (gs_repos_dialog_parent_class)->dispose (object); + } + +-- +GitLab + + +From 4c6f4f20c904600a3c5d8ea446ed3c3b0ef0808d Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Wed, 6 Oct 2021 14:41:55 +0200 +Subject: [PATCH 4/5] gs-application: Invoke page reload after the refresh is + finished + +The refresh can cause new applications or alternative sources being +found in the newly enabled repositories, thus reload the pages, to +reflect the current state. +--- + src/gs-application.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/gs-application.c b/src/gs-application.c +index df982df19..6ecc093f1 100644 +--- a/src/gs-application.c ++++ b/src/gs-application.c +@@ -1384,6 +1384,9 @@ gs_application_refresh_cb (GsPluginLoader *plugin_loader, + if (!success && + !g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) + g_warning ("failed to refresh: %s", error->message); ++ ++ if (success) ++ g_signal_emit_by_name (self->plugin_loader, "reload", 0, NULL); + } + + void +-- +GitLab + + +From b1277d97ba4495de434eb3be4ea1f17b80ac1ef8 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Wed, 6 Oct 2021 14:44:09 +0200 +Subject: [PATCH 5/5] gs-overview-page: Refresh the application after + third-party repositories enable/disable + +The enable/disable can cause other applications being found, thus call +the refresh, to update the repositories information. +--- + src/gs-overview-page.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/src/gs-overview-page.c b/src/gs-overview-page.c +index 9ba33fb2b..bcf02243d 100644 +--- a/src/gs-overview-page.c ++++ b/src/gs-overview-page.c +@@ -13,6 +13,7 @@ + #include + #include + ++#include "gs-application.h" + #include "gs-shell.h" + #include "gs-overview-page.h" + #include "gs-app-list-private.h" +@@ -568,6 +569,8 @@ third_party_response_cb (GtkInfoBar *info_bar, + gint response_id, + GsOverviewPage *self) + { ++ GApplication *application; ++ + if (response_id == GTK_RESPONSE_YES) + fedora_third_party_enable (self); + else +@@ -575,6 +578,10 @@ third_party_response_cb (GtkInfoBar *info_bar, + + self->third_party_needs_question = FALSE; + refresh_third_party_repo (self); ++ ++ application = g_application_get_default (); ++ if (application) ++ gs_application_refresh (GS_APPLICATION (application)); + } + + static gboolean +-- +GitLab + diff --git a/0004-filtered-system-flathub.patch b/0004-filtered-system-flathub.patch new file mode 100644 index 0000000..93cd865 --- /dev/null +++ b/0004-filtered-system-flathub.patch @@ -0,0 +1,331 @@ +From 03ea59cc8db6bec34d56205d62ec495315e9ea96 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 21 Sep 2021 13:55:57 +0200 +Subject: [PATCH 1/5] gs-page: Use correct action when install/remove repo app + +Since the split of the install/remove action for apps and repos +the GsPage should use correct action too. This can happen for example +when installing a .flatpakrepo file. +--- + src/gs-page.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/gs-page.c b/src/gs-page.c +index ca1fbfc70..dfaadbc39 100644 +--- a/src/gs-page.c ++++ b/src/gs-page.c +@@ -280,7 +280,10 @@ gs_page_install_app (GsPage *page, + } + + helper = g_slice_new0 (GsPageHelper); +- helper->action = GS_PLUGIN_ACTION_INSTALL; ++ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) ++ helper->action = GS_PLUGIN_ACTION_INSTALL_REPO; ++ else ++ helper->action = GS_PLUGIN_ACTION_INSTALL; + helper->app = g_object_ref (app); + helper->page = g_object_ref (page); + helper->cancellable = g_object_ref (cancellable); +@@ -466,7 +469,10 @@ gs_page_remove_app (GsPage *page, GsApp *app, GCancellable *cancellable) + + /* pending install */ + helper = g_slice_new0 (GsPageHelper); +- helper->action = GS_PLUGIN_ACTION_REMOVE; ++ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) ++ helper->action = GS_PLUGIN_ACTION_REMOVE_REPO; ++ else ++ helper->action = GS_PLUGIN_ACTION_REMOVE; + helper->app = g_object_ref (app); + helper->page = g_object_ref (page); + helper->cancellable = cancellable != NULL ? g_object_ref (cancellable) : NULL; +-- +GitLab + + +From 576f9e6d25fcd3edd7fdf769e342a382af1307e3 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 21 Sep 2021 13:58:03 +0200 +Subject: [PATCH 2/5] flatpak: Save also remote's filter on the flatpak-app + +This can be used when updating existing remote, to reflect the new +filter. It can be also used to verify the installed and existing +remotes match with its filter. +--- + plugins/flatpak/gs-flatpak-app.c | 12 ++++++++++++ + plugins/flatpak/gs-flatpak-app.h | 3 +++ + 2 files changed, 15 insertions(+) + +diff --git a/plugins/flatpak/gs-flatpak-app.c b/plugins/flatpak/gs-flatpak-app.c +index cf98248a8..b59515b2f 100644 +--- a/plugins/flatpak/gs-flatpak-app.c ++++ b/plugins/flatpak/gs-flatpak-app.c +@@ -176,3 +176,15 @@ gs_flatpak_app_get_main_app_ref_name (GsApp *app) + { + return gs_app_get_metadata_item (app, "flatpak::mainApp"); + } ++ ++void ++gs_flatpak_app_set_repo_filter (GsApp *app, const gchar *filter) ++{ ++ gs_app_set_metadata (app, "flatpak::RepoFilter", filter); ++} ++ ++const gchar * ++gs_flatpak_app_get_repo_filter (GsApp *app) ++{ ++ return gs_app_get_metadata_item (app, "flatpak::RepoFilter"); ++} +diff --git a/plugins/flatpak/gs-flatpak-app.h b/plugins/flatpak/gs-flatpak-app.h +index ab6c10af4..610c8a8f3 100644 +--- a/plugins/flatpak/gs-flatpak-app.h ++++ b/plugins/flatpak/gs-flatpak-app.h +@@ -58,5 +58,8 @@ void gs_flatpak_app_set_runtime_url (GsApp *app, + void gs_flatpak_app_set_main_app_ref_name (GsApp *app, + const gchar *main_app_ref); + const gchar *gs_flatpak_app_get_main_app_ref_name (GsApp *app); ++void gs_flatpak_app_set_repo_filter (GsApp *app, ++ const gchar *filter); ++const gchar *gs_flatpak_app_get_repo_filter (GsApp *app); + + G_END_DECLS +-- +GitLab + + +From cb809158e81157b53245e4c290ada418d5bcd03d Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 21 Sep 2021 14:02:47 +0200 +Subject: [PATCH 3/5] flatpak: Store filter and description on a remote app + +Store the description also on an installed remote, not only on the file +remote. Similarly store also the filters for the remotes. +--- + plugins/flatpak/gs-flatpak-utils.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/plugins/flatpak/gs-flatpak-utils.c b/plugins/flatpak/gs-flatpak-utils.c +index 8b107b37c..7aa735b0b 100644 +--- a/plugins/flatpak/gs-flatpak-utils.c ++++ b/plugins/flatpak/gs-flatpak-utils.c +@@ -72,6 +72,8 @@ gs_flatpak_app_new_from_remote (GsPlugin *plugin, + { + g_autofree gchar *title = NULL; + g_autofree gchar *url = NULL; ++ g_autofree gchar *filter = NULL; ++ g_autofree gchar *description = NULL; + g_autoptr(GsApp) app = NULL; + + app = gs_flatpak_app_new (flatpak_remote_get_name (xremote)); +@@ -101,11 +103,19 @@ gs_flatpak_app_new_from_remote (GsPlugin *plugin, + * not the remote title */ + gs_app_set_origin_ui (app, _("Applications")); + ++ description = flatpak_remote_get_description (xremote); ++ if (description != NULL) ++ gs_app_set_description (app, GS_APP_QUALITY_NORMAL, description); ++ + /* url */ + url = flatpak_remote_get_url (xremote); + if (url != NULL) + gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url); + ++ filter = flatpak_remote_get_filter (xremote); ++ if (filter != NULL) ++ gs_flatpak_app_set_repo_filter (app, filter); ++ + /* success */ + return g_steal_pointer (&app); + } +@@ -127,6 +137,7 @@ gs_flatpak_app_new_from_repo_file (GFile *file, + g_autofree gchar *repo_id = NULL; + g_autofree gchar *repo_title = NULL; + g_autofree gchar *repo_url = NULL; ++ g_autofree gchar *repo_filter = NULL; + g_autoptr(GError) error_local = NULL; + g_autoptr(GKeyFile) kf = NULL; + g_autoptr(GsApp) app = NULL; +@@ -229,6 +240,9 @@ gs_flatpak_app_new_from_repo_file (GFile *file, + g_autoptr(GIcon) icon = gs_remote_icon_new (repo_icon); + gs_app_add_icon (app, icon); + } ++ repo_filter = g_key_file_get_string (kf, "Flatpak Repo", "Filter", NULL); ++ if (repo_filter != NULL && *repo_filter != '\0') ++ gs_flatpak_app_set_repo_filter (app, repo_filter); + + /* success */ + return g_steal_pointer (&app); +-- +GitLab + + +From a4f8501e3e54b702b5ff2af4bb9aaf6f8d6c324c Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 21 Sep 2021 14:05:03 +0200 +Subject: [PATCH 4/5] flatpak: Match existing and file remote only if it + matches also the filter + +The filter can change the content, thus match two remotes only if also +the filter matches. +--- + plugins/flatpak/gs-plugin-flatpak.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c +index 9bdfa80bf..29ba29700 100644 +--- a/plugins/flatpak/gs-plugin-flatpak.c ++++ b/plugins/flatpak/gs-plugin-flatpak.c +@@ -1287,6 +1287,8 @@ gs_plugin_flatpak_file_to_app_repo (GsPlugin *plugin, + g_debug ("%s", error_local->message); + continue; + } ++ if (g_strcmp0 (gs_flatpak_app_get_repo_filter (app), gs_flatpak_app_get_repo_filter (app_tmp)) != 0) ++ continue; + return g_steal_pointer (&app_tmp); + } + +-- +GitLab + + +From 2a94efbda64d94ba6ac27cfd08190b62f52df000 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 21 Sep 2021 14:06:35 +0200 +Subject: [PATCH 5/5] flatpak: Update existing remote from a .flatpakref file + +Update existing remote's title, description and filter when installing +a .flatpakref file, to match what had been installed, with new-enough +flatpak library. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1453 +--- + plugins/flatpak/gs-flatpak-utils.c | 6 ++++++ + plugins/flatpak/gs-flatpak.c | 11 +++++++++-- + 2 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/plugins/flatpak/gs-flatpak-utils.c b/plugins/flatpak/gs-flatpak-utils.c +index 7aa735b0b..ec0b397e3 100644 +--- a/plugins/flatpak/gs-flatpak-utils.c ++++ b/plugins/flatpak/gs-flatpak-utils.c +@@ -72,8 +72,10 @@ gs_flatpak_app_new_from_remote (GsPlugin *plugin, + { + g_autofree gchar *title = NULL; + g_autofree gchar *url = NULL; ++ #if FLATPAK_CHECK_VERSION(1, 4, 0) + g_autofree gchar *filter = NULL; + g_autofree gchar *description = NULL; ++ #endif + g_autoptr(GsApp) app = NULL; + + app = gs_flatpak_app_new (flatpak_remote_get_name (xremote)); +@@ -103,18 +105,22 @@ gs_flatpak_app_new_from_remote (GsPlugin *plugin, + * not the remote title */ + gs_app_set_origin_ui (app, _("Applications")); + ++ #if FLATPAK_CHECK_VERSION(1, 4, 0) + description = flatpak_remote_get_description (xremote); + if (description != NULL) + gs_app_set_description (app, GS_APP_QUALITY_NORMAL, description); ++ #endif + + /* url */ + url = flatpak_remote_get_url (xremote); + if (url != NULL) + gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url); + ++ #if FLATPAK_CHECK_VERSION(1, 4, 0) + filter = flatpak_remote_get_filter (xremote); + if (filter != NULL) + gs_flatpak_app_set_repo_filter (app, filter); ++ #endif + + /* success */ + return g_steal_pointer (&app); +diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c +index 55a91af2a..fa7df00c9 100644 +--- a/plugins/flatpak/gs-flatpak.c ++++ b/plugins/flatpak/gs-flatpak.c +@@ -1608,9 +1608,16 @@ gs_flatpak_app_install_source (GsFlatpak *self, + gs_app_get_id (app), + cancellable, NULL); + if (xremote != NULL) { +- /* if the remote already exists, just enable it */ +- g_debug ("enabling existing remote %s", flatpak_remote_get_name (xremote)); ++ /* if the remote already exists, just enable it and update it */ ++ g_debug ("modifying existing remote %s", flatpak_remote_get_name (xremote)); + flatpak_remote_set_disabled (xremote, FALSE); ++ if (gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_REPO) { ++ flatpak_remote_set_title (xremote, gs_app_get_origin_ui (app)); ++ #if FLATPAK_CHECK_VERSION(1, 4, 0) ++ flatpak_remote_set_filter (xremote, gs_flatpak_app_get_repo_filter (app)); ++ flatpak_remote_set_description (xremote, gs_app_get_description (app)); ++ #endif ++ } + } else if (!is_install) { + g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "Cannot enable flatpak remote '%s', remote not found", gs_app_get_id (app)); + } else { +-- +GitLab + +From 1a32bd3eaaf7ec0322c46599e3b949080a410806 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Thu, 7 Oct 2021 19:28:38 +0200 +Subject: [PATCH] flatpak: Update remote appstream data when its filter changed + +When overwriting existing remote, make sure the appstream data is updated +as well when the filter changed, to have shown relevant applications. + +Related to https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1453 +--- + plugins/flatpak/gs-flatpak.c | 18 +++++++++++++++++- + 1 file changed, 17 insertions(+), 1 deletion(-) + +diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c +index bd87d57ed..88bb69a72 100644 +--- a/plugins/flatpak/gs-flatpak.c ++++ b/plugins/flatpak/gs-flatpak.c +@@ -1623,6 +1623,9 @@ gs_flatpak_app_install_source (GsFlatpak *self, + GError **error) + { + g_autoptr(FlatpakRemote) xremote = NULL; ++ #if FLATPAK_CHECK_VERSION(1, 4, 0) ++ gboolean filter_changed = FALSE; ++ #endif + + xremote = flatpak_installation_get_remote_by_name (self->installation, + gs_app_get_id (app), +@@ -1632,11 +1635,13 @@ gs_flatpak_app_install_source (GsFlatpak *self, + g_debug ("modifying existing remote %s", flatpak_remote_get_name (xremote)); + flatpak_remote_set_disabled (xremote, FALSE); + if (gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_REPO) { +- flatpak_remote_set_title (xremote, gs_app_get_origin_ui (app)); + #if FLATPAK_CHECK_VERSION(1, 4, 0) ++ g_autofree gchar *current_filter = flatpak_remote_get_filter (xremote); ++ filter_changed = g_strcmp0 (current_filter, gs_flatpak_app_get_repo_filter (app)) != 0; + flatpak_remote_set_filter (xremote, gs_flatpak_app_get_repo_filter (app)); + flatpak_remote_set_description (xremote, gs_app_get_description (app)); + #endif ++ flatpak_remote_set_title (xremote, gs_app_get_origin_ui (app)); + } + } else if (!is_install) { + g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "Cannot enable flatpak remote '%s', remote not found", gs_app_get_id (app)); +@@ -1666,6 +1671,17 @@ gs_flatpak_app_install_source (GsFlatpak *self, + /* success */ + gs_app_set_state (app, GS_APP_STATE_INSTALLED); + ++ #if FLATPAK_CHECK_VERSION(1, 4, 0) ++ if (filter_changed) { ++ g_autoptr(GError) local_error = NULL; ++ const gchar *remote_name = flatpak_remote_get_name (xremote); ++ if (!flatpak_installation_update_appstream_sync (self->installation, remote_name, NULL, NULL, cancellable, &local_error) && ++ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { ++ g_warning ("Failed to update appstream data for flatpak remote '%s': %s", ++ remote_name, local_error->message); ++ } ++ } ++ #endif + gs_plugin_repository_changed (self->plugin, app); + + return TRUE; +-- +GitLab + diff --git a/0005-repos-dialog-can-show-apps.patch b/0005-repos-dialog-can-show-apps.patch new file mode 100644 index 0000000..eb985f3 --- /dev/null +++ b/0005-repos-dialog-can-show-apps.patch @@ -0,0 +1,163 @@ +From 1338c8f47b7ebd0e3bd360499c7ab42a0da885e8 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 16:12:33 +0200 +Subject: [PATCH 1/2] packagekit: Update GsApp state and kind only when created + the app instance + +The gs_plugin_packagekit_add_results() can reuse GsApp instances from +the plugin cache, which can already have set property state and kind, +but this was not checked for, which could cause runtime warnings about +invalid state or kind change. + +A reproducer is to open Repositories dialog, which lists available repositories +and the applications being installed in each of them (added as 'related'). +These installed applications can have set state 'updatable' or have refined +their 'kind' already. +--- + plugins/packagekit/packagekit-common.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/plugins/packagekit/packagekit-common.c b/plugins/packagekit/packagekit-common.c +index 16b53727a..dc79c2f62 100644 +--- a/plugins/packagekit/packagekit-common.c ++++ b/plugins/packagekit/packagekit-common.c +@@ -241,12 +241,14 @@ gs_plugin_packagekit_add_results (GsPlugin *plugin, + /* process packages */ + for (i = 0; i < array_filtered->len; i++) { + g_autoptr(GsApp) app = NULL; ++ GsAppState state = GS_APP_STATE_UNKNOWN; + package = g_ptr_array_index (array_filtered, i); + + app = gs_plugin_cache_lookup (plugin, pk_package_get_id (package)); + if (app == NULL) { + app = gs_app_new (NULL); + gs_plugin_packagekit_set_packaging_format (plugin, app); ++ gs_app_set_management_plugin (app, "packagekit"); + gs_app_add_source (app, pk_package_get_name (package)); + gs_app_add_source_id (app, pk_package_get_id (package)); + gs_plugin_cache_add (plugin, pk_package_get_id (package), app); +@@ -259,14 +261,13 @@ gs_plugin_packagekit_add_results (GsPlugin *plugin, + pk_package_get_summary (package)); + gs_app_set_metadata (app, "GnomeSoftware::Creator", + gs_plugin_get_name (plugin)); +- gs_app_set_management_plugin (app, "packagekit"); + gs_app_set_version (app, pk_package_get_version (package)); + switch (pk_package_get_info (package)) { + case PK_INFO_ENUM_INSTALLED: +- gs_app_set_state (app, GS_APP_STATE_INSTALLED); ++ state = GS_APP_STATE_INSTALLED; + break; + case PK_INFO_ENUM_AVAILABLE: +- gs_app_set_state (app, GS_APP_STATE_AVAILABLE); ++ state = GS_APP_STATE_AVAILABLE; + break; + case PK_INFO_ENUM_INSTALLING: + case PK_INFO_ENUM_UPDATING: +@@ -276,14 +277,16 @@ gs_plugin_packagekit_add_results (GsPlugin *plugin, + break; + case PK_INFO_ENUM_UNAVAILABLE: + case PK_INFO_ENUM_REMOVING: +- gs_app_set_state (app, GS_APP_STATE_UNAVAILABLE); ++ state = GS_APP_STATE_UNAVAILABLE; + break; + default: +- gs_app_set_state (app, GS_APP_STATE_UNKNOWN); + g_warning ("unknown info state of %s", + pk_info_enum_to_string (pk_package_get_info (package))); + } +- gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC); ++ if (state != GS_APP_STATE_UNKNOWN && gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) ++ gs_app_set_state (app, state); ++ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_UNKNOWN) ++ gs_app_set_kind (app, AS_COMPONENT_KIND_GENERIC); + gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE); + gs_app_list_add (list, app); + } +-- +GitLab + + +From 18a893fa83ebd10cea75831e9d9a7398a60c14ce Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 16:18:22 +0200 +Subject: [PATCH 2/2] gs-plugin-loader: Ensure correct list is used on the job + when refining wildcards + +The plugin job is reused when refining the apps, including the wildcards, +but the gs_plugin_loader_run_refine_internal() can be called multiple times, +with different lists. Adding refined wildcards to the original list causes +invalid data being provided to the called. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1485 +--- + lib/gs-plugin-loader.c | 21 ++++++++++++++++++++- + 1 file changed, 20 insertions(+), 1 deletion(-) + +diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c +index 4d5240c32..7f5859a05 100644 +--- a/lib/gs-plugin-loader.c ++++ b/lib/gs-plugin-loader.c +@@ -937,14 +937,24 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper, + GCancellable *cancellable, + GError **error) + { ++ g_autoptr(GsAppList) previous_list = NULL; ++ ++ if (list != gs_plugin_job_get_list (helper->plugin_job)) { ++ previous_list = g_object_ref (gs_plugin_job_get_list (helper->plugin_job)); ++ gs_plugin_job_set_list (helper->plugin_job, list); ++ } ++ + /* try to adopt each application with a plugin */ + gs_plugin_loader_run_adopt (helper->plugin_loader, list); + + /* run each plugin */ + if (!gs_plugin_loader_run_refine_filter (helper, list, + GS_PLUGIN_REFINE_FLAGS_DEFAULT, +- cancellable, error)) ++ cancellable, error)) { ++ if (previous_list != NULL) ++ gs_plugin_job_set_list (helper->plugin_job, previous_list); + return FALSE; ++ } + + /* ensure these are sorted by score */ + if (gs_plugin_job_has_refine_flags (helper->plugin_job, +@@ -983,6 +993,8 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper, + addons_list, + cancellable, + error)) { ++ if (previous_list != NULL) ++ gs_plugin_job_set_list (helper->plugin_job, previous_list); + return FALSE; + } + } +@@ -1004,6 +1016,8 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper, + list2, + cancellable, + error)) { ++ if (previous_list != NULL) ++ gs_plugin_job_set_list (helper->plugin_job, previous_list); + return FALSE; + } + } +@@ -1032,11 +1046,16 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper, + related_list, + cancellable, + error)) { ++ if (previous_list != NULL) ++ gs_plugin_job_set_list (helper->plugin_job, previous_list); + return FALSE; + } + } + } + ++ if (previous_list != NULL) ++ gs_plugin_job_set_list (helper->plugin_job, previous_list); ++ + /* success */ + return TRUE; + } +-- +GitLab + diff --git a/0006-optional-repos-cannot-be-disabled.patch b/0006-optional-repos-cannot-be-disabled.patch new file mode 100644 index 0000000..20777b4 --- /dev/null +++ b/0006-optional-repos-cannot-be-disabled.patch @@ -0,0 +1,665 @@ +From 429ec744e6cdf2155772d7db463ca193231facdc Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 21 Sep 2021 14:53:27 +0200 +Subject: gs-repos-dialog: Cannot disable all 3rd-party repositories + +All the 3rd-party repositories should be disable-able, thus the 3rd-party +section should not use the heuristics to disallow disable of some of them. +This could be seen on a 'flathub' Flatpak repository installed for +the system, which is not allowed to be disabled in the Flatpak section. + +diff --git a/src/gs-repo-row.c b/src/gs-repo-row.c +index 57493c2c..7df24e0e 100644 +--- a/src/gs-repo-row.c ++++ b/src/gs-repo-row.c +@@ -27,6 +27,7 @@ typedef struct + guint busy_counter; + gboolean supports_remove; + gboolean supports_enable_disable; ++ gboolean always_allow_enable_disable; + } GsRepoRowPrivate; + + G_DEFINE_TYPE_WITH_PRIVATE (GsRepoRow, gs_repo_row, GTK_TYPE_LIST_BOX_ROW) +@@ -86,7 +87,7 @@ refresh_ui (GsRepoRow *row) + is_system_repo = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_PROVENANCE); + + /* Disable for the system repos, if installed */ +- gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive || !is_system_repo)); ++ gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive || !is_system_repo || priv->always_allow_enable_disable)); + gtk_widget_set_visible (priv->remove_button, priv->supports_remove && !is_system_repo); + + /* Set only the 'state' to visually indicate the state is not saved yet */ +@@ -337,13 +338,30 @@ gs_repo_row_class_init (GsRepoRowClass *klass) + gtk_widget_class_bind_template_child_private (widget_class, GsRepoRow, disable_switch); + } + ++/* ++ * gs_repo_row_new: ++ * @plugin_loader: a #GsPluginLoader ++ * @repo: a #GsApp to represent the repo in the new row ++ * @always_allow_enable_disable: always allow enabled/disable of the @repo ++ * ++ * The @plugin_loader is used to check which operations the associated plugin ++ * for the @repo can do and which not, to show only relevant buttons on the row. ++ * ++ * The @always_allow_enable_disable, when %TRUE, means that the @repo in this row ++ * can be always enabled/disabled by the user, if supported by the related plugin, ++ * regardless of the other heuristics, which can avoid the repo enable/disable. ++ * ++ * Returns: (transfer full): a newly created #GsRepoRow ++ */ + GtkWidget * + gs_repo_row_new (GsPluginLoader *plugin_loader, +- GsApp *repo) ++ GsApp *repo, ++ gboolean always_allow_enable_disable) + { + GsRepoRow *row = g_object_new (GS_TYPE_REPO_ROW, NULL); + GsRepoRowPrivate *priv = gs_repo_row_get_instance_private (row); + priv->plugin_loader = g_object_ref (plugin_loader); ++ priv->always_allow_enable_disable = always_allow_enable_disable; + gs_repo_row_set_repo (row, repo); + return GTK_WIDGET (row); + } +diff --git a/src/gs-repo-row.h b/src/gs-repo-row.h +index e6f24bc8..83c8cdf4 100644 +--- a/src/gs-repo-row.h ++++ b/src/gs-repo-row.h +@@ -25,7 +25,8 @@ struct _GsRepoRowClass + }; + + GtkWidget *gs_repo_row_new (GsPluginLoader *plugin_loader, +- GsApp *repo); ++ GsApp *repo, ++ gboolean always_allow_enable_disable); + GsApp *gs_repo_row_get_repo (GsRepoRow *row); + void gs_repo_row_mark_busy (GsRepoRow *row); + void gs_repo_row_unmark_busy (GsRepoRow *row); +diff --git a/src/gs-repos-dialog.c b/src/gs-repos-dialog.c +index 98aa0f20..0f24149c 100644 +--- a/src/gs-repos-dialog.c ++++ b/src/gs-repos-dialog.c +@@ -484,7 +484,7 @@ add_repo (GsReposDialog *dialog, + origin_ui = g_strdup (gs_app_get_management_plugin (repo)); + section = g_hash_table_lookup (dialog->sections, origin_ui); + if (section == NULL) { +- section = gs_repos_section_new (dialog->plugin_loader); ++ section = gs_repos_section_new (dialog->plugin_loader, FALSE); + hdy_preferences_group_set_title (HDY_PREFERENCES_GROUP (section), + origin_ui); + g_signal_connect_object (section, "remove-clicked", +@@ -627,7 +627,7 @@ get_sources_cb (GsPluginLoader *plugin_loader, + gtk_container_add (GTK_CONTAINER (widget), row); + gtk_container_add (GTK_CONTAINER (dialog->content_page), widget); + +- section = GS_REPOS_SECTION (gs_repos_section_new (dialog->plugin_loader)); ++ section = GS_REPOS_SECTION (gs_repos_section_new (dialog->plugin_loader, 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); +diff --git a/src/gs-repos-section.c b/src/gs-repos-section.c +index 3bf59ad7..a9b08200 100644 +--- a/src/gs-repos-section.c ++++ b/src/gs-repos-section.c +@@ -20,6 +20,7 @@ struct _GsReposSection + GtkListBox *list; + GsPluginLoader *plugin_loader; + gchar *sort_key; ++ gboolean always_allow_enable_disable; + }; + + G_DEFINE_TYPE (GsReposSection, gs_repos_section, HDY_TYPE_PREFERENCES_GROUP) +@@ -130,8 +131,23 @@ gs_repos_section_init (GsReposSection *self) + G_CALLBACK (gs_repos_section_row_activated_cb), self); + } + ++/* ++ * gs_repos_section_new: ++ * @plugin_loader: a #GsPluginLoader ++ * @always_allow_enable_disable: always allow enable/disable of the repos in this section ++ * ++ * Creates a new #GsReposSection. The %plugin_loader is passed ++ * to each #GsRepoRow, the same as the @always_allow_enable_disable. ++ * ++ * The @always_allow_enable_disable, when %TRUE, means that every repo in this section ++ * can be enabled/disabled by the user, if supported by the related plugin, regardless ++ * of the other heuristics, which can avoid the repo enable/disable. ++ * ++ * Returns: (transfer full): a newly created #GsReposSection ++ */ + GtkWidget * +-gs_repos_section_new (GsPluginLoader *plugin_loader) ++gs_repos_section_new (GsPluginLoader *plugin_loader, ++ gboolean always_allow_enable_disable) + { + GsReposSection *self; + +@@ -140,6 +156,7 @@ gs_repos_section_new (GsPluginLoader *plugin_loader) + self = g_object_new (GS_TYPE_REPOS_SECTION, NULL); + + self->plugin_loader = g_object_ref (plugin_loader); ++ self->always_allow_enable_disable = always_allow_enable_disable; + + return GTK_WIDGET (self); + } +@@ -159,7 +176,7 @@ gs_repos_section_add_repo (GsReposSection *self, + if (!self->sort_key) + self->sort_key = g_strdup (gs_app_get_metadata_item (repo, "GnomeSoftware::SortKey")); + +- row = gs_repo_row_new (self->plugin_loader, repo); ++ row = gs_repo_row_new (self->plugin_loader, repo, self->always_allow_enable_disable); + + g_signal_connect (row, "remove-clicked", + G_CALLBACK (repo_remove_clicked_cb), self); +diff --git a/src/gs-repos-section.h b/src/gs-repos-section.h +index 6e29769c..9a58a3e1 100644 +--- a/src/gs-repos-section.h ++++ b/src/gs-repos-section.h +@@ -20,7 +20,8 @@ G_BEGIN_DECLS + + G_DECLARE_FINAL_TYPE (GsReposSection, gs_repos_section, GS, REPOS_SECTION, HdyPreferencesGroup) + +-GtkWidget *gs_repos_section_new (GsPluginLoader *plugin_loader); ++GtkWidget *gs_repos_section_new (GsPluginLoader *plugin_loader, ++ gboolean always_allow_enable_disable); + void gs_repos_section_add_repo (GsReposSection *self, + GsApp *repo); + const gchar *gs_repos_section_get_title (GsReposSection *self); +From dca731ff0daf904911dd6815fb9a1b181329c887 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 11:00:20 +0200 +Subject: [PATCH 1/4] gs-repo-row: Use GS_APP_QUIRK_COMPULSORY to recognize + required repositories + +The GS_APP_QUIRK_PROVENANCE quirk does not mean it's also required repository, +thus use the GS_APP_QUIRK_COMPULSORY for repos, which cannot be disabled. +The GS_APP_QUIRK_PROVENANCE is used only for repositories, which cannot be removed. +--- + src/gs-repo-row.c | 10 ++++++---- + 1 file changed, 6 insertions(+), 4 deletions(-) + +diff --git a/src/gs-repo-row.c b/src/gs-repo-row.c +index 87926092f..bbf67c194 100644 +--- a/src/gs-repo-row.c ++++ b/src/gs-repo-row.c +@@ -48,7 +48,8 @@ refresh_ui (GsRepoRow *row) + gboolean active = FALSE; + gboolean state_sensitive = FALSE; + gboolean busy = priv->busy_counter> 0; +- gboolean is_system_repo; ++ gboolean is_provenance; ++ gboolean is_compulsory; + + if (priv->repo == NULL) { + gtk_widget_set_sensitive (priv->disable_switch, FALSE); +@@ -87,11 +88,12 @@ refresh_ui (GsRepoRow *row) + break; + } + +- is_system_repo = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_PROVENANCE); ++ is_provenance = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_PROVENANCE); ++ is_compulsory = gs_app_has_quirk (priv->repo, GS_APP_QUIRK_COMPULSORY); + + /* Disable for the system repos, if installed */ +- gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive || !is_system_repo || priv->always_allow_enable_disable)); +- gtk_widget_set_visible (priv->remove_button, priv->supports_remove && !is_system_repo); ++ gtk_widget_set_sensitive (priv->disable_switch, priv->supports_enable_disable && (state_sensitive || !is_compulsory || priv->always_allow_enable_disable)); ++ gtk_widget_set_visible (priv->remove_button, priv->supports_remove && !is_provenance && !is_compulsory); + + /* Set only the 'state' to visually indicate the state is not saved yet */ + if (busy) +-- +GitLab + + +From 026218b9d3211de243dfc49eca8b8d46633882b0 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 11:03:31 +0200 +Subject: [PATCH 2/4] gs-plugin-provenance: Improve search speed in list of + repositories + +Use a GHashTable for bare repository names and a GPtrArray for those +with wildcards. This helps with speed, due to not traversing all +the repository names with the fnmatch() call. +--- + plugins/core/gs-plugin-provenance.c | 55 ++++++++++++++++++++--------- + 1 file changed, 38 insertions(+), 17 deletions(-) + +diff --git a/plugins/core/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c +index 97ff76798..a72c25a27 100644 +--- a/plugins/core/gs-plugin-provenance.c ++++ b/plugins/core/gs-plugin-provenance.c +@@ -19,7 +19,8 @@ + + struct GsPluginData { + GSettings *settings; +- gchar **sources; ++ GHashTable *repos; /* gchar *name ~> NULL */ ++ GPtrArray *wildcards; /* non-NULL, when have names with wildcards */ + }; + + static gchar ** +@@ -42,8 +43,24 @@ gs_plugin_provenance_settings_changed_cb (GSettings *settings, + { + GsPluginData *priv = gs_plugin_get_data (plugin); + if (g_strcmp0 (key, "official-repos") == 0) { +- g_strfreev (priv->sources); +- priv->sources = gs_plugin_provenance_get_sources (plugin); ++ /* The keys are stolen by the hash table, thus free only the array */ ++ g_autofree gchar **repos = NULL; ++ g_hash_table_remove_all (priv->repos); ++ g_clear_pointer (&priv->wildcards, g_ptr_array_unref); ++ repos = gs_plugin_provenance_get_sources (plugin); ++ for (guint ii = 0; repos && repos[ii]; ii++) { ++ if (strchr (repos[ii], '*') || ++ strchr (repos[ii], '?') || ++ strchr (repos[ii], '[')) { ++ if (priv->wildcards == NULL) ++ priv->wildcards = g_ptr_array_new_with_free_func (g_free); ++ g_ptr_array_add (priv->wildcards, g_steal_pointer (&(repos[ii]))); ++ } else { ++ g_hash_table_insert (priv->repos, g_steal_pointer (&(repos[ii])), NULL); ++ } ++ } ++ if (priv->wildcards != NULL) ++ g_ptr_array_add (priv->wildcards, NULL); + } + } + +@@ -52,9 +69,10 @@ gs_plugin_initialize (GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData)); + priv->settings = g_settings_new ("org.gnome.software"); ++ priv->repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_signal_connect (priv->settings, "changed", + G_CALLBACK (gs_plugin_provenance_settings_changed_cb), plugin); +- priv->sources = gs_plugin_provenance_get_sources (plugin); ++ gs_plugin_provenance_settings_changed_cb (priv->settings, "official-repos", plugin); + + /* after the package source is set */ + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dummy"); +@@ -66,7 +84,8 @@ void + gs_plugin_destroy (GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_get_data (plugin); +- g_strfreev (priv->sources); ++ g_hash_table_unref (priv->repos); ++ g_clear_pointer (&priv->wildcards, g_ptr_array_unref); + g_object_unref (priv->settings); + } + +@@ -74,12 +93,12 @@ static gboolean + refine_app (GsPlugin *plugin, + GsApp *app, + GsPluginRefineFlags flags, ++ GHashTable *repos, ++ GPtrArray *wildcards, + GCancellable *cancellable, + GError **error) + { +- GsPluginData *priv = gs_plugin_get_data (plugin); + const gchar *origin; +- gchar **sources; + + /* not required */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) +@@ -87,14 +106,10 @@ refine_app (GsPlugin *plugin, + if (gs_app_has_quirk (app, GS_APP_QUIRK_PROVENANCE)) + return TRUE; + +- /* nothing to search */ +- sources = priv->sources; +- if (sources == NULL || sources[0] == NULL) +- return TRUE; +- + /* simple case */ + origin = gs_app_get_origin (app); +- if (origin != NULL && gs_utils_strv_fnmatch (sources, origin)) { ++ if (origin != NULL && (g_hash_table_contains (repos, origin) || ++ (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin)))) { + gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); + return TRUE; + } +@@ -103,7 +118,8 @@ refine_app (GsPlugin *plugin, + * provenance quirk to the system-configured repositories (but not + * user-configured ones). */ + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY && +- gs_utils_strv_fnmatch (sources, gs_app_get_id (app))) { ++ (g_hash_table_contains (repos, gs_app_get_id (app)) || ++ (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, gs_app_get_id (app))))) { + if (gs_app_get_scope (app) != AS_COMPONENT_SCOPE_USER) + gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); + return TRUE; +@@ -118,7 +134,8 @@ refine_app (GsPlugin *plugin, + return TRUE; + if (g_str_has_prefix (origin + 1, "installed:")) + origin += 10; +- if (gs_utils_strv_fnmatch (sources, origin + 1)) { ++ if (g_hash_table_contains (repos, origin + 1) || ++ (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin + 1))) { + gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); + return TRUE; + } +@@ -133,17 +150,21 @@ gs_plugin_refine (GsPlugin *plugin, + GError **error) + { + GsPluginData *priv = gs_plugin_get_data (plugin); ++ g_autoptr(GHashTable) repos = NULL; ++ g_autoptr(GPtrArray) wildcards = NULL; + + /* nothing to do here */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) + return TRUE; ++ repos = g_hash_table_ref (priv->repos); ++ wildcards = priv->wildcards != NULL ? g_ptr_array_ref (priv->wildcards) : NULL; + /* nothing to search */ +- if (priv->sources == NULL || priv->sources[0] == NULL) ++ if (g_hash_table_size (repos) == 0) + return TRUE; + + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); +- if (!refine_app (plugin, app, flags, cancellable, error)) ++ if (!refine_app (plugin, app, flags, repos, wildcards, cancellable, error)) + return FALSE; + } + +-- +GitLab + + +From b5e3356aff5fcd257248f9bb697e272c879249ae Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 13:03:44 +0200 +Subject: [PATCH 3/4] settings: Add 'required-repos' key + +To be used to list repositories, which cannot be removed or disabled. +It's a complementary option for the 'official-repos' key. +--- + data/org.gnome.software.gschema.xml | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/data/org.gnome.software.gschema.xml b/data/org.gnome.software.gschema.xml +index db1c27ce4..0e5706b7c 100644 +--- a/data/org.gnome.software.gschema.xml ++++ b/data/org.gnome.software.gschema.xml +@@ -94,6 +94,10 @@ + [] + A list of official repositories that should not be considered 3rd party + ++ ++ [] ++ A list of required repositories that cannot be disabled or removed ++ + + [] + A list of official repositories that should be considered free software +-- +GitLab + + +From d6b8b206a596bb520a0b77066898b44a5ef18920 Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Tue, 5 Oct 2021 14:16:56 +0200 +Subject: [PATCH 4/4] gs-plugin-provenance: Handle also 'required-repos' key + +Let it handle also 'required-repos' settings key, beside the 'official-repos' +key, which are close enough to share the same code and memory. With this +done the repositories can be marked as compulsory, independently from the official +repositories. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1479 +--- + plugins/core/gs-plugin-provenance.c | 142 +++++++++++++++++++++------- + 1 file changed, 108 insertions(+), 34 deletions(-) + +diff --git a/plugins/core/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c +index a72c25a27..22f3c98e1 100644 +--- a/plugins/core/gs-plugin-provenance.c ++++ b/plugins/core/gs-plugin-provenance.c +@@ -14,26 +14,61 @@ + /* + * SECTION: + * Sets the package provenance to TRUE if installed by an official +- * software source. ++ * software source. Also sets compulsory quirk when a required repository. + */ + + struct GsPluginData { + GSettings *settings; +- GHashTable *repos; /* gchar *name ~> NULL */ +- GPtrArray *wildcards; /* non-NULL, when have names with wildcards */ ++ GHashTable *repos; /* gchar *name ~> guint flags */ ++ GPtrArray *provenance_wildcards; /* non-NULL, when have names with wildcards */ ++ GPtrArray *compulsory_wildcards; /* non-NULL, when have names with wildcards */ + }; + ++static GHashTable * ++gs_plugin_provenance_remove_by_flag (GHashTable *old_repos, ++ GsAppQuirk quirk) ++{ ++ GHashTable *new_repos = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); ++ GHashTableIter iter; ++ gpointer key, value; ++ g_hash_table_iter_init (&iter, old_repos); ++ while (g_hash_table_iter_next (&iter, &key, &value)) { ++ guint flags = GPOINTER_TO_UINT (value); ++ flags = flags & (~quirk); ++ if (flags != 0) ++ g_hash_table_insert (new_repos, g_strdup (key), GUINT_TO_POINTER (flags)); ++ } ++ return new_repos; ++} ++ ++static void ++gs_plugin_provenance_add_quirks (GsApp *app, ++ guint quirks) ++{ ++ GsAppQuirk array[] = { ++ GS_APP_QUIRK_PROVENANCE, ++ GS_APP_QUIRK_COMPULSORY ++ }; ++ for (guint ii = 0; ii < G_N_ELEMENTS (array); ii++) { ++ if ((quirks & array[ii]) != 0) ++ gs_app_add_quirk (app, array[ii]); ++ } ++} ++ + static gchar ** +-gs_plugin_provenance_get_sources (GsPlugin *plugin) ++gs_plugin_provenance_get_sources (GsPlugin *plugin, ++ const gchar *key) + { + GsPluginData *priv = gs_plugin_get_data (plugin); + const gchar *tmp; + tmp = g_getenv ("GS_SELF_TEST_PROVENANCE_SOURCES"); + if (tmp != NULL) { ++ if (g_strcmp0 (key, "required-repos") == 0) ++ return NULL; + g_debug ("using custom provenance sources of %s", tmp); + return g_strsplit (tmp, ",", -1); + } +- return g_settings_get_strv (priv->settings, "official-repos"); ++ return g_settings_get_strv (priv->settings, key); + } + + static void +@@ -42,25 +77,43 @@ gs_plugin_provenance_settings_changed_cb (GSettings *settings, + GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_get_data (plugin); ++ GsAppQuirk quirk = GS_APP_QUIRK_NONE; ++ GPtrArray **pwildcards = NULL; ++ + if (g_strcmp0 (key, "official-repos") == 0) { ++ quirk = GS_APP_QUIRK_PROVENANCE; ++ pwildcards = &priv->provenance_wildcards; ++ } else if (g_strcmp0 (key, "required-repos") == 0) { ++ quirk = GS_APP_QUIRK_COMPULSORY; ++ pwildcards = &priv->compulsory_wildcards; ++ } ++ ++ if (quirk != GS_APP_QUIRK_NONE) { + /* The keys are stolen by the hash table, thus free only the array */ + g_autofree gchar **repos = NULL; +- g_hash_table_remove_all (priv->repos); +- g_clear_pointer (&priv->wildcards, g_ptr_array_unref); +- repos = gs_plugin_provenance_get_sources (plugin); ++ g_autoptr(GHashTable) old_repos = priv->repos; ++ g_autoptr(GPtrArray) old_wildcards = *pwildcards; ++ GHashTable *new_repos = gs_plugin_provenance_remove_by_flag (old_repos, quirk); ++ GPtrArray *new_wildcards = NULL; ++ repos = gs_plugin_provenance_get_sources (plugin, key); + for (guint ii = 0; repos && repos[ii]; ii++) { +- if (strchr (repos[ii], '*') || +- strchr (repos[ii], '?') || +- strchr (repos[ii], '[')) { +- if (priv->wildcards == NULL) +- priv->wildcards = g_ptr_array_new_with_free_func (g_free); +- g_ptr_array_add (priv->wildcards, g_steal_pointer (&(repos[ii]))); ++ gchar *repo = g_steal_pointer (&(repos[ii])); ++ if (strchr (repo, '*') || ++ strchr (repo, '?') || ++ strchr (repo, '[')) { ++ if (new_wildcards == NULL) ++ new_wildcards = g_ptr_array_new_with_free_func (g_free); ++ g_ptr_array_add (new_wildcards, repo); + } else { +- g_hash_table_insert (priv->repos, g_steal_pointer (&(repos[ii])), NULL); ++ g_hash_table_insert (new_repos, repo, ++ GUINT_TO_POINTER (quirk | ++ GPOINTER_TO_UINT (g_hash_table_lookup (new_repos, repo)))); + } + } +- if (priv->wildcards != NULL) +- g_ptr_array_add (priv->wildcards, NULL); ++ if (new_wildcards != NULL) ++ g_ptr_array_add (new_wildcards, NULL); ++ priv->repos = new_repos; ++ *pwildcards = new_wildcards; + } + } + +@@ -73,6 +126,7 @@ gs_plugin_initialize (GsPlugin *plugin) + g_signal_connect (priv->settings, "changed", + G_CALLBACK (gs_plugin_provenance_settings_changed_cb), plugin); + gs_plugin_provenance_settings_changed_cb (priv->settings, "official-repos", plugin); ++ gs_plugin_provenance_settings_changed_cb (priv->settings, "required-repos", plugin); + + /* after the package source is set */ + gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dummy"); +@@ -85,20 +139,42 @@ gs_plugin_destroy (GsPlugin *plugin) + { + GsPluginData *priv = gs_plugin_get_data (plugin); + g_hash_table_unref (priv->repos); +- g_clear_pointer (&priv->wildcards, g_ptr_array_unref); ++ g_clear_pointer (&priv->provenance_wildcards, g_ptr_array_unref); ++ g_clear_pointer (&priv->compulsory_wildcards, g_ptr_array_unref); + g_object_unref (priv->settings); + } + ++static gboolean ++gs_plugin_provenance_find_repo_flags (GHashTable *repos, ++ GPtrArray *provenance_wildcards, ++ GPtrArray *compulsory_wildcards, ++ const gchar *repo, ++ guint *out_flags) ++{ ++ if (repo == NULL || *repo == '\0') ++ return FALSE; ++ *out_flags = GPOINTER_TO_UINT (g_hash_table_lookup (repos, repo)); ++ if (provenance_wildcards != NULL && ++ gs_utils_strv_fnmatch ((gchar **) provenance_wildcards->pdata, repo)) ++ *out_flags |= GS_APP_QUIRK_PROVENANCE; ++ if (compulsory_wildcards != NULL && ++ gs_utils_strv_fnmatch ((gchar **) compulsory_wildcards->pdata, repo)) ++ *out_flags |= GS_APP_QUIRK_COMPULSORY; ++ return *out_flags != 0; ++} ++ + static gboolean + refine_app (GsPlugin *plugin, + GsApp *app, + GsPluginRefineFlags flags, + GHashTable *repos, +- GPtrArray *wildcards, ++ GPtrArray *provenance_wildcards, ++ GPtrArray *compulsory_wildcards, + GCancellable *cancellable, + GError **error) + { + const gchar *origin; ++ guint quirks; + + /* not required */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) +@@ -108,9 +184,8 @@ refine_app (GsPlugin *plugin, + + /* simple case */ + origin = gs_app_get_origin (app); +- if (origin != NULL && (g_hash_table_contains (repos, origin) || +- (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin)))) { +- gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); ++ if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, origin, &quirks)) { ++ gs_plugin_provenance_add_quirks (app, quirks); + return TRUE; + } + +@@ -118,10 +193,9 @@ refine_app (GsPlugin *plugin, + * provenance quirk to the system-configured repositories (but not + * user-configured ones). */ + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY && +- (g_hash_table_contains (repos, gs_app_get_id (app)) || +- (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, gs_app_get_id (app))))) { ++ gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, gs_app_get_id (app), &quirks)) { + if (gs_app_get_scope (app) != AS_COMPONENT_SCOPE_USER) +- gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); ++ gs_plugin_provenance_add_quirks (app, quirks); + return TRUE; + } + +@@ -134,11 +208,9 @@ refine_app (GsPlugin *plugin, + return TRUE; + if (g_str_has_prefix (origin + 1, "installed:")) + origin += 10; +- if (g_hash_table_contains (repos, origin + 1) || +- (wildcards != NULL && gs_utils_strv_fnmatch ((gchar **) wildcards->pdata, origin + 1))) { +- gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); +- return TRUE; +- } ++ if (gs_plugin_provenance_find_repo_flags (repos, provenance_wildcards, compulsory_wildcards, origin + 1, &quirks)) ++ gs_plugin_provenance_add_quirks (app, quirks); ++ + return TRUE; + } + +@@ -151,20 +223,22 @@ gs_plugin_refine (GsPlugin *plugin, + { + GsPluginData *priv = gs_plugin_get_data (plugin); + g_autoptr(GHashTable) repos = NULL; +- g_autoptr(GPtrArray) wildcards = NULL; ++ g_autoptr(GPtrArray) provenance_wildcards = NULL; ++ g_autoptr(GPtrArray) compulsory_wildcards = NULL; + + /* nothing to do here */ + if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) == 0) + return TRUE; + repos = g_hash_table_ref (priv->repos); +- wildcards = priv->wildcards != NULL ? g_ptr_array_ref (priv->wildcards) : NULL; ++ provenance_wildcards = priv->provenance_wildcards != NULL ? g_ptr_array_ref (priv->provenance_wildcards) : NULL; ++ compulsory_wildcards = priv->compulsory_wildcards != NULL ? g_ptr_array_ref (priv->compulsory_wildcards) : NULL; + /* nothing to search */ +- if (g_hash_table_size (repos) == 0) ++ if (g_hash_table_size (repos) == 0 && provenance_wildcards == NULL && compulsory_wildcards == NULL) + return TRUE; + + for (guint i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); +- if (!refine_app (plugin, app, flags, repos, wildcards, cancellable, error)) ++ if (!refine_app (plugin, app, flags, repos, provenance_wildcards, compulsory_wildcards, cancellable, error)) + return FALSE; + } + +-- +GitLab + diff --git a/0007-compulsory-only-for-repos.patch b/0007-compulsory-only-for-repos.patch new file mode 100644 index 0000000..fb266df --- /dev/null +++ b/0007-compulsory-only-for-repos.patch @@ -0,0 +1,42 @@ +From 895d1ca748f4f33a852853f5f07903fb549fb66f Mon Sep 17 00:00:00 2001 +From: Milan Crha +Date: Mon, 11 Oct 2021 09:13:59 +0200 +Subject: [PATCH] gs-plugin-provenance: Set COMPULSORY quirk only on REPOSITORY + apps + +The compulsory quirk related to repositories, which cannot be removed, +not to the applications provided by those repositories, thus set that +quirk only on repositories, not on the apps from it. + +Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1488 +--- + plugins/core/gs-plugin-provenance.c | 13 +++++-------- + 1 file changed, 5 insertions(+), 8 deletions(-) + +diff --git a/plugins/core/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c +index 22f3c98e..e44a55f0 100644 +--- a/plugins/core/gs-plugin-provenance.c ++++ b/plugins/core/gs-plugin-provenance.c +@@ -45,14 +45,11 @@ static void + gs_plugin_provenance_add_quirks (GsApp *app, + guint quirks) + { +- GsAppQuirk array[] = { +- GS_APP_QUIRK_PROVENANCE, +- GS_APP_QUIRK_COMPULSORY +- }; +- for (guint ii = 0; ii < G_N_ELEMENTS (array); ii++) { +- if ((quirks & array[ii]) != 0) +- gs_app_add_quirk (app, array[ii]); +- } ++ if ((quirks & GS_APP_QUIRK_PROVENANCE) != 0) ++ gs_app_add_quirk (app, GS_APP_QUIRK_PROVENANCE); ++ if ((quirks & GS_APP_QUIRK_COMPULSORY) != 0 && ++ gs_app_get_kind (app) == AS_COMPONENT_KIND_REPOSITORY) ++ gs_app_add_quirk (app, GS_APP_QUIRK_COMPULSORY); + } + + static gchar ** +-- +2.31.1 + diff --git a/gnome-software.spec b/gnome-software.spec index f672833..b98bc54 100644 --- a/gnome-software.spec +++ b/gnome-software.spec @@ -12,7 +12,7 @@ Name: gnome-software Version: 41.0 -Release: 1%{?dist} +Release: 2%{?dist} Summary: A software center for GNOME License: GPLv2+ @@ -20,6 +20,12 @@ URL: https://wiki.gnome.org/Apps/Software Source0: https://download.gnome.org/sources/gnome-software/41/%{name}-%{tarball_version}.tar.xz Patch01: 0001-crash-with-broken-theme.patch +Patch02: 0002-correct-update-notifications.patch +Patch03: 0003-refresh-on-repository-change.patch +Patch04: 0004-filtered-system-flathub.patch +Patch05: 0005-repos-dialog-can-show-apps.patch +Patch06: 0006-optional-repos-cannot-be-disabled.patch +Patch07: 0007-compulsory-only-for-repos.patch BuildRequires: appstream-devel >= %{appstream_version} BuildRequires: gcc @@ -130,6 +136,7 @@ cat >> %{buildroot}%{_datadir}/glib-2.0/schemas/org.gnome.software-fedora.gschem 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' ] %endif FOE @@ -199,6 +206,15 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop %{_datadir}/gtk-doc/html/gnome-software %changelog +* Tue Oct 12 2021 Milan Crha - 41.0-2 +- Resolves: #2012699 (Backport changes from Fedora 35) +- Add patch to mark compulsory only repos, not apps from it +- Resolves: #2011176 (flathub repo can't be added through gnome-software) +- Resolves: #2010660 (gs-repos-dialog: Can show also desktop applications) +- Resolves: #2010353 (Optional repos cannot be disabled) +- Resolves: #2010740 (Refresh on repository setup change) +- Resolves: #2009063 (Correct update notifications) + * Mon Sep 20 2021 Milan Crha - 41.0-1 - Resolves: #2005770 (Update to 41.0)