[gnome-control-center/T20771: 34/44] info: add a way to check for manual updates



commit 1ce373f07b9b7438ab18458d6fb9d54bf185c083
Author: Cosimo Cecchi <cosimo endlessm com>
Date:   Fri Sep 26 18:08:59 2014 -0700

    info: add a way to check for manual updates
    
    Coordinate with the EosUpdater daemon to manually check for available
    updates and possibly install them.

 panels/info/Makefile.am              |   13 +-
 panels/info/cc-info-overview-panel.c |  338 +++++++++++++++++++++++++++++++++-
 panels/info/eos-updater.xml          |   39 ++++
 panels/info/info-overview.ui         |   56 +++++--
 4 files changed, 431 insertions(+), 15 deletions(-)
---
diff --git a/panels/info/Makefile.am b/panels/info/Makefile.am
index 5c7e9d7..a8c7d47 100644
--- a/panels/info/Makefile.am
+++ b/panels/info/Makefile.am
@@ -15,7 +15,9 @@ noinst_LTLIBRARIES = libinfo.la
 
 BUILT_SOURCES =                        \
        cc-info-resources.c     \
-       cc-info-resources.h
+       cc-info-resources.h     \
+       eos-updater-generated.c \
+       eos-updater-generated.h
 
 libinfo_la_SOURCES =           \
        $(BUILT_SOURCES)        \
@@ -49,6 +51,13 @@ cc-info-resources.c: info.gresource.xml $(resource_files)
 cc-info-resources.h: info.gresource.xml $(resource_files)
        $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-header --c-name 
cc_info $<
 
+eos-updater-generated.c: eos-updater-generated.h
+eos-updater-generated.h: $(srcdir)/eos-updater.xml
+       gdbus-codegen $<                                \
+       --interface-prefix com.endlessm.                \
+       --generate-c-code eos-updater-generated         \
+       --c-namespace Eos
+
 @INTLTOOL_DESKTOP_RULE@
 
 desktopdir = $(datadir)/applications
@@ -67,6 +76,6 @@ update-from-gsd:
        git commit -m "info: Update from gnome-settings-daemon" $(SPACEFILES)
 
 CLEANFILES = $(desktop_in_files) $(desktop_DATA) $(BUILT_SOURCES)
-EXTRA_DIST = $(resource_files) info.gresource.xml info-cleanup-test.txt
+EXTRA_DIST = $(resource_files) info.gresource.xml info-cleanup-test.txt $(srcdir)/eos-updater.xml
 
 -include $(top_srcdir)/git.mk
diff --git a/panels/info/cc-info-overview-panel.c b/panels/info/cc-info-overview-panel.c
index 2e77ab9..d86c8ba 100644
--- a/panels/info/cc-info-overview-panel.c
+++ b/panels/info/cc-info-overview-panel.c
@@ -23,6 +23,7 @@
 
 #include "cc-info-panel.h"
 #include "cc-info-resources.h"
+#include "eos-updater-generated.h"
 #include "info-cleanup.h"
 
 #include <glib.h>
@@ -47,6 +48,21 @@
 
 #include "cc-info-overview-panel.h"
 
+typedef enum {
+  EOS_UPDATER_STATE_NONE = 0,
+  EOS_UPDATER_STATE_READY,
+  EOS_UPDATER_STATE_ERROR,
+  EOS_UPDATER_STATE_POLLING,
+  EOS_UPDATER_STATE_UPDATE_AVAILABLE,
+  EOS_UPDATER_STATE_FETCHING,
+  EOS_UPDATER_STATE_UPDATE_READY,
+  EOS_UPDATER_STATE_APPLYING_UPDATE,
+  EOS_UPDATER_STATE_UPDATE_APPLIED,
+  EOS_UPDATER_N_STATES,
+} EosUpdaterState;
+
+#define EOS_UPDATER_ERROR_LIVE_BOOT_STR "com.endlessm.Updater.Error.LiveBoot"
+#define EOS_UPDATER_ERROR_NON_OSTREE_STR "com.endlessm.Updater.Error.NotOstreeSystem"
 
 typedef struct {
   /* Will be one or 2 GPU name strings, or "Unknown" */
@@ -66,6 +82,10 @@ typedef struct
   GtkWidget      *graphics_label;
   GtkWidget      *virt_type_label;
 
+  GtkWidget      *os_updates_box;
+  GtkWidget      *os_updates_label;
+  GtkWidget      *os_updates_spinner;
+
   /* Virtualisation labels */
   GtkWidget      *label8;
   GtkWidget      *grid1;
@@ -82,6 +102,11 @@ typedef struct
   guint64         total_bytes;
 
   GraphicsData   *graphics_data;
+
+  EosUpdater     *updater_proxy;
+  GDBusProxy     *session_proxy;
+  GCancellable   *updater_cancellable;
+  gboolean        updater_activated;
 } CcInfoOverviewPanelPrivate;
 
 struct _CcInfoOverviewPanel
@@ -92,7 +117,13 @@ struct _CcInfoOverviewPanel
  CcInfoOverviewPanelPrivate *priv;
 };
 
-static void get_primary_disc_info_start (CcInfoOverviewPanel *self);
+static void get_primary_disc_info_start     (CcInfoOverviewPanel *self);
+static void sync_state_from_updater         (CcInfoOverviewPanel *self,
+                                             gboolean             is_initial_state);
+static void sync_initial_state_from_updater (CcInfoOverviewPanel *self);
+static gboolean updates_link_activated      (GtkLabel            *label,
+                                             gchar               *uri,
+                                             CcInfoOverviewPanel *self);
 
 typedef struct
 {
@@ -108,6 +139,298 @@ typedef struct
 G_DEFINE_TYPE_WITH_PRIVATE (CcInfoOverviewPanel, cc_info_overview_panel, CC_TYPE_PANEL)
 
 static gboolean
+is_updater_state_spinning (EosUpdaterState state)
+{
+  switch (state)
+    {
+    case EOS_UPDATER_STATE_POLLING:
+    case EOS_UPDATER_STATE_FETCHING:
+    case EOS_UPDATER_STATE_APPLYING_UPDATE:
+      return TRUE;
+    default:
+      return FALSE;
+    }
+}
+
+static gboolean
+is_updater_state_interactive (CcInfoOverviewPanel *self,
+                              EosUpdaterState      state)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+  switch (state)
+    {
+    case EOS_UPDATER_STATE_READY:
+    case EOS_UPDATER_STATE_ERROR:
+    case EOS_UPDATER_STATE_UPDATE_AVAILABLE:
+    case EOS_UPDATER_STATE_UPDATE_READY:
+      return (!priv->updater_activated);
+    case EOS_UPDATER_STATE_UPDATE_APPLIED:
+      return TRUE;
+    default:
+      return FALSE;
+    }
+}
+
+static const gchar *
+get_message_for_updater_state (CcInfoOverviewPanel *self,
+                               EosUpdaterState      state)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+  switch (state)
+    {
+    case EOS_UPDATER_STATE_NONE:
+    case EOS_UPDATER_STATE_READY:
+      if (priv->updater_activated)
+        return _("No updates available");
+      else
+        return _("Check for updates now");
+    case EOS_UPDATER_STATE_ERROR:
+      return _("Update failed");
+    case EOS_UPDATER_STATE_POLLING:
+      return _("Checking for updates…");
+    case EOS_UPDATER_STATE_UPDATE_AVAILABLE:
+    case EOS_UPDATER_STATE_UPDATE_READY:
+      return _("Install updates now");
+    case EOS_UPDATER_STATE_FETCHING:
+    case EOS_UPDATER_STATE_APPLYING_UPDATE:
+      return _("Installing updates…");
+    case EOS_UPDATER_STATE_UPDATE_APPLIED:
+      return _("Restart to complete update");
+    default:
+      return NULL;
+    }
+}
+
+static void
+updates_apply_completed (GObject      *object,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+  EosUpdater *proxy = (EosUpdater *) object;
+  g_autoptr(GError) error = NULL;
+
+  eos_updater_call_apply_finish (proxy, res, &error);
+  if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      CcInfoOverviewPanel *self = user_data;
+      g_warning ("Failed to call Apply() on EOS updater: %s", error->message);
+      sync_state_from_updater (self, FALSE);
+    }
+}
+
+static void
+updates_fetch_completed (GObject      *object,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+  EosUpdater *proxy = (EosUpdater *) object;
+  g_autoptr(GError) error = NULL;
+
+  eos_updater_call_fetch_finish (proxy, res, &error);
+  if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      CcInfoOverviewPanel *self = user_data;
+      g_warning ("Failed to call Fetch() on EOS updater: %s", error->message);
+      sync_state_from_updater (self, FALSE);
+    }
+}
+
+static void
+updates_poll_completed (GObject      *object,
+                        GAsyncResult *res,
+                        gpointer      user_data)
+{
+  EosUpdater *proxy = (EosUpdater *) object;
+  g_autoptr(GError) error = NULL;
+
+  eos_updater_call_poll_finish (proxy, res, &error);
+  if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    {
+      CcInfoOverviewPanel *self = user_data;
+      g_warning ("Failed to call Poll() on EOS updater: %s", error->message);
+      sync_state_from_updater (self, FALSE);
+    }
+}
+
+static gboolean
+updates_link_activated (GtkLabel            *label,
+                        gchar               *uri,
+                        CcInfoOverviewPanel *self)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+  EosUpdaterState state;
+
+  state = eos_updater_get_state (priv->updater_proxy);
+  g_assert (is_updater_state_interactive (self, state));
+  priv->updater_activated = TRUE;
+
+  switch (state)
+    {
+    case EOS_UPDATER_STATE_ERROR:
+    case EOS_UPDATER_STATE_READY:
+      eos_updater_call_poll (priv->updater_proxy,
+                             priv->updater_cancellable,
+                             updates_poll_completed, self);
+      break;
+    case EOS_UPDATER_STATE_UPDATE_AVAILABLE:
+      eos_updater_call_fetch (priv->updater_proxy,
+                              priv->updater_cancellable,
+                              updates_fetch_completed, self);
+      break;
+    case EOS_UPDATER_STATE_UPDATE_READY:
+      eos_updater_call_apply (priv->updater_proxy,
+                              priv->updater_cancellable,
+                              updates_apply_completed, self);
+      break;
+    case EOS_UPDATER_STATE_UPDATE_APPLIED:
+      g_dbus_proxy_call (priv->session_proxy,
+                         "Reboot",
+                         NULL,
+                         G_DBUS_CALL_FLAGS_NONE,
+                         -1, NULL, NULL, NULL);
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
+  return TRUE;
+}
+
+static void
+updates_maybe_do_automatic_step (CcInfoOverviewPanel *self)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+  EosUpdaterState state;
+
+  if (!priv->updater_activated)
+    return;
+
+  state = eos_updater_get_state (priv->updater_proxy);
+  switch (state)
+    {
+    case EOS_UPDATER_STATE_UPDATE_AVAILABLE:
+      eos_updater_call_fetch (priv->updater_proxy,
+                              priv->updater_cancellable,
+                              updates_fetch_completed, self);
+      break;
+    case EOS_UPDATER_STATE_UPDATE_READY:
+      eos_updater_call_apply (priv->updater_proxy,
+                              priv->updater_cancellable,
+                              updates_apply_completed, self);
+      break;
+    default:
+      break;
+    }
+}
+
+static void
+sync_state_from_updater (CcInfoOverviewPanel *self,
+                         gboolean             is_initial_state)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+  EosUpdaterState state;
+  gboolean is_live_boot, is_non_ostree;
+  gboolean state_spinning, state_interactive;
+  const gchar *message, *error_name;
+  g_autofree gchar *markup = NULL;
+
+  state = eos_updater_get_state (priv->updater_proxy);
+  error_name = eos_updater_get_error_name (priv->updater_proxy);
+  is_live_boot = state == EOS_UPDATER_STATE_ERROR &&
+    g_strcmp0 (error_name, EOS_UPDATER_ERROR_LIVE_BOOT_STR) == 0;
+  is_non_ostree = state == EOS_UPDATER_STATE_ERROR &&
+    g_strcmp0 (error_name, EOS_UPDATER_ERROR_NON_OSTREE_STR) == 0;
+
+  /* Attempt to clear the error by pretending to be ready, which will
+   * trigger a poll
+   */
+  if (state == EOS_UPDATER_STATE_ERROR && is_initial_state)
+    state = EOS_UPDATER_STATE_READY;
+
+  state_spinning = is_updater_state_spinning (state);
+  state_interactive = is_updater_state_interactive (self, state);
+  message = get_message_for_updater_state (self, state);
+
+  gtk_widget_set_visible (priv->os_updates_spinner, state_spinning);
+  g_object_set (priv->os_updates_spinner, "active", state_spinning, NULL);
+
+  gtk_widget_set_visible (priv->os_updates_label, !is_live_boot && !is_non_ostree);
+  if (state_interactive)
+    markup = g_strdup_printf ("<a href='updates-link'>%s</a>", message);
+  else
+    markup = g_strdup (message);
+
+  gtk_label_set_markup (GTK_LABEL (priv->os_updates_label), markup);
+
+  updates_maybe_do_automatic_step (self);
+}
+
+static void
+updater_state_changed (EosUpdater          *proxy,
+                       GParamSpec          *pspec,
+                       CcInfoOverviewPanel *self)
+{
+  sync_state_from_updater (self, FALSE);
+}
+
+static void
+sync_initial_state_from_updater (CcInfoOverviewPanel *self)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+
+  priv->updater_cancellable = g_cancellable_new ();
+
+  sync_state_from_updater (self, TRUE);
+
+  g_signal_connect (priv->updater_proxy, "notify::state", G_CALLBACK (updater_state_changed), self);
+}
+
+static void
+info_overview_panel_setup_eos_updater (CcInfoOverviewPanel *self)
+{
+  CcInfoOverviewPanelPrivate *priv = cc_info_overview_panel_get_instance_private (self);
+  g_autoptr(GError) error = NULL;
+
+  priv->updater_proxy = eos_updater_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                                            G_DBUS_PROXY_FLAGS_NONE,
+                                                            "com.endlessm.Updater",
+                                                            "/com/endlessm/Updater",
+                                                            NULL,
+                                                            &error);
+
+  if (error)
+    {
+      g_critical ("Unable to get a proxy to the EOS updater: %s. Updates will not be available.",
+                  error->message);
+    }
+
+  priv->session_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                       G_DBUS_PROXY_FLAGS_NONE,
+                                                       NULL,
+                                                       "org.gnome.SessionManager",
+                                                       "/org/gnome/SessionManager",
+                                                       "org.gnome.SessionManager",
+                                                       NULL,
+                                                       &error);
+
+  if (error)
+    {
+      g_critical ("Unable to get a proxy to gnome-session: %s. Updates will not be available.",
+                  error->message);
+    }
+
+  if (!priv->updater_proxy || !priv->session_proxy)
+    {
+      gtk_widget_set_visible (priv->os_updates_box, FALSE);
+    }
+  else
+    {
+      g_signal_connect (priv->os_updates_label, "activate-link", G_CALLBACK (updates_link_activated), self);
+      sync_initial_state_from_updater (self);
+    }
+}
+
+static gboolean
 on_attribution_label_link (GtkLabel            *label,
                            gchar               *uri,
                            CcInfoOverviewPanel *self)
@@ -930,6 +1253,15 @@ cc_info_overview_panel_finalize (GObject *object)
   g_free (priv->gnome_date);
   g_free (priv->gnome_distributor);
 
+  if (priv->updater_cancellable)
+    {
+      g_cancellable_cancel (priv->updater_cancellable);
+      g_clear_object (&priv->updater_cancellable);
+    }
+
+  g_clear_object (&priv->updater_proxy);
+  g_clear_object (&priv->session_proxy);
+
   G_OBJECT_CLASS (cc_info_overview_panel_parent_class)->finalize (object);
 }
 
@@ -951,6 +1283,9 @@ cc_info_overview_panel_class_init (CcInfoOverviewPanelClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, processor_label);
   gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, os_name_label);
   gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, os_type_label);
+  gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, os_updates_box);
+  gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, os_updates_label);
+  gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, os_updates_spinner);
   gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, disk_label);
   gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, graphics_label);
   gtk_widget_class_bind_template_child_private (widget_class, CcInfoOverviewPanel, virt_type_label);
@@ -971,6 +1306,7 @@ cc_info_overview_panel_init (CcInfoOverviewPanel *self)
 
   info_overview_panel_setup_overview (self);
   info_overview_panel_setup_virt (self);
+  info_overview_panel_setup_eos_updater (self);
 }
 
 GtkWidget *
diff --git a/panels/info/eos-updater.xml b/panels/info/eos-updater.xml
new file mode 100644
index 0000000..3fa9aae
--- /dev/null
+++ b/panels/info/eos-updater.xml
@@ -0,0 +1,39 @@
+<node>
+  <interface name="com.endlessm.Updater">
+    <method name="Poll" ></method>
+    <method name="Fetch"></method>
+    <method name="Apply"></method>
+
+    <property name="State"            type="u" access="read"/>
+    <property name="UpdateID"         type="s" access="read"/>
+    <property name="UpdateRefspec"    type="s" access="read"/>
+    <property name="OriginalRefspec"  type="s" access="read"/>
+    <property name="CurrentID"        type="s" access="read"/>
+    <property name="UpdateLabel"      type="s" access="read"/>
+    <property name="UpdateMessage"    type="s" access="read"/>
+    <property name="DownloadSize"     type="x" access="read"/>
+    <property name="DownloadedBytes"  type="x" access="read"/>
+    <property name="UnpackedSize"     type="x" access="read"/>
+    <property name="FullDownloadSize" type="x" access="read"/>
+    <property name="FullUnpackedSize" type="x" access="read"/>
+    <!-- An error code, in an unspecified error domain! See ErrorName. -->
+    <property name="ErrorCode"        type="u" access="read"/>
+    <!-- a fully-qualified D-Bus error name, as might be returned from a D-Bus
+         method. "com.endlessm.Updater.Error.LiveBoot" when the system is a
+         read-only live USB, where updates are not supported.
+      -->
+    <property name="ErrorName"        type="s" access="read"/>
+    <!-- a human-readable, albeit unlocalized, error message -->
+    <property name="ErrorMessage"     type="s" access="read"/>
+
+    <signal name="StateChanged">
+      <arg type="u" name="state"/>
+    </signal>
+
+    <signal name="Progress">
+      <arg type="x" name="fetched"/>
+      <arg type="x" name="expected"/>
+    </signal>
+
+  </interface>
+</node>
diff --git a/panels/info/info-overview.ui b/panels/info/info-overview.ui
index 3b4eb3b..d05fd7b 100644
--- a/panels/info/info-overview.ui
+++ b/panels/info/info-overview.ui
@@ -301,20 +301,52 @@
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="attribution_label">
+          <object class="GtkBox">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="use_markup">True</property>
-            <property name="xalign">0</property>
-            <property name="margin_left">22</property>
-            <property name="label" translatable="yes">&lt;a href='attribution-link'&gt;Endless Terms of 
Use&lt;/a&gt;</property>
-            <signal name="activate-link" handler="on_attribution_label_link" object="CcInfoOverviewPanel" 
swapped="no" />
-            <attributes>
-              <attribute name="scale" value="0.83"/>
-            </attributes>
-            <style>
-              <class name="dim-label"/>
-            </style>
+            <property name="orientation">horizontal</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkLabel" id="attribution_label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_markup">True</property>
+                <property name="xalign">0.0</property>
+                <property name="margin_left">22</property>
+                <property name="hexpand">True</property>
+                <property name="label" translatable="yes">&lt;a href='attribution-link'&gt;Endless Terms of 
Use&lt;/a&gt;</property>
+                <signal name="activate-link" handler="on_attribution_label_link" 
object="CcInfoOverviewPanel" swapped="no" />
+                <attributes>
+                  <attribute name="scale" value="0.83"/>
+                </attributes>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox" id="os_updates_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">horizontal</property>
+                <property name="spacing">10</property>
+                <child>
+                  <object class="GtkSpinner" id="os_updates_spinner">
+                    <property name="visible">False</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="os_updates_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="use_markup">True</property>
+                    <property name="label"></property>
+                  </object>
+                </child>
+              </object>
+            </child>
           </object>
           <packing>
             <property name="expand">False</property>


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