[gnome-software/wip/hughsie/GsPluginEvent] Allow plugins to report non-critical events up to the UI



commit 8ab9f2282f91de1fbe6610c144d3858819886d0f
Author: Richard Hughes <richard hughsie com>
Date:   Tue Sep 6 18:49:12 2016 +0100

    Allow plugins to report non-critical events up to the UI
    
    Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=770918

 src/Makefile.am               |    4 +
 src/gnome-software.ui         |   83 ++++++++++++++++++++-
 src/gs-plugin-event.c         |  166 +++++++++++++++++++++++++++++++++++++++++
 src/gs-plugin-event.h         |   58 ++++++++++++++
 src/gs-plugin-loader.c        |   65 ++++++++++++++++
 src/gs-plugin-loader.h        |    1 +
 src/gs-plugin.c               |   42 ++++++++++
 src/gs-plugin.h               |    7 ++-
 src/gs-shell.c                |  130 ++++++++++++++++++++++++++++++++
 src/plugins/gs-plugin-dummy.c |   26 +++++++
 10 files changed, 580 insertions(+), 2 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index b8677dd..a7047c8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -115,6 +115,7 @@ gnome_software_cmd_SOURCES =                                \
        gs-debug.c                                      \
        gs-utils.c                                      \
        gs-os-release.c                                 \
+       gs-plugin-event.c                               \
        gs-plugin-loader.c                              \
        gs-plugin-loader-sync.c                         \
        gs-category.c                                   \
@@ -192,6 +193,8 @@ gnome_software_SOURCES =                            \
        gs-page.h                                       \
        gs-plugin.c                                     \
        gs-plugin.h                                     \
+       gs-plugin-event.c                               \
+       gs-plugin-event.h                               \
        gs-plugin-private.h                             \
        gs-plugin-vfuncs.h                              \
        gs-progress-button.c                            \
@@ -340,6 +343,7 @@ gs_self_test_SOURCES =                                              \
        gs-category.c                                           \
        gs-common.c                                             \
        gs-os-release.c                                         \
+       gs-plugin-event.c                                       \
        gs-plugin-loader-sync.c                                 \
        gs-plugin-loader.c                                      \
        gs-plugin.c                                             \
diff --git a/src/gnome-software.ui b/src/gnome-software.ui
index 52755ee..e06882f 100644
--- a/src/gnome-software.ui
+++ b/src/gnome-software.ui
@@ -276,6 +276,87 @@
         </child>
 
         <child>
+          <object class="GtkInfoBar" id="infobar_events">
+            <property name="app_paintable">True</property>
+            <property name="can_focus">False</property>
+            <property name="show_close_button">True</property>
+            <child internal-child="action_area">
+              <object class="GtkButtonBox">
+                <property name="can_focus">False</property>
+                <property name="layout_style">end</property>
+                <child>
+                  <object class="GtkButton" id="button_events_sources">
+                    <property name="label" translatable="yes" comments="button in the info bar">Software 
Sources</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>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child internal-child="content_area">
+              <object class="GtkBox">
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
+                <property name="border_width">12</property>
+                <child>
+                  <object class="GtkLabel" id="label_events_title">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label">Some Title</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_events">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label">Longer text that explains things some more.</property>
+                    <property name="wrap">True</property>
+                    <property name="wrap_mode">word-char</property>
+                    <property name="xalign">0</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+
+        <child>
           <object class="GtkStack" id="stack_main">
             <property name="visible">True</property>
             <child>
@@ -358,7 +439,7 @@
           <packing>
             <property name="expand">True</property>
             <property name="fill">True</property>
-            <property name="position">2</property>
+            <property name="position">3</property>
           </packing>
         </child>
       </object>
diff --git a/src/gs-plugin-event.c b/src/gs-plugin-event.c
new file mode 100644
index 0000000..173b399
--- /dev/null
+++ b/src/gs-plugin-event.c
@@ -0,0 +1,166 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 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.
+ */
+
+/**
+ * SECTION:gs-event
+ * @title: GsPluginEvent
+ * @include: gnome-software.h
+ * @stability: Unstable
+ * @short_description: Infomation about a plugin event
+ *
+ * These functions provide a way for plugins to tell the UI layer about events
+ * that may require displaying to the user. Plugins should not assume that a
+ * specific event is actually shown to the user as it may be ignored
+ * automatically.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gs-plugin-event.h"
+
+struct _GsPluginEvent
+{
+       GObject                  parent_instance;
+       GsApp                   *app;
+       GsPluginEventFlag        flags;
+};
+
+G_DEFINE_TYPE (GsPluginEvent, gs_plugin_event, G_TYPE_OBJECT)
+
+/**
+ * gs_plugin_event_set_app:
+ * @event: A #GsPluginEvent
+ * @app: A #GsApp
+ *
+ * Set the application (or source, or whatever component) that caused the event
+ * to be created.
+ *
+ * Since: 3.22
+ **/
+void
+gs_plugin_event_set_app (GsPluginEvent *event, GsApp *app)
+{
+       g_return_if_fail (GS_IS_PLUGIN_EVENT (event));
+       g_return_if_fail (GS_IS_APP (app));
+       g_set_object (&event->app, app);
+}
+
+/**
+ * gs_plugin_event_get_app:
+ * @event: A #GsPluginEvent
+ *
+ * Gets an application that created the event.
+ *
+ * Returns: (transfer none): a #GsApp, or %NULL if unset
+ *
+ * Since: 3.22
+ **/
+GsApp *
+gs_plugin_event_get_app (GsPluginEvent *event)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_EVENT (event), NULL);
+       return event->app;
+}
+
+/**
+ * gs_plugin_event_get_kind:
+ * @event: A #GsPluginEvent
+ * @unique_id: A unique_id
+ *
+ * Gets the kind of the event.
+ *
+ * Returns: a #GsPluginEventKind, e.g. %GS_PLUGIN_EVENT_KIND_WARNING
+ *
+ * Since: 3.22
+ **/
+void
+gs_plugin_event_add_flag (GsPluginEvent *event, GsPluginEventFlag flag)
+{
+       g_return_if_fail (GS_IS_PLUGIN_EVENT (event));
+       event->flags |= flag;
+}
+
+/**
+ * gs_plugin_event_set_kind:
+ * @event: A #GsPluginEvent
+ * @kind: A #GsPluginEvent, e.g. %GS_PLUGIN_EVENT_KIND_WARNING
+ *
+ * Sets the kind of the event.
+ *
+ * Returns: #GsPluginEventKind, e.g. 
+ *
+ * Since: 3.22
+ **/
+void
+gs_plugin_event_remove_flag (GsPluginEvent *event, GsPluginEventFlag flag)
+{
+       g_return_if_fail (GS_IS_PLUGIN_EVENT (event));
+       event->flags &= ~flag;
+}
+
+gboolean
+gs_plugin_event_has_flag (GsPluginEvent *event, GsPluginEventFlag flag)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_EVENT (event), FALSE);
+       return (event->flags & flag > 0);
+}
+
+static void
+gs_plugin_event_finalize (GObject *object)
+{
+       GsPluginEvent *event = GS_PLUGIN_EVENT (object);
+       if (event->app != NULL)
+               g_object_unref (event->app);
+       G_OBJECT_CLASS (gs_plugin_event_parent_class)->finalize (object);
+}
+
+static void
+gs_plugin_event_class_init (GsPluginEventClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = gs_plugin_event_finalize;
+}
+
+static void
+gs_plugin_event_init (GsPluginEvent *event)
+{
+}
+
+/**
+ * gs_plugin_event_new:
+ *
+ * Creates a new event.
+ *
+ * Returns: A newly allocated #GsPluginEvent
+ *
+ * Since: 3.22
+ **/
+GsPluginEvent *
+gs_plugin_event_new (void)
+{
+       GsPluginEvent *event;
+       event = g_object_new (GS_TYPE_PLUGIN_EVENT, NULL);
+       return GS_PLUGIN_EVENT (event);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-plugin-event.h b/src/gs-plugin-event.h
new file mode 100644
index 0000000..49b5c42
--- /dev/null
+++ b/src/gs-plugin-event.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef __GS_PLUGIN_EVENT
+#define __GS_PLUGIN_EVENT
+
+#include <glib-object.h>
+
+#include "gs-app.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PLUGIN_EVENT (gs_plugin_event_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPluginEvent, gs_plugin_event, GS, PLUGIN_EVENT, GObject)
+
+typedef enum {
+       GS_PLUGIN_EVENT_FLAG_NONE       = 0,
+       GS_PLUGIN_EVENT_FLAG_ACTIVE     = 1 << 0,
+       GS_PLUGIN_EVENT_FLAG_VISIBLE    = 1 << 1,
+       GS_PLUGIN_EVENT_FLAG_WARNING    = 1 << 2,
+       GS_PLUGIN_EVENT_FLAG_LAST
+} GsPluginEventFlag;
+
+GsPluginEvent          *gs_plugin_event_new            (void);
+void                    gs_plugin_event_set_app        (GsPluginEvent          *event,
+                                                        GsApp                  *app);
+GsApp                  *gs_plugin_event_get_app        (GsPluginEvent          *event);
+void                    gs_plugin_event_add_flag       (GsPluginEvent          *event,
+                                                        GsPluginEventFlag       flag);
+void                    gs_plugin_event_remove_flag    (GsPluginEvent          *event,
+                                                        GsPluginEventFlag       flag);
+gboolean                gs_plugin_event_has_flag       (GsPluginEvent          *event,
+                                                        GsPluginEventFlag       flag);
+
+G_END_DECLS
+
+#endif /* __GS_PLUGIN_EVENT */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 83a9fb3..1b5f894 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -52,6 +52,7 @@ typedef struct
        GPtrArray               *pending_apps;
 
        GSettings               *settings;
+       GPtrArray               *events;
 
        gchar                   **compatible_projects;
        guint                    scale;
@@ -71,6 +72,12 @@ enum {
        SIGNAL_LAST
 };
 
+enum {
+       PROP_0,
+       PROP_EVENTS,
+       PROP_LAST
+};
+
 static guint signals [SIGNAL_LAST] = { 0 };
 
 typedef void            (*GsPluginFunc)                (GsPlugin       *plugin);
@@ -3356,6 +3363,23 @@ gs_plugin_loader_get_enabled (GsPluginLoader *plugin_loader,
        return gs_plugin_get_enabled (plugin);
 }
 
+GPtrArray *
+gs_plugin_loader_get_events (GsPluginLoader *plugin_loader)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       return priv->events;
+}
+
+static void
+gs_plugin_loader_add_event_cb (GsPlugin *plugin,
+                              GsPluginEvent *event,
+                              GsPluginLoader *plugin_loader)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       g_ptr_array_add (priv->events, g_object_ref (event));
+       g_object_notify (G_OBJECT (plugin_loader), "events");
+}
+
 static void
 gs_plugin_loader_status_changed_cb (GsPlugin *plugin,
                                    GsApp *app,
@@ -3465,6 +3489,9 @@ gs_plugin_loader_open_plugin (GsPluginLoader *plugin_loader,
        g_signal_connect (plugin, "status-changed",
                          G_CALLBACK (gs_plugin_loader_status_changed_cb),
                          plugin_loader);
+       g_signal_connect (plugin, "add-event",
+                         G_CALLBACK (gs_plugin_loader_add_event_cb),
+                         plugin_loader);
        gs_plugin_set_soup_session (plugin, priv->soup_session);
        gs_plugin_set_auth_array (plugin, priv->auth_array);
        gs_plugin_set_profile (plugin, priv->profile);
@@ -3835,6 +3862,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);
+               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);
@@ -3868,6 +3923,7 @@ gs_plugin_loader_finalize (GObject *object)
        g_free (priv->locale);
        g_free (priv->language);
        g_object_unref (priv->global_cache);
+       g_ptr_array_unref (priv->events);
 
        g_mutex_clear (&priv->pending_apps_mutex);
 
@@ -3877,11 +3933,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,
@@ -3919,6 +3983,7 @@ gs_plugin_loader_init (GsPluginLoader *plugin_loader)
 
        priv->scale = 1;
        priv->global_cache = gs_app_list_new ();
+       priv->events = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        priv->plugins = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        priv->status_last = GS_PLUGIN_STATUS_LAST;
        priv->pending_apps = g_ptr_array_new_with_free_func ((GFreeFunc) g_object_unref);
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index daac548..9945e88 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -87,6 +87,7 @@ typedef void   (*GsPluginLoaderFinishedFunc)          (GsPluginLoader *plugin_loader,
                                                         gpointer        user_data);
 
 GsPluginLoader *gs_plugin_loader_new                   (void);
+GPtrArray      *gs_plugin_loader_get_events            (GsPluginLoader *plugin_loader);
 void            gs_plugin_loader_get_installed_async   (GsPluginLoader *plugin_loader,
                                                         GsPluginRefineFlags flags,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-plugin.c b/src/gs-plugin.c
index 5d37fd8..d40b7f6 100644
--- a/src/gs-plugin.c
+++ b/src/gs-plugin.c
@@ -90,6 +90,7 @@ enum {
        SIGNAL_UPDATES_CHANGED,
        SIGNAL_STATUS_CHANGED,
        SIGNAL_RELOAD,
+       SIGNAL_ADD_EVENT,
        SIGNAL_LAST
 };
 
@@ -850,6 +851,7 @@ typedef struct {
        GsPlugin        *plugin;
        GsApp           *app;
        GsPluginStatus   status;
+       GsPluginEvent   *event;
        guint            percentage;
 } GsPluginStatusHelper;
 
@@ -891,6 +893,39 @@ gs_plugin_status_update (GsPlugin *plugin, GsApp *app, GsPluginStatus status)
 }
 
 static gboolean
+gs_plugin_add_event_cb (gpointer user_data)
+{
+       GsPluginStatusHelper *helper = (GsPluginStatusHelper *) user_data;
+       gs_plugin_event_add_flag (helper->event, GS_PLUGIN_EVENT_FLAG_ACTIVE);
+       g_signal_emit (helper->plugin,
+                      signals[SIGNAL_ADD_EVENT], 0,
+                      helper->event);
+       g_object_unref (helper->event);
+       g_slice_free (GsPluginStatusHelper, helper);
+       return FALSE;
+}
+
+/**
+ * gs_plugin_add_event:
+ * @plugin: a #GsPlugin
+ * @event: a #GsPluginEvent
+ *
+ * Provide a way for plugins to tell the UI layer about events that may require
+ * displaying to the user. Plugins should not assume that a specific event is
+ * actually shown to the user as it may be ignored automatically.
+ *
+ * Since: 3.22
+ **/
+void
+gs_plugin_add_event (GsPlugin *plugin, GsPluginEvent *event)
+{
+       GsPluginStatusHelper *helper = g_slice_new0 (GsPluginStatusHelper);
+       helper->plugin = plugin;
+       helper->event = g_object_ref (event);
+       g_idle_add (gs_plugin_add_event_cb, helper);
+}
+
+static gboolean
 gs_plugin_app_launch_cb (gpointer user_data)
 {
        GAppInfo *appinfo = (GAppInfo *) user_data;
@@ -1326,6 +1361,13 @@ gs_plugin_class_init (GsPluginClass *klass)
                              NULL, NULL, g_cclosure_marshal_generic,
                              G_TYPE_NONE, 2, GS_TYPE_APP, G_TYPE_UINT);
 
+       signals [SIGNAL_ADD_EVENT] =
+               g_signal_new ("add-event",
+                             G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GsPluginClass, add_event),
+                             NULL, NULL, g_cclosure_marshal_generic,
+                             G_TYPE_NONE, 1, GS_TYPE_PLUGIN_EVENT);
+
        signals [SIGNAL_RELOAD] =
                g_signal_new ("reload",
                              G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 7c34db9..0c098b1 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -32,6 +32,7 @@
 #include "gs-app-list.h"
 #include "gs-auth.h"
 #include "gs-category.h"
+#include "gs-plugin-event.h"
 
 G_BEGIN_DECLS
 
@@ -47,7 +48,9 @@ struct _GsPluginClass
                                                         GsApp          *app,
                                                         guint           status);
        void                    (*reload)               (GsPlugin       *plugin);
-       gpointer                 padding[28];
+       void                    (*add_event)            (GsPlugin       *plugin,
+                                                        GsPluginEvent  *event);
+       gpointer                 padding[27];
 };
 
 typedef struct GsPluginData    GsPluginData;
@@ -293,6 +296,8 @@ gboolean     gs_plugin_app_launch                   (GsPlugin       *plugin,
 void            gs_plugin_updates_changed              (GsPlugin       *plugin);
 void            gs_plugin_reload                       (GsPlugin       *plugin);
 const gchar    *gs_plugin_status_to_string             (GsPluginStatus  status);
+void            gs_plugin_add_event                    (GsPlugin       *plugin,
+                                                        GsPluginEvent  *event);
 
 G_END_DECLS
 
diff --git a/src/gs-shell.c b/src/gs-shell.c
index cfd23b7..213e511 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -413,6 +413,12 @@ save_back_entry (GsShell *shell)
 }
 
 static void
+gs_shell_plugin_events_sources_cb (GtkWidget *widget, GsShell *shell)
+{
+       gs_shell_show_sources (shell);
+}
+
+static void
 gs_shell_back_button_cb (GtkWidget *widget, GsShell *shell)
 {
        GsShellPrivate *priv = gs_shell_get_instance_private (shell);
@@ -614,6 +620,119 @@ gs_shell_monitor_permission (GsShell *shell)
                                  G_CALLBACK (on_permission_changed), shell);
 }
 
+static void
+gs_shell_events_show (GsShell *shell, GsPluginEvent *event)
+{
+       GsApp *app = gs_plugin_event_get_app (event);
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       GtkWidget *widget;
+       const gchar *tmp;
+       g_autoptr(GString) header = g_string_new ("");
+
+       /* set type */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "infobar_events"));
+       if (gs_plugin_event_has_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING)) {
+               gtk_info_bar_set_message_type (GTK_INFO_BAR (widget),
+                                              GTK_MESSAGE_WARNING);
+       } else {
+               gtk_info_bar_set_message_type (GTK_INFO_BAR (widget),
+                                              GTK_MESSAGE_INFO);
+       }
+       gtk_widget_set_visible (widget, TRUE);
+
+       /* custom error messages */
+       if (app != NULL &&
+           gs_app_get_kind (app) == AS_APP_KIND_SOURCE &&
+           gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
+               tmp = gs_app_get_name (app);
+               if (tmp == NULL)
+                       tmp = gs_app_get_origin_ui (app);
+               if (tmp == NULL)
+                       tmp = gs_app_get_origin_hostname (app);
+               if (tmp == NULL)
+                       tmp = gs_app_get_origin (app);
+               if (tmp == NULL)
+                       tmp = "invalid";
+               /* TRANSLATORS: source refers to a remote or repo */
+               g_string_append_printf (header, _("Failed to load source ā€˜%sā€™"), tmp);
+       } else {
+               /* TRANSLATORS: fallback case for a plugin event */
+               g_string_append (header, "Important notice");
+       }
+
+       /* sources button */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_sources"));
+       gtk_widget_set_visible (widget,
+                               app != NULL &&
+                               gs_app_get_kind (app) == AS_APP_KIND_SOURCE);
+
+       /* set header */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "label_events_title"));
+       gtk_label_set_label (GTK_LABEL (widget), header->str);
+       gtk_widget_set_visible (widget, header->len > 0);
+
+       /* fill in detail */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "label_events"));
+       gtk_label_set_label (GTK_LABEL (widget), gs_app_get_summary_missing (app));
+}
+
+static void
+gs_shell_events_rescan (GsShell *shell)
+{
+       GPtrArray *events;
+       GtkWidget *widget;
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       guint i;
+
+       /* find the first active event and show it */
+       events = gs_plugin_loader_get_events (priv->plugin_loader);
+       for (i = 0; i < events->len; i++) {
+               GsPluginEvent *event = g_ptr_array_index (events, i);
+               if (gs_plugin_event_has_flag (event, GS_PLUGIN_EVENT_FLAG_ACTIVE)) {
+                       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_VISIBLE);
+                       gs_shell_events_show (shell, event);
+                       return;
+               }
+       }
+
+       /* nothing to show */
+       g_debug ("no events to show");
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "infobar_events"));
+       gtk_widget_set_visible (widget, FALSE);
+}
+
+static void
+gs_shell_events_notify_cb (GsPluginLoader *plugin_loader,
+                          GParamSpec *pspec,
+                          GsShell *shell)
+{
+       gs_shell_events_rescan (shell);
+}
+
+
+static void
+gs_shell_plugin_event_response_cb (GtkInfoBar *info_bar,
+                                  gint response_id,
+                                  GsShell *shell)
+{
+       GPtrArray *events;
+       GsShellPrivate *priv = gs_shell_get_instance_private (shell);
+       guint i;
+
+       /* mark any events currently showing as invalid */
+       events = gs_plugin_loader_get_events (priv->plugin_loader);
+       for (i = 0; i < events->len; i++) {
+               GsPluginEvent *event = g_ptr_array_index (events, i);
+               if (gs_plugin_event_has_flag (event, GS_PLUGIN_EVENT_FLAG_VISIBLE)) {
+                       gs_plugin_event_remove_flag (event, GS_PLUGIN_EVENT_FLAG_ACTIVE);
+                       gs_plugin_event_remove_flag (event, GS_PLUGIN_EVENT_FLAG_VISIBLE);
+               }
+       }
+
+       /* show the next event */
+       gs_shell_events_rescan (shell);
+}
+
 void
 gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *cancellable)
 {
@@ -625,6 +744,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);
@@ -683,6 +805,14 @@ gs_shell_setup (GsShell *shell, GsPluginLoader *plugin_loader, GCancellable *can
        g_signal_connect (widget, "clicked",
                          G_CALLBACK (gs_shell_overview_button_cb), shell);
 
+       /* set up infobar */
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "infobar_events"));
+       g_signal_connect (widget, "response",
+                         G_CALLBACK (gs_shell_plugin_event_response_cb), shell);
+       widget = GTK_WIDGET (gtk_builder_get_object (priv->builder, "button_events_sources"));
+       g_signal_connect (widget, "clicked",
+                         G_CALLBACK (gs_shell_plugin_events_sources_cb), shell);
+
        priv->shell_overview = GS_SHELL_OVERVIEW (gtk_builder_get_object (priv->builder, "shell_overview"));
        gs_shell_overview_setup (priv->shell_overview,
                                 shell,
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index d2ded6f..8725d80 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -60,6 +60,28 @@ gs_plugin_initialize (GsPlugin *plugin)
        gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_CONFLICTS, "odrs");
 }
 
+static void
+gs_plugin_dummy_add_event (GsPlugin *plugin)
+{
+       g_autoptr(GsPluginEvent) event = gs_plugin_event_new ();
+       g_autoptr(GsApp) source = gs_app_new ("dummy");
+       gs_app_set_kind (source, AS_APP_KIND_SOURCE);
+       gs_app_set_origin_hostname (source, "packages.dummy.org");
+       gs_app_set_name (source, GS_APP_QUALITY_NORMAL, "Dummy");
+       gs_app_set_state (source, AS_APP_STATE_UNAVAILABLE);
+       gs_app_set_summary_missing (source, "Metadata download failed: Could not reach destination.");
+       gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
+       gs_plugin_event_set_app (event, source);
+       gs_plugin_add_event (plugin, event);
+}
+
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+       gs_plugin_dummy_add_event (plugin);
+       return TRUE;
+}
+
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
@@ -579,6 +601,10 @@ gs_plugin_refresh (GsPlugin *plugin,
        if (flags & GS_PLUGIN_REFRESH_FLAGS_PAYLOAD)
                delay_ms += 5000;
 
+       /* show warning */
+       if (flags & GS_PLUGIN_REFRESH_FLAGS_METADATA)
+               gs_plugin_dummy_add_event (plugin);
+
        /* do delay */
        return gs_plugin_dummy_delay (plugin, app, delay_ms, cancellable, error);
 }


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