Commit 774a67b5 authored by Matthias Clasen's avatar Matthias Clasen

Download updates

This moves more functionality from the gsd updates plugin
here:

* Notify about successful or unsuccessful offline updates
  15 seconds after start.
* Monitor for available offline updates and notify about
  them, once per hour.
* Refresh the cache once per day, after 6am.
* If important updates are available, or updates have not
  been installed for at least 7 days, download all
  available updates. We rely on PackageKit to prepare
  an offline update when updates have been downloaded.
* Avoid mobile networks for cache refresh and downloads.

https://bugzilla.gnome.org/show_bug.cgi?id=709121
parent e9e8fe2f
......@@ -25,6 +25,7 @@
#include <packagekit-glib2/packagekit.h>
#include "gs-offline-updates.h"
#include "gs-utils.h"
static void
child_exit_cb (GPid pid, gint status, gpointer user_data)
......@@ -61,6 +62,7 @@ gs_offline_updates_trigger (void)
gboolean ret;
GError *error = NULL;
const gchar *argv[3];
GDateTime *now;
argv[0] = "pkexec";
argv[1] = LIBEXECDIR "/pk-trigger-offline-update";
......@@ -76,6 +78,10 @@ gs_offline_updates_trigger (void)
error->message);
g_error_free (error);
}
now = g_date_time_new_now_local ();
gs_save_timestamp_to_file ("install-timestamp", now);
g_date_time_unref (now);
}
void
......
......@@ -24,25 +24,33 @@
#include <string.h>
#include <glib/gi18n.h>
#include <packagekit-glib2/packagekit.h>
#include <gsettings-desktop-schemas/gdesktop-enums.h>
#include "gs-update-monitor.h"
#include "gs-utils.h"
#include "gs-offline-updates.h"
#define GS_UPDATES_CHECK_OFFLINE_TIMEOUT 30 /* seconds */
#define GS_REENABLE_OFFLINE_UPDATE_TIMEOUT 300 /* seconds */
#define GS_UPDATES_ICON_NORMAL "software-update-available-symbolic"
#define GS_UPDATES_ICON_URGENT "software-update-urgent-symbolic"
struct _GsUpdateMonitor {
GObject parent;
GsApplication *application;
GApplication *application;
GCancellable *cancellable;
guint check_hourly_id;
GDateTime *check_timestamp;
GDateTime *install_timestamp;
gboolean refresh_cache_due;
gboolean get_updates_due;
gboolean network_available;
gchar **pending_downloads;
PkTask *task;
PkControl *control;
GFile *offline_update_file;
GFileMonitor *offline_update_monitor;
gboolean offline_update_notified;
guint reenable_offline_update_id;
guint check_offline_update_id;
};
......@@ -52,20 +60,25 @@ struct _GsUpdateMonitorClass {
G_DEFINE_TYPE (GsUpdateMonitor, gs_update_monitor, G_TYPE_OBJECT)
static void notify_offline_update_available (GsUpdateMonitor *monitor);
static gboolean
reenable_offline_update (gpointer data)
reenable_offline_update_notification (gpointer data)
{
GsUpdateMonitor *monitor = data;
monitor->offline_update_notified = FALSE;
monitor->reenable_offline_update_id = 0;
notify_offline_update_available (monitor);
return G_SOURCE_REMOVE;
}
static void
notify_offline_update_available (GsUpdateMonitor *monitor)
{
guint id;
GNotification *n;
const gchar *title;
const gchar *body;
......@@ -78,9 +91,9 @@ notify_offline_update_available (GsUpdateMonitor *monitor)
monitor->offline_update_notified = TRUE;
/* don't notify more often than every 5 minutes */
id = g_timeout_add_seconds (GS_REENABLE_OFFLINE_UPDATE_TIMEOUT, reenable_offline_update, monitor);
g_source_set_name_by_id (id, "[gnome-software] reenable_offline_update");
/* don't notify more often than once every hour */
monitor->reenable_offline_update_id = g_timeout_add_seconds (3600, reenable_offline_update_notification, monitor);
g_source_set_name_by_id (monitor->reenable_offline_update_id, "[gnome-software] reenable_offline_update_notification");
title = _("Software Updates Available");
body = _("Important OS and application updates are ready to be installed");
......@@ -89,28 +102,29 @@ notify_offline_update_available (GsUpdateMonitor *monitor)
g_notification_add_button_with_target (n, _("View"), "app.set-mode", "s", "updates");
g_notification_add_button (n, _("Not Now"), "app.nop");
g_notification_set_default_action_and_target (n, "app.set-mode", "s", "updates");
g_application_send_notification (g_application_get_default (), "updates-available", n);
g_application_send_notification (monitor->application, "updates-available", n);
g_object_unref (n);
}
static void
offline_update_cb (GFileMonitor *file_monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
GsUpdateMonitor *monitor)
offline_update_monitor_cb (GFileMonitor *file_monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
GsUpdateMonitor *monitor)
{
notify_offline_update_available (monitor);
}
static gboolean
initial_offline_update_check (gpointer data)
static void
start_monitoring_offline_updates (GsUpdateMonitor *monitor)
{
GsUpdateMonitor *monitor = data;
monitor->offline_update_file = g_file_new_for_path ("/var/lib/PackageKit/prepared-update");
monitor->offline_update_monitor = g_file_monitor_file (monitor->offline_update_file, 0, NULL, NULL);
g_signal_connect (monitor->offline_update_monitor, "changed",
G_CALLBACK (offline_update_monitor_cb), monitor);
notify_offline_update_available (monitor);
return G_SOURCE_REMOVE;
}
static gboolean
......@@ -147,7 +161,7 @@ check_offline_update_cb (gpointer user_data)
notification = g_notification_new (title);
g_notification_set_body (notification, message);
icon = g_themed_icon_new (GS_UPDATES_ICON_URGENT);
icon = g_themed_icon_new ("software-update-urgent-symbolic");
g_notification_set_icon (notification, icon);
g_object_unref (icon);
if (success)
......@@ -156,37 +170,341 @@ check_offline_update_cb (gpointer user_data)
g_notification_add_button (notification, _("Show Details"), "app.show-offline-update-error");
g_notification_add_button (notification, _("OK"), "app.clear-offline-updates");
g_application_send_notification (g_application_get_default (), "offline-updates", notification);
g_application_send_notification (monitor->application, "offline-updates", notification);
g_object_unref (notification);
out:
start_monitoring_offline_updates (monitor);
monitor->check_offline_update_id = 0;
return G_SOURCE_REMOVE;
}
static gboolean
has_important_updates (GPtrArray *packages)
{
guint i;
PkPackage *pkg;
for (i = 0; i < packages->len; i++) {
pkg = g_ptr_array_index (packages, i);
if (pk_package_get_info (pkg) == PK_INFO_ENUM_SECURITY ||
pk_package_get_info (pkg) == PK_INFO_ENUM_IMPORTANT)
return TRUE;
}
return FALSE;
}
static gboolean
no_updates_for_a_week (GsUpdateMonitor *monitor)
{
GDateTime *last_update;
GDateTime *now;
GTimeSpan d;
last_update = gs_read_timestamp_from_file ("install-timestamp");
if (!last_update)
return TRUE;
now = g_date_time_new_now_local ();
d = g_date_time_difference (now, last_update);
g_date_time_unref (last_update);
g_date_time_unref (now);
if (d >= 7 * G_TIME_SPAN_DAY)
return TRUE;
return FALSE;
}
static void
gs_update_monitor_init (GsUpdateMonitor *monitor)
package_download_finished_cb (GObject *object,
GAsyncResult *res,
gpointer data)
{
guint id;
GsUpdateMonitor *monitor = data;
PkResults *results;
GError *error = NULL;
PkError *error_code;
results = pk_client_generic_finish (PK_CLIENT (object), res, &error);
if (results == NULL) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning ("failed to download: %s", error->message);
}
g_error_free (error);
return;
}
monitor->offline_update_file = g_file_new_for_path ("/var/lib/PackageKit/prepared-update");
monitor->offline_update_monitor = g_file_monitor_file (monitor->offline_update_file, 0, NULL, NULL);
error_code = pk_results_get_error_code (results);
if (error_code != NULL) {
g_warning ("failed to download: %s, %s",
pk_error_enum_to_string (pk_error_get_code (error_code)),
pk_error_get_details (error_code));
g_object_unref (error_code);
g_object_unref (results);
return;
}
g_signal_connect (monitor->offline_update_monitor, "changed",
G_CALLBACK (offline_update_cb), monitor);
g_debug ("Downloaded updates");
g_clear_pointer (&monitor->pending_downloads, g_strfreev);
g_object_unref (results);
}
static void
download_updates (GsUpdateMonitor *monitor)
{
if (monitor->pending_downloads == NULL)
return;
if (!monitor->network_available)
return;
g_debug ("Downloading updates");
pk_task_update_packages_async (monitor->task,
monitor->pending_downloads,
monitor->cancellable,
NULL, NULL,
package_download_finished_cb,
monitor);
}
static void
get_updates_finished_cb (GObject *object,
GAsyncResult *res,
gpointer data)
{
GsUpdateMonitor *monitor = data;
PkResults *results;
PkError *error_code;
GError *error = NULL;
GPtrArray *packages;
guint i;
PkPackage *pkg;
results = pk_client_generic_finish (PK_CLIENT (object), res, &error);
if (results == NULL) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning ("failed to get updates: %s", error->message);
}
g_error_free (error);
return;
}
error_code = pk_results_get_error_code (results);
if (error_code != NULL) {
g_warning ("failed to get updates: %s, %s",
pk_error_enum_to_string (pk_error_get_code (error_code)),
pk_error_get_details (error_code));
g_object_unref (error_code);
g_object_unref (results);
return;
}
/* we succeeded */
monitor->get_updates_due = FALSE;
packages = pk_results_get_package_array (results);
g_debug ("Got %d updates", packages->len);
if (has_important_updates (packages) ||
no_updates_for_a_week (monitor)) {
monitor->pending_downloads = g_new0 (gchar *, packages->len + 1);
for (i = 0; i < packages->len; i++) {
pkg = (PkPackage *)g_ptr_array_index (packages, i);
monitor->pending_downloads[i] = g_strdup (pk_package_get_id (pkg));
}
monitor->pending_downloads[packages->len] = NULL;
download_updates (monitor);
}
g_ptr_array_unref (packages);
g_object_unref (results);
}
static void
get_updates (GsUpdateMonitor *monitor)
{
if (monitor->refresh_cache_due)
return;
if (!monitor->get_updates_due)
return;
g_debug ("Getting updates");
pk_client_get_updates_async (PK_CLIENT (monitor->task),
pk_bitfield_value (PK_FILTER_ENUM_NONE),
monitor->cancellable,
NULL, NULL,
get_updates_finished_cb,
monitor);
}
static void
refresh_cache_finished_cb (GObject *object,
GAsyncResult *res,
gpointer data)
{
GsUpdateMonitor *monitor = data;
PkResults *results;
PkError *error_code;
GError *error = NULL;
results = pk_client_generic_finish (PK_CLIENT (object), res, &error);
if (results == NULL) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warning ("failed to refresh the cache: %s", error->message);
}
g_error_free (error);
return;
}
error_code = pk_results_get_error_code (results);
if (error_code != NULL) {
g_warning ("failed to refresh the cache: %s, %s",
pk_error_enum_to_string (pk_error_get_code (error_code)),
pk_error_get_details (error_code));
g_object_unref (error_code);
g_object_unref (results);
return;
}
monitor->refresh_cache_due = FALSE;
id = g_timeout_add_seconds (GS_REENABLE_OFFLINE_UPDATE_TIMEOUT,
initial_offline_update_check,
monitor);
g_source_set_name_by_id (id, "[gnome-software] initial_offline_update_check");
g_object_unref (results);
get_updates (monitor);
}
static void
refresh_cache (GsUpdateMonitor *monitor)
{
if (!monitor->refresh_cache_due)
return;
if (!monitor->network_available)
return;
g_debug ("Refreshing cache");
pk_client_refresh_cache_async (PK_CLIENT (monitor->task),
TRUE,
monitor->cancellable,
NULL, NULL,
refresh_cache_finished_cb,
monitor);
}
static gboolean
check_hourly_cb (gpointer data)
{
GsUpdateMonitor *monitor = data;
g_debug ("Hourly updates check");
/* no need to check again */
if (monitor->refresh_cache_due)
return G_SOURCE_CONTINUE;
if (monitor->check_timestamp != NULL) {
GDateTime *now;
gint now_year, now_month, now_day, now_hour;
gint year, month, day;
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_unref (now);
g_date_time_get_ymd (monitor->check_timestamp, &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 G_SOURCE_CONTINUE;
/* ...and past 6am */
if (!(now_hour >= 6))
return G_SOURCE_CONTINUE;
g_clear_pointer (&monitor->check_timestamp, g_date_time_unref);
}
g_debug ("Daily update check due");
monitor->check_timestamp = g_date_time_new_now_local ();
gs_save_timestamp_to_file ("check-timestamp", monitor->check_timestamp);
monitor->refresh_cache_due = TRUE;
monitor->get_updates_due = TRUE;
refresh_cache (monitor);
return G_SOURCE_CONTINUE;
}
static void
notify_network_state_cb (PkControl *control,
GParamSpec *pspec,
GsUpdateMonitor *monitor)
{
PkNetworkEnum network_state;
gboolean available;
g_object_get (control, "network-state", &network_state, NULL);
if (network_state == PK_NETWORK_ENUM_OFFLINE ||
network_state == PK_NETWORK_ENUM_MOBILE)
available = FALSE;
else
available = TRUE;
if (monitor->network_available != available) {
monitor->network_available = available;
refresh_cache (monitor);
get_updates (monitor);
download_updates (monitor);
}
}
static void
gs_update_monitor_init (GsUpdateMonitor *monitor)
{
monitor->check_offline_update_id =
g_timeout_add_seconds (GS_UPDATES_CHECK_OFFLINE_TIMEOUT,
check_offline_update_cb,
monitor);
g_timeout_add_seconds (15, check_offline_update_cb, monitor);
g_source_set_name_by_id (monitor->check_offline_update_id,
"[gnpome-software] check_offline_update_cb");
"[gnome-software] check_offline_update_cb");
monitor->check_timestamp = gs_read_timestamp_from_file ("check-timestamp");
monitor->check_hourly_id =
g_timeout_add_seconds (3600, check_hourly_cb, monitor);
g_source_set_name_by_id (monitor->check_hourly_id,
"[gnome-software] check_hourly_cb");
monitor->cancellable = g_cancellable_new ();
monitor->task = pk_task_new ();
g_object_set (monitor->task,
"background", TRUE,
"interactive", FALSE,
"only-download", TRUE,
NULL);
monitor->network_available = FALSE;
monitor->control = pk_control_new ();
g_signal_connect (monitor->control, "notify::network-state",
G_CALLBACK (notify_network_state_cb), monitor);
}
static void
......@@ -194,13 +512,29 @@ gs_update_monitor_finalize (GObject *object)
{
GsUpdateMonitor *monitor = GS_UPDATE_MONITOR (object);
if (monitor->cancellable) {
g_cancellable_cancel (monitor->cancellable);
g_clear_object (&monitor->cancellable);
}
if (monitor->check_hourly_id != 0) {
g_source_remove (monitor->check_hourly_id);
monitor->check_hourly_id = 0;
}
if (monitor->check_offline_update_id != 0) {
g_source_remove (monitor->check_offline_update_id);
monitor->check_offline_update_id = 0;
}
if (monitor->reenable_offline_update_id != 0) {
g_source_remove (monitor->reenable_offline_update_id);
monitor->reenable_offline_update_id = 0;
}
g_clear_pointer (&monitor->pending_downloads, g_strfreev);
g_clear_pointer (&monitor->check_timestamp, g_date_time_unref);
g_clear_object (&monitor->task);
g_clear_object (&monitor->control);
g_clear_object (&monitor->offline_update_file);
g_clear_object (&monitor->offline_update_monitor);
g_application_release (G_APPLICATION (monitor->application));
g_application_release (monitor->application);
G_OBJECT_CLASS (gs_update_monitor_parent_class)->finalize (object);
}
......@@ -218,8 +552,8 @@ gs_update_monitor_new (GsApplication *application)
GsUpdateMonitor *monitor;
monitor = GS_UPDATE_MONITOR (g_object_new (GS_TYPE_UPDATE_MONITOR, NULL));
monitor->application = application;
g_application_hold (G_APPLICATION (application));
monitor->application = G_APPLICATION (application);
g_application_hold (monitor->application);
return monitor;
}
......
......@@ -340,4 +340,58 @@ gs_reboot (GCallback reboot_failed)
g_object_unref (bus);
}
GDateTime *
gs_read_timestamp_from_file (const gchar *name)
{
gchar *file;
gchar *contents;
GDateTime *result;
result = NULL;
file = g_build_filename (g_get_user_data_dir (),
"gnome-software", name, NULL);
if (g_file_get_contents (file, &contents, NULL, NULL)) {
gint64 timestamp;
gchar *endptr = NULL;
timestamp = g_ascii_strtoll (contents, &endptr, 0);
if (endptr) {
g_warning ("Could not read %s timestamp: %s", name, contents);
} else {
result = g_date_time_new_from_unix_local (timestamp);
}
g_free (contents);
}
g_free (file);
return result;
}
gboolean
gs_save_timestamp_to_file (const gchar *name,
GDateTime *date)
{
gchar *file;
gchar *contents;
gint64 timestamp;
gboolean result;
GError *error = NULL;
result = TRUE;
timestamp = g_date_time_to_unix (date);
contents = g_strdup_printf ("%" G_GINT64_FORMAT, timestamp);
file = g_build_filename (g_get_user_data_dir (),
"gnome-software", name, NULL);
if (!g_file_set_contents (file, contents, -1, &error)) {
g_warning ("Could not save %s timestamp: %s",
name, error->message);
g_error_free (error);
result = FALSE;
}
g_free (contents);
g_free (file);
return result;
}
/* vim: set noexpandtab: */
......@@ -49,6 +49,9 @@ GdkPixbuf *gs_pixbuf_load (const gchar *icon_name,
guint icon_size,
GError **error);
void gs_reboot (GCallback reboot_failed);
GDateTime *gs_read_timestamp_from_file (const gchar *name);
gboolean gs_save_timestamp_to_file (const gchar *name,
GDateTime *date);
G_END_DECLS
......