[gnome-software] Show some non-fatal error messages if installing fails



commit b4d22eb4fdf6859e50b3e2e7d5d54bd42451ddef
Author: Richard Hughes <richard hughsie com>
Date:   Sun Apr 17 16:49:28 2016 +0100

    Show some non-fatal error messages if installing fails

 src/gs-app.c                  |   36 +++++++++++++++++++++-
 src/gs-app.h                  |    3 ++
 src/gs-page.c                 |   28 +++++++++++++++++
 src/gs-plugin-loader.c        |   25 +++++++++++++++
 src/gs-self-test.c            |   34 +++++++++++++++++++++
 src/gs-utils.c                |   67 +++++++++++++++++++++++++++++++++++++----
 src/plugins/gs-plugin-dummy.c |   21 +++++++++++++
 7 files changed, 206 insertions(+), 8 deletions(-)
---
diff --git a/src/gs-app.c b/src/gs-app.c
index cc4c73b..1c06828 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -68,6 +68,7 @@ struct _GsApp
        gchar                   *summary_missing;
        gchar                   *description;
        GsAppQuality             description_quality;
+       GError                  *last_error;
        GPtrArray               *screenshots;
        GPtrArray               *categories;
        GPtrArray               *keywords;
@@ -175,6 +176,8 @@ gs_app_to_string (GsApp *app)
 
        str = g_string_new ("GsApp:\n");
        gs_app_kv_lpad (str, "kind", as_app_kind_to_string (app->kind));
+       if (app->last_error != NULL)
+               gs_app_kv_lpad (str, "last-error", app->last_error->message);
        gs_app_kv_lpad (str, "compulsory",
                        gs_app_has_quirk (app, AS_APP_QUIRK_COMPULSORY)
                        ? "True" : "False");
@@ -445,8 +448,13 @@ gs_app_get_progress (GsApp *app)
 void
 gs_app_set_state_recover (GsApp *app)
 {
-       if (app->state_recover != AS_APP_STATE_UNKNOWN)
-               gs_app_set_state (app, app->state_recover);
+       if (app->state_recover == AS_APP_STATE_UNKNOWN)
+               return;
+       if (app->state_recover == app->state)
+               return;
+       app->state = app->state_recover;
+       app->state_recover = AS_APP_STATE_UNKNOWN;
+       gs_app_queue_notify (app, "state");
 }
 
 /**
@@ -564,6 +572,9 @@ gs_app_set_state_internal (GsApp *app, AsAppState state)
                g_debug ("non-transient state now %s",
                         as_app_state_to_string (state));
                app->state_recover = state;
+
+               /* clear the error as the application has changed state */
+               g_clear_error (&app->last_error);
                break;
        }
 
@@ -2224,6 +2235,25 @@ gs_app_get_match_value (GsApp *app)
 }
 
 /**
+ * gs_app_get_last_error:
+ */
+GError *
+gs_app_get_last_error (GsApp *app)
+{
+       return app->last_error;
+}
+
+/**
+ * gs_app_set_last_error:
+ */
+void
+gs_app_set_last_error (GsApp *app, GError *error)
+{
+       g_clear_error (&app->last_error);
+       app->last_error = g_error_copy (error);
+}
+
+/**
  * gs_app_get_property:
  */
 static void
@@ -2375,6 +2405,8 @@ gs_app_finalize (GObject *object)
        g_ptr_array_unref (app->categories);
        if (app->keywords != NULL)
                g_ptr_array_unref (app->keywords);
+       if (app->last_error != NULL)
+               g_error_free (app->last_error);
 
        G_OBJECT_CLASS (gs_app_parent_class)->finalize (object);
 }
diff --git a/src/gs-app.h b/src/gs-app.h
index 8b1ff4d..7e10980 100644
--- a/src/gs-app.h
+++ b/src/gs-app.h
@@ -242,6 +242,9 @@ gboolean     gs_app_has_quirk               (GsApp          *app,
                                                 AsAppQuirk      quirk);
 void            gs_app_add_quirk               (GsApp          *app,
                                                 AsAppQuirk      quirk);
+GError         *gs_app_get_last_error          (GsApp          *app);
+void            gs_app_set_last_error          (GsApp          *app,
+                                                GError         *error);
 
 G_END_DECLS
 
diff --git a/src/gs-page.c b/src/gs-page.c
index db91dea..b7dd9da 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -60,6 +60,7 @@ gs_page_app_installed_cb (GObject *source,
                           GAsyncResult *res,
                           gpointer user_data)
 {
+       GError *last_error;
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsPageHelper *helper = (GsPageHelper *) user_data;
        GsPage *page = helper->page;
@@ -81,6 +82,19 @@ gs_page_app_installed_cb (GObject *source,
                goto out;
        }
 
+       /* non-fatal error */
+       last_error = gs_app_get_last_error (helper->app);
+       if (last_error != NULL) {
+               g_warning ("failed to install %s: %s",
+                          gs_app_get_id (helper->app),
+                          last_error->message);
+               gs_app_notify_failed_modal (helper->app,
+                                           gs_shell_get_window (priv->shell),
+                                           GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                           last_error);
+               goto out;
+       }
+
        /* only show this if the window is not active */
        if (gs_app_get_state (helper->app) != AS_APP_STATE_QUEUED_FOR_INSTALL &&
            !gs_shell_is_active (priv->shell))
@@ -98,6 +112,7 @@ gs_page_app_removed_cb (GObject *source,
                         GAsyncResult *res,
                         gpointer user_data)
 {
+       GError *last_error;
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsPageHelper *helper = (GsPageHelper *) user_data;
        GsPage *page = helper->page;
@@ -117,6 +132,19 @@ gs_page_app_removed_cb (GObject *source,
                goto out;
        }
 
+       /* non-fatal error */
+       last_error = gs_app_get_last_error (helper->app);
+       if (last_error != NULL) {
+               g_warning ("failed to remove %s: %s",
+                          gs_app_get_id (helper->app),
+                          last_error->message);
+               gs_app_notify_failed_modal (helper->app,
+                                           gs_shell_get_window (priv->shell),
+                                           GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                           last_error);
+               goto out;
+       }
+
        if (GS_PAGE_GET_CLASS (page)->app_removed != NULL)
                GS_PAGE_GET_CLASS (page)->app_removed (page, helper->app);
 
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 68fa9e7..107e1bd 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -615,6 +615,29 @@ gs_plugin_loader_get_app_is_compatible (GsApp *app, gpointer user_data)
 }
 
 /**
+ * gs_plugin_loader_set_app_error:
+ **/
+static void
+gs_plugin_loader_set_app_error (GsApp *app, GError *error)
+{
+       if (error == NULL)
+               return;
+
+       /* random, non-plugin error domains are never shown to the user */
+       if (error->domain == GS_PLUGIN_ERROR &&
+           gs_app_get_last_error (app) == NULL) {
+               g_debug ("saving error for %s: %s",
+                        gs_app_get_id (app),
+                        error->message);
+               gs_app_set_last_error (app, error);
+       } else {
+               g_warning ("not saving error for %s: %s",
+                          gs_app_get_id (app),
+                          error->message);
+       }
+}
+
+/**
  * gs_plugin_loader_run_action:
  **/
 static gboolean
@@ -658,6 +681,7 @@ gs_plugin_loader_run_action (GsPluginLoader *plugin_loader,
                        g_warning ("failed to call %s on %s: %s",
                                   function_name, plugin->name,
                                   error_local->message);
+                       gs_plugin_loader_set_app_error (app, error_local);
                        continue;
                }
                anything_ran = TRUE;
@@ -3931,6 +3955,7 @@ gs_plugin_loader_update_thread_cb (GTask *task,
                                g_warning ("failed to call %s on %s: %s",
                                           function_name, plugin->name,
                                           error_local->message);
+                               gs_plugin_loader_set_app_error (app, error_local);
                                continue;
                        }
                }
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index cfbf395..993a614 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -196,6 +196,37 @@ gs_plugin_loader_install_func (GsPluginLoader *plugin_loader)
 }
 
 static void
+gs_plugin_loader_error_func (GsPluginLoader *plugin_loader)
+{
+       gboolean ret;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GError) error = NULL;
+       GError *last_error;
+
+       /* suppress this */
+       g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+                              "failed to call gs_plugin_update_app on dummy*");
+
+       /* update, which should cause an error to be emitted */
+       app = gs_app_new ("chiron.desktop");
+       gs_app_set_management_plugin (app, "dummy");
+       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       ret = gs_plugin_loader_app_action (plugin_loader, app,
+                                          GS_PLUGIN_LOADER_ACTION_UPDATE,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+
+       /* ensure we failed the plugin action */
+       g_test_assert_expected_messages ();
+
+       /* retrieve the error from the application */
+       last_error = gs_app_get_last_error (app);
+       g_assert_error (last_error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NO_NETWORK);
+}
+
+static void
 gs_plugin_loader_refine_func (GsPluginLoader *plugin_loader)
 {
        gboolean ret;
@@ -515,6 +546,9 @@ main (int argc, char **argv)
        g_test_add_data_func ("/gnome-software/plugin-loader{install}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_install_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{error}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_error_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{installed}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_installed_func);
diff --git a/src/gs-utils.c b/src/gs-utils.c
index fc8c7ba..f727b72 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -161,32 +161,87 @@ gs_app_notify_failed_modal (GsApp *app,
 {
        GtkWidget *dialog;
        const gchar *title;
-       g_autofree gchar *msg = NULL;
+       gboolean show_detailed_error;
+       g_autoptr(GString) msg = NULL;
 
+       /* TRANSLATORS: install or removed failed */
        title = _("Sorry, this did not work");
+
+       /* say what we tried to do */
+       msg = g_string_new ("");
        switch (action) {
        case GS_PLUGIN_LOADER_ACTION_INSTALL:
                /* TRANSLATORS: this is when the install fails */
-               msg = g_strdup_printf (_("Installation of %s failed."),
-                                      gs_app_get_name (app));
+               g_string_append_printf (msg, _("Installation of %s failed."),
+                                       gs_app_get_name (app));
                break;
        case GS_PLUGIN_LOADER_ACTION_REMOVE:
                /* TRANSLATORS: this is when the remove fails */
-               msg = g_strdup_printf (_("Removal of %s failed."),
-                                      gs_app_get_name (app));
+               g_string_append_printf (msg, _("Removal of %s failed."),
+                                       gs_app_get_name (app));
                break;
        default:
                g_assert_not_reached ();
                break;
        }
+       g_string_append (msg, " ");
+
+       /* give details about the error */
+       switch (error->code) {
+       case GS_PLUGIN_ERROR_NO_NETWORK:
+               /* TRANSLATORS: the package manager needed to download
+                * something with no network available */
+               g_string_append (msg, _("Internet access was required but wasn’t available."));
+               g_string_append (msg, " ");
+               /* TRANSLATORS: plug in the network cable... */
+               g_string_append (msg, _("Please make sure that you have internet access and try again."));
+               show_detailed_error = FALSE;
+               break;
+       case GS_PLUGIN_ERROR_NO_SPACE:
+               /* TRANSLATORS: we ran out of disk space */
+               g_string_append (msg, _("There wasn’t enough disk space."));
+               g_string_append (msg, " ");
+               /* TRANSLATORS: delete some stuff! */
+               g_string_append (msg, _("Please free up some space and try again."));
+               show_detailed_error = FALSE;
+               break;
+       default:
+               /* TRANSLATORS: we didn't handle the error type */
+               g_string_append (msg, _("If the problem persists, contact your software provider."));
+               show_detailed_error = TRUE;
+       }
+
        dialog = gtk_message_dialog_new (parent_window,
                                         GTK_DIALOG_MODAL |
+                                        GTK_DIALOG_USE_HEADER_BAR |
                                         GTK_DIALOG_DESTROY_WITH_PARENT,
                                         GTK_MESSAGE_ERROR,
                                         GTK_BUTTONS_CLOSE,
                                         "%s", title);
        gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
-                                                 "%s", msg);
+                                                 "%s", msg->str);
+
+       /* detailed error in an expander */
+       if (show_detailed_error) {
+               GtkWidget *vbox;
+               GtkWidget *expander;
+               GtkWidget *scrolled_window;
+               GtkWidget *label;
+
+               vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+               /* TRANSLATORS: this is an expander title */
+               expander = gtk_expander_new (_("Show Details"));
+               gtk_widget_set_margin_start (expander, 36);
+               gtk_widget_set_margin_end (expander, 36);
+               scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+               gtk_container_add (GTK_CONTAINER (expander), scrolled_window);
+               label = gtk_label_new (error->message);
+               gtk_container_add (GTK_CONTAINER (scrolled_window), label);
+               gtk_box_pack_end (GTK_BOX (vbox), expander, FALSE, TRUE, 4);
+               gtk_widget_show_all (expander);
+
+       }
+
        g_signal_connect (dialog, "response",
                          G_CALLBACK (gtk_widget_destroy), NULL);
        gtk_window_present (GTK_WINDOW (dialog));
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index c5862b5..6426fd0 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -297,6 +297,27 @@ gs_plugin_app_install (GsPlugin *plugin,
 }
 
 /**
+ * gs_plugin_update_app:
+ */
+gboolean
+gs_plugin_update_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+               return TRUE;
+
+       /* always fail */
+       g_set_error_literal (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_NO_NETWORK,
+                            "no network connection is available");
+       return FALSE;
+}
+
+/**
  * gs_plugin_refine_app:
  */
 gboolean


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