[gnome-software] Allow the user to restart the currently running gnome-software instance



commit b4012c274e1ef2c14183f8f81fbc9e83ef92c903
Author: Richard Hughes <richard hughsie com>
Date:   Tue Feb 21 12:47:28 2017 +0000

    Allow the user to restart the currently running gnome-software instance
    
    This can happen if new plugins have been installed at runtime or if the user
    has used something like jhbuild or rpm to live update a running instance.
    
    To do this, use a new helper process that watches for the D-Bus name correctly.
    
    Resolves: https://bugzilla.gnome.org/show_bug.cgi?id=778949

 src/Makefile.am       |   15 +++
 src/gnome-software.ui |   11 +++
 src/gs-application.c  |   10 ++
 src/gs-restarter.c    |  229 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/gs-shell.c        |   21 +++++
 5 files changed, 286 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 7ace0d5..f964836 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -110,6 +110,7 @@ gnome-software-service.desktop: gnome-software-service.desktop.in Makefile
        $(AM_V_GEN) sed -e "s|\@bindir\@|$(bindir)|" $<> $@
 
 libexec_PROGRAMS =                                     \
+       gnome-software-restarter                        \
        gnome-software-cmd
 
 gnome_software_cmd_SOURCES =                           \
@@ -308,6 +309,20 @@ gnome_software_LDFLAGS =                           \
        $(PIE_LDFLAGS)                                  \
        $(RELRO_LDFLAGS)
 
+gnome_software_restarter_SOURCES =                     \
+       gs-restarter.c
+
+gnome_software_restarter_LDADD =                       \
+       $(GLIB_LIBS)
+
+gnome_software_restarter_CFLAGS =                      \
+       -DLIBEXECDIR=\"$(libexecdir)\"                  \
+       $(WARN_CFLAGS)
+
+gnome_software_restarter_LDFLAGS =                     \
+       $(PIE_LDFLAGS)                                  \
+       $(RELRO_LDFLAGS)
+
 packagekit_built_sources = gs-packagekit-generated.c gs-packagekit-generated.h
 $(packagekit_built_sources): Makefile.am org.freedesktop.PackageKit.xml
        $(AM_V_GEN) gdbus-codegen                               \
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index 16d0a57..2e5e1a5 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -378,6 +378,17 @@
                           </packing>
                         </child>
                         <child>
+                          <object class="GtkButton" id="button_events_restart_required">
+                            <property name="label" translatable="yes" comments="button in the info 
bar">Restart Now</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                          </packing>
+                        </child>
+                        <child>
                           <object class="GtkButton" id="button_events_more_info">
                             <property name="label" translatable="yes" comments="button in the info bar">More 
Information</property>
                             <property name="can_focus">True</property>
diff --git a/src/gs-application.c b/src/gs-application.c
index 0f6ace1..5abf7b1 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -446,6 +446,15 @@ reboot_activated (GSimpleAction *action,
 }
 
 static void
+shutdown_activated (GSimpleAction *action,
+                   GVariant      *parameter,
+                   gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       g_application_quit (G_APPLICATION (app));
+}
+
+static void
 reboot_and_install (GSimpleAction *action,
                    GVariant      *parameter,
                    gpointer       data)
@@ -718,6 +727,7 @@ static GActionEntry actions[] = {
        { "profile", profile_activated, NULL, NULL, NULL },
        { "reboot-and-install", reboot_and_install, NULL, NULL, NULL },
        { "reboot", reboot_activated, NULL, NULL, NULL },
+       { "shutdown", shutdown_activated, NULL, NULL, NULL },
        { "set-mode", set_mode_activated, "s", NULL, NULL },
        { "search", search_activated, "s", NULL, NULL },
        { "details", details_activated, "(ss)", NULL, NULL },
diff --git a/src/gs-restarter.c b/src/gs-restarter.c
new file mode 100644
index 0000000..f95e2fd
--- /dev/null
+++ b/src/gs-restarter.c
@@ -0,0 +1,229 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <stdlib.h>
+
+#define GS_BINARY_NAME                 "gnome-software"
+#define GS_DBUS_BUS_NAME               "org.gnome.Software"
+#define GS_DBUS_OBJECT_PATH            "/org/gnome/Software"
+#define GS_DBUS_INTERFACE_NAME         "org.gtk.Actions"
+
+typedef struct {
+       GMainLoop       *loop;
+       GDBusConnection *connection;
+       gboolean         is_running;
+       gboolean         timed_out;
+} GsRestarterPrivate;
+
+static void
+gs_restarter_on_name_appeared_cb (GDBusConnection *connection,
+                                 const gchar *name,
+                                 const gchar *name_owner,
+                                 gpointer user_data)
+{
+       GsRestarterPrivate *priv = (GsRestarterPrivate *) user_data;
+       priv->is_running = TRUE;
+       g_debug ("%s appeared", GS_DBUS_BUS_NAME);
+       if (g_main_loop_is_running (priv->loop))
+               g_main_loop_quit (priv->loop);
+}
+
+static void
+gs_restarter_on_name_vanished_cb (GDBusConnection *connection,
+                                 const gchar *name,
+                                 gpointer user_data)
+{
+       GsRestarterPrivate *priv = (GsRestarterPrivate *) user_data;
+       priv->is_running = FALSE;
+       g_debug ("%s vanished", GS_DBUS_BUS_NAME);
+       if (g_main_loop_is_running (priv->loop))
+               g_main_loop_quit (priv->loop);
+}
+
+static gboolean
+gs_restarter_loop_timeout_cb (gpointer user_data)
+{
+       GsRestarterPrivate *priv = (GsRestarterPrivate *) user_data;
+       priv->timed_out = TRUE;
+       g_main_loop_quit (priv->loop);
+       return TRUE;
+}
+
+static gboolean
+gs_restarter_wait_for_timeout (GsRestarterPrivate *priv,
+                              guint timeout_ms,
+                              GError **error)
+{
+       guint timer_id;
+       priv->timed_out = FALSE;
+       timer_id = g_timeout_add (timeout_ms, gs_restarter_loop_timeout_cb, priv);
+       g_main_loop_run (priv->loop);
+       g_source_remove (timer_id);
+       if (priv->timed_out) {
+               g_set_error (error,
+                            G_IO_ERROR,
+                            G_IO_ERROR_TIMED_OUT,
+                            "Waited for %ums", timeout_ms);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static GsRestarterPrivate *
+gs_restarter_private_new (void)
+{
+       GsRestarterPrivate *priv = g_new0 (GsRestarterPrivate, 1);
+       priv->loop = g_main_loop_new (NULL, FALSE);
+       return priv;
+}
+
+static void
+gs_restarter_private_free (GsRestarterPrivate *priv)
+{
+       if (priv->connection != NULL)
+               g_object_unref (priv->connection);
+       g_main_loop_unref (priv->loop);
+       g_free (priv);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsRestarterPrivate, gs_restarter_private_free)
+
+static gboolean
+gs_restarter_create_new_process (GsRestarterPrivate *priv, GError **error)
+{
+       g_autofree gchar *binary_filename = NULL;
+
+       /* start executable */
+       binary_filename = g_build_filename (BINDIR, GS_BINARY_NAME, NULL);
+       g_debug ("starting new binary %s", binary_filename);
+       if (!g_spawn_command_line_async (binary_filename, error))
+               return FALSE;
+
+       /* wait for the bus name to appear */
+       if (!gs_restarter_wait_for_timeout (priv, 15000, error)) {
+               g_prefix_error (error, "%s did not appear: ", GS_DBUS_BUS_NAME);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+gs_restarter_destroy_old_process (GsRestarterPrivate *priv, GError **error)
+{
+       GVariantBuilder args_params;
+       GVariantBuilder args_platform_data;
+       g_autoptr(GVariant) reply = NULL;
+
+       /* call a GtkAction */
+       g_variant_builder_init (&args_params, g_variant_type_new ("av"));
+       g_variant_builder_init (&args_platform_data, g_variant_type_new ("a{sv}"));
+       reply = g_dbus_connection_call_sync (priv->connection,
+                                            GS_DBUS_BUS_NAME,
+                                            GS_DBUS_OBJECT_PATH,
+                                            GS_DBUS_INTERFACE_NAME,
+                                            "Activate",
+                                            g_variant_new ("(sava{sv})",
+                                                           "shutdown",
+                                                           &args_params,
+                                                           &args_platform_data),
+                                            NULL,
+                                            G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                                            5000,
+                                            NULL,
+                                            error);
+       if (reply == NULL) {
+               g_prefix_error (error, "Failed to send RequestShutdown: ");
+               return FALSE;
+       }
+
+       /* wait for the name to disappear from the bus */
+       if (!gs_restarter_wait_for_timeout (priv, 30000, error)) {
+               g_prefix_error (error, "Failed to see %s vanish: ", GS_DBUS_BUS_NAME);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+gs_restarter_setup_watcher (GsRestarterPrivate *priv, GError **error)
+{
+       /* watch the name appear and vanish */
+       priv->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+       if (priv->connection == NULL) {
+               g_prefix_error (error, "Failed to get D-Bus connection: ");
+               return FALSE;
+       }
+       g_bus_watch_name_on_connection (priv->connection,
+                                       GS_DBUS_BUS_NAME,
+                                       G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                       gs_restarter_on_name_appeared_cb,
+                                       gs_restarter_on_name_vanished_cb,
+                                       priv,
+                                       NULL);
+
+       /* wait for one of the callbacks to be called */
+       if (!gs_restarter_wait_for_timeout (priv, 50, error)) {
+               g_prefix_error (error, "Failed to watch %s: ", GS_DBUS_BUS_NAME);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+       g_autoptr(GsRestarterPrivate) priv = NULL;
+       g_autoptr(GError) error = NULL;
+
+       /* show all debugging */
+       g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+       /* set up the watcher */
+       priv = gs_restarter_private_new ();
+       if (!gs_restarter_setup_watcher (priv, &error)) {
+               g_warning ("Failed to set up: %s", error->message);
+               return EXIT_FAILURE;
+       }
+
+       /* kill the old process */
+       if (priv->is_running) {
+               if (!gs_restarter_destroy_old_process (priv, &error)) {
+                       g_warning ("Failed to quit service: %s", error->message);
+                       return EXIT_FAILURE;
+               }
+       }
+
+       /* start a new process */
+       if (!gs_restarter_create_new_process (priv, &error)) {
+               g_warning ("Failed to start service: %s", error->message);
+               return EXIT_FAILURE;
+       }
+
+       /* success */
+       g_debug ("%s process successfully restarted", GS_DBUS_BUS_NAME);
+       return EXIT_SUCCESS;
+}
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 9744777..5a057c6 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -480,6 +480,14 @@ gs_shell_plugin_events_more_info_cb (GtkWidget *widget, GsShell *shell)
 }
 
 static void
+gs_shell_plugin_events_restart_required_cb (GtkWidget *widget, GsShell *shell)
+{
+       g_autoptr(GError) error = NULL;
+       if (!g_spawn_command_line_async (LIBEXECDIR "/gnome-software-restarter", &error))
+               g_warning ("failed to restart: %s", error->message);
+}
+
+static void
 gs_shell_go_back (GsShell *shell)
 {
        GsShellPrivate *priv = gs_shell_get_instance_private (shell);
@@ -738,6 +746,7 @@ typedef enum {
        GS_SHELL_EVENT_BUTTON_NO_SPACE          = 1 << 1,
        GS_SHELL_EVENT_BUTTON_NETWORK_SETTINGS  = 1 << 2,
        GS_SHELL_EVENT_BUTTON_MORE_INFO         = 1 << 3,
+       GS_SHELL_EVENT_BUTTON_RESTART_REQUIRED  = 1 << 4,
        GS_SHELL_EVENT_BUTTON_LAST
 } GsShellEventButtons;
 
@@ -765,10 +774,18 @@ gs_shell_show_event_app_notify (GsShell *shell,
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_network_settings"));
        gtk_widget_set_visible (widget, (buttons & GS_SHELL_EVENT_BUTTON_NETWORK_SETTINGS) > 0);
 
+       /* restart button */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_restart_required"));
+       gtk_widget_set_visible (widget, (buttons & GS_SHELL_EVENT_BUTTON_RESTART_REQUIRED) > 0);
+
        /* more-info button */
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_more_info"));
        gtk_widget_set_visible (widget, (buttons & GS_SHELL_EVENT_BUTTON_MORE_INFO) > 0);
 
+       /* dismiss button */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_dismiss"));
+       gtk_widget_set_visible (widget, (buttons & GS_SHELL_EVENT_BUTTON_RESTART_REQUIRED) == 0);
+
        /* set title */
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "label_events"));
        gtk_label_set_markup (GTK_LABEL (widget), title);
@@ -1451,6 +1468,7 @@ gs_shell_show_event_fallback (GsShell *shell, GsPluginEvent *event)
                        g_string_append (str, _("This application needs to be "
                                                "restarted to use new plugins."));
                }
+               buttons |= GS_SHELL_EVENT_BUTTON_RESTART_REQUIRED;
                break;
        case GS_PLUGIN_ERROR_AC_POWER_REQUIRED:
                /* TRANSLATORS: need to be connected to the AC power */
@@ -1669,6 +1687,9 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
        widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_more_info"));
        g_signal_connect (widget, "clicked",
                          G_CALLBACK (gs_shell_plugin_events_more_info_cb), shell);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_restart_required"));
+       g_signal_connect (widget, "clicked",
+                         G_CALLBACK (gs_shell_plugin_events_restart_required_cb), shell);
 
        priv->shell_overview = GS_SHELL_OVERVIEW (gtk_builder_get_object (priv->builder, "shell_overview"));
        gs_shell_overview_setup (priv->shell_overview,


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