[gnome-software] Download updates



commit 774a67b55ca21901d4dca58717015503fce8db1b
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Oct 26 19:12:16 2013 -0400

    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

 src/gs-offline-updates.c |    6 +
 src/gs-update-monitor.c  |  420 +++++++++++++++++++++++++++++++++++++++++-----
 src/gs-utils.c           |   54 ++++++
 src/gs-utils.h           |    3 +
 4 files changed, 440 insertions(+), 43 deletions(-)
---
diff --git a/src/gs-offline-updates.c b/src/gs-offline-updates.c
index d427c58..bfbc763 100644
--- a/src/gs-offline-updates.c
+++ b/src/gs-offline-updates.c
@@ -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
diff --git a/src/gs-update-monitor.c b/src/gs-update-monitor.c
index 450c88a..dad1cc0 100644
--- a/src/gs-update-monitor.c
+++ b/src/gs-update-monitor.c
@@ -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;
 }
diff --git a/src/gs-utils.c b/src/gs-utils.c
index 4b1af7e..6b9dc60 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -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: */
diff --git a/src/gs-utils.h b/src/gs-utils.h
index ecfdd84..9fc36b8 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -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
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]