summaryrefslogtreecommitdiffstats
path: root/src/gs-updates-page.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/gs-updates-page.c1467
1 files changed, 1467 insertions, 0 deletions
diff --git a/src/gs-updates-page.c b/src/gs-updates-page.c
new file mode 100644
index 0000000..4456ddd
--- /dev/null
+++ b/src/gs-updates-page.c
@@ -0,0 +1,1467 @@
+/* -*- 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) 2014-2018 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "gs-shell.h"
+#include "gs-updates-page.h"
+#include "gs-common.h"
+#include "gs-app-row.h"
+#include "gs-plugin-private.h"
+#include "gs-removal-dialog.h"
+#include "gs-update-monitor.h"
+#include "gs-updates-section.h"
+#include "gs-upgrade-banner.h"
+#include "gs-application.h"
+
+typedef enum {
+ GS_UPDATES_PAGE_FLAG_NONE = 0,
+ GS_UPDATES_PAGE_FLAG_HAS_UPDATES = 1 << 0,
+ GS_UPDATES_PAGE_FLAG_HAS_UPGRADES = 1 << 1,
+ GS_UPDATES_PAGE_FLAG_LAST
+} GsUpdatesPageFlags;
+
+typedef enum {
+ GS_UPDATES_PAGE_STATE_STARTUP,
+ GS_UPDATES_PAGE_STATE_ACTION_REFRESH,
+ GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES,
+ GS_UPDATES_PAGE_STATE_MANAGED,
+ GS_UPDATES_PAGE_STATE_IDLE,
+ GS_UPDATES_PAGE_STATE_FAILED,
+ GS_UPDATES_PAGE_STATE_LAST,
+} GsUpdatesPageState;
+
+struct _GsUpdatesPage
+{
+ GsPage parent_instance;
+
+ GsPluginLoader *plugin_loader;
+ GCancellable *cancellable;
+ GCancellable *cancellable_refresh;
+ GCancellable *cancellable_upgrade_download;
+ GSettings *settings;
+ GSettings *desktop_settings;
+ gboolean cache_valid;
+ guint action_cnt;
+ GsShell *shell;
+ GsUpdatesPageState state;
+ GsUpdatesPageFlags result_flags;
+ GtkWidget *button_refresh;
+ GtkWidget *header_spinner_start;
+ GtkWidget *header_start_box;
+ gboolean has_agreed_to_mobile_data;
+ gboolean ampm_available;
+ guint updates_counter;
+ gboolean is_narrow;
+
+ GtkWidget *updates_box;
+ GtkWidget *button_updates_mobile;
+ GtkWidget *button_updates_offline;
+ GtkWidget *updates_failed_page;
+ GtkLabel *uptodate_description;
+ GtkWidget *scrolledwindow_updates;
+ GtkWidget *spinner_updates;
+ GtkWidget *stack_updates;
+ GtkWidget *upgrade_banner;
+ GtkWidget *infobar_end_of_life;
+ GtkWidget *label_end_of_life;
+
+ GtkSizeGroup *sizegroup_name;
+ GtkSizeGroup *sizegroup_button_label;
+ GtkSizeGroup *sizegroup_button_image;
+ GtkSizeGroup *sizegroup_header;
+ GsUpdatesSection *sections[GS_UPDATES_SECTION_KIND_LAST];
+
+ guint refresh_last_checked_id;
+};
+
+enum {
+ COLUMN_UPDATE_APP,
+ COLUMN_UPDATE_NAME,
+ COLUMN_UPDATE_VERSION,
+ COLUMN_UPDATE_LAST
+};
+
+G_DEFINE_TYPE (GsUpdatesPage, gs_updates_page, GS_TYPE_PAGE)
+
+typedef enum {
+ PROP_IS_NARROW = 1,
+ /* Overrides: */
+ PROP_VADJUSTMENT,
+ PROP_TITLE,
+ PROP_COUNTER,
+} GsUpdatesPageProperty;
+
+static GParamSpec *obj_props[PROP_IS_NARROW + 1] = { NULL, };
+
+static void
+gs_updates_page_set_flag (GsUpdatesPage *self, GsUpdatesPageFlags flag)
+{
+ self->result_flags |= flag;
+}
+
+static void
+gs_updates_page_clear_flag (GsUpdatesPage *self, GsUpdatesPageFlags flag)
+{
+ self->result_flags &= ~flag;
+}
+
+static const gchar *
+gs_updates_page_state_to_string (GsUpdatesPageState state)
+{
+ if (state == GS_UPDATES_PAGE_STATE_STARTUP)
+ return "startup";
+ if (state == GS_UPDATES_PAGE_STATE_ACTION_REFRESH)
+ return "action-refresh";
+ if (state == GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES)
+ return "action-get-updates";
+ if (state == GS_UPDATES_PAGE_STATE_MANAGED)
+ return "managed";
+ if (state == GS_UPDATES_PAGE_STATE_IDLE)
+ return "idle";
+ if (state == GS_UPDATES_PAGE_STATE_FAILED)
+ return "failed";
+ return NULL;
+}
+
+static void
+gs_updates_page_invalidate (GsUpdatesPage *self)
+{
+ self->cache_valid = FALSE;
+}
+
+static GsUpdatesSectionKind
+_get_app_section (GsApp *app)
+{
+ if (gs_app_get_state (app) == GS_APP_STATE_UPDATABLE_LIVE ||
+ gs_app_get_state (app) == GS_APP_STATE_INSTALLING) {
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FIRMWARE)
+ return GS_UPDATES_SECTION_KIND_ONLINE_FIRMWARE;
+ return GS_UPDATES_SECTION_KIND_ONLINE;
+ }
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FIRMWARE)
+ return GS_UPDATES_SECTION_KIND_OFFLINE_FIRMWARE;
+ return GS_UPDATES_SECTION_KIND_OFFLINE;
+}
+
+static GsAppList *
+_get_all_apps (GsUpdatesPage *self)
+{
+ GsAppList *apps = gs_app_list_new ();
+ for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) {
+ GsAppList *list = gs_updates_section_get_list (self->sections[i]);
+ gs_app_list_add_list (apps, list);
+ }
+ return apps;
+}
+
+static guint
+_get_num_updates (GsUpdatesPage *self)
+{
+ guint count = 0;
+ g_autoptr(GsAppList) apps = _get_all_apps (self);
+
+ for (guint i = 0; i < gs_app_list_length (apps); ++i) {
+ GsApp *app = gs_app_list_index (apps, i);
+ if (gs_app_is_updatable (app) ||
+ gs_app_get_state (app) == GS_APP_STATE_INSTALLING)
+ ++count;
+ }
+ return count;
+}
+
+static gchar *
+gs_updates_page_last_checked_time_string (GsUpdatesPage *self,
+ gint *out_hours_ago,
+ gint *out_days_ago)
+{
+ gint64 last_checked;
+ gchar *res;
+
+ g_settings_get (self->settings, "check-timestamp", "x", &last_checked);
+ res = gs_utils_time_to_string (last_checked);
+ if (res) {
+ g_assert (gs_utils_split_time_difference (last_checked, NULL, out_hours_ago, out_days_ago, NULL, NULL, NULL));
+ }
+
+ return res;
+}
+
+static void
+refresh_headerbar_updates_counter (GsUpdatesPage *self)
+{
+ guint new_updates_counter;
+
+ new_updates_counter = _get_num_updates (self);
+ if (!gs_plugin_loader_get_allow_updates (self->plugin_loader) ||
+ self->state == GS_UPDATES_PAGE_STATE_FAILED)
+ new_updates_counter = 0;
+
+ if (new_updates_counter == self->updates_counter)
+ return;
+
+ self->updates_counter = new_updates_counter;
+ g_object_notify (G_OBJECT (self), "counter");
+}
+
+static void
+gs_updates_page_remove_last_checked_timeout (GsUpdatesPage *self)
+{
+ if (self->refresh_last_checked_id) {
+ g_source_remove (self->refresh_last_checked_id);
+ self->refresh_last_checked_id = 0;
+ }
+}
+
+static void
+gs_updates_page_refresh_last_checked (GsUpdatesPage *self);
+
+static gboolean
+gs_updates_page_refresh_last_checked_cb (gpointer user_data)
+{
+ GsUpdatesPage *self = user_data;
+ gs_updates_page_refresh_last_checked (self);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gs_updates_page_refresh_last_checked (GsUpdatesPage *self)
+{
+ g_autofree gchar *checked_str = NULL;
+ gint hours_ago, days_ago;
+ checked_str = gs_updates_page_last_checked_time_string (self, &hours_ago, &days_ago);
+ if (checked_str != NULL) {
+ g_autofree gchar *last_checked = NULL;
+ guint interval;
+
+ /* TRANSLATORS: This is the time when we last checked for updates */
+ last_checked = g_strdup_printf (_("Last checked: %s"), checked_str);
+ gtk_label_set_label (self->uptodate_description, last_checked);
+ gtk_widget_set_visible (GTK_WIDGET (self->uptodate_description), TRUE);
+
+ if (hours_ago < 1)
+ interval = 60;
+ else if (days_ago < 7)
+ interval = 60 * 60;
+ else
+ interval = 60 * 60 * 24;
+
+ gs_updates_page_remove_last_checked_timeout (self);
+
+ self->refresh_last_checked_id = g_timeout_add_seconds (interval,
+ gs_updates_page_refresh_last_checked_cb, self);
+ } else {
+ gtk_widget_set_visible (GTK_WIDGET (self->uptodate_description), FALSE);
+ }
+}
+
+static void
+gs_updates_page_update_ui_state (GsUpdatesPage *self)
+{
+ gboolean allow_mobile_refresh = TRUE;
+
+ gs_updates_page_remove_last_checked_timeout (self);
+
+ if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_UPDATES)
+ return;
+
+ /* spinners */
+ switch (self->state) {
+ case GS_UPDATES_PAGE_STATE_STARTUP:
+ case GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES:
+ case GS_UPDATES_PAGE_STATE_ACTION_REFRESH:
+ gtk_spinner_start (GTK_SPINNER (self->spinner_updates));
+ break;
+ default:
+ gtk_spinner_stop (GTK_SPINNER (self->spinner_updates));
+ gtk_spinner_stop (GTK_SPINNER (self->header_spinner_start));
+ gtk_widget_hide (self->header_spinner_start);
+ break;
+ }
+
+ /* headerbar refresh icon */
+ switch (self->state) {
+ case GS_UPDATES_PAGE_STATE_ACTION_REFRESH:
+ case GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES:
+ gtk_button_set_icon_name (GTK_BUTTON (self->button_refresh), "media-playback-stop-symbolic");
+ gtk_widget_show (self->button_refresh);
+ break;
+ case GS_UPDATES_PAGE_STATE_STARTUP:
+ case GS_UPDATES_PAGE_STATE_MANAGED:
+ gtk_widget_hide (self->button_refresh);
+ break;
+ case GS_UPDATES_PAGE_STATE_IDLE:
+ gtk_button_set_icon_name (GTK_BUTTON (self->button_refresh), "view-refresh-symbolic");
+ if (self->result_flags != GS_UPDATES_PAGE_FLAG_NONE) {
+ gtk_widget_show (self->button_refresh);
+ } else {
+ if (gs_plugin_loader_get_network_metered (self->plugin_loader) &&
+ !self->has_agreed_to_mobile_data)
+ allow_mobile_refresh = FALSE;
+ gtk_widget_set_visible (self->button_refresh, allow_mobile_refresh);
+ }
+ break;
+ case GS_UPDATES_PAGE_STATE_FAILED:
+ gtk_button_set_icon_name (GTK_BUTTON (self->button_refresh), "view-refresh-symbolic");
+ gtk_widget_show (self->button_refresh);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ gtk_widget_set_sensitive (self->button_refresh,
+ gs_plugin_loader_get_network_available (self->plugin_loader));
+
+ /* stack */
+ switch (self->state) {
+ case GS_UPDATES_PAGE_STATE_MANAGED:
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "managed");
+ break;
+ case GS_UPDATES_PAGE_STATE_FAILED:
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "failed");
+ break;
+ case GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES:
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates),
+ "spinner");
+ break;
+ case GS_UPDATES_PAGE_STATE_ACTION_REFRESH:
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "spinner");
+ break;
+ case GS_UPDATES_PAGE_STATE_STARTUP:
+ case GS_UPDATES_PAGE_STATE_IDLE:
+
+ /* if have updates, just show the view, otherwise show network */
+ if (self->result_flags != GS_UPDATES_PAGE_FLAG_NONE) {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "view");
+ break;
+ }
+
+ /* check we have a "free" network connection */
+ if (gs_plugin_loader_get_network_available (self->plugin_loader) &&
+ !gs_plugin_loader_get_network_metered (self->plugin_loader)) {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "uptodate");
+ break;
+ }
+
+ /* expensive network connection */
+ if (gs_plugin_loader_get_network_metered (self->plugin_loader)) {
+ if (self->has_agreed_to_mobile_data) {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "uptodate");
+ } else {
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "mobile");
+ }
+ break;
+ }
+
+ /* no network connection */
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_updates), "offline");
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ /* any updates? */
+ gtk_widget_set_visible (self->updates_box,
+ self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPDATES);
+
+ /* last checked label */
+ if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (self->stack_updates)), "uptodate") == 0)
+ gs_updates_page_refresh_last_checked (self);
+
+ /* update the counter in headerbar */
+ refresh_headerbar_updates_counter (self);
+}
+
+static void
+gs_updates_page_set_state (GsUpdatesPage *self, GsUpdatesPageState state)
+{
+ g_debug ("setting state from %s to %s (has-update:%i, has-upgrade:%i)",
+ gs_updates_page_state_to_string (self->state),
+ gs_updates_page_state_to_string (state),
+ (self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPDATES) > 0,
+ (self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPGRADES) > 0);
+ self->state = state;
+ gs_updates_page_update_ui_state (self);
+}
+
+static void
+gs_updates_page_decrement_refresh_count (GsUpdatesPage *self)
+{
+ /* every job increments this */
+ if (self->action_cnt == 0) {
+ g_warning ("action_cnt already zero!");
+ return;
+ }
+ if (--self->action_cnt > 0)
+ return;
+
+ /* all done */
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_IDLE);
+}
+
+static void
+gs_updates_page_network_available_notify_cb (GsPluginLoader *plugin_loader,
+ GParamSpec *pspec,
+ GsUpdatesPage *self)
+{
+ gs_updates_page_update_ui_state (self);
+}
+
+static void
+gs_updates_page_get_updates_cb (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GsUpdatesPage *self)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsAppList) list = NULL;
+
+ self->cache_valid = TRUE;
+
+ /* get the results */
+ list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
+ if (list == NULL) {
+ gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES);
+ if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("updates-shell: failed to get updates: %s", error->message);
+ adw_status_page_set_description (ADW_STATUS_PAGE (self->updates_failed_page),
+ error->message);
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_FAILED);
+ refresh_headerbar_updates_counter (self);
+ return;
+ }
+
+ /* add the results */
+ for (guint i = 0; i < gs_app_list_length (list); i++) {
+ GsApp *app = gs_app_list_index (list, i);
+ GsUpdatesSectionKind section = _get_app_section (app);
+ gs_updates_section_add_app (self->sections[section], app);
+ }
+
+ /* update the counter in headerbar */
+ refresh_headerbar_updates_counter (self);
+
+ /* no results */
+ if (gs_app_list_length (list) == 0) {
+ g_debug ("updates-shell: no updates to show");
+ gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES);
+ } else {
+ gs_updates_page_set_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES);
+ }
+
+ /* only when both set */
+ gs_updates_page_decrement_refresh_count (self);
+}
+
+static void
+gs_updates_page_get_upgrades_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+ GsUpdatesPage *self = GS_UPDATES_PAGE (user_data);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsAppList) list = NULL;
+
+ /* get the results */
+ list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
+ if (list == NULL) {
+ gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES);
+ if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_warning ("updates-shell: failed to get upgrades: %s",
+ error->message);
+ }
+ } else if (gs_app_list_length (list) == 0) {
+ g_debug ("updates-shell: no upgrades to show");
+ gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES);
+ gtk_widget_set_visible (self->upgrade_banner, FALSE);
+ } else {
+ /* rely on the app list already being sorted with the
+ * chronologically newest release last */
+ GsApp *app = gs_app_list_index (list, gs_app_list_length (list) - 1);
+ g_debug ("got upgrade %s", gs_app_get_id (app));
+ gs_upgrade_banner_set_app (GS_UPGRADE_BANNER (self->upgrade_banner), app);
+ gs_updates_page_set_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES);
+ gtk_widget_set_visible (self->upgrade_banner, TRUE);
+ }
+
+ /* only when both set */
+ gs_updates_page_decrement_refresh_count (self);
+}
+
+typedef struct {
+ GsApp *app; /* (owned) */
+ GsUpdatesPage *self; /* (owned) */
+} GsPageHelper;
+
+static GsPageHelper *
+gs_page_helper_new (GsUpdatesPage *self,
+ GsApp *app)
+{
+ GsPageHelper *helper;
+ helper = g_slice_new0 (GsPageHelper);
+ helper->self = g_object_ref (self);
+ helper->app = g_object_ref (app);
+ return helper;
+}
+
+static void
+gs_page_helper_free (GsPageHelper *helper)
+{
+ g_clear_object (&helper->app);
+ g_clear_object (&helper->self);
+ g_slice_free (GsPageHelper, helper);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
+
+static void
+gs_updates_page_refine_system_finished_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+ g_autoptr(GsPageHelper) helper = user_data;
+ GsUpdatesPage *self = helper->self;
+ GsApp *app = helper->app;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GString) str = g_string_new (NULL);
+
+ /* get result */
+ 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_warning ("Failed to refine system: %s", error->message);
+ return;
+ }
+
+ /* show or hide the end of life notification */
+ if (gs_app_get_state (app) != GS_APP_STATE_UNAVAILABLE) {
+ gtk_info_bar_set_revealed (GTK_INFO_BAR (self->infobar_end_of_life), FALSE);
+ return;
+ }
+
+ /* construct a sufficiently scary message */
+ if (gs_app_get_name (app) != NULL && gs_app_get_version (app) != NULL) {
+ /* TRANSLATORS: the first %s is the distro name, e.g. 'Fedora'
+ * and the second %s is the distro version, e.g. '25' */
+ g_string_append_printf (str, _("%s %s is no longer supported."),
+ gs_app_get_name (app),
+ gs_app_get_version (app));
+ } else {
+ g_string_append (str, _("Your operating system is no longer supported."));
+ }
+ g_string_append (str, " ");
+
+ /* TRANSLATORS: EOL distros do not get important updates */
+ g_string_append (str, _("This means that it does not receive security updates."));
+ g_string_append (str, " ");
+
+ /* TRANSLATORS: upgrade refers to a major update, e.g. Fedora 25 to 26 */
+ g_string_append (str, _("It is recommended that you upgrade to a more recent version."));
+
+ gtk_label_set_label (GTK_LABEL (self->label_end_of_life), str->str);
+ gtk_info_bar_set_revealed (GTK_INFO_BAR (self->infobar_end_of_life), TRUE);
+
+}
+
+static void
+gs_updates_page_get_system_finished_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ guint64 refine_flags;
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+ GsUpdatesPage *self = user_data;
+ GsPageHelper *helper;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ g_autoptr(GError) error = NULL;
+
+ app = gs_plugin_loader_get_system_app_finish (plugin_loader, res, &error);
+ if (app == 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_warning ("Failed to get system: %s", error->message);
+ return;
+ }
+
+ g_return_if_fail (GS_IS_UPDATES_PAGE (self));
+
+ refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION;
+
+ helper = gs_page_helper_new (self, app);
+ plugin_job = gs_plugin_job_refine_new_for_app (app, refine_flags);
+ gs_plugin_job_set_interactive (plugin_job, TRUE);
+ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+ self->cancellable,
+ gs_updates_page_refine_system_finished_cb,
+ helper);
+}
+
+static void
+gs_updates_page_load (GsUpdatesPage *self)
+{
+ guint64 refine_flags;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ if (self->action_cnt > 0)
+ return;
+
+ /* remove all existing apps */
+ for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++)
+ gs_updates_section_remove_all (self->sections[i]);
+
+ refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION;
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES);
+ self->action_cnt++;
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+ "interactive", TRUE,
+ "refine-flags", refine_flags,
+ "dedupe-flags", GS_APP_LIST_FILTER_FLAG_NONE,
+ NULL);
+ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+ self->cancellable,
+ (GAsyncReadyCallback) gs_updates_page_get_updates_cb,
+ self);
+
+ /* get the system state */
+ gs_plugin_loader_get_system_app_async (self->plugin_loader, self->cancellable,
+ gs_updates_page_get_system_finished_cb, self);
+
+ /* don't refresh every each time */
+ if ((self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPGRADES) == 0) {
+ refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED;
+ g_object_unref (plugin_job);
+ plugin_job = gs_plugin_job_list_distro_upgrades_new (GS_PLUGIN_LIST_DISTRO_UPGRADES_FLAGS_INTERACTIVE,
+ refine_flags);
+ gs_plugin_loader_job_process_async (self->plugin_loader,
+ plugin_job,
+ self->cancellable,
+ gs_updates_page_get_upgrades_cb,
+ self);
+ self->action_cnt++;
+ }
+}
+
+static void
+gs_updates_page_reload (GsPage *page)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (page);
+
+ if (self->state == GS_UPDATES_PAGE_STATE_ACTION_REFRESH) {
+ g_debug ("ignoring reload as refresh is already in progress");
+ return;
+ }
+
+ gs_updates_page_invalidate (self);
+ gs_updates_page_load (self);
+}
+
+static void
+gs_updates_page_switch_to (GsPage *page)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (page);
+
+ if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_UPDATES) {
+ g_warning ("Called switch_to(updates) when in mode %s",
+ gs_shell_get_mode_string (self->shell));
+ return;
+ }
+
+ gtk_widget_set_visible (self->button_refresh, TRUE);
+
+ /* no need to refresh */
+ if (self->cache_valid) {
+ gs_updates_page_update_ui_state (self);
+ return;
+ }
+
+ if (self->state == GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES) {
+ gs_updates_page_update_ui_state (self);
+ return;
+ }
+ gs_updates_page_load (self);
+}
+
+static void
+gs_updates_page_switch_from (GsPage *page)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (page);
+ gs_updates_page_remove_last_checked_timeout (self);
+}
+
+static void
+gs_updates_page_refresh_cb (GsPluginLoader *plugin_loader,
+ GAsyncResult *res,
+ GsUpdatesPage *self)
+{
+ gboolean ret;
+ g_autoptr(GDateTime) now = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /* get the results */
+ ret = gs_plugin_loader_job_action_finish (plugin_loader, res, &error);
+ if (!ret) {
+ /* user cancel */
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) ||
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_IDLE);
+ return;
+ }
+ g_warning ("failed to refresh: %s", error->message);
+ adw_status_page_set_description (ADW_STATUS_PAGE (self->updates_failed_page),
+ error->message);
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_FAILED);
+ return;
+ }
+
+ /* update the last checked timestamp */
+ now = g_date_time_new_now_local ();
+ g_settings_set (self->settings, "check-timestamp", "x",
+ g_date_time_to_unix (now));
+
+ /* get the new list */
+ gs_updates_page_invalidate (self);
+ gs_page_switch_to (GS_PAGE (self));
+ gs_page_scroll_up (GS_PAGE (self));
+}
+
+static void
+gs_updates_page_get_new_updates (GsUpdatesPage *self)
+{
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* force a check for updates and download */
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_ACTION_REFRESH);
+
+ g_cancellable_cancel (self->cancellable_refresh);
+ g_clear_object (&self->cancellable_refresh);
+ self->cancellable_refresh = g_cancellable_new ();
+
+ plugin_job = gs_plugin_job_refresh_metadata_new (1,
+ GS_PLUGIN_REFRESH_METADATA_FLAGS_INTERACTIVE);
+ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+ self->cancellable_refresh,
+ (GAsyncReadyCallback) gs_updates_page_refresh_cb,
+ self);
+}
+
+static void
+gs_updates_page_show_network_settings (GsUpdatesPage *self)
+{
+ g_autoptr(GError) error = NULL;
+ if (!g_spawn_command_line_async ("gnome-control-center wifi", &error))
+ g_warning ("Failed to open the control center: %s", error->message);
+}
+
+static void
+gs_updates_page_refresh_confirm_cb (GtkDialog *dialog,
+ GtkResponseType response_type,
+ GsUpdatesPage *self)
+{
+ /* unmap the dialog */
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ switch (response_type) {
+ case GTK_RESPONSE_REJECT:
+ /* open the control center */
+ gs_updates_page_show_network_settings (self);
+ break;
+ case GTK_RESPONSE_ACCEPT:
+ self->has_agreed_to_mobile_data = TRUE;
+ gs_updates_page_get_new_updates (self);
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_DELETE_EVENT:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gs_updates_page_button_network_settings_cb (GtkWidget *widget,
+ GsUpdatesPage *self)
+{
+ gs_updates_page_show_network_settings (self);
+}
+
+static void
+gs_updates_page_button_mobile_refresh_cb (GtkWidget *widget,
+ GsUpdatesPage *self)
+{
+ self->has_agreed_to_mobile_data = TRUE;
+ gs_updates_page_get_new_updates (self);
+}
+
+static void
+gs_updates_page_button_refresh_cb (GtkWidget *widget,
+ GsUpdatesPage *self)
+{
+ GtkWidget *dialog;
+ GtkWindow *parent_window = GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW));
+
+ /* cancel existing action? */
+ if (self->state == GS_UPDATES_PAGE_STATE_ACTION_REFRESH) {
+ g_cancellable_cancel (self->cancellable_refresh);
+ g_clear_object (&self->cancellable_refresh);
+ return;
+ }
+
+ /* check we have a "free" network connection */
+ if (gs_plugin_loader_get_network_available (self->plugin_loader) &&
+ !gs_plugin_loader_get_network_metered (self->plugin_loader)) {
+ gs_updates_page_get_new_updates (self);
+
+ /* expensive network connection */
+ } else if (gs_plugin_loader_get_network_available (self->plugin_loader) &&
+ gs_plugin_loader_get_network_metered (self->plugin_loader)) {
+ if (self->has_agreed_to_mobile_data) {
+ gs_updates_page_get_new_updates (self);
+ return;
+ }
+ dialog = gtk_message_dialog_new (parent_window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_USE_HEADER_BAR |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: this is to explain that downloading updates may cost money */
+ _("Charges May Apply"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ /* TRANSLATORS: we need network
+ * to do the updates check */
+ _("Checking for updates while using mobile broadband could cause you to incur charges."));
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ /* TRANSLATORS: this is a link to the
+ * control-center network panel */
+ _("Check _Anyway"),
+ GTK_RESPONSE_ACCEPT);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_updates_page_refresh_confirm_cb),
+ self);
+ gs_shell_modal_dialog_present (self->shell, GTK_WINDOW (dialog));
+
+ /* no network connection */
+ } else {
+ dialog = gtk_message_dialog_new (parent_window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_USE_HEADER_BAR |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: can't do updates check */
+ _("No Network"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ /* TRANSLATORS: we need network
+ * to do the updates check */
+ _("Internet access is required to check for updates."));
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ /* TRANSLATORS: this is a link to the
+ * control-center network panel */
+ _("Network Settings"),
+ GTK_RESPONSE_REJECT);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_updates_page_refresh_confirm_cb),
+ self);
+ gs_shell_modal_dialog_present (self->shell, GTK_WINDOW (dialog));
+ }
+}
+
+static void
+gs_updates_page_pending_apps_changed_cb (GsPluginLoader *plugin_loader,
+ GsUpdatesPage *self)
+{
+ gs_updates_page_invalidate (self);
+}
+
+static void
+upgrade_download_finished_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+ g_autoptr(GsPageHelper) helper = user_data;
+ g_autoptr(GError) error = NULL;
+
+ 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))
+ return;
+ g_warning ("failed to upgrade-download: %s", error->message);
+ }
+}
+
+static void
+gs_updates_page_upgrade_download_cb (GsUpgradeBanner *upgrade_banner,
+ GsUpdatesPage *self)
+{
+ GsApp *app;
+ GsPageHelper *helper;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ app = gs_upgrade_banner_get_app (upgrade_banner);
+ if (app == NULL) {
+ g_warning ("no upgrade available to download");
+ return;
+ }
+
+ helper = gs_page_helper_new (self, app);
+
+ if (self->cancellable_upgrade_download != NULL)
+ g_object_unref (self->cancellable_upgrade_download);
+ self->cancellable_upgrade_download = g_cancellable_new ();
+ g_debug ("Starting upgrade download with cancellable %p", self->cancellable_upgrade_download);
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
+ "interactive", TRUE,
+ "app", app,
+ NULL);
+ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+ self->cancellable_upgrade_download,
+ upgrade_download_finished_cb,
+ helper);
+}
+
+static void
+_cancel_trigger_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (user_data);
+ g_autoptr(GError) error = NULL;
+ if (!gs_plugin_loader_job_action_finish (self->plugin_loader, res, &error)) {
+ g_warning ("failed to cancel trigger: %s", error->message);
+ return;
+ }
+}
+
+static void
+upgrade_reboot_failed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsUpdatesPage *self = (GsUpdatesPage *) user_data;
+ GsApp *app;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* get result */
+ if (gs_utils_invoke_reboot_finish (source, res, &error))
+ return;
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_debug ("Calling reboot had been cancelled");
+ else if (error != NULL)
+ g_warning ("Calling reboot failed: %s", error->message);
+
+ app = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner));
+ if (app == NULL) {
+ g_warning ("no upgrade to cancel");
+ return;
+ }
+
+ /* cancel trigger */
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE_CANCEL,
+ "app", app,
+ NULL);
+ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+ self->cancellable,
+ _cancel_trigger_failed_cb,
+ self);
+}
+
+static void
+upgrade_trigger_finished_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GsUpdatesPage *self = (GsUpdatesPage *) user_data;
+ g_autoptr(GError) error = NULL;
+
+ /* get the results */
+ if (!gs_plugin_loader_job_action_finish (self->plugin_loader, res, &error)) {
+ g_warning ("Failed to trigger offline update: %s", error->message);
+ return;
+ }
+
+ /* trigger reboot */
+ gs_utils_invoke_reboot_async (NULL, upgrade_reboot_failed_cb, self);
+}
+
+static void
+trigger_upgrade (GsUpdatesPage *self)
+{
+ GsApp *upgrade;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ upgrade = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner));
+ if (upgrade == NULL) {
+ g_warning ("no upgrade available to install");
+ return;
+ }
+
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
+ "interactive", TRUE,
+ "app", upgrade,
+ NULL);
+ gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+ self->cancellable,
+ upgrade_trigger_finished_cb,
+ self);
+}
+
+static void
+gs_updates_page_upgrade_confirm_cb (GtkDialog *dialog,
+ GtkResponseType response_type,
+ GsUpdatesPage *self)
+{
+ /* unmap the dialog */
+ gtk_window_destroy (GTK_WINDOW (dialog));
+
+ switch (response_type) {
+ case GTK_RESPONSE_ACCEPT:
+ g_debug ("agreed to upgrade removing apps");
+ trigger_upgrade (self);
+ break;
+ case GTK_RESPONSE_CANCEL:
+ g_debug ("cancelled removal dialog");
+ break;
+ case GTK_RESPONSE_DELETE_EVENT:
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gs_updates_page_upgrade_install_cb (GsUpgradeBanner *upgrade_banner,
+ GsUpdatesPage *self)
+{
+ GsAppList *removals;
+ GsApp *upgrade;
+ GtkWidget *dialog;
+ guint cnt = 0;
+ guint i;
+
+ upgrade = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner));
+ if (upgrade == NULL) {
+ g_warning ("no upgrade available to install");
+ return;
+ }
+
+ /* count the removals */
+ removals = gs_app_get_related (upgrade);
+ for (i = 0; i < gs_app_list_length (removals); i++) {
+ GsApp *app = gs_app_list_index (removals, i);
+ if (gs_app_get_state (app) != GS_APP_STATE_UNAVAILABLE)
+ continue;
+ cnt++;
+ }
+
+ if (cnt == 0) {
+ /* no need for a removal confirmation dialog */
+ trigger_upgrade (self);
+ return;
+ }
+
+ dialog = gs_removal_dialog_new ();
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gs_updates_page_upgrade_confirm_cb),
+ self);
+ gs_removal_dialog_show_upgrade_removals (GS_REMOVAL_DIALOG (dialog),
+ upgrade);
+ gs_shell_modal_dialog_present (self->shell, GTK_WINDOW (dialog));
+}
+
+static void
+gs_updates_page_invalidate_downloaded_upgrade (GsUpdatesPage *self)
+{
+ GsApp *app;
+ app = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner));
+ if (app == NULL)
+ return;
+ if (gs_app_get_state (app) != GS_APP_STATE_UPDATABLE)
+ return;
+ gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+ g_debug ("resetting %s to AVAILABLE as the updates have changed",
+ gs_app_get_id (app));
+}
+
+static gboolean
+gs_shell_update_are_updates_in_progress (GsUpdatesPage *self)
+{
+ g_autoptr(GsAppList) list = _get_all_apps (self);
+ for (guint i = 0; i < gs_app_list_length (list); i++) {
+ GsApp *app = gs_app_list_index (list, i);
+ switch (gs_app_get_state (app)) {
+ case GS_APP_STATE_INSTALLING:
+ case GS_APP_STATE_REMOVING:
+ return TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ return FALSE;
+}
+
+static void
+gs_updates_page_changed_cb (GsPluginLoader *plugin_loader,
+ GsUpdatesPage *self)
+{
+ /* if we do a live update and the upgrade is waiting to be deployed
+ * then make sure all new packages are downloaded */
+ gs_updates_page_invalidate_downloaded_upgrade (self);
+
+ /* check to see if any apps in the app list are in a processing state */
+ if (gs_shell_update_are_updates_in_progress (self)) {
+ g_debug ("ignoring updates-changed as updates in progress");
+ return;
+ }
+
+ /* refresh updates list */
+ gs_updates_page_reload (GS_PAGE (self));
+}
+
+static void
+gs_updates_page_status_changed_cb (GsPluginLoader *plugin_loader,
+ GsApp *app,
+ GsPluginStatus status,
+ GsUpdatesPage *self)
+{
+ switch (status) {
+ case GS_PLUGIN_STATUS_INSTALLING:
+ case GS_PLUGIN_STATUS_REMOVING:
+ if (app == NULL ||
+ (gs_app_get_kind (app) != AS_COMPONENT_KIND_OPERATING_SYSTEM &&
+ gs_app_get_id (app) != NULL)) {
+ /* if we do a install or remove then make sure all new
+ * packages are downloaded */
+ gs_updates_page_invalidate_downloaded_upgrade (self);
+ }
+ break;
+ default:
+ break;
+ }
+
+ gs_updates_page_update_ui_state (self);
+}
+
+static void
+gs_updates_page_allow_updates_notify_cb (GsPluginLoader *plugin_loader,
+ GParamSpec *pspec,
+ GsUpdatesPage *self)
+{
+ if (!gs_plugin_loader_get_allow_updates (plugin_loader)) {
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_MANAGED);
+ return;
+ }
+ gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_IDLE);
+}
+
+static void
+gs_updates_page_upgrade_cancel_cb (GsUpgradeBanner *upgrade_banner,
+ GsUpdatesPage *self)
+{
+ g_debug ("Cancelling upgrade download with %p", self->cancellable_upgrade_download);
+ g_cancellable_cancel (self->cancellable_upgrade_download);
+}
+
+static gboolean
+gs_updates_page_setup (GsPage *page,
+ GsShell *shell,
+ GsPluginLoader *plugin_loader,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (page);
+
+ g_return_val_if_fail (GS_IS_UPDATES_PAGE (self), TRUE);
+
+ for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) {
+ self->sections[i] = gs_updates_section_new (i, plugin_loader, page);
+ gs_updates_section_set_size_groups (self->sections[i],
+ self->sizegroup_name,
+ self->sizegroup_button_label,
+ self->sizegroup_button_image,
+ self->sizegroup_header);
+ gtk_widget_set_vexpand (GTK_WIDGET (self->sections[i]), FALSE);
+ g_object_bind_property (G_OBJECT (self), "is-narrow",
+ self->sections[i], "is-narrow",
+ G_BINDING_SYNC_CREATE);
+ gtk_box_append (GTK_BOX (self->updates_box), GTK_WIDGET (self->sections[i]));
+ }
+
+ self->shell = shell;
+
+ self->plugin_loader = g_object_ref (plugin_loader);
+ g_signal_connect (self->plugin_loader, "pending-apps-changed",
+ G_CALLBACK (gs_updates_page_pending_apps_changed_cb),
+ self);
+ g_signal_connect (self->plugin_loader, "status-changed",
+ G_CALLBACK (gs_updates_page_status_changed_cb),
+ self);
+ g_signal_connect (self->plugin_loader, "updates-changed",
+ G_CALLBACK (gs_updates_page_changed_cb),
+ self);
+ g_signal_connect_object (self->plugin_loader, "notify::allow-updates",
+ G_CALLBACK (gs_updates_page_allow_updates_notify_cb),
+ self, 0);
+ g_signal_connect_object (self->plugin_loader, "notify::network-available",
+ G_CALLBACK (gs_updates_page_network_available_notify_cb),
+ self, 0);
+ self->cancellable = g_object_ref (cancellable);
+
+ /* setup system upgrades */
+ g_signal_connect (self->upgrade_banner, "download-clicked",
+ G_CALLBACK (gs_updates_page_upgrade_download_cb), self);
+ g_signal_connect (self->upgrade_banner, "install-clicked",
+ G_CALLBACK (gs_updates_page_upgrade_install_cb), self);
+ g_signal_connect (self->upgrade_banner, "cancel-clicked",
+ G_CALLBACK (gs_updates_page_upgrade_cancel_cb), self);
+
+ self->header_start_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_widget_set_visible (self->header_start_box, TRUE);
+ gs_page_set_header_start_widget (GS_PAGE (self), self->header_start_box);
+
+ self->header_spinner_start = gtk_spinner_new ();
+ gtk_box_prepend (GTK_BOX (self->header_start_box), self->header_spinner_start);
+
+ /* setup update details window */
+ self->button_refresh = gtk_button_new_from_icon_name ("view-refresh-symbolic");
+ gtk_accessible_update_property (GTK_ACCESSIBLE (self->button_refresh),
+ GTK_ACCESSIBLE_PROPERTY_LABEL, _("Check for updates"),
+ -1);
+ gtk_box_prepend (GTK_BOX (self->header_start_box), self->button_refresh);
+ g_signal_connect (self->button_refresh, "clicked",
+ G_CALLBACK (gs_updates_page_button_refresh_cb),
+ self);
+
+ g_signal_connect (self->button_updates_mobile, "clicked",
+ G_CALLBACK (gs_updates_page_button_mobile_refresh_cb),
+ self);
+ g_signal_connect (self->button_updates_offline, "clicked",
+ G_CALLBACK (gs_updates_page_button_network_settings_cb),
+ self);
+
+ /* set initial state */
+ if (!gs_plugin_loader_get_allow_updates (self->plugin_loader))
+ self->state = GS_UPDATES_PAGE_STATE_MANAGED;
+ return TRUE;
+}
+
+static void
+gs_updates_page_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (object);
+
+ switch ((GsUpdatesPageProperty) prop_id) {
+ case PROP_IS_NARROW:
+ g_value_set_boolean (value, gs_updates_page_get_is_narrow (self));
+ break;
+ case PROP_VADJUSTMENT:
+ g_value_set_object (value, gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolledwindow_updates)));
+ break;
+ case PROP_TITLE:
+ g_value_set_string (value, C_("Apps to be updated", "Updates"));
+ break;
+ case PROP_COUNTER:
+ g_value_set_uint (value, self->updates_counter);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_updates_page_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (object);
+
+ switch ((GsUpdatesPageProperty) prop_id) {
+ case PROP_IS_NARROW:
+ gs_updates_page_set_is_narrow (self, g_value_get_boolean (value));
+ break;
+ case PROP_VADJUSTMENT:
+ case PROP_TITLE:
+ case PROP_COUNTER:
+ /* Read only */
+ g_assert_not_reached ();
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_updates_page_dispose (GObject *object)
+{
+ GsUpdatesPage *self = GS_UPDATES_PAGE (object);
+
+ gs_updates_page_remove_last_checked_timeout (self);
+
+ g_cancellable_cancel (self->cancellable_refresh);
+ g_clear_object (&self->cancellable_refresh);
+ g_cancellable_cancel (self->cancellable_upgrade_download);
+ g_clear_object (&self->cancellable_upgrade_download);
+
+ for (guint i = 0; i < GS_UPDATES_SECTION_KIND_LAST; i++) {
+ if (self->sections[i] != NULL) {
+ gtk_widget_unparent (GTK_WIDGET (self->sections[i]));
+ self->sections[i] = NULL;
+ }
+ }
+
+ g_clear_object (&self->plugin_loader);
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->settings);
+ g_clear_object (&self->desktop_settings);
+
+ g_clear_object (&self->sizegroup_name);
+ g_clear_object (&self->sizegroup_button_label);
+ g_clear_object (&self->sizegroup_button_image);
+ g_clear_object (&self->sizegroup_header);
+
+ G_OBJECT_CLASS (gs_updates_page_parent_class)->dispose (object);
+}
+
+static void
+gs_updates_page_class_init (GsUpdatesPageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GsPageClass *page_class = GS_PAGE_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = gs_updates_page_get_property;
+ object_class->set_property = gs_updates_page_set_property;
+ object_class->dispose = gs_updates_page_dispose;
+
+ page_class->switch_to = gs_updates_page_switch_to;
+ page_class->switch_from = gs_updates_page_switch_from;
+ page_class->reload = gs_updates_page_reload;
+ page_class->setup = gs_updates_page_setup;
+
+ /**
+ * GsUpdatesPage:is-narrow:
+ *
+ * Whether the page is in narrow mode.
+ *
+ * In narrow mode, the page will take up less horizontal space, doing so
+ * by e.g. using icons rather than labels in buttons. This is needed to
+ * keep the UI useable on small form-factors like smartphones.
+ *
+ * Since: 41
+ */
+ obj_props[PROP_IS_NARROW] =
+ g_param_spec_boolean ("is-narrow", NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
+
+ g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment");
+ g_object_class_override_property (object_class, PROP_TITLE, "title");
+ g_object_class_override_property (object_class, PROP_COUNTER, "counter");
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-updates-page.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, updates_box);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, button_updates_mobile);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, button_updates_offline);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, updates_failed_page);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, uptodate_description);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, scrolledwindow_updates);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, spinner_updates);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, stack_updates);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, upgrade_banner);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, infobar_end_of_life);
+ gtk_widget_class_bind_template_child (widget_class, GsUpdatesPage, label_end_of_life);
+}
+
+static void
+gs_updates_page_init (GsUpdatesPage *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->state = GS_UPDATES_PAGE_STATE_STARTUP;
+ self->settings = g_settings_new ("org.gnome.software");
+ self->desktop_settings = g_settings_new ("org.gnome.desktop.interface");
+
+ self->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->sizegroup_button_label = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->sizegroup_button_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ self->sizegroup_header = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
+
+}
+
+/**
+ * gs_updates_page_get_is_narrow:
+ * @self: a #GsUpdatesPage
+ *
+ * Get the value of #GsUpdatesPage:is-narrow.
+ *
+ * Returns: %TRUE if the page is in narrow mode, %FALSE otherwise
+ *
+ * Since: 41
+ */
+gboolean
+gs_updates_page_get_is_narrow (GsUpdatesPage *self)
+{
+ g_return_val_if_fail (GS_IS_UPDATES_PAGE (self), FALSE);
+
+ return self->is_narrow;
+}
+
+/**
+ * gs_updates_page_set_is_narrow:
+ * @self: a #GsUpdatesPage
+ * @is_narrow: %TRUE to set the page in narrow mode, %FALSE otherwise
+ *
+ * Set the value of #GsUpdatesPage:is-narrow.
+ *
+ * Since: 41
+ */
+void
+gs_updates_page_set_is_narrow (GsUpdatesPage *self, gboolean is_narrow)
+{
+ g_return_if_fail (GS_IS_UPDATES_PAGE (self));
+
+ is_narrow = !!is_narrow;
+
+ if (self->is_narrow == is_narrow)
+ return;
+
+ self->is_narrow = is_narrow;
+ g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_IS_NARROW]);
+}
+
+GsUpdatesPage *
+gs_updates_page_new (void)
+{
+ return GS_UPDATES_PAGE (g_object_new (GS_TYPE_UPDATES_PAGE, NULL));
+}