[gnome-software] Allow plugins to report non-critical events up to the UI



commit c6260d0e60325679f898963d2f02689640fcfd90
Author: Richard Hughes <richard hughsie com>
Date:   Thu Sep 8 13:37:45 2016 +0100

    Allow plugins to report non-critical events up to the UI
    
    This also removes the hacky last_error GsApp private API and will allow us to
    convert the ugly modal failure dialogs to in-app notifications soon.
    
    To do this, automatically create GsPluginEvent objects for certain errors.

 src/gs-app-private.h   |    3 -
 src/gs-app.c           |   40 -------
 src/gs-page.c          |   36 ------
 src/gs-plugin-loader.c |  292 +++++++++++++++++++++++++++++++++++++++++++++---
 src/gs-plugin-loader.h |    7 +
 src/gs-plugin-vfuncs.h |    9 +-
 src/gs-self-test.c     |   33 +++++-
 src/gs-shell-updates.c |   14 +--
 src/gs-shell.c         |   57 ++++++++++
 9 files changed, 370 insertions(+), 121 deletions(-)
---
diff --git a/src/gs-app-private.h b/src/gs-app-private.h
index 9fb8391..0a83c81 100644
--- a/src/gs-app-private.h
+++ b/src/gs-app-private.h
@@ -26,9 +26,6 @@
 
 G_BEGIN_DECLS
 
-GError         *gs_app_get_last_error          (GsApp          *app);
-void            gs_app_set_last_error          (GsApp          *app,
-                                                GError         *error);
 void            gs_app_set_priority            (GsApp          *app,
                                                 guint           priority);
 guint           gs_app_get_priority            (GsApp          *app);
diff --git a/src/gs-app.c b/src/gs-app.c
index 5d57ec0..654e2cc 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -75,7 +75,6 @@ struct _GsApp
        gchar                   *summary_missing;
        gchar                   *description;
        GsAppQuality             description_quality;
-       GError                  *last_error;
        GPtrArray               *screenshots;
        GPtrArray               *categories;
        GPtrArray               *key_colors;
@@ -253,8 +252,6 @@ gs_app_to_string (GsApp *app)
        str = g_string_new ("GsApp:");
        g_string_append_printf (str, " [%p]\n", app);
        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, "state", as_app_state_to_string (app->state));
        if (app->quirk > 0) {
                g_autofree gchar *qstr = _as_app_quirk_to_string (app->quirk);
@@ -793,9 +790,6 @@ gs_app_set_state_internal (GsApp *app, AsAppState state)
                                 app->id, 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;
        }
 
@@ -3185,38 +3179,6 @@ gs_app_get_priority (GsApp *app)
        return app->priority;
 }
 
-/**
- * gs_app_get_last_error:
- * @app: a #GsApp
- *
- * Get the last error.
- *
- * Returns: a #GError, or %NULL
- *
- * Since: 3.22
- **/
-GError *
-gs_app_get_last_error (GsApp *app)
-{
-       return app->last_error;
-}
-
-/**
- * gs_app_set_last_error:
- * @app: a #GsApp
- * @error: a #GError
- *
- * Sets the last error.
- *
- * Since: 3.22
- **/
-void
-gs_app_set_last_error (GsApp *app, GError *error)
-{
-       g_clear_error (&app->last_error);
-       app->last_error = g_error_copy (error);
-}
-
 static void
 gs_app_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
@@ -3365,8 +3327,6 @@ gs_app_finalize (GObject *object)
        g_ptr_array_unref (app->key_colors);
        if (app->keywords != NULL)
                g_ptr_array_unref (app->keywords);
-       if (app->last_error != NULL)
-               g_error_free (app->last_error);
        if (app->local_file != NULL)
                g_object_unref (app->local_file);
        if (app->pixbuf != NULL)
diff --git a/src/gs-page.c b/src/gs-page.c
index 9a501b7..c739302 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -128,7 +128,6 @@ gs_page_app_installed_cb (GObject *source,
                           gpointer user_data)
 {
        g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
-       GError *last_error;
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsPage *page = helper->page;
        GsPagePrivate *priv = gs_page_get_instance_private (page);
@@ -169,23 +168,6 @@ gs_page_app_installed_cb (GObject *source,
                g_warning ("failed to install %s: %s",
                           gs_app_get_id (helper->app),
                           error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (priv->shell),
-                                           GS_PLUGIN_ACTION_INSTALL,
-                                           error);
-               return;
-       }
-
-       /* 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_ACTION_INSTALL,
-                                           last_error);
                return;
        }
 
@@ -204,7 +186,6 @@ gs_page_app_removed_cb (GObject *source,
                         gpointer user_data)
 {
        g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
-       GError *last_error;
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsPage *page = helper->page;
        GsPagePrivate *priv = gs_page_get_instance_private (page);
@@ -243,23 +224,6 @@ gs_page_app_removed_cb (GObject *source,
                }
 
                g_warning ("failed to remove: %s", error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (priv->shell),
-                                           GS_PLUGIN_ACTION_REMOVE,
-                                           error);
-               return;
-       }
-
-       /* 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_ACTION_REMOVE,
-                                           last_error);
                return;
        }
 
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 738d1d0..322eb0f 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -30,6 +30,7 @@
 #include "gs-category-private.h"
 #include "gs-plugin-loader.h"
 #include "gs-plugin.h"
+#include "gs-plugin-event.h"
 #include "gs-plugin-private.h"
 #include "gs-common.h"
 
@@ -52,6 +53,7 @@ typedef struct
        GPtrArray               *pending_apps;
 
        GSettings               *settings;
+       GHashTable              *events_by_id;          /* unique-id : GsPluginEvent */
 
        gchar                   **compatible_projects;
        guint                    scale;
@@ -71,6 +73,12 @@ enum {
        SIGNAL_LAST
 };
 
+enum {
+       PROP_0,
+       PROP_EVENTS,
+       PROP_LAST
+};
+
 static guint signals [SIGNAL_LAST] = { 0 };
 
 typedef void            (*GsPluginFunc)                (GsPlugin       *plugin);
@@ -238,6 +246,71 @@ gs_plugin_loader_action_stop (GsPluginLoader *plugin_loader, GsPlugin *plugin)
        }
 }
 
+static gboolean
+gs_plugin_loader_notify_idle_cb (gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (user_data);
+       g_object_notify (G_OBJECT (plugin_loader), "events");
+       return FALSE;
+}
+
+static void
+gs_plugin_loader_add_event (GsPluginLoader *plugin_loader, GsPluginEvent *event)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       g_hash_table_insert (priv->events_by_id,
+                            (gpointer) gs_plugin_event_get_unique_id (event),
+                            g_object_ref (event));
+       g_idle_add (gs_plugin_loader_notify_idle_cb, plugin_loader);
+}
+
+/* if the error is worthy of notifying then create a plugin event */
+static void
+gs_plugin_loader_create_event_from_error (GsPluginLoader *plugin_loader,
+                                         GsPluginAction action,
+                                         GsPlugin *plugin,
+                                         GsApp *app,
+                                         const GError *error)
+{
+       guint i;
+       g_autoptr(GsApp) origin = NULL;
+       g_auto(GStrv) split = NULL;
+       g_autoptr(GsPluginEvent) event = NULL;
+
+       /* invalid */
+       if (error == NULL)
+               return;
+       if (error->domain != GS_PLUGIN_ERROR)
+               return;
+
+       /* create plugin event */
+       event = gs_plugin_event_new ();
+       if (app != NULL)
+               gs_plugin_event_set_app (event, app);
+       gs_plugin_event_set_error (event, error);
+       gs_plugin_event_set_action (event, action);
+       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
+
+       /* can we find a unique ID */
+       split = g_strsplit_set (error->message, "[]: ", -1);
+       for (i = 0; split[i] != NULL; i++) {
+               if (as_utils_unique_id_valid (split[i])) {
+                       origin = gs_plugin_cache_lookup (plugin, split[i]);
+                       if (origin != NULL) {
+                               g_debug ("found origin %s in error",
+                                        gs_app_get_unique_id (origin));
+                               gs_plugin_event_set_origin (event, origin);
+                               break;
+                       } else {
+                               g_debug ("no unique ID found for %s", split[i]);
+                       }
+               }
+       }
+
+       /* add event to queue */
+       gs_plugin_loader_add_event (plugin_loader, event);
+}
+
 static void
 gs_plugin_loader_run_adopt (GsPluginLoader *plugin_loader, GsAppList *list)
 {
@@ -378,6 +451,11 @@ gs_plugin_loader_run_refine_internal (GsPluginLoader *plugin_loader,
                                           function_name,
                                           gs_plugin_get_name (plugin),
                                           error_local->message);
+                               gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                         GS_PLUGIN_ACTION_REFINE,
+                                                                         plugin,
+                                                                         NULL, /* app */
+                                                                         error_local);
                                continue;
                        }
                }
@@ -403,6 +481,11 @@ gs_plugin_loader_run_refine_internal (GsPluginLoader *plugin_loader,
                                                   function_name_app,
                                                   gs_plugin_get_name (plugin),
                                                   error_local->message);
+                                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                                 GS_PLUGIN_ACTION_REFINE,
+                                                                                 plugin,
+                                                                                 app,
+                                                                                 error_local);
                                        continue;
                                }
                        }
@@ -580,6 +663,7 @@ static void gs_plugin_loader_add_os_update_item (GsAppList *list);
 
 static GsAppList *
 gs_plugin_loader_run_results (GsPluginLoader *plugin_loader,
+                             GsPluginAction action,
                              const gchar *function_name,
                              GsPluginRefineFlags flags,
                              GCancellable *cancellable,
@@ -644,6 +728,11 @@ gs_plugin_loader_run_results (GsPluginLoader *plugin_loader,
                                   function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 action,
+                                                                 plugin,
+                                                                 NULL, /* app */
+                                                                 error_local);
                        continue;
                }
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
@@ -897,23 +986,21 @@ gs_plugin_loader_get_app_is_compatible (GsApp *app, gpointer user_data)
        return FALSE;
 }
 
-static void
-gs_plugin_loader_set_app_error (GsApp *app, GError *error)
+/**
+ * gs_plugin_loader_get_event_by_id:
+ * @list: A #GsAppList
+ * @unique_id: A unique_id
+ *
+ * Finds the first matching event in the list using the usual wildcard
+ * rules allowed in unique_ids.
+ *
+ * Returns: (transfer none): a #GsPluginEvent, or %NULL if not found
+ **/
+GsPluginEvent *
+gs_plugin_loader_get_event_by_id (GsPluginLoader *plugin_loader, const gchar *unique_id)
 {
-       if (error == NULL)
-               return;
-
-       /* random, non-plugin error domains are never shown to the user */
-       if (error->domain == GS_PLUGIN_ERROR) {
-               g_debug ("saving error for %s: %s",
-                        gs_app_get_unique_id (app),
-                        error->message);
-               gs_app_set_last_error (app, error);
-       } else {
-               g_warning ("not saving error for %s: %s",
-                          gs_app_get_unique_id (app),
-                          error->message);
-       }
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       return g_hash_table_lookup (priv->events_by_id, unique_id);
 }
 
 static gboolean
@@ -929,6 +1016,7 @@ gs_plugin_loader_is_auth_error (GError *err)
 static gboolean
 gs_plugin_loader_run_action (GsPluginLoader *plugin_loader,
                             GsApp *app,
+                            GsPluginAction action,
                             const gchar *function_name,
                             GCancellable *cancellable,
                             GError **error)
@@ -984,7 +1072,11 @@ gs_plugin_loader_run_action (GsPluginLoader *plugin_loader,
                                   function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
-                       gs_plugin_loader_set_app_error (app, error_local);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 action,
+                                                                 plugin,
+                                                                 app,
+                                                                 error_local);
                        continue;
                }
                anything_ran = TRUE;
@@ -1084,6 +1176,7 @@ gs_plugin_loader_get_updates_thread_cb (GTask *task,
                method_name = "gs_plugin_add_updates_historical";
 
        state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   state->action,
                                                    method_name,
                                                    state->flags,
                                                    cancellable,
@@ -1185,6 +1278,7 @@ gs_plugin_loader_get_distro_upgrades_thread_cb (GTask *task,
        GError *error = NULL;
 
        state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   state->action,
                                                    "gs_plugin_add_distro_upgrades",
                                                    state->flags,
                                                    cancellable,
@@ -1263,6 +1357,7 @@ gs_plugin_loader_get_unvoted_reviews_thread_cb (GTask *task,
        GError *error = NULL;
 
        state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   state->action,
                                                    "gs_plugin_add_unvoted_reviews",
                                                    state->flags,
                                                    cancellable,
@@ -1341,6 +1436,7 @@ gs_plugin_loader_get_sources_thread_cb (GTask *task,
        GError *error = NULL;
 
        state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   state->action,
                                                    "gs_plugin_add_sources",
                                                    state->flags,
                                                    cancellable,
@@ -1423,6 +1519,7 @@ gs_plugin_loader_get_installed_thread_cb (GTask *task,
 
        /* do things that would block */
        state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   state->action,
                                                    "gs_plugin_add_installed",
                                                    state->flags,
                                                    cancellable,
@@ -1535,6 +1632,7 @@ gs_plugin_loader_get_popular_thread_cb (GTask *task,
        } else {
                /* do things that would block */
                state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                           state->action,
                                                            "gs_plugin_add_popular",
                                                            state->flags,
                                                            cancellable,
@@ -1623,6 +1721,7 @@ gs_plugin_loader_get_featured_thread_cb (GTask *task,
 
        /* do things that would block */
        state->list = gs_plugin_loader_run_results (plugin_loader,
+                                                   state->action,
                                                    "gs_plugin_add_featured",
                                                    state->flags,
                                                    cancellable,
@@ -2373,6 +2472,11 @@ gs_plugin_loader_get_categories_thread_cb (GTask *task,
                                   function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 state->action,
+                                                                 plugin,
+                                                                 NULL, /* app */
+                                                                 error_local);
                        continue;
                }
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
@@ -2507,6 +2611,11 @@ gs_plugin_loader_get_category_apps_thread_cb (GTask *task,
                                   function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 state->action,
+                                                                 plugin,
+                                                                 NULL, /* app */
+                                                                 error_local);
                        continue;
                }
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
@@ -2730,6 +2839,7 @@ gs_plugin_loader_app_action_thread_cb (GTask *task,
        /* perform action */
        ret = gs_plugin_loader_run_action (plugin_loader,
                                           state->app,
+                                          state->action,
                                           state->function_name,
                                           cancellable,
                                           &error);
@@ -2842,6 +2952,11 @@ gs_plugin_loader_review_action_thread_cb (GTask *task,
                                   state->function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 state->action,
+                                                                 plugin,
+                                                                 state->app,
+                                                                 error_local);
                        continue;
                }
                anything_ran = TRUE;
@@ -3373,6 +3488,73 @@ gs_plugin_loader_get_enabled (GsPluginLoader *plugin_loader,
        return gs_plugin_get_enabled (plugin);
 }
 
+/**
+ * gs_plugin_loader_get_events:
+ * @plugin_loader: A #GsPluginLoader
+ *
+ * Gets all plugin events, even ones that are not active or visible anymore.
+ *
+ * Returns: (transfer container) (element-type GsPluginEvent): events
+ **/
+GPtrArray *
+gs_plugin_loader_get_events (GsPluginLoader *plugin_loader)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       GPtrArray *events = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+       GList *l;
+       g_autoptr(GList) keys = NULL;
+
+       /* just add everything */
+       keys = g_hash_table_get_keys (priv->events_by_id);
+       for (l = keys; l != NULL; l = l->next) {
+               const gchar *key = l->data;
+               GsPluginEvent *event = g_hash_table_lookup (priv->events_by_id, key);
+               g_ptr_array_add (events, g_object_ref (event));
+       }
+       return events;
+}
+
+/**
+ * gs_plugin_loader_get_event_default:
+ * @plugin_loader: A #GsPluginLoader
+ *
+ * Gets an active plugin event where active means that it was not been
+ * already dismissed by the user.
+ *
+ * Returns: a #GsPluginEvent, or %NULL for none
+ **/
+GsPluginEvent *
+gs_plugin_loader_get_event_default (GsPluginLoader *plugin_loader)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       GList *l;
+       g_autoptr(GList) keys = NULL;
+
+       /* just add everything */
+       keys = g_hash_table_get_keys (priv->events_by_id);
+       for (l = keys; l != NULL; l = l->next) {
+               const gchar *key = l->data;
+               GsPluginEvent *event = g_hash_table_lookup (priv->events_by_id, key);
+               if (!gs_plugin_event_has_flag (event, GS_PLUGIN_EVENT_FLAG_INVALID))
+                       return event;
+       }
+       return NULL;
+}
+
+/**
+ * gs_plugin_loader_remove_events:
+ * @plugin_loader: A #GsPluginLoader
+ *
+ * Removes all plugin events from the loader. This function should only be
+ * called from the self tests.
+ **/
+void
+gs_plugin_loader_remove_events (GsPluginLoader *plugin_loader)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       g_hash_table_remove_all (priv->events_by_id);
+}
+
 static void
 gs_plugin_loader_status_changed_cb (GsPlugin *plugin,
                                    GsApp *app,
@@ -3823,6 +4005,11 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
                                g_debug ("disabling %s as setup failed: %s",
                                         gs_plugin_get_name (plugin),
                                         error_local->message);
+                               gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                         GS_PLUGIN_ACTION_SETUP,
+                                                                         plugin,
+                                                                         NULL, /* app */
+                                                                         error_local);
                        }
                        gs_plugin_set_enabled (plugin, FALSE);
                }
@@ -3852,6 +4039,34 @@ gs_plugin_loader_dump_state (GsPluginLoader *plugin_loader)
 }
 
 static void
+gs_plugin_loader_get_property (GObject *object, guint prop_id,
+                              GValue *value, GParamSpec *pspec)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+
+       switch (prop_id) {
+       case PROP_EVENTS:
+               g_value_set_pointer (value, priv->events_by_id);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_plugin_loader_set_property (GObject *object, guint prop_id,
+                              const GValue *value, GParamSpec *pspec)
+{
+       switch (prop_id) {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
 gs_plugin_loader_dispose (GObject *object)
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
@@ -3885,6 +4100,7 @@ gs_plugin_loader_finalize (GObject *object)
        g_free (priv->locale);
        g_free (priv->language);
        g_object_unref (priv->global_cache);
+       g_hash_table_unref (priv->events_by_id);
 
        g_mutex_clear (&priv->pending_apps_mutex);
 
@@ -3894,11 +4110,19 @@ gs_plugin_loader_finalize (GObject *object)
 static void
 gs_plugin_loader_class_init (GsPluginLoaderClass *klass)
 {
+       GParamSpec *pspec;
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+       object_class->get_property = gs_plugin_loader_get_property;
+       object_class->set_property = gs_plugin_loader_set_property;
        object_class->dispose = gs_plugin_loader_dispose;
        object_class->finalize = gs_plugin_loader_finalize;
 
+       pspec = g_param_spec_string ("events", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READABLE);
+       g_object_class_install_property (object_class, PROP_EVENTS, pspec);
+
        signals [SIGNAL_STATUS_CHANGED] =
                g_signal_new ("status-changed",
                              G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
@@ -3943,6 +4167,18 @@ gs_plugin_loader_init (GsPluginLoader *plugin_loader)
        priv->profile = as_profile_new ();
        priv->settings = g_settings_new ("org.gnome.software");
 
+#if AS_CHECK_VERSION(0,6,2)
+       priv->events_by_id = g_hash_table_new_full ((GHashFunc) as_utils_unique_id_hash,
+                                                   (GEqualFunc) as_utils_unique_id_equal,
+                                                   NULL,
+                                                   (GDestroyNotify) g_object_unref);
+#else
+       priv->events_by_id = g_hash_table_new_full (g_str_hash,
+                                                   g_str_equal,
+                                                   NULL,
+                                                   (GDestroyNotify) g_object_unref);
+#endif
+
        /* share a soup session (also disable the double-compression) */
        priv->soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, gs_user_agent (),
                                                            SOUP_SESSION_TIMEOUT, 10,
@@ -4111,6 +4347,12 @@ gs_plugin_loader_run_refresh (GsPluginLoader *plugin_loader,
                                g_propagate_error (error, error_local);
                                error_local = NULL;
                                return FALSE;
+                       } else {
+                               gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                         GS_PLUGIN_ACTION_REFRESH,
+                                                                         plugin,
+                                                                         NULL, /* app */
+                                                                         error_local);
                        }
                        g_warning ("failed to call %s on %s: %s",
                                   function_name,
@@ -4277,6 +4519,11 @@ gs_plugin_loader_file_to_app_thread_cb (GTask *task,
                                   function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 state->action,
+                                                                 plugin,
+                                                                 NULL, /* app */
+                                                                 error_local);
                        continue;
                }
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
@@ -4458,6 +4705,11 @@ gs_plugin_loader_update_thread_cb (GTask *task,
                                   function_name,
                                   gs_plugin_get_name (plugin),
                                   error_local->message);
+                       gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                 state->action,
+                                                                 plugin,
+                                                                 NULL, /* app */
+                                                                 error_local);
                        continue;
                }
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
@@ -4509,7 +4761,11 @@ gs_plugin_loader_update_thread_cb (GTask *task,
                                           function_name,
                                           gs_plugin_get_name (plugin),
                                           error_local->message);
-                               gs_plugin_loader_set_app_error (app, error_local);
+                               gs_plugin_loader_create_event_from_error (plugin_loader,
+                                                                         state->action,
+                                                                         plugin,
+                                                                         app,
+                                                                         error_local);
                                continue;
                        }
                }
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index c43a2dd..6a977ca 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -27,6 +27,7 @@
 #include "gs-app.h"
 #include "gs-auth.h"
 #include "gs-category.h"
+#include "gs-plugin-event.h"
 #include "gs-plugin-private.h"
 
 G_BEGIN_DECLS
@@ -234,6 +235,12 @@ void                gs_plugin_loader_set_network_status    (GsPluginLoader 
*plugin_loader,
 gboolean        gs_plugin_loader_get_plugin_supported  (GsPluginLoader *plugin_loader,
                                                         const gchar    *plugin_func);
 
+GPtrArray      *gs_plugin_loader_get_events            (GsPluginLoader *plugin_loader);
+GsPluginEvent  *gs_plugin_loader_get_event_by_id       (GsPluginLoader *plugin_loader,
+                                                        const gchar    *unique_id);
+GsPluginEvent  *gs_plugin_loader_get_event_default     (GsPluginLoader *plugin_loader);
+void            gs_plugin_loader_remove_events         (GsPluginLoader *plugin_loader);
+
 G_END_DECLS
 
 #endif /* __GS_PLUGIN_LOADER_H */
diff --git a/src/gs-plugin-vfuncs.h b/src/gs-plugin-vfuncs.h
index 525a2ae..2205320 100644
--- a/src/gs-plugin-vfuncs.h
+++ b/src/gs-plugin-vfuncs.h
@@ -513,8 +513,7 @@ gboolean     gs_plugin_update_cancel                (GsPlugin       *plugin,
  * to complete.
  *
  * On failure the error message returned will usually only be shown on the
- * console, but it may also be retained on the #GsApp object.
- * The UI code can retrieve the error using gs_app_get_last_error().
+ * console, but they can also be retrieved using gs_plugin_loader_get_events().
  *
  * NOTE: Once the action is complete, the plugin must set the new state of @app
  * to %AS_APP_STATE_INSTALLED.
@@ -543,8 +542,7 @@ gboolean     gs_plugin_app_install                  (GsPlugin       *plugin,
  * to complete.
  *
  * On failure the error message returned will usually only be shown on the
- * console, but it may also be retained on the #GsApp object.
- * The UI code can retrieve the error using gs_app_get_last_error().
+ * console, but they can also be retrieved using gs_plugin_loader_get_events().
  *
  * NOTE: Once the action is complete, the plugin must set the new state of @app
  * to %AS_APP_STATE_AVAILABLE or %AS_APP_STATE_UNKNOWN if not known.
@@ -591,8 +589,7 @@ gboolean     gs_plugin_app_set_rating               (GsPlugin       *plugin,
  * to complete.
  *
  * On failure the error message returned will usually only be shown on the
- * console, but it may also be retained on the #GsApp object.
- * The UI code can retrieve the error using gs_app_get_last_error().
+ * console, but they can also be retrieved using gs_plugin_loader_get_events().
  *
  * NOTE: Once the action is complete, the plugin must set the new state of @app
  * to %AS_APP_STATE_INSTALLED or %AS_APP_STATE_UNKNOWN if not known.
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index 8571a61..f5f2e7a 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -371,10 +371,15 @@ gs_plugin_loader_install_func (GsPluginLoader *plugin_loader)
 static void
 gs_plugin_loader_error_func (GsPluginLoader *plugin_loader)
 {
+       GsPluginEvent *event;
+       const GError *app_error;
        gboolean ret;
-       g_autoptr(GsApp) app = NULL;
        g_autoptr(GError) error = NULL;
-       GError *last_error;
+       g_autoptr(GPtrArray) events = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* remove previous errors */
+       gs_plugin_loader_remove_events (plugin_loader);
 
        /* suppress this */
        g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
@@ -394,9 +399,27 @@ gs_plugin_loader_error_func (GsPluginLoader *plugin_loader)
        /* 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);
+       /* get event by app-id */
+       event = gs_plugin_loader_get_event_by_id (plugin_loader,
+                                                 "*/*/*/source/dummy/*");
+       g_assert (event != NULL);
+       g_assert (gs_plugin_event_get_app (event) == app);
+
+       /* get last active event */
+       event = gs_plugin_loader_get_event_default (plugin_loader);
+       g_assert (event != NULL);
+       g_assert (gs_plugin_event_get_app (event) == app);
+
+       /* check all the events */
+       events = gs_plugin_loader_get_events (plugin_loader);
+       g_assert_cmpint (events->len, ==, 1);
+       event = g_ptr_array_index (events, 0);
+       g_assert (gs_plugin_event_get_app (event) == app);
+       app_error = gs_plugin_event_get_error (event);
+       g_assert (app_error != NULL);
+       g_assert_error (app_error,
+                       GS_PLUGIN_ERROR,
+                       GS_PLUGIN_ERROR_DOWNLOAD_FAILED);
 }
 
 static void
diff --git a/src/gs-shell-updates.c b/src/gs-shell-updates.c
index 365074a..ea9f4a7 100644
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@ -35,6 +35,7 @@
 #include "gs-update-list.h"
 #include "gs-update-monitor.h"
 #include "gs-upgrade-banner.h"
+#include "gs-utils.h"
 #include "gs-application.h"
 #include "gs-utils.h"
 
@@ -1005,7 +1006,6 @@ upgrade_download_finished_cb (GObject *source,
                               gpointer user_data)
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GError *last_error;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
 
@@ -1014,18 +1014,6 @@ upgrade_download_finished_cb (GObject *source,
                        return;
                g_warning ("failed to upgrade-download: %s", error->message);
        }
-
-       last_error = gs_app_get_last_error (helper->app);
-       if (last_error != NULL) {
-               g_warning ("failed to upgrade-download %s: %s",
-                          gs_app_get_id (helper->app),
-                          last_error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (helper->self->shell),
-                                           GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
-                                           last_error);
-               return;
-       }
 }
 
 static void
diff --git a/src/gs-shell.c b/src/gs-shell.c
index cfd23b7..7221458 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -614,6 +614,57 @@ gs_shell_monitor_permission (GsShell *shell)
                                  G_CALLBACK (on_permission_changed), shell);
 }
 
+static gboolean
+gs_shell_show_event (GsShell *shell, GsPluginEvent *event)
+{
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       gs_app_notify_failed_modal (gs_plugin_event_get_app (event),
+                                   priv->main_window,
+                                   gs_plugin_event_get_action (event),
+                                   gs_plugin_event_get_error (event));
+       /* we want to mark the event as invalid */
+       return FALSE;
+}
+
+static void
+gs_shell_rescan_events (GsShell *shell)
+{
+       GsPluginEvent *event;
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       GtkWidget *widget;
+       guint i;
+
+       /* find the first active event and show it */
+       event = gs_plugin_loader_get_event_default (priv->plugin_loader);
+       if (event != NULL) {
+               if (!gs_shell_show_event (shell, event)) {
+                       GsPluginAction action = gs_plugin_event_get_action (event);
+                       const GError *error = gs_plugin_event_get_error (event);
+                       g_warning ("not handling error %s for action %s: %s",
+                                  gs_plugin_error_to_string (error->code),
+                                  gs_plugin_action_to_string (action),
+                                  error->message);
+                       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_INVALID);
+                       return;
+               }
+               gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_VISIBLE);
+               return;
+       }
+
+       /* nothing to show */
+       g_debug ("no events to show");
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "notification_event"));
+       gtk_widget_set_visible (widget, FALSE);
+}
+
+static void
+gs_shell_events_notify_cb (GsPluginLoader *plugin_loader,
+                          GParamSpec *pspec,
+                          GsShell *shell)
+{
+       gs_shell_rescan_events (shell);
+}
+
 void
 gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *cancellable)
 {
@@ -625,6 +676,9 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
        priv->plugin_loader = g_object_ref (plugin_loader);
        g_signal_connect (priv->plugin_loader, "reload",
                          G_CALLBACK (gs_shell_reload_cb), shell);
+       g_signal_connect_object (priv->plugin_loader, "notify::events",
+                                G_CALLBACK (gs_shell_events_notify_cb),
+                                shell, 0);
        priv->cancellable = g_object_ref (cancellable);
 
        gs_shell_monitor_permission (shell);
@@ -749,6 +803,9 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
        /* load content */
        g_signal_connect (priv->shell_loading, "refreshed",
                          G_CALLBACK (initial_overview_load_done), shell);
+
+       /* coldplug */
+       gs_shell_rescan_events (shell);
 }
 
 void


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