Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
gnome-software/src/gs-update-monitor.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1501 lines (1281 sloc)
48 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- | |
| * vi:set noexpandtab tabstop=8 shiftwidth=8: | |
| * | |
| * Copyright (C) 2013-2018 Richard Hughes <richard@hughsie.com> | |
| * Copyright (C) 2013 Matthias Clasen <mclasen@redhat.com> | |
| * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com> | |
| * | |
| * SPDX-License-Identifier: GPL-2.0-or-later | |
| */ | |
| #include "config.h" | |
| #include <string.h> | |
| #include <glib/gi18n.h> | |
| #include <gsettings-desktop-schemas/gdesktop-enums.h> | |
| #include <locale.h> | |
| #include "gs-update-monitor.h" | |
| #include "gs-common.h" | |
| #define SECONDS_IN_AN_HOUR (60 * 60) | |
| #define SECONDS_IN_A_DAY (SECONDS_IN_AN_HOUR * 24) | |
| #define MINUTES_IN_A_DAY (SECONDS_IN_A_DAY / 60) | |
| struct _GsUpdateMonitor { | |
| GObject parent; | |
| GsApplication *application; | |
| /* We use three cancellables: | |
| * - @shutdown_cancellable is cancelled only during shutdown/dispose of | |
| * the #GsUpdateMonitor, to avoid long-running operations keeping the | |
| * monitor alive. | |
| * - @update_cancellable is for update/upgrade operations, and is | |
| * cancelled if they should be cancelled, such as if the computer has | |
| * to start trying to save power. | |
| * - @refresh_cancellable is for refreshes and other inconsequential | |
| * operations which can be cancelled more readily than | |
| * @update_cancellable with fewer consequences. It’s cancelled if the | |
| * computer is going into low power mode, or if network connectivity | |
| * changes. | |
| */ | |
| GCancellable *shutdown_cancellable; /* (owned) (not nullable) */ | |
| GCancellable *update_cancellable; /* (owned) (not nullable) */ | |
| GCancellable *refresh_cancellable; /* (owned) (not nullable) */ | |
| GSettings *settings; | |
| GsPluginLoader *plugin_loader; | |
| GDBusProxy *proxy_upower; | |
| GError *last_offline_error; | |
| GNetworkMonitor *network_monitor; | |
| guint network_changed_handler; | |
| #if GLIB_CHECK_VERSION(2, 69, 1) | |
| GPowerProfileMonitor *power_profile_monitor; /* (owned) (nullable) */ | |
| gulong power_profile_changed_handler; | |
| #endif | |
| guint cleanup_notifications_id; /* at startup */ | |
| guint check_startup_id; /* 60s after startup */ | |
| guint check_hourly_id; /* and then every hour */ | |
| guint check_daily_id; /* every 3rd day */ | |
| gint64 last_notification_time_usec; /* to notify once per day only */ | |
| }; | |
| G_DEFINE_TYPE (GsUpdateMonitor, gs_update_monitor, G_TYPE_OBJECT) | |
| typedef struct { | |
| GsUpdateMonitor *monitor; | |
| } DownloadUpdatesData; | |
| static void | |
| download_updates_data_free (DownloadUpdatesData *data) | |
| { | |
| g_clear_object (&data->monitor); | |
| g_slice_free (DownloadUpdatesData, data); | |
| } | |
| G_DEFINE_AUTOPTR_CLEANUP_FUNC(DownloadUpdatesData, download_updates_data_free); | |
| typedef struct { | |
| GsUpdateMonitor *monitor; | |
| GsApp *app; | |
| } WithAppData; | |
| static WithAppData * | |
| with_app_data_new (GsUpdateMonitor *monitor, | |
| GsApp *app) | |
| { | |
| WithAppData *data; | |
| data = g_slice_new0 (WithAppData); | |
| data->monitor = g_object_ref (monitor); | |
| data->app = g_object_ref (app); | |
| return data; | |
| } | |
| static void | |
| with_app_data_free (WithAppData *data) | |
| { | |
| g_clear_object (&data->monitor); | |
| g_clear_object (&data->app); | |
| g_slice_free (WithAppData, data); | |
| } | |
| G_DEFINE_AUTOPTR_CLEANUP_FUNC(WithAppData, with_app_data_free); | |
| static void | |
| check_updates_kind (GsAppList *apps, | |
| gboolean *out_has_important, | |
| gboolean *out_all_downloaded, | |
| gboolean *out_any_downloaded) | |
| { | |
| gboolean has_important, all_downloaded, any_downloaded; | |
| guint ii, len; | |
| GsApp *app; | |
| len = gs_app_list_length (apps); | |
| has_important = FALSE; | |
| all_downloaded = len > 0; | |
| any_downloaded = FALSE; | |
| for (ii = 0; ii < len && (!has_important || all_downloaded || !any_downloaded); ii++) { | |
| gboolean is_important; | |
| app = gs_app_list_index (apps, ii); | |
| is_important = gs_app_get_update_urgency (app) == AS_URGENCY_KIND_CRITICAL; | |
| has_important = has_important || is_important; | |
| if (gs_app_is_downloaded (app)) | |
| any_downloaded = TRUE; | |
| else | |
| all_downloaded = FALSE; | |
| } | |
| *out_has_important = has_important; | |
| *out_all_downloaded = all_downloaded; | |
| *out_any_downloaded = any_downloaded; | |
| } | |
| static gboolean | |
| get_timestamp_difference_days (GsUpdateMonitor *monitor, const gchar *timestamp, gint64 *out_days) | |
| { | |
| gint64 tmp; | |
| g_autoptr(GDateTime) last_update = NULL; | |
| g_autoptr(GDateTime) now = NULL; | |
| g_return_val_if_fail (out_days != NULL, FALSE); | |
| g_settings_get (monitor->settings, timestamp, "x", &tmp); | |
| if (tmp == 0) | |
| return FALSE; | |
| last_update = g_date_time_new_from_unix_local (tmp); | |
| if (last_update == NULL) { | |
| g_warning ("failed to set timestamp %" G_GINT64_FORMAT, tmp); | |
| return FALSE; | |
| } | |
| now = g_date_time_new_now_local (); | |
| *out_days = g_date_time_difference (now, last_update) / G_TIME_SPAN_DAY; | |
| return TRUE; | |
| } | |
| static gboolean | |
| check_if_timestamp_more_than_days_ago (GsUpdateMonitor *monitor, const gchar *timestamp, guint days) | |
| { | |
| gint64 timestamp_days; | |
| if (!get_timestamp_difference_days (monitor, timestamp, ×tamp_days)) | |
| return TRUE; | |
| return timestamp_days >= days; | |
| } | |
| static gboolean | |
| should_download_updates (GsUpdateMonitor *monitor) | |
| { | |
| #ifdef HAVE_MOGWAI | |
| return TRUE; | |
| #else | |
| return g_settings_get_boolean (monitor->settings, "download-updates"); | |
| #endif | |
| } | |
| /* The days below are discussed at https://gitlab.gnome.org/GNOME/gnome-software/-/issues/947 | |
| and https://wiki.gnome.org/Design/Apps/Software/Updates#Tentative_Design */ | |
| static gboolean | |
| should_notify_about_pending_updates (GsUpdateMonitor *monitor, | |
| GsAppList *apps, | |
| const gchar **out_title, | |
| const gchar **out_body) | |
| { | |
| gboolean has_important = FALSE, all_downloaded = FALSE, any_downloaded = FALSE; | |
| gboolean should_download, res = FALSE; | |
| gint64 timestamp_days; | |
| if (!get_timestamp_difference_days (monitor, "update-notification-timestamp", ×tamp_days)) { | |
| /* Large-enough number to succeed for the initial test */ | |
| timestamp_days = 365; | |
| } | |
| should_download = should_download_updates (monitor); | |
| check_updates_kind (apps, &has_important, &all_downloaded, &any_downloaded); | |
| if (!gs_app_list_length (apps)) { | |
| /* Notify only when the download is disabled and it's the 4th day or it's more than 7 days */ | |
| if (!should_download && (timestamp_days >= 7 || timestamp_days == 4)) { | |
| *out_title = _("Software Updates Are Out of Date"); | |
| *out_body = _("Please check for software updates."); | |
| res = TRUE; | |
| } | |
| } else if (has_important) { | |
| if (timestamp_days >= 1) { | |
| if (all_downloaded) { | |
| *out_title = _("Critical Software Update Ready to Install"); | |
| *out_body = _("An important software update is ready to be installed."); | |
| res = TRUE; | |
| } else if (!should_download) { | |
| *out_title = _("Critical Software Updates Available to Download"); | |
| *out_body = _("Important: critical software updates are waiting."); | |
| res = TRUE; | |
| } | |
| } | |
| } else if (all_downloaded) { | |
| if (timestamp_days >= 3) { | |
| *out_title = _("Software Updates Ready to Install"); | |
| *out_body = _("Software updates are waiting and ready to be installed."); | |
| res = TRUE; | |
| } | |
| /* To not hide downloaded updates for 14 days when new updates were discovered meanwhile. | |
| Never show "Available to Download" when it's supposed to download the updates. */ | |
| } else if (!should_download && timestamp_days >= 14) { | |
| *out_title = _("Software Updates Available to Download"); | |
| *out_body = _("Please download waiting software updates."); | |
| res = TRUE; | |
| } | |
| g_debug ("%s: last_test_days:%" G_GINT64_FORMAT " n-apps:%u should_download:%d has_important:%d " | |
| "all_downloaded:%d any_downloaded:%d res:%d%s%s%s%s", G_STRFUNC, | |
| timestamp_days, gs_app_list_length (apps), should_download, has_important, | |
| all_downloaded, any_downloaded, res, | |
| res ? " reason:" : "", | |
| res ? *out_title : "", | |
| res ? "|" : "", | |
| res ? *out_body : ""); | |
| return res; | |
| } | |
| static void | |
| reset_update_notification_timestamp (GsUpdateMonitor *monitor) | |
| { | |
| g_autoptr(GDateTime) now = NULL; | |
| now = g_date_time_new_now_local (); | |
| g_settings_set (monitor->settings, "update-notification-timestamp", "x", | |
| g_date_time_to_unix (now)); | |
| } | |
| static void | |
| notify_about_pending_updates (GsUpdateMonitor *monitor, | |
| GsAppList *apps) | |
| { | |
| const gchar *title = NULL, *body = NULL; | |
| gint64 time_diff_sec; | |
| g_autoptr(GNotification) nn = NULL; | |
| 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; | |
| } | |
| if (!should_notify_about_pending_updates (monitor, apps, &title, &body)) { | |
| g_debug ("No update notification needed"); | |
| return; | |
| } | |
| /* To force reload of the Updates page, thus it reflects what | |
| the update-monitor notifies about */ | |
| gs_plugin_loader_emit_updates_changed (monitor->plugin_loader); | |
| monitor->last_notification_time_usec = g_get_real_time (); | |
| g_debug ("Notify about update: '%s'", title); | |
| nn = g_notification_new (title); | |
| g_notification_set_body (nn, body); | |
| g_notification_set_default_action_and_target (nn, "app.set-mode", "s", "updates"); | |
| gs_application_send_notification (monitor->application, "updates-available", nn, MINUTES_IN_A_DAY); | |
| /* Keep the old notification time when there are no updates and the update download is disabled, | |
| to notify the user every day after 7 days of no update check */ | |
| if (gs_app_list_length (apps) || | |
| should_download_updates (monitor)) | |
| reset_update_notification_timestamp (monitor); | |
| } | |
| static gboolean | |
| _filter_by_app_kind (GsApp *app, gpointer user_data) | |
| { | |
| AsComponentKind kind = GPOINTER_TO_UINT (user_data); | |
| return gs_app_get_kind (app) == kind; | |
| } | |
| static gboolean | |
| _sort_by_rating_cb (GsApp *app1, GsApp *app2, gpointer user_data) | |
| { | |
| if (gs_app_get_rating (app1) < gs_app_get_rating (app2)) | |
| return -1; | |
| if (gs_app_get_rating (app1) > gs_app_get_rating (app2)) | |
| return 1; | |
| return 0; | |
| } | |
| static GNotification * | |
| _build_autoupdated_notification (GsUpdateMonitor *monitor, GsAppList *list) | |
| { | |
| guint need_restart_cnt = 0; | |
| g_autoptr(GsAppList) list_apps = NULL; | |
| g_autoptr(GNotification) n = NULL; | |
| g_autoptr(GString) body = g_string_new (NULL); | |
| g_autofree gchar *title = NULL; | |
| /* filter out apps */ | |
| list_apps = gs_app_list_copy (list); | |
| gs_app_list_filter (list_apps, | |
| _filter_by_app_kind, | |
| GUINT_TO_POINTER(AS_COMPONENT_KIND_DESKTOP_APP)); | |
| gs_app_list_sort (list_apps, _sort_by_rating_cb, NULL); | |
| /* FIXME: add the apps that are currently active that use one | |
| * of the updated runtimes */ | |
| if (gs_app_list_length (list_apps) == 0) { | |
| g_debug ("no desktop apps in updated list, ignoring"); | |
| return NULL; | |
| } | |
| /* how many apps needs updating */ | |
| for (guint i = 0; i < gs_app_list_length (list_apps); i++) { | |
| GsApp *app = gs_app_list_index (list_apps, i); | |
| if (gs_app_has_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT)) | |
| need_restart_cnt++; | |
| } | |
| /* >1 app updated */ | |
| if (gs_app_list_length (list_apps) > 0) { | |
| if (need_restart_cnt > 0) { | |
| /* TRANSLATORS: apps were auto-updated and restart is required */ | |
| title = g_strdup_printf (ngettext ("%u App Updated — Restart Required", | |
| "%u Apps Updated — Restart Required", | |
| gs_app_list_length (list_apps)), | |
| gs_app_list_length (list_apps)); | |
| } else { | |
| /* TRANSLATORS: apps were auto-updated */ | |
| title = g_strdup_printf (ngettext ("%u App Updated", | |
| "%u Apps Updated", | |
| gs_app_list_length (list_apps)), | |
| gs_app_list_length (list_apps)); | |
| } | |
| } | |
| /* 1 app updated */ | |
| if (gs_app_list_length (list_apps) == 1) { | |
| GsApp *app = gs_app_list_index (list_apps, 0); | |
| /* TRANSLATORS: %1 is an app name, e.g. Firefox */ | |
| g_string_append_printf (body, _("%s has been updated."), gs_app_get_name (app)); | |
| if (need_restart_cnt > 0) { | |
| /* TRANSLATORS: the app needs restarting */ | |
| g_string_append_printf (body, " %s", _("Please restart the app.")); | |
| } | |
| /* 2 apps updated */ | |
| } else if (gs_app_list_length (list_apps) == 2) { | |
| GsApp *app1 = gs_app_list_index (list_apps, 0); | |
| GsApp *app2 = gs_app_list_index (list_apps, 1); | |
| /* TRANSLATORS: %1 and %2 are both app names, e.g. Firefox */ | |
| g_string_append_printf (body, _("%s and %s have been updated."), | |
| gs_app_get_name (app1), | |
| gs_app_get_name (app2)); | |
| if (need_restart_cnt > 0) { | |
| g_string_append (body, " "); | |
| /* TRANSLATORS: at least one app needs restarting */ | |
| g_string_append_printf (body, ngettext ("%u app requires a restart.", | |
| "%u apps require a restart.", | |
| need_restart_cnt), | |
| need_restart_cnt); | |
| } | |
| /* 3+ apps */ | |
| } else if (gs_app_list_length (list_apps) >= 3) { | |
| GsApp *app1 = gs_app_list_index (list_apps, 0); | |
| GsApp *app2 = gs_app_list_index (list_apps, 1); | |
| GsApp *app3 = gs_app_list_index (list_apps, 2); | |
| /* TRANSLATORS: %1, %2 and %3 are all app names, e.g. Firefox */ | |
| g_string_append_printf (body, _("Includes %s, %s and %s."), | |
| gs_app_get_name (app1), | |
| gs_app_get_name (app2), | |
| gs_app_get_name (app3)); | |
| if (need_restart_cnt > 0) { | |
| g_string_append (body, " "); | |
| /* TRANSLATORS: at least one app needs restarting */ | |
| g_string_append_printf (body, ngettext ("%u app requires a restart.", | |
| "%u apps require a restart.", | |
| need_restart_cnt), | |
| need_restart_cnt); | |
| } | |
| } | |
| /* create the notification */ | |
| n = g_notification_new (title); | |
| if (body->len > 0) | |
| g_notification_set_body (n, body->str); | |
| g_notification_set_default_action_and_target (n, "app.set-mode", "s", "updated"); | |
| return g_steal_pointer (&n); | |
| } | |
| typedef struct { | |
| GsUpdateMonitor *monitor; /* (owned) */ | |
| GsPluginJob *job; /* (owned) */ | |
| } UpdateAppsData; | |
| static void | |
| update_apps_data_free (UpdateAppsData *data) | |
| { | |
| g_clear_object (&data->monitor); | |
| g_clear_object (&data->job); | |
| g_free (data); | |
| } | |
| G_DEFINE_AUTOPTR_CLEANUP_FUNC (UpdateAppsData, update_apps_data_free) | |
| static void | |
| update_finished_cb (GObject *object, GAsyncResult *res, gpointer user_data) | |
| { | |
| g_autoptr(UpdateAppsData) data = g_steal_pointer (&user_data); | |
| GsUpdateMonitor *monitor = data->monitor; | |
| GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object); | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GsAppList) list = NULL; | |
| /* get result */ | |
| list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); | |
| if (list == NULL) { | |
| gs_plugin_loader_claim_job_error (plugin_loader, | |
| NULL, | |
| data->job, | |
| error); | |
| return; | |
| } | |
| /* notifications are optional */ | |
| if (g_settings_get_boolean (monitor->settings, "download-updates-notify")) { | |
| g_autoptr(GNotification) n = NULL; | |
| gs_application_withdraw_notification (monitor->application, "updates-installed"); | |
| n = _build_autoupdated_notification (monitor, list); | |
| if (n != NULL) | |
| gs_application_send_notification (monitor->application, "updates-installed", n, MINUTES_IN_A_DAY); | |
| } | |
| } | |
| static gboolean | |
| _should_auto_update (GsApp *app) | |
| { | |
| if (gs_app_get_state (app) != GS_APP_STATE_UPDATABLE_LIVE) | |
| return FALSE; | |
| if (gs_app_has_quirk (app, GS_APP_QUIRK_NEW_PERMISSIONS)) | |
| return FALSE; | |
| if (gs_app_has_quirk (app, GS_APP_QUIRK_DO_NOT_AUTO_UPDATE)) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| static void | |
| download_finished_cb (GObject *object, GAsyncResult *res, gpointer user_data) | |
| { | |
| g_autoptr(UpdateAppsData) data = g_steal_pointer (&user_data); | |
| GsUpdateMonitor *monitor = data->monitor; | |
| GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object); | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GsAppList) list = NULL; | |
| g_autoptr(GsAppList) update_online = NULL; | |
| g_autoptr(GsAppList) update_offline = NULL; | |
| GsAppList *job_apps; | |
| /* the returned list is always empty, the existence indicates success */ | |
| list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); | |
| if (list == NULL) { | |
| gs_plugin_loader_claim_job_error (plugin_loader, | |
| NULL, | |
| data->job, | |
| error); | |
| return; | |
| } | |
| job_apps = gs_plugin_job_update_apps_get_apps (GS_PLUGIN_JOB_UPDATE_APPS (data->job)); | |
| update_online = gs_app_list_new (); | |
| update_offline = gs_app_list_new (); | |
| for (guint i = 0; i < gs_app_list_length (job_apps); i++) { | |
| GsApp *app = gs_app_list_index (job_apps, i); | |
| if (_should_auto_update (app)) { | |
| g_debug ("auto-updating %s", gs_app_get_unique_id (app)); | |
| gs_app_list_add (update_online, app); | |
| } else { | |
| gs_app_list_add (update_offline, app); | |
| } | |
| } | |
| /* install any apps that can be installed LIVE */ | |
| if (gs_app_list_length (update_online) > 0) { | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| plugin_job = gs_plugin_job_update_apps_new (update_online, | |
| GS_PLUGIN_UPDATE_APPS_FLAGS_NONE); | |
| gs_plugin_job_set_propagate_error (plugin_job, TRUE); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->update_cancellable, | |
| update_finished_cb, | |
| g_steal_pointer (&data)); | |
| } | |
| /* show a notification for offline updates */ | |
| if (gs_app_list_length (update_offline) > 0) | |
| notify_about_pending_updates (monitor, update_offline); | |
| } | |
| static void | |
| get_updates_finished_cb (GObject *object, GAsyncResult *res, gpointer user_data) | |
| { | |
| g_autoptr(DownloadUpdatesData) download_updates_data = (DownloadUpdatesData *) user_data; | |
| GsUpdateMonitor *monitor = download_updates_data->monitor; | |
| guint64 security_timestamp = 0; | |
| guint64 security_timestamp_old = 0; | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GsAppList) apps = NULL; | |
| gboolean should_download; | |
| /* get result */ | |
| apps = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error); | |
| if (apps == 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 updates: %s", error->message); | |
| return; | |
| } | |
| /* no updates */ | |
| if (gs_app_list_length (apps) == 0) { | |
| g_debug ("no updates; withdrawing updates-available notification"); | |
| gs_application_withdraw_notification (monitor->application, "updates-available"); | |
| return; | |
| } | |
| /* find security updates, or clear timestamp if there are now none */ | |
| g_settings_get (monitor->settings, | |
| "security-timestamp", "x", &security_timestamp_old); | |
| for (guint i = 0; i < gs_app_list_length (apps); i++) { | |
| GsApp *app = gs_app_list_index (apps, i); | |
| guint64 size_download_bytes; | |
| GsSizeType size_download_type = gs_app_get_size_download (app, &size_download_bytes); | |
| if (gs_app_get_update_urgency (app) == AS_URGENCY_KIND_CRITICAL && | |
| size_download_type == GS_SIZE_TYPE_VALID && | |
| size_download_bytes > 0) { | |
| security_timestamp = (guint64) g_get_monotonic_time (); | |
| break; | |
| } | |
| } | |
| if (security_timestamp_old != security_timestamp) { | |
| g_settings_set (monitor->settings, | |
| "security-timestamp", "x", security_timestamp); | |
| } | |
| g_debug ("got %u updates", gs_app_list_length (apps)); | |
| should_download = should_download_updates (monitor); | |
| if (should_download && | |
| (security_timestamp_old != security_timestamp || | |
| check_if_timestamp_more_than_days_ago (monitor, "install-timestamp", 14))) { | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| g_autoptr(UpdateAppsData) data = NULL; | |
| /* download any updates; individual plugins are responsible for deciding | |
| * whether it’s appropriate to unconditionally download the updates, or | |
| * to schedule the download in accordance with the user’s metered data | |
| * preferences */ | |
| plugin_job = gs_plugin_job_update_apps_new (apps, | |
| GS_PLUGIN_UPDATE_APPS_FLAGS_NO_APPLY); | |
| gs_plugin_job_set_propagate_error (plugin_job, TRUE); | |
| data = g_new0 (UpdateAppsData, 1); | |
| data->monitor = g_object_ref (monitor); | |
| data->job = g_object_ref (plugin_job); | |
| g_debug ("Getting updates"); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->refresh_cancellable, | |
| download_finished_cb, | |
| g_steal_pointer (&data)); | |
| } else { | |
| g_autoptr(GsAppList) update_online = NULL; | |
| g_autoptr(GsAppList) update_offline = NULL; | |
| GsAppList *notify_list; | |
| update_online = gs_app_list_new (); | |
| update_offline = gs_app_list_new (); | |
| for (guint i = 0; i < gs_app_list_length (apps); i++) { | |
| GsApp *app = gs_app_list_index (apps, i); | |
| if (_should_auto_update (app)) { | |
| g_debug ("download for auto-update %s", gs_app_get_unique_id (app)); | |
| gs_app_list_add (update_online, app); | |
| } else { | |
| gs_app_list_add (update_offline, app); | |
| } | |
| } | |
| g_debug ("Received %u apps to update, %u are online and %u offline updates; will%s download online updates", | |
| gs_app_list_length (apps), | |
| gs_app_list_length (update_online), | |
| gs_app_list_length (update_offline), | |
| should_download ? "" : " not"); | |
| if (should_download && gs_app_list_length (update_online) > 0) { | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| g_autoptr(UpdateAppsData) data = NULL; | |
| plugin_job = gs_plugin_job_update_apps_new (update_online, | |
| GS_PLUGIN_UPDATE_APPS_FLAGS_NO_APPLY); | |
| gs_plugin_job_set_propagate_error (plugin_job, TRUE); | |
| data = g_new0 (UpdateAppsData, 1); | |
| data->monitor = g_object_ref (monitor); | |
| data->job = g_object_ref (plugin_job); | |
| g_debug ("Getting %u online updates", gs_app_list_length (update_online)); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->refresh_cancellable, | |
| download_finished_cb, | |
| g_steal_pointer (&data)); | |
| } | |
| if (should_download) | |
| notify_list = update_offline; | |
| else | |
| notify_list = apps; | |
| notify_about_pending_updates (monitor, notify_list); | |
| } | |
| } | |
| static gboolean | |
| should_show_upgrade_notification (GsUpdateMonitor *monitor) | |
| { | |
| return check_if_timestamp_more_than_days_ago (monitor, "upgrade-notification-timestamp", 7); | |
| } | |
| static void | |
| get_system_finished_cb (GObject *object, GAsyncResult *res, gpointer data) | |
| { | |
| GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object); | |
| GsUpdateMonitor *monitor = data; | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GNotification) n = NULL; | |
| g_autoptr(GsApp) app = NULL; | |
| /* get result */ | |
| 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; | |
| } | |
| /* might be already showing, so just withdraw it and re-issue it */ | |
| gs_application_withdraw_notification (monitor->application, "eol"); | |
| /* do not show when the main window is active */ | |
| if (gs_application_has_active_window (monitor->application)) | |
| return; | |
| /* is not EOL */ | |
| if (gs_app_get_state (app) != GS_APP_STATE_UNAVAILABLE) | |
| return; | |
| /* TRANSLATORS: this is when the current operating system version goes end-of-life */ | |
| n = g_notification_new (_("Operating System Updates Unavailable")); | |
| /* TRANSLATORS: this is the message dialog for the distro EOL notice */ | |
| g_notification_set_body (n, _("Upgrade to continue receiving security updates.")); | |
| g_notification_set_default_action_and_target (n, "app.set-mode", "s", "updates"); | |
| gs_application_send_notification (monitor->application, "eol", n, MINUTES_IN_A_DAY); | |
| } | |
| static void | |
| get_upgrades_finished_cb (GObject *object, | |
| GAsyncResult *res, | |
| gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = GS_UPDATE_MONITOR (data); | |
| GsApp *app; | |
| g_autofree gchar *body = NULL; | |
| g_autoptr(GDateTime) now = NULL; | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GNotification) n = NULL; | |
| g_autoptr(GsAppList) apps = NULL; | |
| /* get result */ | |
| apps = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error); | |
| if (apps == 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 upgrades: %s", | |
| error->message); | |
| } | |
| return; | |
| } | |
| /* no results */ | |
| if (gs_app_list_length (apps) == 0) { | |
| g_debug ("no upgrades; withdrawing upgrades-available notification"); | |
| gs_application_withdraw_notification (monitor->application, "upgrades-available"); | |
| return; | |
| } | |
| /* do not show if gnome-software is already open */ | |
| if (gs_application_has_active_window (monitor->application)) | |
| return; | |
| /* only nag about upgrades once per week */ | |
| if (!should_show_upgrade_notification (monitor)) | |
| return; | |
| g_debug ("showing distro upgrade notification"); | |
| now = g_date_time_new_now_local (); | |
| g_settings_set (monitor->settings, "upgrade-notification-timestamp", "x", | |
| g_date_time_to_unix (now)); | |
| /* rely on the app list already being sorted with the | |
| * chronologically newest release last */ | |
| app = gs_app_list_index (apps, gs_app_list_length (apps) - 1); | |
| /* TRANSLATORS: this is a distro upgrade, the replacement would be the | |
| * distro name, e.g. 'Fedora' */ | |
| body = g_strdup_printf (_("A new version of %s is available to install"), | |
| gs_app_get_name (app)); | |
| /* TRANSLATORS: this is a distro upgrade */ | |
| n = g_notification_new (_("Software Upgrade Available")); | |
| g_notification_set_body (n, body); | |
| g_notification_set_default_action_and_target (n, "app.set-mode", "s", "updates"); | |
| gs_application_send_notification (monitor->application, "upgrades-available", n, MINUTES_IN_A_DAY); | |
| } | |
| static void | |
| get_updates (GsUpdateMonitor *monitor) | |
| { | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| g_autoptr(DownloadUpdatesData) download_updates_data = NULL; | |
| /* disabled in gsettings or from a plugin */ | |
| if (!gs_plugin_loader_get_allow_updates (monitor->plugin_loader)) { | |
| g_debug ("not getting updates as not enabled"); | |
| return; | |
| } | |
| download_updates_data = g_slice_new0 (DownloadUpdatesData); | |
| download_updates_data->monitor = g_object_ref (monitor); | |
| /* NOTE: this doesn't actually do any network access */ | |
| g_debug ("Getting updates"); | |
| plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES, | |
| "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS | | |
| GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY, | |
| NULL); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->update_cancellable, | |
| get_updates_finished_cb, | |
| g_steal_pointer (&download_updates_data)); | |
| } | |
| void | |
| gs_update_monitor_autoupdate (GsUpdateMonitor *monitor) | |
| { | |
| get_updates (monitor); | |
| } | |
| static void | |
| get_upgrades (GsUpdateMonitor *monitor) | |
| { | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| /* disabled in gsettings or from a plugin */ | |
| if (!gs_plugin_loader_get_allow_updates (monitor->plugin_loader)) { | |
| g_debug ("not getting upgrades as not enabled"); | |
| return; | |
| } | |
| /* NOTE: this doesn't actually do any network access, it relies on the | |
| * AppStream data being up to date, either by the appstream-data | |
| * package being up-to-date, or the metadata being auto-downloaded */ | |
| g_debug ("Getting upgrades"); | |
| plugin_job = gs_plugin_job_list_distro_upgrades_new (GS_PLUGIN_LIST_DISTRO_UPGRADES_FLAGS_NONE, | |
| GS_PLUGIN_REFINE_FLAGS_NONE); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->update_cancellable, | |
| get_upgrades_finished_cb, | |
| monitor); | |
| } | |
| static void | |
| get_system (GsUpdateMonitor *monitor) | |
| { | |
| g_autoptr(GsApp) app = NULL; | |
| g_debug ("Getting system"); | |
| gs_plugin_loader_get_system_app_async (monitor->plugin_loader, monitor->update_cancellable, | |
| get_system_finished_cb, monitor); | |
| } | |
| static void | |
| refresh_cache_finished_cb (GObject *object, | |
| GAsyncResult *res, | |
| gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = data; | |
| g_autoptr(GDateTime) now = NULL; | |
| g_autoptr(GError) error = NULL; | |
| if (!gs_plugin_loader_job_action_finish (GS_PLUGIN_LOADER (object), 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 refresh the cache: %s", error->message); | |
| return; | |
| } | |
| /* update the last checked timestamp */ | |
| now = g_date_time_new_now_local (); | |
| g_settings_set (monitor->settings, "check-timestamp", "x", | |
| g_date_time_to_unix (now)); | |
| get_updates (monitor); | |
| } | |
| typedef enum { | |
| UP_DEVICE_LEVEL_UNKNOWN, | |
| UP_DEVICE_LEVEL_NONE, | |
| UP_DEVICE_LEVEL_DISCHARGING, | |
| UP_DEVICE_LEVEL_LOW, | |
| UP_DEVICE_LEVEL_CRITICAL, | |
| UP_DEVICE_LEVEL_ACTION, | |
| UP_DEVICE_LEVEL_LAST | |
| } UpDeviceLevel; | |
| static void | |
| install_language_pack_cb (GObject *object, GAsyncResult *res, gpointer data) | |
| { | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(WithAppData) with_app_data = data; | |
| if (!gs_plugin_loader_job_action_finish (GS_PLUGIN_LOADER (object), res, &error)) { | |
| if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) && | |
| !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | |
| g_debug ("failed to install language pack: %s", error->message); | |
| return; | |
| } else { | |
| g_debug ("language pack for %s installed", | |
| gs_app_get_name (with_app_data->app)); | |
| } | |
| } | |
| static void | |
| get_language_pack_cb (GObject *object, GAsyncResult *res, gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = GS_UPDATE_MONITOR (data); | |
| GsApp *app; | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GsAppList) app_list = NULL; | |
| app_list = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error); | |
| if (app_list == NULL) { | |
| if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) && | |
| !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | |
| g_debug ("failed to find language pack: %s", error->message); | |
| return; | |
| } | |
| /* none found */ | |
| if (gs_app_list_length (app_list) == 0) { | |
| g_debug ("no language pack found"); | |
| return; | |
| } | |
| /* there should be one langpack for a given locale */ | |
| app = g_object_ref (gs_app_list_index (app_list, 0)); | |
| if (!gs_app_is_installed (app)) { | |
| WithAppData *with_app_data; | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| with_app_data = with_app_data_new (monitor, app); | |
| plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL, | |
| "app", app, | |
| NULL); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->update_cancellable, | |
| install_language_pack_cb, | |
| with_app_data); | |
| } | |
| } | |
| /* | |
| * determines active locale and looks for langpacks | |
| * installs located language pack, if not already | |
| */ | |
| static void | |
| check_language_pack (GsUpdateMonitor *monitor) { | |
| const gchar *locale; | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| locale = setlocale (LC_MESSAGES, NULL); | |
| plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_LANGPACKS, | |
| "search", locale, | |
| "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON, | |
| NULL); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->update_cancellable, | |
| get_language_pack_cb, | |
| monitor); | |
| } | |
| static void | |
| check_updates (GsUpdateMonitor *monitor) | |
| { | |
| gint64 tmp; | |
| gboolean refresh_on_metered; | |
| g_autoptr(GDateTime) last_refreshed = NULL; | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| /* never check for updates when offline */ | |
| if (!gs_plugin_loader_get_network_available (monitor->plugin_loader)) | |
| return; | |
| /* check for language pack */ | |
| check_language_pack (monitor); | |
| #ifdef HAVE_MOGWAI | |
| refresh_on_metered = TRUE; | |
| #else | |
| refresh_on_metered = g_settings_get_boolean (monitor->settings, | |
| "refresh-when-metered"); | |
| #endif | |
| if (!refresh_on_metered && | |
| gs_plugin_loader_get_network_metered (monitor->plugin_loader)) | |
| return; | |
| /* never refresh when the battery is low */ | |
| if (monitor->proxy_upower != NULL) { | |
| g_autoptr(GVariant) val = NULL; | |
| val = g_dbus_proxy_get_cached_property (monitor->proxy_upower, | |
| "WarningLevel"); | |
| if (val != NULL) { | |
| guint32 level = g_variant_get_uint32 (val); | |
| if (level >= UP_DEVICE_LEVEL_LOW) { | |
| g_debug ("not getting updates on low power"); | |
| return; | |
| } | |
| } | |
| } else { | |
| g_debug ("no UPower support, so not doing power level checks"); | |
| } | |
| #if GLIB_CHECK_VERSION(2, 69, 1) | |
| /* never refresh when in power saver mode */ | |
| if (monitor->power_profile_monitor != NULL) { | |
| if (g_power_profile_monitor_get_power_saver_enabled (monitor->power_profile_monitor)) { | |
| g_debug ("Not getting updates with power saver enabled"); | |
| return; | |
| } | |
| } else { | |
| g_debug ("No power profile monitor support, so not doing power profile checks"); | |
| } | |
| #endif | |
| g_settings_get (monitor->settings, "check-timestamp", "x", &tmp); | |
| last_refreshed = g_date_time_new_from_unix_local (tmp); | |
| if (last_refreshed != NULL) { | |
| gint now_year, now_month, now_day, now_hour; | |
| gint year, month, day; | |
| g_autoptr(GDateTime) now = NULL; | |
| now = g_date_time_new_now_local (); | |
| g_date_time_get_ymd (now, &now_year, &now_month, &now_day); | |
| now_hour = g_date_time_get_hour (now); | |
| g_date_time_get_ymd (last_refreshed, &year, &month, &day); | |
| /* check that it is the next day */ | |
| if (!((now_year > year) || | |
| (now_year == year && now_month > month) || | |
| (now_year == year && now_month == month && now_day > day))) | |
| return; | |
| /* ...and past 6am */ | |
| if (!(now_hour >= 6)) | |
| return; | |
| } | |
| if (!should_download_updates (monitor)) { | |
| get_updates (monitor); | |
| return; | |
| } | |
| g_debug ("Daily update check due"); | |
| plugin_job = gs_plugin_job_refresh_metadata_new (60 * 60 * 24, | |
| GS_PLUGIN_REFRESH_METADATA_FLAGS_NONE); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, plugin_job, | |
| monitor->refresh_cancellable, | |
| refresh_cache_finished_cb, | |
| monitor); | |
| } | |
| static gboolean | |
| check_hourly_cb (gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = data; | |
| g_debug ("Hourly updates check"); | |
| check_updates (monitor); | |
| return G_SOURCE_CONTINUE; | |
| } | |
| static gboolean | |
| check_thrice_daily_cb (gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = data; | |
| g_debug ("Daily upgrades check"); | |
| get_upgrades (monitor); | |
| get_system (monitor); | |
| return G_SOURCE_CONTINUE; | |
| } | |
| static void | |
| stop_upgrades_check (GsUpdateMonitor *monitor) | |
| { | |
| if (monitor->check_daily_id == 0) | |
| return; | |
| g_source_remove (monitor->check_daily_id); | |
| monitor->check_daily_id = 0; | |
| } | |
| static void | |
| restart_upgrades_check (GsUpdateMonitor *monitor) | |
| { | |
| stop_upgrades_check (monitor); | |
| get_upgrades (monitor); | |
| monitor->check_daily_id = g_timeout_add_seconds (SECONDS_IN_A_DAY / 3, | |
| check_thrice_daily_cb, | |
| monitor); | |
| } | |
| static void | |
| stop_updates_check (GsUpdateMonitor *monitor) | |
| { | |
| if (monitor->check_hourly_id == 0) | |
| return; | |
| g_source_remove (monitor->check_hourly_id); | |
| monitor->check_hourly_id = 0; | |
| } | |
| static void | |
| restart_updates_check (GsUpdateMonitor *monitor) | |
| { | |
| stop_updates_check (monitor); | |
| check_updates (monitor); | |
| monitor->check_hourly_id = g_timeout_add_seconds (SECONDS_IN_AN_HOUR, check_hourly_cb, | |
| monitor); | |
| } | |
| static gboolean | |
| check_updates_on_startup_cb (gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = data; | |
| g_debug ("First hourly updates check"); | |
| restart_updates_check (monitor); | |
| if (gs_plugin_loader_get_allow_updates (monitor->plugin_loader)) | |
| restart_upgrades_check (monitor); | |
| monitor->check_startup_id = 0; | |
| return G_SOURCE_REMOVE; | |
| } | |
| static void | |
| check_updates_upower_changed_cb (GDBusProxy *proxy, | |
| GParamSpec *pspec, | |
| GsUpdateMonitor *monitor) | |
| { | |
| g_debug ("upower changed updates check"); | |
| check_updates (monitor); | |
| } | |
| static void | |
| network_available_notify_cb (GsPluginLoader *plugin_loader, | |
| GParamSpec *pspec, | |
| GsUpdateMonitor *monitor) | |
| { | |
| check_updates (monitor); | |
| } | |
| static void | |
| get_updates_historical_cb (GObject *object, GAsyncResult *res, gpointer data) | |
| { | |
| GsUpdateMonitor *monitor = data; | |
| GsApp *app; | |
| const gchar *message; | |
| const gchar *title; | |
| guint64 time_last_notified; | |
| g_autoptr(GError) error = NULL; | |
| g_autoptr(GsAppList) apps = NULL; | |
| g_autoptr(GNotification) notification = NULL; | |
| /* get result */ | |
| apps = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error); | |
| if (apps == NULL) { | |
| if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) || | |
| g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { | |
| g_debug ("Failed to get historical updates: %s", error->message); | |
| g_clear_error (&monitor->last_offline_error); | |
| return; | |
| } | |
| /* save this in case the user clicks the | |
| * 'Show Details' button from the notification below */ | |
| g_clear_error (&monitor->last_offline_error); | |
| monitor->last_offline_error = g_error_copy (error); | |
| /* TRANSLATORS: title when we offline updates have failed */ | |
| notification = g_notification_new (_("Software Updates Failed")); | |
| /* TRANSLATORS: message when we offline updates have failed */ | |
| g_notification_set_body (notification, _("An important operating system update failed to be installed.")); | |
| g_notification_add_button (notification, _("Show Details"), "app.show-offline-update-error"); | |
| g_notification_set_default_action (notification, "app.show-offline-update-error"); | |
| gs_application_send_notification (monitor->application, "offline-updates", notification, MINUTES_IN_A_DAY); | |
| return; | |
| } | |
| /* no results */ | |
| if (gs_app_list_length (apps) == 0) { | |
| g_debug ("no historical updates; withdrawing notification"); | |
| gs_application_withdraw_notification (monitor->application, "updates-available"); | |
| return; | |
| } | |
| /* have we notified about this before */ | |
| app = gs_app_list_index (apps, 0); | |
| g_settings_get (monitor->settings, | |
| "install-timestamp", "x", &time_last_notified); | |
| if (time_last_notified >= gs_app_get_install_date (app)) | |
| return; | |
| if (gs_app_get_kind (app) == AS_COMPONENT_KIND_OPERATING_SYSTEM) { | |
| /* TRANSLATORS: Notification title when we've done a distro upgrade */ | |
| notification = g_notification_new (_("System Upgrade Complete")); | |
| /* TRANSLATORS: This is the notification body when we've done a | |
| * distro upgrade. First %s is the distro name and the 2nd %s | |
| * is the version, e.g. "Welcome to Fedora 28!" */ | |
| message = g_strdup_printf (_("Welcome to %s %s!"), | |
| gs_app_get_name (app), | |
| gs_app_get_version (app)); | |
| g_notification_set_body (notification, message); | |
| } else { | |
| /* TRANSLATORS: title when we've done offline updates */ | |
| title = ngettext ("Software Update Installed", | |
| "Software Updates Installed", | |
| gs_app_list_length (apps)); | |
| /* TRANSLATORS: message when we've done offline updates */ | |
| message = ngettext ("An important operating system update has been installed.", | |
| "Important operating system updates have been installed.", | |
| gs_app_list_length (apps)); | |
| notification = g_notification_new (title); | |
| g_notification_set_body (notification, message); | |
| /* TRANSLATORS: Button to look at the updates that were installed. | |
| * Note that it has nothing to do with the app reviews, the | |
| * users can't express their opinions here. In some languages | |
| * "Review (evaluate) something" is a different translation than | |
| * "Review (browse) something." */ | |
| g_notification_add_button_with_target (notification, C_("updates", "Review"), "app.set-mode", "s", "updated"); | |
| g_notification_set_default_action_and_target (notification, "app.set-mode", "s", "updated"); | |
| } | |
| gs_application_send_notification (monitor->application, "offline-updates", notification, MINUTES_IN_A_DAY); | |
| /* update the timestamp so we don't show again */ | |
| g_settings_set (monitor->settings, | |
| "install-timestamp", "x", gs_app_get_install_date (app)); | |
| reset_update_notification_timestamp (monitor); | |
| } | |
| static gboolean | |
| cleanup_notifications_cb (gpointer user_data) | |
| { | |
| GsUpdateMonitor *monitor = user_data; | |
| g_autoptr(GsPluginJob) plugin_job = NULL; | |
| /* this doesn't do any network access, and is only called once just | |
| * after startup, so don’t cancel it with refreshes/updates */ | |
| g_debug ("getting historical updates for fresh session"); | |
| plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL, | |
| "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION, | |
| NULL); | |
| gs_plugin_loader_job_process_async (monitor->plugin_loader, | |
| plugin_job, | |
| monitor->shutdown_cancellable, | |
| get_updates_historical_cb, | |
| monitor); | |
| /* wait until first check to show */ | |
| gs_application_withdraw_notification (monitor->application, "updates-available"); | |
| monitor->cleanup_notifications_id = 0; | |
| return G_SOURCE_REMOVE; | |
| } | |
| void | |
| gs_update_monitor_show_error (GsUpdateMonitor *monitor, GtkWindow *window) | |
| { | |
| const gchar *title; | |
| const gchar *msg; | |
| gboolean show_detailed_error; | |
| /* can this happen in reality? */ | |
| if (monitor->last_offline_error == NULL) | |
| return; | |
| /* TRANSLATORS: this is when the offline update failed */ | |
| title = _("Failed To Update"); | |
| if (g_error_matches (monitor->last_offline_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED)) { | |
| /* TRANSLATORS: the user must have updated manually after | |
| * the updates were prepared */ | |
| msg = _("The system was already up to date."); | |
| show_detailed_error = TRUE; | |
| } else if (g_error_matches (monitor->last_offline_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED) || | |
| g_error_matches (monitor->last_offline_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { | |
| /* TRANSLATORS: the user aborted the update manually */ | |
| msg = _("The update was cancelled."); | |
| show_detailed_error = FALSE; | |
| } else if (g_error_matches (monitor->last_offline_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NO_NETWORK)) { | |
| /* TRANSLATORS: the package manager needed to download | |
| * something with no network available */ | |
| msg = _("Internet access was required but wasn’t available. " | |
| "Please make sure that you have internet access and try again."); | |
| show_detailed_error = FALSE; | |
| } else if (g_error_matches (monitor->last_offline_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NO_SECURITY)) { | |
| /* TRANSLATORS: if the package is not signed correctly */ | |
| msg = _("There were security issues with the update. " | |
| "Please consult your software provider for more details."); | |
| show_detailed_error = TRUE; | |
| } else if (g_error_matches (monitor->last_offline_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NO_SPACE)) { | |
| /* TRANSLATORS: we ran out of disk space */ | |
| msg = _("There wasn’t enough disk space. Please free up some space and try again."); | |
| show_detailed_error = FALSE; | |
| } else { | |
| /* TRANSLATORS: We didn't handle the error type */ | |
| msg = _("We’re sorry: the update failed to install. " | |
| "Please wait for another update and try again. " | |
| "If the problem persists, contact your software provider."); | |
| show_detailed_error = TRUE; | |
| } | |
| gs_utils_show_error_dialog (window, | |
| title, | |
| msg, | |
| show_detailed_error ? monitor->last_offline_error->message : NULL); | |
| } | |
| static void | |
| allow_updates_notify_cb (GsPluginLoader *plugin_loader, | |
| GParamSpec *pspec, | |
| GsUpdateMonitor *monitor) | |
| { | |
| if (gs_plugin_loader_get_allow_updates (plugin_loader)) { | |
| /* We restart the updates check here to avoid the user | |
| * potentially waiting for the hourly check */ | |
| restart_updates_check (monitor); | |
| restart_upgrades_check (monitor); | |
| } else { | |
| stop_upgrades_check (monitor); | |
| } | |
| } | |
| static void | |
| gs_update_monitor_network_changed_cb (GNetworkMonitor *network_monitor, | |
| gboolean available, | |
| GsUpdateMonitor *monitor) | |
| { | |
| /* cancel an on-going refresh if we're now in a metered connection */ | |
| if (!g_settings_get_boolean (monitor->settings, "refresh-when-metered") && | |
| g_network_monitor_get_network_metered (network_monitor)) { | |
| g_cancellable_cancel (monitor->refresh_cancellable); | |
| g_object_unref (monitor->refresh_cancellable); | |
| monitor->refresh_cancellable = g_cancellable_new (); | |
| } else { | |
| /* Else, it might be time to check for updates */ | |
| check_updates (monitor); | |
| } | |
| } | |
| #if GLIB_CHECK_VERSION(2, 69, 1) | |
| static void | |
| gs_update_monitor_power_profile_changed_cb (GObject *object, | |
| GParamSpec *pspec, | |
| gpointer user_data) | |
| { | |
| GsUpdateMonitor *self = GS_UPDATE_MONITOR (user_data); | |
| if (g_power_profile_monitor_get_power_saver_enabled (self->power_profile_monitor)) { | |
| /* Cancel ongoing jobs, if we’re now in power saving mode. */ | |
| g_cancellable_cancel (self->refresh_cancellable); | |
| g_object_unref (self->refresh_cancellable); | |
| self->refresh_cancellable = g_cancellable_new (); | |
| g_cancellable_cancel (self->update_cancellable); | |
| g_object_unref (self->update_cancellable); | |
| self->update_cancellable = g_cancellable_new (); | |
| } else { | |
| /* Else, it might be time to check for updates */ | |
| check_updates (self); | |
| } | |
| } | |
| #endif | |
| static void | |
| gs_update_monitor_init (GsUpdateMonitor *monitor) | |
| { | |
| GNetworkMonitor *network_monitor; | |
| g_autoptr(GError) error = NULL; | |
| monitor->settings = g_settings_new ("org.gnome.software"); | |
| /* cleanup at startup */ | |
| monitor->cleanup_notifications_id = | |
| g_idle_add (cleanup_notifications_cb, monitor); | |
| /* do a first check 60 seconds after login, and then every hour */ | |
| monitor->check_startup_id = | |
| g_timeout_add_seconds (60, check_updates_on_startup_cb, monitor); | |
| /* we use three cancellables because we want to be able to cancel refresh | |
| * operations more opportunistically than other operations, since | |
| * they’re less important and cancelling them doesn’t result in much | |
| * wasted work, and we want to be able to cancel some operations only on | |
| * shutdown. */ | |
| monitor->shutdown_cancellable = g_cancellable_new (); | |
| monitor->update_cancellable = g_cancellable_new (); | |
| monitor->refresh_cancellable = g_cancellable_new (); | |
| /* connect to UPower to get the system power state */ | |
| monitor->proxy_upower = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, | |
| G_DBUS_PROXY_FLAGS_NONE, | |
| NULL, | |
| "org.freedesktop.UPower", | |
| "/org/freedesktop/UPower/devices/DisplayDevice", | |
| "org.freedesktop.UPower.Device", | |
| NULL, | |
| &error); | |
| if (monitor->proxy_upower != NULL) { | |
| g_signal_connect (monitor->proxy_upower, "notify", | |
| G_CALLBACK (check_updates_upower_changed_cb), | |
| monitor); | |
| } else { | |
| g_warning ("failed to connect to upower: %s", error->message); | |
| } | |
| network_monitor = g_network_monitor_get_default (); | |
| if (network_monitor != NULL) { | |
| monitor->network_monitor = g_object_ref (network_monitor); | |
| monitor->network_changed_handler = g_signal_connect (monitor->network_monitor, | |
| "network-changed", | |
| G_CALLBACK (gs_update_monitor_network_changed_cb), | |
| monitor); | |
| } | |
| #if GLIB_CHECK_VERSION(2, 69, 1) | |
| monitor->power_profile_monitor = g_power_profile_monitor_dup_default (); | |
| if (monitor->power_profile_monitor != NULL) | |
| monitor->power_profile_changed_handler = g_signal_connect (monitor->power_profile_monitor, | |
| "notify::power-saver-enabled", | |
| G_CALLBACK (gs_update_monitor_power_profile_changed_cb), | |
| monitor); | |
| #endif | |
| } | |
| static void | |
| gs_update_monitor_dispose (GObject *object) | |
| { | |
| GsUpdateMonitor *monitor = GS_UPDATE_MONITOR (object); | |
| if (monitor->network_changed_handler != 0) { | |
| g_signal_handler_disconnect (monitor->network_monitor, | |
| monitor->network_changed_handler); | |
| monitor->network_changed_handler = 0; | |
| } | |
| #if GLIB_CHECK_VERSION(2, 69, 1) | |
| g_clear_signal_handler (&monitor->power_profile_changed_handler, monitor->power_profile_monitor); | |
| g_clear_object (&monitor->power_profile_monitor); | |
| #endif | |
| g_cancellable_cancel (monitor->update_cancellable); | |
| g_clear_object (&monitor->update_cancellable); | |
| g_cancellable_cancel (monitor->refresh_cancellable); | |
| g_clear_object (&monitor->refresh_cancellable); | |
| g_cancellable_cancel (monitor->shutdown_cancellable); | |
| g_clear_object (&monitor->shutdown_cancellable); | |
| stop_updates_check (monitor); | |
| stop_upgrades_check (monitor); | |
| if (monitor->check_startup_id != 0) { | |
| g_source_remove (monitor->check_startup_id); | |
| monitor->check_startup_id = 0; | |
| } | |
| if (monitor->cleanup_notifications_id != 0) { | |
| g_source_remove (monitor->cleanup_notifications_id); | |
| monitor->cleanup_notifications_id = 0; | |
| } | |
| if (monitor->plugin_loader != NULL) { | |
| g_signal_handlers_disconnect_by_func (monitor->plugin_loader, | |
| network_available_notify_cb, | |
| monitor); | |
| g_clear_object (&monitor->plugin_loader); | |
| } | |
| g_clear_object (&monitor->settings); | |
| g_clear_object (&monitor->proxy_upower); | |
| G_OBJECT_CLASS (gs_update_monitor_parent_class)->dispose (object); | |
| } | |
| static void | |
| gs_update_monitor_finalize (GObject *object) | |
| { | |
| GsUpdateMonitor *monitor = GS_UPDATE_MONITOR (object); | |
| g_application_release (G_APPLICATION (monitor->application)); | |
| g_clear_error (&monitor->last_offline_error); | |
| G_OBJECT_CLASS (gs_update_monitor_parent_class)->finalize (object); | |
| } | |
| static void | |
| gs_update_monitor_class_init (GsUpdateMonitorClass *klass) | |
| { | |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); | |
| object_class->dispose = gs_update_monitor_dispose; | |
| object_class->finalize = gs_update_monitor_finalize; | |
| } | |
| GsUpdateMonitor * | |
| gs_update_monitor_new (GsApplication *application, | |
| GsPluginLoader *plugin_loader) | |
| { | |
| GsUpdateMonitor *monitor; | |
| monitor = GS_UPDATE_MONITOR (g_object_new (GS_TYPE_UPDATE_MONITOR, NULL)); | |
| monitor->application = application; | |
| g_application_hold (G_APPLICATION (monitor->application)); | |
| monitor->plugin_loader = g_object_ref (plugin_loader); | |
| g_signal_connect (monitor->plugin_loader, "notify::allow-updates", | |
| G_CALLBACK (allow_updates_notify_cb), monitor); | |
| g_signal_connect (monitor->plugin_loader, "notify::network-available", | |
| G_CALLBACK (network_available_notify_cb), monitor); | |
| return monitor; | |
| } |