[gnome-software] trivial: Split out core plugins into a new directory



commit abca4426be67a3de5562ace8b33af135a7ffa1f3
Author: Richard Hughes <richard hughsie com>
Date:   Tue Mar 7 13:33:36 2017 +0000

    trivial: Split out core plugins into a new directory

 configure.ac                                       |    2 +
 plugins/Makefile.am                                |   89 +--
 plugins/core/Makefile.am                           |   79 ++
 plugins/core/gs-appstream.c                        | 1147 +++++++++++++++++++
 plugins/core/gs-appstream.h                        |   71 ++
 plugins/{ => core}/gs-desktop-common.c             |    0
 plugins/{ => core}/gs-desktop-common.h             |    0
 plugins/{ => core}/gs-plugin-appstream.c           |    0
 plugins/{ => core}/gs-plugin-desktop-categories.c  |    0
 plugins/{ => core}/gs-plugin-desktop-menu-path.c   |    0
 plugins/{ => core}/gs-plugin-generic-updates.c     |    0
 plugins/{ => core}/gs-plugin-hardcoded-blacklist.c |    0
 plugins/{ => core}/gs-plugin-hardcoded-featured.c  |    0
 plugins/{ => core}/gs-plugin-hardcoded-popular.c   |    0
 plugins/{ => core}/gs-plugin-icons.c               |    0
 plugins/{ => core}/gs-plugin-key-colors.c          |    0
 plugins/{ => core}/gs-plugin-provenance-license.c  |    0
 plugins/{ => core}/gs-plugin-provenance.c          |    0
 plugins/dummy/Makefile.am                          |   23 +
 plugins/{ => dummy}/gs-plugin-dummy.c              |    0
 plugins/dummy/gs-self-test.c                       |  715 ++++++++++++
 plugins/gs-appstream.c                             | 1148 +-------------------
 plugins/gs-appstream.h                             |   72 +--
 plugins/gs-self-test.c                             |  533 +---------
 po/POTFILES.in                                     |    4 +-
 25 files changed, 2048 insertions(+), 1835 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 832564b..8bc54ae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -484,7 +484,9 @@ doc/api/Makefile
 lib/Makefile
 lib/gnome-software.pc
 plugins/Makefile
+plugins/core/Makefile
 plugins/dpkg/Makefile
+plugins/dummy/Makefile
 plugins/external-appstream/Makefile
 plugins/fwupd/Makefile
 plugins/limba/Makefile
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index ea9f640..a8b7907 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -2,10 +2,11 @@
 AUTOMAKE_OPTIONS = 1.7
 
 # core
-SUBDIRS =
+SUBDIRS = core
 
 # non-core, but no configure option required
 SUBDIRS += dpkg
+SUBDIRS += dummy
 SUBDIRS += ubuntuone
 SUBDIRS += repos
 
@@ -52,24 +53,13 @@ AM_CPPFLAGS =                                               \
        -DLOCALSTATEDIR=\""$(localstatedir)"\"          \
        -DLOCALEDIR=\""$(localedir)"\"                  \
        -DTESTDATADIR=\""$(top_srcdir)/data"\"          \
-       -DLOCALPLUGINDIR=\""$(top_builddir)/src/plugins/.libs"\"
+       -DLOCALPLUGINDIR=\""$(top_builddir)/plugins/.libs"\"    \
+       -DLOCALPLUGINDIR_CORE=\""$(top_builddir)/plugins/core/.libs"\"
 
 plugindir = $(GS_PLUGIN_DIR)
 plugin_LTLIBRARIES =                                   \
-       libgs_plugin_appstream.la                       \
-       libgs_plugin_key-colors.la                      \
-       libgs_plugin_desktop-categories.la              \
-       libgs_plugin_desktop-menu-path.la               \
-       libgs_plugin_dummy.la                           \
-       libgs_plugin_generic-updates.la                 \
-       libgs_plugin_hardcoded-blacklist.la             \
-       libgs_plugin_hardcoded-popular.la               \
-       libgs_plugin_hardcoded-featured.la              \
        libgs_plugin_fedora-distro-upgrades.la          \
-       libgs_plugin_provenance.la                      \
-       libgs_plugin_provenance-license.la              \
-       libgs_plugin_fedora-tagger-usage.la             \
-       libgs_plugin_icons.la
+       libgs_plugin_fedora-tagger-usage.la
 
 appdata_in_files =
 
@@ -89,21 +79,6 @@ if HAVE_FLATPAK
 plugin_LTLIBRARIES += libgs_plugin_flatpak.la
 endif
 
-libgs_plugin_dummy_la_SOURCES = gs-plugin-dummy.c
-libgs_plugin_dummy_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_dummy_la_LDFLAGS = -module -avoid-version
-libgs_plugin_dummy_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_generic_updates_la_SOURCES = gs-plugin-generic-updates.c
-libgs_plugin_generic_updates_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_generic_updates_la_LDFLAGS = -module -avoid-version
-libgs_plugin_generic_updates_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_key_colors_la_SOURCES = gs-plugin-key-colors.c
-libgs_plugin_key_colors_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_key_colors_la_LDFLAGS = -module -avoid-version
-libgs_plugin_key_colors_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
 if HAVE_SHELL_EXTENSIONS
 libgs_plugin_shell_extensions_la_SOURCES = gs-plugin-shell-extensions.c
 libgs_plugin_shell_extensions_la_LIBADD = $(GS_PLUGIN_LIBS) $(JSON_GLIB_LIBS)
@@ -123,16 +98,6 @@ libgs_plugin_modalias_la_LDFLAGS = -module -avoid-version
 libgs_plugin_modalias_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(GUDEV_CFLAGS)
 endif
 
-libgs_plugin_provenance_la_SOURCES = gs-plugin-provenance.c
-libgs_plugin_provenance_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_provenance_la_LDFLAGS = -module -avoid-version
-libgs_plugin_provenance_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_provenance_license_la_SOURCES = gs-plugin-provenance-license.c
-libgs_plugin_provenance_license_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_provenance_license_la_LDFLAGS = -module -avoid-version
-libgs_plugin_provenance_license_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
 libgs_plugin_fedora_tagger_usage_la_SOURCES = gs-plugin-fedora-tagger-usage.c
 libgs_plugin_fedora_tagger_usage_la_LIBADD = $(GS_PLUGIN_LIBS) $(SQLITE_LIBS)
 libgs_plugin_fedora_tagger_usage_la_LDFLAGS = -module -avoid-version
@@ -144,19 +109,6 @@ libgs_plugin_epiphany_la_LIBADD = $(GS_PLUGIN_LIBS)
 libgs_plugin_epiphany_la_LDFLAGS = -module -avoid-version
 libgs_plugin_epiphany_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
 
-libgs_plugin_icons_la_SOURCES = gs-plugin-icons.c
-libgs_plugin_icons_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_icons_la_LDFLAGS = -module -avoid-version
-libgs_plugin_icons_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_appstream_la_SOURCES =                    \
-       gs-appstream.c                                  \
-       gs-appstream.h                                  \
-       gs-plugin-appstream.c
-libgs_plugin_appstream_la_LIBADD = $(GS_PLUGIN_LIBS) $(APPSTREAM_LIBS)
-libgs_plugin_appstream_la_LDFLAGS = -module -avoid-version
-libgs_plugin_appstream_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
 if HAVE_FLATPAK
 appdata_in_files += org.gnome.Software.Plugin.Flatpak.metainfo.xml.in
 libgs_plugin_flatpak_la_SOURCES =                      \
@@ -172,37 +124,6 @@ libgs_plugin_flatpak_la_LDFLAGS = -module -avoid-version
 libgs_plugin_flatpak_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(FLATPAK_CFLAGS)
 endif
 
-libgs_plugin_desktop_categories_la_SOURCES =           \
-       gs-plugin-desktop-categories.c                  \
-       gs-desktop-common.c                             \
-       gs-desktop-common.h
-libgs_plugin_desktop_categories_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_desktop_categories_la_LDFLAGS = -module -avoid-version
-libgs_plugin_desktop_categories_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_desktop_menu_path_la_SOURCES =            \
-       gs-plugin-desktop-menu-path.c                   \
-       gs-desktop-common.c                             \
-       gs-desktop-common.h
-libgs_plugin_desktop_menu_path_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_desktop_menu_path_la_LDFLAGS = -module -avoid-version
-libgs_plugin_desktop_menu_path_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_hardcoded_blacklist_la_SOURCES = gs-plugin-hardcoded-blacklist.c
-libgs_plugin_hardcoded_blacklist_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_hardcoded_blacklist_la_LDFLAGS = -module -avoid-version
-libgs_plugin_hardcoded_blacklist_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_hardcoded_popular_la_SOURCES = gs-plugin-hardcoded-popular.c
-libgs_plugin_hardcoded_popular_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_hardcoded_popular_la_LDFLAGS = -module -avoid-version
-libgs_plugin_hardcoded_popular_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
-libgs_plugin_hardcoded_featured_la_SOURCES = gs-plugin-hardcoded-featured.c
-libgs_plugin_hardcoded_featured_la_LIBADD = $(GS_PLUGIN_LIBS)
-libgs_plugin_hardcoded_featured_la_LDFLAGS = -module -avoid-version
-libgs_plugin_hardcoded_featured_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
-
 # MetaInfo files
 %.metainfo.xml: %.metainfo.xml.in
        $(AM_V_GEN) msgfmt --xml -d $(top_srcdir)/po --template $< -o $@
diff --git a/plugins/core/Makefile.am b/plugins/core/Makefile.am
new file mode 100644
index 0000000..d070e00
--- /dev/null
+++ b/plugins/core/Makefile.am
@@ -0,0 +1,79 @@
+plugindir = $(GS_PLUGIN_DIR)
+plugin_LTLIBRARIES =                                   \
+       libgs_plugin_appstream.la                       \
+       libgs_plugin_key-colors.la                      \
+       libgs_plugin_desktop-categories.la              \
+       libgs_plugin_desktop-menu-path.la               \
+       libgs_plugin_generic-updates.la                 \
+       libgs_plugin_hardcoded-blacklist.la             \
+       libgs_plugin_hardcoded-popular.la               \
+       libgs_plugin_hardcoded-featured.la              \
+       libgs_plugin_provenance.la                      \
+       libgs_plugin_provenance-license.la              \
+       libgs_plugin_icons.la
+
+libgs_plugin_generic_updates_la_SOURCES = gs-plugin-generic-updates.c
+libgs_plugin_generic_updates_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_generic_updates_la_LDFLAGS = -module -avoid-version
+libgs_plugin_generic_updates_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_key_colors_la_SOURCES = gs-plugin-key-colors.c
+libgs_plugin_key_colors_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_key_colors_la_LDFLAGS = -module -avoid-version
+libgs_plugin_key_colors_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_provenance_la_SOURCES = gs-plugin-provenance.c
+libgs_plugin_provenance_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_provenance_la_LDFLAGS = -module -avoid-version
+libgs_plugin_provenance_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_provenance_license_la_SOURCES = gs-plugin-provenance-license.c
+libgs_plugin_provenance_license_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_provenance_license_la_LDFLAGS = -module -avoid-version
+libgs_plugin_provenance_license_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_icons_la_SOURCES = gs-plugin-icons.c
+libgs_plugin_icons_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_icons_la_LDFLAGS = -module -avoid-version
+libgs_plugin_icons_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_appstream_la_SOURCES =                    \
+       gs-appstream.c                                  \
+       gs-appstream.h                                  \
+       gs-plugin-appstream.c
+libgs_plugin_appstream_la_LIBADD = $(GS_PLUGIN_LIBS) $(APPSTREAM_LIBS)
+libgs_plugin_appstream_la_LDFLAGS = -module -avoid-version
+libgs_plugin_appstream_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_desktop_categories_la_SOURCES =           \
+       gs-plugin-desktop-categories.c                  \
+       gs-desktop-common.c                             \
+       gs-desktop-common.h
+libgs_plugin_desktop_categories_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_desktop_categories_la_LDFLAGS = -module -avoid-version
+libgs_plugin_desktop_categories_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_desktop_menu_path_la_SOURCES =            \
+       gs-plugin-desktop-menu-path.c                   \
+       gs-desktop-common.c                             \
+       gs-desktop-common.h
+libgs_plugin_desktop_menu_path_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_desktop_menu_path_la_LDFLAGS = -module -avoid-version
+libgs_plugin_desktop_menu_path_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_hardcoded_blacklist_la_SOURCES = gs-plugin-hardcoded-blacklist.c
+libgs_plugin_hardcoded_blacklist_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_hardcoded_blacklist_la_LDFLAGS = -module -avoid-version
+libgs_plugin_hardcoded_blacklist_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_hardcoded_popular_la_SOURCES = gs-plugin-hardcoded-popular.c
+libgs_plugin_hardcoded_popular_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_hardcoded_popular_la_LDFLAGS = -module -avoid-version
+libgs_plugin_hardcoded_popular_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+libgs_plugin_hardcoded_featured_la_SOURCES = gs-plugin-hardcoded-featured.c
+libgs_plugin_hardcoded_featured_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_hardcoded_featured_la_LDFLAGS = -module -avoid-version
+libgs_plugin_hardcoded_featured_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/core/gs-appstream.c b/plugins/core/gs-appstream.c
new file mode 100644
index 0000000..673f626
--- /dev/null
+++ b/plugins/core/gs-appstream.c
@@ -0,0 +1,1147 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015-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.
+ */
+
+#include "config.h"
+
+#include <gnome-software.h>
+
+#include "gs-appstream.h"
+
+#define        GS_APPSTREAM_MAX_SCREENSHOTS    5
+
+GsApp *
+gs_appstream_create_app (GsPlugin *plugin, AsApp *item, GError **error)
+{
+       const gchar *unique_id = as_app_get_unique_id (item);
+       GsApp *app = gs_plugin_cache_lookup (plugin, unique_id);
+       if (app == NULL) {
+               app = gs_app_new (as_app_get_id (item));
+               gs_app_set_metadata (app, "GnomeSoftware::Creator",
+                                    gs_plugin_get_name (plugin));
+               if (!gs_appstream_refine_app (plugin, app, item, error)) {
+                       g_object_unref (app);
+                       return NULL;
+               }
+               gs_plugin_cache_add (plugin, unique_id, app);
+       }
+       return app;
+}
+
+static AsIcon *
+gs_appstream_get_icon_by_kind (AsApp *app, AsIconKind icon_kind)
+{
+       GPtrArray *icons;
+       guint i;
+
+       icons = as_app_get_icons (app);
+       for (i = 0; i < icons->len; i++) {
+               AsIcon *icon = g_ptr_array_index (icons, i);
+               if (as_icon_get_kind (icon) == icon_kind)
+                       return icon;
+       }
+       return NULL;
+}
+
+static AsIcon *
+gs_appstream_get_icon_by_kind_and_size (AsApp *app, AsIconKind icon_kind, guint sz)
+{
+       GPtrArray *icons;
+       guint i;
+
+       icons = as_app_get_icons (app);
+       for (i = 0; i < icons->len; i++) {
+               AsIcon *icon = g_ptr_array_index (icons, i);
+               if (as_icon_get_kind (icon) == icon_kind &&
+                   as_icon_get_width (icon) == sz &&
+                   as_icon_get_height (icon) == sz)
+                       return icon;
+       }
+       return NULL;
+}
+
+static void
+gs_refine_item_icon (GsPlugin *plugin, GsApp *app, AsApp *item)
+{
+       AsIcon *icon;
+
+       /* try a stock icon first */
+       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_STOCK);
+       if (icon != NULL)
+               gs_app_add_icon (app, icon);
+
+       /* if HiDPI get a 128px cached icon */
+       if (gs_plugin_get_scale (plugin) == 2) {
+               icon = gs_appstream_get_icon_by_kind_and_size (item,
+                                                              AS_ICON_KIND_CACHED,
+                                                              128);
+               if (icon != NULL)
+                       gs_app_add_icon (app, icon);
+       }
+
+       /* non-HiDPI cached icon */
+       icon = gs_appstream_get_icon_by_kind_and_size (item,
+                                                      AS_ICON_KIND_CACHED,
+                                                      64);
+       if (icon != NULL)
+               gs_app_add_icon (app, icon);
+
+       /* prefer local */
+       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_LOCAL);
+       if (icon != NULL) {
+               /* does not exist, so try to find using the icon theme */
+               if (as_icon_get_kind (icon) == AS_ICON_KIND_LOCAL &&
+                   as_icon_get_filename (icon) == NULL) {
+                       g_debug ("converting missing LOCAL icon %s to STOCK",
+                                as_icon_get_name (icon));
+                       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+               }
+               gs_app_add_icon (app, icon);
+       }
+
+       /* remote as a last resort */
+       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_REMOTE);
+       if (icon != NULL)
+               gs_app_add_icon (app, icon);
+}
+
+static gboolean
+gs_appstream_refine_add_addons (GsPlugin *plugin,
+                               GsApp *app,
+                               AsApp *item,
+                               GError **error)
+{
+       GPtrArray *addons;
+       guint i;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* we only care about addons to desktop apps */
+       if (gs_app_get_kind (app) != AS_APP_KIND_DESKTOP)
+               return TRUE;
+
+       /* search categories for the search term */
+       ptask = as_profile_start (gs_plugin_get_profile (plugin),
+                                 "appstream::refine-addons{%s}",
+                                 gs_app_get_unique_id (app));
+       g_assert (ptask != NULL);
+
+       addons = as_app_get_addons (item);
+       if (addons == NULL)
+               return TRUE;
+
+       for (i = 0; i < addons->len; i++) {
+               AsApp *as_addon = g_ptr_array_index (addons, i);
+               g_autoptr(GsApp) addon = NULL;
+
+               addon = gs_appstream_create_app (plugin, as_addon, error);
+               if (addon == NULL)
+                       return FALSE;
+
+               /* add all the data we can */
+               if (!gs_appstream_refine_app (plugin, addon, as_addon, error))
+                       return FALSE;
+               gs_app_add_addon (app, addon);
+       }
+       return TRUE;
+}
+
+static void
+gs_appstream_refine_add_screenshots (GsApp *app, AsApp *item)
+{
+       AsScreenshot *ss;
+       GPtrArray *images_as;
+       GPtrArray *screenshots_as;
+       guint i;
+
+       /* do we have any to add */
+       screenshots_as = as_app_get_screenshots (item);
+       if (screenshots_as->len == 0)
+               return;
+
+       /* does the app already have some */
+       gs_app_add_kudo (app, GS_APP_KUDO_HAS_SCREENSHOTS);
+       if (gs_app_get_screenshots(app)->len > 0)
+               return;
+
+       /* add any we know */
+       for (i = 0; i < screenshots_as->len &&
+                   i < GS_APPSTREAM_MAX_SCREENSHOTS; i++) {
+               ss = g_ptr_array_index (screenshots_as, i);
+               images_as = as_screenshot_get_images (ss);
+               if (images_as->len == 0)
+                       continue;
+               if (as_screenshot_get_kind (ss) == AS_SCREENSHOT_KIND_UNKNOWN)
+                       continue;
+               gs_app_add_screenshot (app, ss);
+       }
+}
+
+static void
+gs_appstream_refine_add_reviews (GsApp *app, AsApp *item)
+{
+       AsReview *review;
+       GPtrArray *reviews;
+       guint i;
+
+       /* do we have any to add */
+       if (gs_app_get_reviews(app)->len > 0)
+               return;
+       reviews = as_app_get_reviews (item);
+       for (i = 0; i < reviews->len; i++) {
+               review = g_ptr_array_index (reviews, i);
+               gs_app_add_review (app, review);
+       }
+}
+
+static void
+gs_appstream_refine_add_provides (GsApp *app, AsApp *item)
+{
+       AsProvide *provide;
+       GPtrArray *provides;
+       guint i;
+
+       /* do we have any to add */
+       if (gs_app_get_provides(app)->len > 0)
+               return;
+       provides = as_app_get_provides (item);
+       for (i = 0; i < provides->len; i++) {
+               provide = g_ptr_array_index (provides, i);
+               gs_app_add_provide (app, provide);
+       }
+}
+
+static gboolean
+gs_appstream_is_recent_release (AsApp *app)
+{
+       AsRelease *release;
+       GPtrArray *releases;
+       guint64 secs;
+
+       /* get newest release */
+       releases = as_app_get_releases (app);
+       if (releases->len == 0)
+               return FALSE;
+       release = g_ptr_array_index (releases, 0);
+
+       /* is last build less than one year ago? */
+       secs = ((guint64) g_get_real_time () / G_USEC_PER_SEC) -
+               as_release_get_timestamp (release);
+       return secs / (60 * 60 * 24) < 365;
+}
+
+static gboolean
+gs_appstream_are_screenshots_perfect (AsApp *app)
+{
+       AsImage *image;
+       AsScreenshot *screenshot;
+       GPtrArray *screenshots;
+       guint height;
+       guint i;
+       guint width;
+
+       screenshots = as_app_get_screenshots (app);
+       if (screenshots->len == 0)
+               return FALSE;
+       for (i = 0; i < screenshots->len; i++) {
+
+               /* get the source image as the thumbs will be resized & padded */
+               screenshot = g_ptr_array_index (screenshots, i);
+               image = as_screenshot_get_source (screenshot);
+               if (image == NULL)
+                       return FALSE;
+
+               width = as_image_get_width (image);
+               height = as_image_get_height (image);
+
+               /* too small */
+               if (width < AS_IMAGE_LARGE_WIDTH || height < AS_IMAGE_LARGE_HEIGHT)
+                       return FALSE;
+
+               /* too large */
+               if (width > AS_IMAGE_LARGE_WIDTH * 2 || height > AS_IMAGE_LARGE_HEIGHT * 2)
+                       return FALSE;
+
+               /* not 16:9 */
+               if ((width / 16) * 9 != height)
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+static void
+gs_appstream_copy_metadata (GsApp *app, AsApp *item)
+{
+       GHashTable *hash;
+       GList *l;
+       g_autoptr(GList) keys = NULL;
+
+       hash = as_app_get_metadata (item);
+       keys = g_hash_table_get_keys (hash);
+       for (l = keys; l != NULL; l = l->next) {
+               const gchar *key = l->data;
+               const gchar *value = g_hash_table_lookup (hash, key);
+               if (gs_app_get_metadata_item (app, key) != NULL)
+                       continue;
+               gs_app_set_metadata (app, key, value);
+       }
+}
+
+GsApp *
+gs_appstream_create_runtime (GsPlugin *plugin,
+                            GsApp *parent,
+                            const gchar *runtime)
+{
+       g_autofree gchar *source = NULL;
+       g_auto(GStrv) split = NULL;
+       g_autoptr(GsApp) app_cache = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* get the name/arch/branch */
+       split = g_strsplit (runtime, "/", -1);
+       if (g_strv_length (split) != 3)
+               return NULL;
+
+       /* create the complete GsApp from the single string */
+       app = gs_app_new (split[0]);
+       source = g_strdup_printf ("runtime/%s", runtime);
+       gs_app_add_source (app, source);
+       gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_FLATPAK);
+       gs_app_set_kind (app, AS_APP_KIND_RUNTIME);
+       gs_app_set_branch (app, split[2]);
+       gs_app_set_scope (app, gs_app_get_scope (parent));
+
+       /* search in the cache */
+       app_cache = gs_plugin_cache_lookup (plugin, gs_app_get_unique_id (app));
+       if (app_cache != NULL) {
+               /* since the cached runtime can have been created somewhere else
+                * (we're using a global cache), we need to make sure that a
+                * source is set */
+               if (gs_app_get_source_default (app_cache) == NULL)
+                       gs_app_add_source (app_cache, source);
+               return g_steal_pointer (&app_cache);
+       }
+
+       /* save in the cache */
+       gs_plugin_cache_add (plugin, NULL, app);
+       return g_steal_pointer (&app);
+}
+
+static void
+gs_refine_item_management_plugin (GsPlugin *plugin, GsApp *app, AsApp *item)
+{
+       GPtrArray *bundles;
+       const gchar *management_plugin = NULL;
+       const gchar *runtime = NULL;
+       guint i;
+
+       /* allow override */
+       management_plugin = as_app_get_metadata_item (item, "GnomeSoftware::Plugin");
+       if (management_plugin != NULL)
+               gs_app_set_management_plugin (app, management_plugin);
+
+       /* find the default bundle kind */
+       bundles = as_app_get_bundles (item);
+       for (i = 0; i < bundles->len; i++) {
+               AsBundle *bundle = g_ptr_array_index (bundles, i);
+               AsBundleKind kind = as_bundle_get_kind (bundle);
+
+               gs_app_add_source (app, as_bundle_get_id (bundle));
+
+               /* automatically add runtime */
+               if (kind == AS_BUNDLE_KIND_FLATPAK) {
+                       runtime = as_bundle_get_runtime (bundle);
+                       if (runtime != NULL) {
+                               g_autoptr(GsApp) app2 = NULL;
+                               app2 = gs_appstream_create_runtime (plugin, app, runtime);
+                               if (app2 != NULL) {
+                                       if (app == app2) {
+                                               g_warning ("%s runtime cannot have runtime!",
+                                                          gs_app_get_unique_id (app));
+                                               break;
+                                       }
+                                       g_debug ("runtime for %s is %s",
+                                                gs_app_get_unique_id (app),
+                                                runtime);
+                                       gs_app_set_update_runtime (app, app2);
+                               }
+                       }
+                       break;
+               }
+       }
+}
+
+static gboolean
+gs_appstream_refine_app_updates (GsPlugin *plugin,
+                                GsApp *app,
+                                AsApp *item,
+                                GError **error)
+{
+       AsUrgencyKind urgency_best = AS_URGENCY_KIND_UNKNOWN;
+       GPtrArray *releases;
+       g_autoptr(GPtrArray) updates_list = NULL;
+
+       /* only for UPDATABLE apps */
+       if (!gs_app_is_updatable (app))
+               return TRUE;
+
+       /* make a list of valid updates */
+       updates_list = g_ptr_array_new ();
+       releases = as_app_get_releases (item);
+       for (guint i = 0; i < releases->len; i++) {
+               AsRelease *rel = g_ptr_array_index (releases, i);
+
+               /* already installed */
+               g_debug ("installable update %s [%u]",
+                        as_release_get_version (rel),
+                        as_release_get_state (rel));
+               if (as_release_get_state (rel) == AS_RELEASE_STATE_INSTALLED)
+                       continue;
+
+               /* use the 'worst' urgency, e.g. critical over enhancement */
+               if (as_release_get_urgency (rel) > urgency_best)
+                       urgency_best = as_release_get_urgency (rel);
+
+               /* add updates with a description */
+               if (as_release_get_description (rel, NULL) == NULL)
+                       continue;
+               g_ptr_array_add (updates_list, rel);
+       }
+
+       /* only set if known */
+       if (urgency_best != AS_URGENCY_KIND_UNKNOWN)
+               gs_app_set_update_urgency (app, urgency_best);
+
+       /* no prefix on each release */
+       if (updates_list->len == 1) {
+               g_autofree gchar *desc = NULL;
+               AsRelease *rel = g_ptr_array_index (updates_list, 0);
+               desc = as_markup_convert (as_release_get_description (rel, NULL),
+                                         AS_MARKUP_CONVERT_FORMAT_SIMPLE,
+                                         error);
+               if (desc == NULL) {
+                       gs_utils_error_convert_appstream (error);
+                       return FALSE;
+               }
+               gs_app_set_update_details (app, desc);
+
+       /* get the descriptions with a version prefix */
+       } else if (updates_list->len > 1) {
+               g_autoptr(GString) update_desc = g_string_new ("");
+               for (guint i = 0; i < updates_list->len; i++) {
+                       g_autofree gchar *desc = NULL;
+                       AsRelease *rel = g_ptr_array_index (updates_list, i);
+                       desc = as_markup_convert (as_release_get_description (rel, NULL),
+                                                 AS_MARKUP_CONVERT_FORMAT_SIMPLE,
+                                                 error);
+                       if (desc == NULL) {
+                               gs_utils_error_convert_appstream (error);
+                               return FALSE;
+                       }
+                       g_string_append_printf (update_desc,
+                                               "Version %s:\n%s\n\n",
+                                               as_release_get_version (rel),
+                                               desc);
+               }
+
+               /* remove trailing newlines */
+               if (update_desc->len > 2)
+                       g_string_truncate (update_desc, update_desc->len - 2);
+               gs_app_set_update_details (app, update_desc->str);
+       }
+
+       /* if there is no already set update version use the newest */
+       if (gs_app_get_update_version (app) == NULL) {
+               AsRelease *rel = as_app_get_release_default (item);
+               if (rel != NULL)
+                       gs_app_set_update_version (app, as_release_get_version (rel));
+       }
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * _gs_utils_locale_has_translations:
+ * @locale: A locale, e.g. "en_GB"
+ *
+ * Looks up if the locale is likely to have translations.
+ *
+ * Returns: %TRUE if the locale should have translations
+ **/
+static gboolean
+_gs_utils_locale_has_translations (const gchar *locale)
+{
+       if (g_strcmp0 (locale, "C") == 0)
+               return FALSE;
+       if (g_strcmp0 (locale, "en") == 0)
+               return FALSE;
+       if (g_strcmp0 (locale, "en_US") == 0)
+               return FALSE;
+       return TRUE;
+}
+
+static AsBundleKind
+gs_appstream_get_bundle_kind (AsApp *item)
+{
+       GPtrArray *bundles;
+       GPtrArray *pkgnames;
+
+       /* prefer bundle */
+       bundles = as_app_get_bundles (item);
+       if (bundles->len > 0) {
+               AsBundle *bundle = g_ptr_array_index (bundles, 0);
+               if (as_bundle_get_kind (bundle) != AS_BUNDLE_KIND_UNKNOWN)
+                       return as_bundle_get_kind (bundle);
+       }
+
+       /* fallback to packages */
+       pkgnames = as_app_get_pkgnames (item);
+       if (pkgnames->len > 0)
+               return AS_BUNDLE_KIND_PACKAGE;
+
+       /* nothing */
+       return AS_BUNDLE_KIND_UNKNOWN;
+}
+
+static gboolean
+gs_appstream_origin_valid (const gchar *origin)
+{
+       if (origin == NULL)
+               return FALSE;
+       if (g_strcmp0 (origin, "") == 0)
+               return FALSE;
+       return TRUE;
+}
+
+gboolean
+gs_appstream_refine_app (GsPlugin *plugin,
+                        GsApp *app,
+                        AsApp *item,
+                        GError **error)
+{
+       AsRequire *req;
+       g_autoptr(GError) error_local = NULL;
+       GHashTable *urls;
+       GPtrArray *array;
+       GPtrArray *pkgnames;
+       GPtrArray *kudos;
+       const gchar *current_desktop;
+       const gchar *tmp;
+       guint i;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* search categories for the search term */
+       ptask = as_profile_start (gs_plugin_get_profile (plugin),
+                                 "appstream::refine-app{%s}",
+                                 gs_app_get_unique_id (app));
+       g_assert (ptask != NULL);
+
+       /* set the kind to be more precise */
+       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN ||
+           gs_app_get_kind (app) == AS_APP_KIND_GENERIC) {
+               gs_app_set_kind (app, as_app_get_kind (item));
+       }
+
+       /* is installed already */
+       if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN &&
+           as_app_get_state (item) != AS_APP_STATE_UNKNOWN) {
+               gs_app_set_state (app, as_app_get_state (item));
+       }
+
+       /* is compatible */
+       req = as_app_get_require_by_value (item,
+                                          AS_REQUIRE_KIND_ID,
+                                          "org.gnome.Software.desktop");
+       if (req != NULL) {
+               if (!as_require_version_compare (req, PACKAGE_VERSION, &error_local)) {
+                       g_set_error (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "not for this gnome-software: %s",
+                                    error_local->message);
+                       return FALSE;
+               }
+       }
+
+       /* types we can never launch */
+       switch (gs_app_get_kind (app)) {
+       case AS_APP_KIND_ADDON:
+       case AS_APP_KIND_CODEC:
+       case AS_APP_KIND_FIRMWARE:
+       case AS_APP_KIND_FONT:
+       case AS_APP_KIND_GENERIC:
+       case AS_APP_KIND_INPUT_METHOD:
+       case AS_APP_KIND_LOCALIZATION:
+       case AS_APP_KIND_OS_UPDATE:
+       case AS_APP_KIND_OS_UPGRADE:
+       case AS_APP_KIND_RUNTIME:
+       case AS_APP_KIND_SOURCE:
+               gs_app_add_quirk (app, AS_APP_QUIRK_NOT_LAUNCHABLE);
+               break;
+       default:
+               break;
+       }
+
+       /* set management plugin automatically */
+       gs_refine_item_management_plugin (plugin, app, item);
+
+       /* set id */
+       if (as_app_get_id (item) != NULL && gs_app_get_id (app) == NULL)
+               gs_app_set_id (app, as_app_get_id (item));
+
+       /* set source */
+       if (gs_app_get_metadata_item (app, "appstream::source-file") == NULL) {
+#if AS_CHECK_VERSION(0,6,9)
+               AsFormat *format = as_app_get_format_by_kind (item, AS_FORMAT_KIND_DESKTOP);
+               if (format != NULL) {
+                       gs_app_set_metadata (app, "appstream::source-file",
+                                            as_format_get_filename (format));
+               }
+#else
+               gs_app_set_metadata (app, "appstream::source-file",
+                                    as_app_get_source_file (item));
+#endif
+       }
+
+       /* scope */
+       if (gs_app_get_scope (app) == AS_APP_SCOPE_UNKNOWN &&
+           as_app_get_scope (item) != AS_APP_SCOPE_UNKNOWN)
+               gs_app_set_scope (app, as_app_get_scope (item));
+
+       /* set branch */
+       if (as_app_get_branch (item) != NULL &&
+           gs_app_get_branch (app) == NULL)
+               gs_app_set_branch (app, as_app_get_branch (item));
+
+       /* set content rating */
+       array = as_app_get_content_ratings (item);
+       for (i = 0; i < array->len; i++) {
+               AsContentRating *cr = g_ptr_array_index (array, i);
+               if (g_strcmp0 (as_content_rating_get_kind (cr), "oars-1.0") == 0) {
+                       gs_app_set_content_rating (app, cr);
+                       break;
+               }
+       }
+
+       /* bundle-kind */
+       if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_UNKNOWN)
+               gs_app_set_bundle_kind (app, gs_appstream_get_bundle_kind (item));
+
+       /* set name */
+       tmp = as_app_get_name (item, NULL);
+       if (tmp != NULL)
+               gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, tmp);
+
+       /* set summary */
+       tmp = as_app_get_comment (item, NULL);
+       if (tmp != NULL) {
+               gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, tmp);
+       }
+
+       /* add urls */
+       urls = as_app_get_urls (item);
+       if (g_hash_table_size (urls) > 0 &&
+           gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL) {
+               GList *l;
+               g_autoptr(GList) keys = NULL;
+               keys = g_hash_table_get_keys (urls);
+               for (l = keys; l != NULL; l = l->next) {
+                       gs_app_set_url (app,
+                                       as_url_kind_from_string (l->data),
+                                       g_hash_table_lookup (urls, l->data));
+               }
+       }
+
+       /* set license */
+       if (as_app_get_project_license (item) != NULL && gs_app_get_license (app) == NULL)
+               gs_app_set_license (app,
+                                   GS_APP_QUALITY_HIGHEST,
+                                   as_app_get_project_license (item));
+
+       /* set keywords */
+       if (as_app_get_keywords (item, NULL) != NULL &&
+           gs_app_get_keywords (app) == NULL) {
+               gs_app_set_keywords (app, as_app_get_keywords (item, NULL));
+               gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS);
+       }
+
+       /* set origin */
+       if (as_app_get_origin (item) != NULL &&
+           gs_app_get_origin (app) == NULL ) {
+               tmp = as_app_get_unique_id (item);
+               if (tmp != NULL) {
+                       if (g_str_has_prefix (tmp, "user/flatpak/") ||
+                           g_str_has_prefix (tmp, "system/flatpak/"))
+                               gs_app_set_origin (app, as_app_get_origin (item));
+               }
+       }
+
+       /* set description */
+       tmp = as_app_get_description (item, NULL);
+       if (tmp != NULL) {
+               g_autofree gchar *from_xml = NULL;
+               from_xml = as_markup_convert_simple (tmp, error);
+               if (from_xml == NULL) {
+                       gs_utils_error_convert_appstream (error);
+                       g_prefix_error (error, "trying to parse '%s': ", tmp);
+                       return FALSE;
+               }
+               gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, from_xml);
+       }
+
+       /* set icon */
+       if (as_app_get_icon_default (item) != NULL &&
+           gs_app_get_icons(app)->len == 0)
+               gs_refine_item_icon (plugin, app, item);
+
+       /* set categories */
+       array = as_app_get_categories (item);
+       if (array != NULL && gs_app_get_categories (app)->len == 0) {
+               for (i = 0; i < array->len; i++) {
+                       tmp = g_ptr_array_index (array, i);
+                       gs_app_add_category (app, tmp);
+               }
+       }
+
+       /* set project group */
+       if (as_app_get_project_group (item) != NULL &&
+           gs_app_get_project_group (app) == NULL)
+               gs_app_set_project_group (app, as_app_get_project_group (item));
+
+       /*
+        * Set the core applications for the current desktop that cannot be
+        * removed -- but note: XDG_CURRENT_DESKTOP="GNOME" is different to
+        * XDG_CURRENT_DESKTOP="Ubuntu:GNOME" here.
+        *
+        * To define what is compulsory for the hybrid desktop either:
+        *
+        *  - Add an appstream merge file downstream with the tag
+        *    <compulsory_for_desktop>Ubuntu:GNOME</compulsory_for_desktop>
+        *
+        *  - Get upstream projects to add the <compulsory_for_desktop> tag
+        */
+       array = as_app_get_compulsory_for_desktops (item);
+       current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
+       for (i = 0; i < array->len; i++) {
+               tmp = g_ptr_array_index (array, i);
+               if (g_strcmp0 (current_desktop, tmp) == 0) {
+                       gs_app_add_quirk (app, AS_APP_QUIRK_COMPULSORY);
+                       break;
+               }
+       }
+
+       /* set id kind */
+       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN)
+               gs_app_set_kind (app, as_app_get_kind (item));
+
+       /* copy all the metadata */
+       gs_appstream_copy_metadata (app, item);
+
+       /* set package names */
+       pkgnames = as_app_get_pkgnames (item);
+       if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0)
+               gs_app_set_sources (app, pkgnames);
+
+       /* set addons */
+       if (!gs_appstream_refine_add_addons (plugin, app, item, error))
+               return FALSE;
+
+       /* set screenshots */
+       gs_appstream_refine_add_screenshots (app, item);
+
+       /* set reviews */
+       gs_appstream_refine_add_reviews (app, item);
+
+       /* set provides */
+       gs_appstream_refine_add_provides (app, item);
+
+       /* are the screenshots perfect */
+       if (gs_appstream_are_screenshots_perfect (item))
+               gs_app_add_kudo (app, GS_APP_KUDO_PERFECT_SCREENSHOTS);
+
+       /* was this application released recently */
+       if (gs_appstream_is_recent_release (item))
+               gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);
+
+       /* add kudos */
+       tmp = gs_plugin_get_locale (plugin);
+       if (!_gs_utils_locale_has_translations (tmp) ||
+           as_app_get_language (item, tmp) > 50)
+               gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);
+
+       /* add a kudo to featured and popular apps */
+       if (as_app_has_kudo (item, "GnomeSoftware::popular"))
+               gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
+       if (as_app_has_category (item, "featured"))
+               gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
+
+       /* add new-style kudos */
+       kudos = as_app_get_kudos (item);
+       for (i = 0; i < kudos->len; i++) {
+               tmp = g_ptr_array_index (kudos, i);
+               switch (as_kudo_kind_from_string (tmp)) {
+               case AS_KUDO_KIND_SEARCH_PROVIDER:
+                       gs_app_add_kudo (app, GS_APP_KUDO_SEARCH_PROVIDER);
+                       break;
+               case AS_KUDO_KIND_USER_DOCS:
+                       gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
+                       break;
+               case AS_KUDO_KIND_APP_MENU:
+                       gs_app_add_kudo (app, GS_APP_KUDO_USES_APP_MENU);
+                       break;
+               case AS_KUDO_KIND_MODERN_TOOLKIT:
+                       gs_app_add_kudo (app, GS_APP_KUDO_MODERN_TOOLKIT);
+                       break;
+               case AS_KUDO_KIND_NOTIFICATIONS:
+                       gs_app_add_kudo (app, GS_APP_KUDO_USES_NOTIFICATIONS);
+                       break;
+               case AS_KUDO_KIND_HIGH_CONTRAST:
+                       gs_app_add_kudo (app, GS_APP_KUDO_HIGH_CONTRAST);
+                       break;
+               case AS_KUDO_KIND_HI_DPI_ICON:
+                       gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       /* we saved the origin hostname in the metadata */
+       tmp = as_app_get_metadata_item (item, "GnomeSoftware::OriginHostnameUrl");
+       if (tmp != NULL && gs_app_get_origin_hostname (app) == NULL)
+               gs_app_set_origin_hostname (app, tmp);
+
+       /* we have an origin in the XML */
+       if (gs_app_get_origin (app) == NULL &&
+           gs_appstream_origin_valid (as_app_get_origin (item)))
+               gs_app_set_origin (app, as_app_get_origin (item));
+
+       /* is there any update information */
+       if (!gs_appstream_refine_app_updates (plugin, app, item, error))
+               return FALSE;
+
+       return TRUE;
+}
+
+static gboolean
+gs_appstream_store_search_item (GsPlugin *plugin,
+                               AsApp *item,
+                               gchar **values,
+                               GsAppList *list,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+       AsApp *item_tmp;
+       GPtrArray *addons;
+       guint i;
+       guint match_value;
+       g_autoptr(GsApp) app = NULL;
+
+       /* match against the app or any of the addons */
+       match_value = as_app_search_matches_all (item, values);
+       addons = as_app_get_addons (item);
+       for (i = 0; i < addons->len; i++) {
+               item_tmp = g_ptr_array_index (addons, i);
+               match_value |= as_app_search_matches_all (item_tmp, values);
+       }
+
+       /* no match */
+       if (match_value == 0)
+               return TRUE;
+
+       /* create app */
+       app = gs_appstream_create_app (plugin, item, error);
+       if (app == NULL)
+               return FALSE;
+       gs_app_set_match_value (app, match_value);
+       gs_app_list_add (list, app);
+       return TRUE;
+}
+
+gboolean
+gs_appstream_store_search (GsPlugin *plugin,
+                          AsStore *store,
+                          gchar **values,
+                          GsAppList *list,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       AsApp *item;
+       GPtrArray *array;
+       gboolean ret = TRUE;
+       guint i;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* search categories for the search term */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "appstream::search");
+       g_assert (ptask != NULL);
+       array = as_store_get_apps (store);
+       for (i = 0; i < array->len; i++) {
+               if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+                       gs_utils_error_convert_gio (error);
+                       return FALSE;
+               }
+
+               item = g_ptr_array_index (array, i);
+               ret = gs_appstream_store_search_item (plugin, item,
+                                                     values, list,
+                                                     cancellable, error);
+               if (!ret)
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+static gboolean
+_as_app_matches_desktop_group_set (AsApp *app, gchar **desktop_groups)
+{
+       guint i;
+       for (i = 0; desktop_groups[i] != NULL; i++) {
+               if (!as_app_has_category (app, desktop_groups[i]))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+static gboolean
+_as_app_matches_desktop_group (AsApp *app, const gchar *desktop_group)
+{
+       g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1);
+       return _as_app_matches_desktop_group_set (app, split);
+}
+
+static void
+gs_appstream_store_add_categories_for_app (GsCategory *parent, AsApp *app)
+{
+       GPtrArray *children;
+       GPtrArray *desktop_groups;
+       GsCategory *category;
+       guint i, j;
+
+       /* find all the sub-categories */
+       children = gs_category_get_children (parent);
+       for (j = 0; j < children->len; j++) {
+               gboolean matched = FALSE;
+               category = GS_CATEGORY (g_ptr_array_index (children, j));
+
+               /* do any desktop_groups match this application */
+               desktop_groups = gs_category_get_desktop_groups (category);
+               for (i = 0; i < desktop_groups->len; i++) {
+                       const gchar *desktop_group = g_ptr_array_index (desktop_groups, i);
+                       if (_as_app_matches_desktop_group (app, desktop_group)) {
+                               matched = TRUE;
+                               break;
+                       }
+               }
+               if (matched) {
+                       gs_category_increment_size (category);
+                       gs_category_increment_size (parent);
+               }
+       }
+}
+
+gboolean
+gs_appstream_store_add_category_apps (GsPlugin *plugin,
+                                     AsStore *store,
+                                     GsCategory *category,
+                                     GsAppList *list,
+                                     GCancellable *cancellable,
+                                     GError **error)
+{
+       GPtrArray *array;
+       GPtrArray *desktop_groups;
+       guint i;
+       guint j;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* just look at each app in turn */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "appstream::add-category-apps");
+       g_assert (ptask != NULL);
+       array = as_store_get_apps (store);
+       desktop_groups = gs_category_get_desktop_groups (category);
+       if (desktop_groups->len == 0) {
+               g_warning ("no desktop_groups for %s", gs_category_get_id (category));
+               return TRUE;
+       }
+       for (j = 0; j < desktop_groups->len; j++) {
+               const gchar *desktop_group = g_ptr_array_index (desktop_groups, j);
+               g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1);
+
+               /* match the app */
+               for (i = 0; i < array->len; i++) {
+                       AsApp *item;
+                       g_autoptr(GsApp) app = NULL;
+
+                       /* no ID is invalid */
+                       item = g_ptr_array_index (array, i);
+                       if (as_app_get_id (item) == NULL)
+                               continue;
+
+                       /* match all the desktop groups */
+                       if (!_as_app_matches_desktop_group_set (item, split))
+                               continue;
+
+                       /* add all the data we can */
+                       app = gs_appstream_create_app (plugin, item, error);
+                       if (app == NULL)
+                               return FALSE;
+                       gs_app_list_add (list, app);
+               }
+       }
+       return TRUE;
+}
+
+gboolean
+gs_appstream_store_add_categories (GsPlugin *plugin,
+                                  AsStore *store,
+                                  GPtrArray *list,
+                                  GCancellable *cancellable,
+                                  GError **error)
+{
+       AsApp *app;
+       GPtrArray *array;
+       guint i;
+       guint j;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* find out how many packages are in each category */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "appstream::add-categories");
+       g_assert (ptask != NULL);
+       array = as_store_get_apps (store);
+       for (i = 0; i < array->len; i++) {
+               app = g_ptr_array_index (array, i);
+               if (as_app_get_id (app) == NULL)
+                       continue;
+               if (as_app_get_priority (app) < 0)
+                       continue;
+               for (j = 0; j < list->len; j++) {
+                       GsCategory *parent = GS_CATEGORY (g_ptr_array_index (list, j));
+                       gs_appstream_store_add_categories_for_app (parent, app);
+               }
+       }
+       return TRUE;
+}
+
+gboolean
+gs_appstream_add_popular (GsPlugin *plugin,
+                         AsStore *store,
+                         GsAppList *list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       AsApp *item;
+       GPtrArray *array;
+       guint i;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* find out how many packages are in each category */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "appstream::add-popular");
+       g_assert (ptask != NULL);
+       array = as_store_get_apps (store);
+       for (i = 0; i < array->len; i++) {
+               g_autoptr(GsApp) app = NULL;
+               item = g_ptr_array_index (array, i);
+               if (as_app_get_id (item) == NULL)
+                       continue;
+               if (!as_app_has_kudo (item, "GnomeSoftware::popular"))
+                       continue;
+               app = gs_app_new (as_app_get_id (item));
+               gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
+               gs_app_list_add (list, app);
+       }
+       return TRUE;
+}
+
+gboolean
+gs_appstream_add_featured (GsPlugin *plugin,
+                          AsStore *store,
+                          GsAppList *list,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       AsApp *item;
+       GPtrArray *array;
+       guint i;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* find out how many packages are in each category */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "appstream::add-featured");
+       g_assert (ptask != NULL);
+       array = as_store_get_apps (store);
+       for (i = 0; i < array->len; i++) {
+               g_autoptr(GsApp) app = NULL;
+               item = g_ptr_array_index (array, i);
+               if (as_app_get_id (item) == NULL)
+                       continue;
+               if (as_app_get_metadata_item (item, "GnomeSoftware::FeatureTile-css") == NULL)
+                       continue;
+               app = gs_app_new (as_app_get_id (item));
+               gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
+               gs_app_list_add (list, app);
+       }
+       return TRUE;
+}
+
+void
+gs_appstream_add_extra_info (GsPlugin *plugin, AsApp *app)
+{
+       const gchar *tmp;
+
+       /* add more search terms */
+       switch (as_app_get_kind (app)) {
+       case AS_APP_KIND_WEB_APP:
+       case AS_APP_KIND_INPUT_METHOD:
+               tmp = as_app_kind_to_string (as_app_get_kind (app));
+               g_debug ("adding keyword '%s' to %s",
+                        tmp, as_app_get_unique_id (app));
+               as_app_add_keyword (app, NULL, tmp);
+               break;
+       default:
+               break;
+       }
+
+       /* fix up these */
+       if (as_app_get_kind (app) == AS_APP_KIND_LOCALIZATION &&
+           g_str_has_prefix (as_app_get_id (app),
+                             "org.fedoraproject.LangPack-")) {
+               g_autoptr(AsIcon) icon = NULL;
+
+               /* add icon */
+               icon = as_icon_new ();
+               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+               as_icon_set_name (icon, "accessories-dictionary-symbolic");
+               as_app_add_icon (app, icon);
+
+               /* add categories */
+               as_app_add_category (app, "Addons");
+               as_app_add_category (app, "Localization");
+       }
+
+       /* fix up drivers with our nonstandard groups */
+       if (as_app_get_kind (app) == AS_APP_KIND_DRIVER) {
+               as_app_add_category (app, "Addons");
+               as_app_add_category (app, "Drivers");
+       }
+}
diff --git a/plugins/core/gs-appstream.h b/plugins/core/gs-appstream.h
new file mode 100644
index 0000000..c4c6d98
--- /dev/null
+++ b/plugins/core/gs-appstream.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015-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 __APPSTREAM_COMMON_H
+#define __APPSTREAM_COMMON_H
+
+#include <gnome-software.h>
+
+G_BEGIN_DECLS
+
+GsApp          *gs_appstream_create_app                (GsPlugin       *plugin,
+                                                        AsApp          *item,
+                                                        GError         **error);
+gboolean        gs_appstream_refine_app                (GsPlugin       *plugin,
+                                                        GsApp          *app,
+                                                        AsApp          *item,
+                                                        GError         **error);
+GsApp          *gs_appstream_create_runtime            (GsPlugin       *plugin,
+                                                        GsApp          *parent,
+                                                        const gchar    *runtime);
+gboolean        gs_appstream_store_search              (GsPlugin       *plugin,
+                                                        AsStore        *store,
+                                                        gchar          **values,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_appstream_store_add_categories      (GsPlugin       *plugin,
+                                                        AsStore        *store,
+                                                        GPtrArray      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_appstream_store_add_category_apps   (GsPlugin       *plugin,
+                                                        AsStore        *store,
+                                                        GsCategory     *category,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_appstream_add_popular               (GsPlugin       *plugin,
+                                                        AsStore        *store,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_appstream_add_featured              (GsPlugin       *plugin,
+                                                        AsStore        *store,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+void            gs_appstream_add_extra_info            (GsPlugin       *plugin,
+                                                        AsApp          *app);
+
+G_END_DECLS
+
+#endif /* __APPSTREAM_COMMON_H */
diff --git a/plugins/gs-desktop-common.c b/plugins/core/gs-desktop-common.c
similarity index 100%
rename from plugins/gs-desktop-common.c
rename to plugins/core/gs-desktop-common.c
diff --git a/plugins/gs-desktop-common.h b/plugins/core/gs-desktop-common.h
similarity index 100%
rename from plugins/gs-desktop-common.h
rename to plugins/core/gs-desktop-common.h
diff --git a/plugins/gs-plugin-appstream.c b/plugins/core/gs-plugin-appstream.c
similarity index 100%
rename from plugins/gs-plugin-appstream.c
rename to plugins/core/gs-plugin-appstream.c
diff --git a/plugins/gs-plugin-desktop-categories.c b/plugins/core/gs-plugin-desktop-categories.c
similarity index 100%
rename from plugins/gs-plugin-desktop-categories.c
rename to plugins/core/gs-plugin-desktop-categories.c
diff --git a/plugins/gs-plugin-desktop-menu-path.c b/plugins/core/gs-plugin-desktop-menu-path.c
similarity index 100%
rename from plugins/gs-plugin-desktop-menu-path.c
rename to plugins/core/gs-plugin-desktop-menu-path.c
diff --git a/plugins/gs-plugin-generic-updates.c b/plugins/core/gs-plugin-generic-updates.c
similarity index 100%
rename from plugins/gs-plugin-generic-updates.c
rename to plugins/core/gs-plugin-generic-updates.c
diff --git a/plugins/gs-plugin-hardcoded-blacklist.c b/plugins/core/gs-plugin-hardcoded-blacklist.c
similarity index 100%
rename from plugins/gs-plugin-hardcoded-blacklist.c
rename to plugins/core/gs-plugin-hardcoded-blacklist.c
diff --git a/plugins/gs-plugin-hardcoded-featured.c b/plugins/core/gs-plugin-hardcoded-featured.c
similarity index 100%
rename from plugins/gs-plugin-hardcoded-featured.c
rename to plugins/core/gs-plugin-hardcoded-featured.c
diff --git a/plugins/gs-plugin-hardcoded-popular.c b/plugins/core/gs-plugin-hardcoded-popular.c
similarity index 100%
rename from plugins/gs-plugin-hardcoded-popular.c
rename to plugins/core/gs-plugin-hardcoded-popular.c
diff --git a/plugins/gs-plugin-icons.c b/plugins/core/gs-plugin-icons.c
similarity index 100%
rename from plugins/gs-plugin-icons.c
rename to plugins/core/gs-plugin-icons.c
diff --git a/plugins/gs-plugin-key-colors.c b/plugins/core/gs-plugin-key-colors.c
similarity index 100%
rename from plugins/gs-plugin-key-colors.c
rename to plugins/core/gs-plugin-key-colors.c
diff --git a/plugins/gs-plugin-provenance-license.c b/plugins/core/gs-plugin-provenance-license.c
similarity index 100%
rename from plugins/gs-plugin-provenance-license.c
rename to plugins/core/gs-plugin-provenance-license.c
diff --git a/plugins/gs-plugin-provenance.c b/plugins/core/gs-plugin-provenance.c
similarity index 100%
rename from plugins/gs-plugin-provenance.c
rename to plugins/core/gs-plugin-provenance.c
diff --git a/plugins/dummy/Makefile.am b/plugins/dummy/Makefile.am
new file mode 100644
index 0000000..ead9bb3
--- /dev/null
+++ b/plugins/dummy/Makefile.am
@@ -0,0 +1,23 @@
+AM_CPPFLAGS =                                          \
+       -DDATADIR=\"$(datadir)\"                        \
+       -DTESTDATADIR=\""$(srcdir)"\"                   \
+       -DLOCALPLUGINDIR=\""$(builddir)/.libs"\"        \
+       -DLOCALPLUGINDIR_CORE=\""$(top_builddir)/plugins/core/.libs"\"
+
+plugindir = $(GS_PLUGIN_DIR)
+plugin_LTLIBRARIES = libgs_plugin_dummy.la
+
+libgs_plugin_dummy_la_SOURCES = gs-plugin-dummy.c
+libgs_plugin_dummy_la_LIBADD = $(GS_PLUGIN_LIBS)
+libgs_plugin_dummy_la_LDFLAGS = -module -avoid-version
+libgs_plugin_dummy_la_CFLAGS = $(GS_PLUGIN_CFLAGS)
+
+if ENABLE_TESTS
+check_PROGRAMS = gs-self-test
+gs_self_test_SOURCES = gs-self-test.c
+gs_self_test_LDADD = $(GS_PRIVATE_LIBS)
+gs_self_test_CFLAGS = $(GS_PRIVATE_CFLAGS)
+TESTS = gs-self-test
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c
similarity index 100%
rename from plugins/gs-plugin-dummy.c
rename to plugins/dummy/gs-plugin-dummy.c
diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c
new file mode 100644
index 0000000..0b05e44
--- /dev/null
+++ b/plugins/dummy/gs-self-test.c
@@ -0,0 +1,715 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013-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 "gnome-software-private.h"
+
+#include "gs-test.h"
+
+static guint _status_changed_cnt = 0;
+
+static void
+gs_plugin_loader_status_changed_cb (GsPluginLoader *plugin_loader,
+                                   GsApp *app,
+                                   GsPluginStatus status,
+                                   gpointer user_data)
+{
+       _status_changed_cnt++;
+}
+
+static void
+gs_plugin_loader_install_func (GsPluginLoader *plugin_loader)
+{
+       gboolean ret;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GError) error = NULL;
+
+       /* install */
+       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_ACTION_INSTALL,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+
+       /* remove */
+       ret = gs_plugin_loader_app_action (plugin_loader, app,
+                                          GS_PLUGIN_ACTION_REMOVE,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+}
+
+static void
+gs_plugin_loader_error_func (GsPluginLoader *plugin_loader)
+{
+       GsPluginEvent *event;
+       const GError *app_error;
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GPtrArray) events = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* drop all caches */
+       gs_plugin_loader_setup_again (plugin_loader);
+
+       /* 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_ACTION_UPDATE,
+                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
+                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+
+       /* 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
+gs_plugin_loader_refine_func (GsPluginLoader *plugin_loader)
+{
+       gboolean ret;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GError) error = NULL;
+
+       /* get the extra bits */
+       app = gs_app_new ("chiron.desktop");
+       gs_app_set_management_plugin (app, "dummy");
+       ret = gs_plugin_loader_app_refine (plugin_loader, app,
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+
+       g_assert_cmpstr (gs_app_get_license (app), ==, "GPL-2.0+");
+       g_assert_cmpstr (gs_app_get_description (app), !=, NULL);
+       g_assert_cmpstr (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE), ==, "http://www.test.org/";);
+}
+
+static void
+gs_plugin_loader_key_colors_func (GsPluginLoader *plugin_loader)
+{
+       GPtrArray *array;
+       gboolean ret;
+       guint i;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GError) error = NULL;
+
+       /* get the extra bits */
+       app = gs_app_new ("zeus.desktop");
+       ret = gs_plugin_loader_app_refine (plugin_loader, app,
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+       array = gs_app_get_key_colors (app);
+       g_assert_cmpint (array->len, >=, 3);
+
+       /* check values are in range */
+       for (i = 0; i < array->len; i++) {
+               GdkRGBA *kc = g_ptr_array_index (array, i);
+               g_assert_cmpfloat (kc->red, >=, 0.f);
+               g_assert_cmpfloat (kc->red, <=, 1.f);
+               g_assert_cmpfloat (kc->green, >=, 0.f);
+               g_assert_cmpfloat (kc->green, <=, 1.f);
+               g_assert_cmpfloat (kc->blue, >=, 0.f);
+               g_assert_cmpfloat (kc->blue, <=, 1.f);
+               g_assert_cmpfloat (kc->alpha, >=, 0.f);
+               g_assert_cmpfloat (kc->alpha, <=, 1.f);
+       }
+}
+
+static void
+gs_plugin_loader_updates_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get the updates list */
+       list = gs_plugin_loader_get_updates (plugin_loader,
+                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
+                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                            NULL,
+                                            &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there are two entries */
+       g_assert_cmpint (gs_app_list_length (list), ==, 3);
+       app = gs_app_list_index (list, 0);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "chiron.desktop");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
+       g_assert_cmpstr (gs_app_get_update_details (app), ==, "Do not crash when using libvirt.");
+       g_assert_cmpint (gs_app_get_update_urgency (app), ==, AS_URGENCY_KIND_HIGH);
+
+       /* get the virtual non-apps OS update */
+       app = gs_app_list_index (list, 1);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.gnome.Software.OsUpdate");
+       g_assert_cmpstr (gs_app_get_name (app), ==, "OS Updates");
+       g_assert_cmpstr (gs_app_get_summary (app), ==, "Includes performance, stability and security 
improvements.");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_OS_UPDATE);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
+       g_assert_cmpint (gs_app_get_related(app)->len, ==, 2);
+
+       /* get the virtual non-apps OS update */
+       app = gs_app_list_index (list, 2);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "proxy.desktop");
+       g_assert (gs_app_has_quirk (app, AS_APP_QUIRK_IS_PROXY));
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
+       g_assert_cmpint (gs_app_get_related(app)->len, ==, 2);
+}
+
+static void
+gs_plugin_loader_distro_upgrades_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app;
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get the updates list */
+       list = gs_plugin_loader_get_distro_upgrades (plugin_loader,
+                                                    GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                                    GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                                    NULL,
+                                                    &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there is one entry */
+       g_assert_cmpint (gs_app_list_length (list), ==, 1);
+       app = gs_app_list_index (list, 0);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.fedoraproject.release-rawhide.upgrade");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_OS_UPGRADE);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+
+       /* this should be set with a higher priority by AppStream */
+       g_assert_cmpstr (gs_app_get_summary (app), ==, "Release specific tagline");
+
+       /* download the update */
+       ret = gs_plugin_loader_app_action (plugin_loader,
+                                          app,
+                                          GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
+
+       /* trigger the update */
+       ret = gs_plugin_loader_app_action (plugin_loader,
+                                          app,
+                                          GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
+}
+
+static void
+gs_plugin_loader_installed_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app;
+       GsApp *addon;
+       GPtrArray *addons;
+       guint64 kudos;
+       g_autofree gchar *menu_path = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get installed packages */
+       list = gs_plugin_loader_get_installed (plugin_loader,
+                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN |
+                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS |
+                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
+                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
+                                              GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                              NULL,
+                                              &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there is one entry */
+       g_assert_cmpint (gs_app_list_length (list), ==, 1);
+       app = gs_app_list_index (list, 0);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "zeus.desktop");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+       g_assert_cmpstr (gs_app_get_name (app), ==, "Zeus");
+       g_assert_cmpstr (gs_app_get_source_default (app), ==, "zeus");
+       g_assert (gs_app_get_pixbuf (app) != NULL);
+
+       /* check various bitfields */
+       g_assert (gs_app_has_quirk (app, AS_APP_QUIRK_PROVENANCE));
+       g_assert_cmpstr (gs_app_get_license (app), ==, "GPL-2.0+");
+       g_assert (gs_app_get_license_is_free (app));
+
+       /* check kudos */
+       kudos = gs_app_get_kudos (app);
+       g_assert (kudos & GS_APP_KUDO_MY_LANGUAGE);
+
+       /* check categories */
+       g_assert (gs_app_has_category (app, "Player"));
+       g_assert (gs_app_has_category (app, "AudioVideo"));
+       g_assert (!gs_app_has_category (app, "ImageProcessing"));
+       g_assert (gs_app_get_menu_path (app) != NULL);
+       menu_path = g_strjoinv ("->", gs_app_get_menu_path (app));
+       g_assert_cmpstr (menu_path, ==, "Audio & Video->Music Players");
+
+       /* check addon */
+       addons = gs_app_get_addons (app);
+       g_assert_cmpint (addons->len, ==, 1);
+       addon = g_ptr_array_index (addons, 0);
+       g_assert_cmpstr (gs_app_get_id (addon), ==, "zeus-spell.addon");
+       g_assert_cmpint (gs_app_get_kind (addon), ==, AS_APP_KIND_ADDON);
+       g_assert_cmpint (gs_app_get_state (addon), ==, AS_APP_STATE_AVAILABLE);
+       g_assert_cmpstr (gs_app_get_name (addon), ==, "Spell Check");
+       g_assert_cmpstr (gs_app_get_source_default (addon), ==, "zeus-spell");
+       g_assert_cmpstr (gs_app_get_license (addon), ==,
+                        "LicenseRef-free=https://www.debian.org/";);
+       g_assert (gs_app_get_pixbuf (addon) == NULL);
+}
+
+static void
+gs_plugin_loader_search_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app;
+       g_autofree gchar *menu_path = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get search result based on addon keyword */
+       list = gs_plugin_loader_search (plugin_loader,
+                                       "spell",
+                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                       NULL,
+                                       &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there is one entry, the parent app */
+       g_assert_cmpint (gs_app_list_length (list), ==, 1);
+       app = gs_app_list_index (list, 0);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "zeus.desktop");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
+}
+
+static void
+gs_plugin_loader_url_to_app_func (GsPluginLoader *plugin_loader)
+{
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       app = gs_plugin_loader_url_to_app (plugin_loader,
+                                          "dummy://chiron.desktop",
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (app != NULL);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "chiron.desktop");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
+}
+
+static void
+gs_plugin_loader_modalias_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app;
+       g_autofree gchar *menu_path = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get search result based on addon keyword */
+       list = gs_plugin_loader_search (plugin_loader,
+                                       "colorhug2",
+                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                       NULL,
+                                       &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there is one entry, the parent app */
+       g_assert_cmpint (gs_app_list_length (list), ==, 1);
+       app = gs_app_list_index (list, 0);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "com.hughski.ColorHug2.driver");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DRIVER);
+       g_assert (gs_app_has_category (app, "Addons"));
+       g_assert (gs_app_has_category (app, "Drivers"));
+}
+
+static void
+gs_plugin_loader_webapps_func (GsPluginLoader *plugin_loader)
+{
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* no epiphany, abort */
+       if (!gs_plugin_loader_get_enabled (plugin_loader, "epiphany"))
+               return;
+
+       /* a webapp with a local icon */
+       app = gs_app_new ("arachne.desktop");
+       gs_app_set_kind (app, AS_APP_KIND_WEB_APP);
+       ret = gs_plugin_loader_app_refine (plugin_loader, app,
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+       g_assert (gs_app_get_pixbuf (app) != NULL);
+}
+
+static void
+gs_plugin_loader_plugin_cache_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app1;
+       GsApp *app2;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list1 = NULL;
+       g_autoptr(GsAppList) list2 = NULL;
+
+       /* ensure we get the same results back from calling the methods twice */
+       list1 = gs_plugin_loader_get_distro_upgrades (plugin_loader,
+                                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                                     NULL,
+                                                     &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list1 != NULL);
+       g_assert_cmpint (gs_app_list_length (list1), ==, 1);
+       app1 = gs_app_list_index (list1, 0);
+
+       list2 = gs_plugin_loader_get_distro_upgrades (plugin_loader,
+                                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                                     NULL,
+                                                     &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list2 != NULL);
+       g_assert_cmpint (gs_app_list_length (list2), ==, 1);
+       app2 = gs_app_list_index (list2, 0);
+
+       /* make sure there is one GObject */
+       g_assert_cmpstr (gs_app_get_id (app1), ==, gs_app_get_id (app2));
+       g_assert (app1 == app2);
+}
+
+static void
+gs_plugin_loader_authentication_func (GsPluginLoader *plugin_loader)
+{
+       GsAuth *auth;
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(AsReview) review = NULL;
+       g_autoptr(AsReview) review2 = NULL;
+
+       /* check initial state */
+       auth = gs_plugin_loader_get_auth_by_id (plugin_loader, "dummy");
+       g_assert (GS_IS_AUTH (auth));
+       g_assert_cmpint (gs_auth_get_flags (auth), ==, 0);
+
+       /* do an action that returns a URL */
+       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
+                                           GS_PLUGIN_ACTION_AUTH_REGISTER,
+                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                           NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID);
+       g_assert (!ret);
+       g_clear_error (&error);
+       g_assert (!gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
+
+       /* do an action that requires a login */
+       app = gs_app_new (NULL);
+       review = as_review_new ();
+       ret = gs_plugin_loader_review_action (plugin_loader, app, review,
+                                             GS_PLUGIN_ACTION_REVIEW_REMOVE,
+                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                             NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_REQUIRED);
+       g_assert (!ret);
+       g_clear_error (&error);
+
+       /* pretend to auth with no credentials */
+       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
+                                           GS_PLUGIN_ACTION_AUTH_LOGIN,
+                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                           NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID);
+       g_assert (!ret);
+       g_clear_error (&error);
+       g_assert (!gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
+
+       /* auth again with correct credentials */
+       gs_auth_set_username (auth, "dummy");
+       gs_auth_set_password (auth, "dummy");
+       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
+                                           GS_PLUGIN_ACTION_AUTH_LOGIN,
+                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                           NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert (gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
+
+       /* do the action that requires a login */
+       review2 = as_review_new ();
+       ret = gs_plugin_loader_review_action (plugin_loader, app, review2,
+                                             GS_PLUGIN_ACTION_REVIEW_REMOVE,
+                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                             NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (ret);
+}
+
+static void
+gs_plugin_loader_wildcard_func (GsPluginLoader *plugin_loader)
+{
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       list = gs_plugin_loader_get_popular (plugin_loader,
+                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                            NULL,
+                                            &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+       g_assert_cmpint (gs_app_list_length (list), ==, 1);
+}
+
+int
+main (int argc, char **argv)
+{
+       gboolean ret;
+       g_autofree gchar *xml = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsPluginLoader) plugin_loader = NULL;
+       const gchar *whitelist[] = {
+               "appstream",
+               "dummy",
+               "generic-updates",
+               "hardcoded-blacklist",
+               "desktop-categories",
+               "desktop-menu-path",
+               "icons",
+               "key-colors",
+               "provenance",
+               "provenance-license",
+               NULL
+       };
+
+       g_test_init (&argc, &argv, NULL);
+       g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+       /* set all the things required as a dummy test harness */
+       g_setenv ("GS_SELF_TEST_LOCALE", "en_GB", TRUE);
+       g_setenv ("GS_SELF_TEST_DUMMY_ENABLE", "1", TRUE);
+       g_setenv ("GS_SELF_TEST_PROVENANCE_SOURCES", "london*,boston", TRUE);
+       g_setenv ("GS_SELF_TEST_PROVENANCE_LICENSE_SOURCES", "london*,boston", TRUE);
+       g_setenv ("GS_SELF_TEST_PROVENANCE_LICENSE_URL", "https://www.debian.org/";, TRUE);
+
+       xml = g_strdup ("<?xml version=\"1.0\"?>\n"
+               "<components version=\"0.9\">\n"
+               "  <component type=\"desktop\">\n"
+               "    <id>chiron.desktop</id>\n"
+               "    <pkgname>chiron</pkgname>\n"
+               "  </component>\n"
+               "  <component type=\"desktop\">\n"
+               "    <id>zeus.desktop</id>\n"
+               "    <name>Zeus</name>\n"
+               "    <summary>A teaching application</summary>\n"
+               "    <pkgname>zeus</pkgname>\n"
+               "    <icon type=\"stock\">drive-harddisk</icon>\n"
+               "    <categories>\n"
+               "      <category>AudioVideo</category>\n"
+               "      <category>Player</category>\n"
+               "    </categories>\n"
+               "    <languages>\n"
+               "      <lang percentage=\"100\">en_GB</lang>\n"
+               "    </languages>\n"
+               "  </component>\n"
+               "  <component type=\"desktop\">\n"
+               "    <id>mate-spell.desktop</id>\n"
+               "    <name>Spell</name>\n"
+               "    <summary>A spelling application for MATE</summary>\n"
+               "    <pkgname>mate-spell</pkgname>\n"
+               "    <icon type=\"stock\">drive-harddisk</icon>\n"
+               "    <project_group>MATE</project_group>\n"
+               "  </component>\n"
+               "  <component type=\"addon\">\n"
+               "    <id>zeus-spell.addon</id>\n"
+               "    <extends>zeus.desktop</extends>\n"
+               "    <name>Spell Check</name>\n"
+               "    <summary>Check the spelling when teaching</summary>\n"
+               "    <pkgname>zeus-spell</pkgname>\n"
+               "  </component>\n"
+               "  <component type=\"desktop\">\n"
+               "    <id>Uninstall Zeus.desktop</id>\n"
+               "    <name>Uninstall Zeus</name>\n"
+               "    <summary>Uninstall the teaching application</summary>\n"
+               "    <icon type=\"stock\">drive-harddisk</icon>\n"
+               "  </component>\n"
+               "  <component type=\"os-upgrade\">\n"
+               "    <id>org.fedoraproject.release-rawhide.upgrade</id>\n"
+               "    <summary>Release specific tagline</summary>\n"
+               "  </component>\n"
+               "</components>\n");
+       g_setenv ("GS_SELF_TEST_APPSTREAM_XML", xml, TRUE);
+
+       /* only critical and error are fatal */
+       g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
+
+       /* we can only load this once per process */
+       plugin_loader = gs_plugin_loader_new ();
+       g_signal_connect (plugin_loader, "status-changed",
+                         G_CALLBACK (gs_plugin_loader_status_changed_cb), NULL);
+       gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR);
+       gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR_CORE);
+       ret = gs_plugin_loader_setup (plugin_loader,
+                                     (gchar**) whitelist,
+                                     NULL,
+                                     GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                     NULL,
+                                     &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert (!gs_plugin_loader_get_enabled (plugin_loader, "notgoingtoexist"));
+       g_assert (gs_plugin_loader_get_enabled (plugin_loader, "appstream"));
+       g_assert (gs_plugin_loader_get_enabled (plugin_loader, "dummy"));
+
+       /* plugin tests go here */
+       g_test_add_data_func ("/gnome-software/plugin-loader{wildcard}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_wildcard_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{authentication}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_authentication_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{plugin-cache}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_plugin_cache_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{key-colors}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_key_colors_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{search}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_search_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{url-to-app}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_url_to_app_func);
+       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);
+       g_test_add_data_func ("/gnome-software/plugin-loader{refine}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_refine_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{updates}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_updates_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{distro-upgrades}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_distro_upgrades_func);
+
+       return g_test_run ();
+}
+
+/* vim: set noexpandtab: */
diff --git a/plugins/gs-appstream.c b/plugins/gs-appstream.c
deleted file mode 100644
index 673f626..0000000
--- a/plugins/gs-appstream.c
+++ /dev/null
@@ -1,1147 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
- *
- * Copyright (C) 2015-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.
- */
-
-#include "config.h"
-
-#include <gnome-software.h>
-
-#include "gs-appstream.h"
-
-#define        GS_APPSTREAM_MAX_SCREENSHOTS    5
-
-GsApp *
-gs_appstream_create_app (GsPlugin *plugin, AsApp *item, GError **error)
-{
-       const gchar *unique_id = as_app_get_unique_id (item);
-       GsApp *app = gs_plugin_cache_lookup (plugin, unique_id);
-       if (app == NULL) {
-               app = gs_app_new (as_app_get_id (item));
-               gs_app_set_metadata (app, "GnomeSoftware::Creator",
-                                    gs_plugin_get_name (plugin));
-               if (!gs_appstream_refine_app (plugin, app, item, error)) {
-                       g_object_unref (app);
-                       return NULL;
-               }
-               gs_plugin_cache_add (plugin, unique_id, app);
-       }
-       return app;
-}
-
-static AsIcon *
-gs_appstream_get_icon_by_kind (AsApp *app, AsIconKind icon_kind)
-{
-       GPtrArray *icons;
-       guint i;
-
-       icons = as_app_get_icons (app);
-       for (i = 0; i < icons->len; i++) {
-               AsIcon *icon = g_ptr_array_index (icons, i);
-               if (as_icon_get_kind (icon) == icon_kind)
-                       return icon;
-       }
-       return NULL;
-}
-
-static AsIcon *
-gs_appstream_get_icon_by_kind_and_size (AsApp *app, AsIconKind icon_kind, guint sz)
-{
-       GPtrArray *icons;
-       guint i;
-
-       icons = as_app_get_icons (app);
-       for (i = 0; i < icons->len; i++) {
-               AsIcon *icon = g_ptr_array_index (icons, i);
-               if (as_icon_get_kind (icon) == icon_kind &&
-                   as_icon_get_width (icon) == sz &&
-                   as_icon_get_height (icon) == sz)
-                       return icon;
-       }
-       return NULL;
-}
-
-static void
-gs_refine_item_icon (GsPlugin *plugin, GsApp *app, AsApp *item)
-{
-       AsIcon *icon;
-
-       /* try a stock icon first */
-       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_STOCK);
-       if (icon != NULL)
-               gs_app_add_icon (app, icon);
-
-       /* if HiDPI get a 128px cached icon */
-       if (gs_plugin_get_scale (plugin) == 2) {
-               icon = gs_appstream_get_icon_by_kind_and_size (item,
-                                                              AS_ICON_KIND_CACHED,
-                                                              128);
-               if (icon != NULL)
-                       gs_app_add_icon (app, icon);
-       }
-
-       /* non-HiDPI cached icon */
-       icon = gs_appstream_get_icon_by_kind_and_size (item,
-                                                      AS_ICON_KIND_CACHED,
-                                                      64);
-       if (icon != NULL)
-               gs_app_add_icon (app, icon);
-
-       /* prefer local */
-       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_LOCAL);
-       if (icon != NULL) {
-               /* does not exist, so try to find using the icon theme */
-               if (as_icon_get_kind (icon) == AS_ICON_KIND_LOCAL &&
-                   as_icon_get_filename (icon) == NULL) {
-                       g_debug ("converting missing LOCAL icon %s to STOCK",
-                                as_icon_get_name (icon));
-                       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               }
-               gs_app_add_icon (app, icon);
-       }
-
-       /* remote as a last resort */
-       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_REMOTE);
-       if (icon != NULL)
-               gs_app_add_icon (app, icon);
-}
-
-static gboolean
-gs_appstream_refine_add_addons (GsPlugin *plugin,
-                               GsApp *app,
-                               AsApp *item,
-                               GError **error)
-{
-       GPtrArray *addons;
-       guint i;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* we only care about addons to desktop apps */
-       if (gs_app_get_kind (app) != AS_APP_KIND_DESKTOP)
-               return TRUE;
-
-       /* search categories for the search term */
-       ptask = as_profile_start (gs_plugin_get_profile (plugin),
-                                 "appstream::refine-addons{%s}",
-                                 gs_app_get_unique_id (app));
-       g_assert (ptask != NULL);
-
-       addons = as_app_get_addons (item);
-       if (addons == NULL)
-               return TRUE;
-
-       for (i = 0; i < addons->len; i++) {
-               AsApp *as_addon = g_ptr_array_index (addons, i);
-               g_autoptr(GsApp) addon = NULL;
-
-               addon = gs_appstream_create_app (plugin, as_addon, error);
-               if (addon == NULL)
-                       return FALSE;
-
-               /* add all the data we can */
-               if (!gs_appstream_refine_app (plugin, addon, as_addon, error))
-                       return FALSE;
-               gs_app_add_addon (app, addon);
-       }
-       return TRUE;
-}
-
-static void
-gs_appstream_refine_add_screenshots (GsApp *app, AsApp *item)
-{
-       AsScreenshot *ss;
-       GPtrArray *images_as;
-       GPtrArray *screenshots_as;
-       guint i;
-
-       /* do we have any to add */
-       screenshots_as = as_app_get_screenshots (item);
-       if (screenshots_as->len == 0)
-               return;
-
-       /* does the app already have some */
-       gs_app_add_kudo (app, GS_APP_KUDO_HAS_SCREENSHOTS);
-       if (gs_app_get_screenshots(app)->len > 0)
-               return;
-
-       /* add any we know */
-       for (i = 0; i < screenshots_as->len &&
-                   i < GS_APPSTREAM_MAX_SCREENSHOTS; i++) {
-               ss = g_ptr_array_index (screenshots_as, i);
-               images_as = as_screenshot_get_images (ss);
-               if (images_as->len == 0)
-                       continue;
-               if (as_screenshot_get_kind (ss) == AS_SCREENSHOT_KIND_UNKNOWN)
-                       continue;
-               gs_app_add_screenshot (app, ss);
-       }
-}
-
-static void
-gs_appstream_refine_add_reviews (GsApp *app, AsApp *item)
-{
-       AsReview *review;
-       GPtrArray *reviews;
-       guint i;
-
-       /* do we have any to add */
-       if (gs_app_get_reviews(app)->len > 0)
-               return;
-       reviews = as_app_get_reviews (item);
-       for (i = 0; i < reviews->len; i++) {
-               review = g_ptr_array_index (reviews, i);
-               gs_app_add_review (app, review);
-       }
-}
-
-static void
-gs_appstream_refine_add_provides (GsApp *app, AsApp *item)
-{
-       AsProvide *provide;
-       GPtrArray *provides;
-       guint i;
-
-       /* do we have any to add */
-       if (gs_app_get_provides(app)->len > 0)
-               return;
-       provides = as_app_get_provides (item);
-       for (i = 0; i < provides->len; i++) {
-               provide = g_ptr_array_index (provides, i);
-               gs_app_add_provide (app, provide);
-       }
-}
-
-static gboolean
-gs_appstream_is_recent_release (AsApp *app)
-{
-       AsRelease *release;
-       GPtrArray *releases;
-       guint64 secs;
-
-       /* get newest release */
-       releases = as_app_get_releases (app);
-       if (releases->len == 0)
-               return FALSE;
-       release = g_ptr_array_index (releases, 0);
-
-       /* is last build less than one year ago? */
-       secs = ((guint64) g_get_real_time () / G_USEC_PER_SEC) -
-               as_release_get_timestamp (release);
-       return secs / (60 * 60 * 24) < 365;
-}
-
-static gboolean
-gs_appstream_are_screenshots_perfect (AsApp *app)
-{
-       AsImage *image;
-       AsScreenshot *screenshot;
-       GPtrArray *screenshots;
-       guint height;
-       guint i;
-       guint width;
-
-       screenshots = as_app_get_screenshots (app);
-       if (screenshots->len == 0)
-               return FALSE;
-       for (i = 0; i < screenshots->len; i++) {
-
-               /* get the source image as the thumbs will be resized & padded */
-               screenshot = g_ptr_array_index (screenshots, i);
-               image = as_screenshot_get_source (screenshot);
-               if (image == NULL)
-                       return FALSE;
-
-               width = as_image_get_width (image);
-               height = as_image_get_height (image);
-
-               /* too small */
-               if (width < AS_IMAGE_LARGE_WIDTH || height < AS_IMAGE_LARGE_HEIGHT)
-                       return FALSE;
-
-               /* too large */
-               if (width > AS_IMAGE_LARGE_WIDTH * 2 || height > AS_IMAGE_LARGE_HEIGHT * 2)
-                       return FALSE;
-
-               /* not 16:9 */
-               if ((width / 16) * 9 != height)
-                       return FALSE;
-       }
-       return TRUE;
-}
-
-static void
-gs_appstream_copy_metadata (GsApp *app, AsApp *item)
-{
-       GHashTable *hash;
-       GList *l;
-       g_autoptr(GList) keys = NULL;
-
-       hash = as_app_get_metadata (item);
-       keys = g_hash_table_get_keys (hash);
-       for (l = keys; l != NULL; l = l->next) {
-               const gchar *key = l->data;
-               const gchar *value = g_hash_table_lookup (hash, key);
-               if (gs_app_get_metadata_item (app, key) != NULL)
-                       continue;
-               gs_app_set_metadata (app, key, value);
-       }
-}
-
-GsApp *
-gs_appstream_create_runtime (GsPlugin *plugin,
-                            GsApp *parent,
-                            const gchar *runtime)
-{
-       g_autofree gchar *source = NULL;
-       g_auto(GStrv) split = NULL;
-       g_autoptr(GsApp) app_cache = NULL;
-       g_autoptr(GsApp) app = NULL;
-
-       /* get the name/arch/branch */
-       split = g_strsplit (runtime, "/", -1);
-       if (g_strv_length (split) != 3)
-               return NULL;
-
-       /* create the complete GsApp from the single string */
-       app = gs_app_new (split[0]);
-       source = g_strdup_printf ("runtime/%s", runtime);
-       gs_app_add_source (app, source);
-       gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_FLATPAK);
-       gs_app_set_kind (app, AS_APP_KIND_RUNTIME);
-       gs_app_set_branch (app, split[2]);
-       gs_app_set_scope (app, gs_app_get_scope (parent));
-
-       /* search in the cache */
-       app_cache = gs_plugin_cache_lookup (plugin, gs_app_get_unique_id (app));
-       if (app_cache != NULL) {
-               /* since the cached runtime can have been created somewhere else
-                * (we're using a global cache), we need to make sure that a
-                * source is set */
-               if (gs_app_get_source_default (app_cache) == NULL)
-                       gs_app_add_source (app_cache, source);
-               return g_steal_pointer (&app_cache);
-       }
-
-       /* save in the cache */
-       gs_plugin_cache_add (plugin, NULL, app);
-       return g_steal_pointer (&app);
-}
-
-static void
-gs_refine_item_management_plugin (GsPlugin *plugin, GsApp *app, AsApp *item)
-{
-       GPtrArray *bundles;
-       const gchar *management_plugin = NULL;
-       const gchar *runtime = NULL;
-       guint i;
-
-       /* allow override */
-       management_plugin = as_app_get_metadata_item (item, "GnomeSoftware::Plugin");
-       if (management_plugin != NULL)
-               gs_app_set_management_plugin (app, management_plugin);
-
-       /* find the default bundle kind */
-       bundles = as_app_get_bundles (item);
-       for (i = 0; i < bundles->len; i++) {
-               AsBundle *bundle = g_ptr_array_index (bundles, i);
-               AsBundleKind kind = as_bundle_get_kind (bundle);
-
-               gs_app_add_source (app, as_bundle_get_id (bundle));
-
-               /* automatically add runtime */
-               if (kind == AS_BUNDLE_KIND_FLATPAK) {
-                       runtime = as_bundle_get_runtime (bundle);
-                       if (runtime != NULL) {
-                               g_autoptr(GsApp) app2 = NULL;
-                               app2 = gs_appstream_create_runtime (plugin, app, runtime);
-                               if (app2 != NULL) {
-                                       if (app == app2) {
-                                               g_warning ("%s runtime cannot have runtime!",
-                                                          gs_app_get_unique_id (app));
-                                               break;
-                                       }
-                                       g_debug ("runtime for %s is %s",
-                                                gs_app_get_unique_id (app),
-                                                runtime);
-                                       gs_app_set_update_runtime (app, app2);
-                               }
-                       }
-                       break;
-               }
-       }
-}
-
-static gboolean
-gs_appstream_refine_app_updates (GsPlugin *plugin,
-                                GsApp *app,
-                                AsApp *item,
-                                GError **error)
-{
-       AsUrgencyKind urgency_best = AS_URGENCY_KIND_UNKNOWN;
-       GPtrArray *releases;
-       g_autoptr(GPtrArray) updates_list = NULL;
-
-       /* only for UPDATABLE apps */
-       if (!gs_app_is_updatable (app))
-               return TRUE;
-
-       /* make a list of valid updates */
-       updates_list = g_ptr_array_new ();
-       releases = as_app_get_releases (item);
-       for (guint i = 0; i < releases->len; i++) {
-               AsRelease *rel = g_ptr_array_index (releases, i);
-
-               /* already installed */
-               g_debug ("installable update %s [%u]",
-                        as_release_get_version (rel),
-                        as_release_get_state (rel));
-               if (as_release_get_state (rel) == AS_RELEASE_STATE_INSTALLED)
-                       continue;
-
-               /* use the 'worst' urgency, e.g. critical over enhancement */
-               if (as_release_get_urgency (rel) > urgency_best)
-                       urgency_best = as_release_get_urgency (rel);
-
-               /* add updates with a description */
-               if (as_release_get_description (rel, NULL) == NULL)
-                       continue;
-               g_ptr_array_add (updates_list, rel);
-       }
-
-       /* only set if known */
-       if (urgency_best != AS_URGENCY_KIND_UNKNOWN)
-               gs_app_set_update_urgency (app, urgency_best);
-
-       /* no prefix on each release */
-       if (updates_list->len == 1) {
-               g_autofree gchar *desc = NULL;
-               AsRelease *rel = g_ptr_array_index (updates_list, 0);
-               desc = as_markup_convert (as_release_get_description (rel, NULL),
-                                         AS_MARKUP_CONVERT_FORMAT_SIMPLE,
-                                         error);
-               if (desc == NULL) {
-                       gs_utils_error_convert_appstream (error);
-                       return FALSE;
-               }
-               gs_app_set_update_details (app, desc);
-
-       /* get the descriptions with a version prefix */
-       } else if (updates_list->len > 1) {
-               g_autoptr(GString) update_desc = g_string_new ("");
-               for (guint i = 0; i < updates_list->len; i++) {
-                       g_autofree gchar *desc = NULL;
-                       AsRelease *rel = g_ptr_array_index (updates_list, i);
-                       desc = as_markup_convert (as_release_get_description (rel, NULL),
-                                                 AS_MARKUP_CONVERT_FORMAT_SIMPLE,
-                                                 error);
-                       if (desc == NULL) {
-                               gs_utils_error_convert_appstream (error);
-                               return FALSE;
-                       }
-                       g_string_append_printf (update_desc,
-                                               "Version %s:\n%s\n\n",
-                                               as_release_get_version (rel),
-                                               desc);
-               }
-
-               /* remove trailing newlines */
-               if (update_desc->len > 2)
-                       g_string_truncate (update_desc, update_desc->len - 2);
-               gs_app_set_update_details (app, update_desc->str);
-       }
-
-       /* if there is no already set update version use the newest */
-       if (gs_app_get_update_version (app) == NULL) {
-               AsRelease *rel = as_app_get_release_default (item);
-               if (rel != NULL)
-                       gs_app_set_update_version (app, as_release_get_version (rel));
-       }
-
-       /* success */
-       return TRUE;
-}
-
-/**
- * _gs_utils_locale_has_translations:
- * @locale: A locale, e.g. "en_GB"
- *
- * Looks up if the locale is likely to have translations.
- *
- * Returns: %TRUE if the locale should have translations
- **/
-static gboolean
-_gs_utils_locale_has_translations (const gchar *locale)
-{
-       if (g_strcmp0 (locale, "C") == 0)
-               return FALSE;
-       if (g_strcmp0 (locale, "en") == 0)
-               return FALSE;
-       if (g_strcmp0 (locale, "en_US") == 0)
-               return FALSE;
-       return TRUE;
-}
-
-static AsBundleKind
-gs_appstream_get_bundle_kind (AsApp *item)
-{
-       GPtrArray *bundles;
-       GPtrArray *pkgnames;
-
-       /* prefer bundle */
-       bundles = as_app_get_bundles (item);
-       if (bundles->len > 0) {
-               AsBundle *bundle = g_ptr_array_index (bundles, 0);
-               if (as_bundle_get_kind (bundle) != AS_BUNDLE_KIND_UNKNOWN)
-                       return as_bundle_get_kind (bundle);
-       }
-
-       /* fallback to packages */
-       pkgnames = as_app_get_pkgnames (item);
-       if (pkgnames->len > 0)
-               return AS_BUNDLE_KIND_PACKAGE;
-
-       /* nothing */
-       return AS_BUNDLE_KIND_UNKNOWN;
-}
-
-static gboolean
-gs_appstream_origin_valid (const gchar *origin)
-{
-       if (origin == NULL)
-               return FALSE;
-       if (g_strcmp0 (origin, "") == 0)
-               return FALSE;
-       return TRUE;
-}
-
-gboolean
-gs_appstream_refine_app (GsPlugin *plugin,
-                        GsApp *app,
-                        AsApp *item,
-                        GError **error)
-{
-       AsRequire *req;
-       g_autoptr(GError) error_local = NULL;
-       GHashTable *urls;
-       GPtrArray *array;
-       GPtrArray *pkgnames;
-       GPtrArray *kudos;
-       const gchar *current_desktop;
-       const gchar *tmp;
-       guint i;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* search categories for the search term */
-       ptask = as_profile_start (gs_plugin_get_profile (plugin),
-                                 "appstream::refine-app{%s}",
-                                 gs_app_get_unique_id (app));
-       g_assert (ptask != NULL);
-
-       /* set the kind to be more precise */
-       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN ||
-           gs_app_get_kind (app) == AS_APP_KIND_GENERIC) {
-               gs_app_set_kind (app, as_app_get_kind (item));
-       }
-
-       /* is installed already */
-       if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN &&
-           as_app_get_state (item) != AS_APP_STATE_UNKNOWN) {
-               gs_app_set_state (app, as_app_get_state (item));
-       }
-
-       /* is compatible */
-       req = as_app_get_require_by_value (item,
-                                          AS_REQUIRE_KIND_ID,
-                                          "org.gnome.Software.desktop");
-       if (req != NULL) {
-               if (!as_require_version_compare (req, PACKAGE_VERSION, &error_local)) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "not for this gnome-software: %s",
-                                    error_local->message);
-                       return FALSE;
-               }
-       }
-
-       /* types we can never launch */
-       switch (gs_app_get_kind (app)) {
-       case AS_APP_KIND_ADDON:
-       case AS_APP_KIND_CODEC:
-       case AS_APP_KIND_FIRMWARE:
-       case AS_APP_KIND_FONT:
-       case AS_APP_KIND_GENERIC:
-       case AS_APP_KIND_INPUT_METHOD:
-       case AS_APP_KIND_LOCALIZATION:
-       case AS_APP_KIND_OS_UPDATE:
-       case AS_APP_KIND_OS_UPGRADE:
-       case AS_APP_KIND_RUNTIME:
-       case AS_APP_KIND_SOURCE:
-               gs_app_add_quirk (app, AS_APP_QUIRK_NOT_LAUNCHABLE);
-               break;
-       default:
-               break;
-       }
-
-       /* set management plugin automatically */
-       gs_refine_item_management_plugin (plugin, app, item);
-
-       /* set id */
-       if (as_app_get_id (item) != NULL && gs_app_get_id (app) == NULL)
-               gs_app_set_id (app, as_app_get_id (item));
-
-       /* set source */
-       if (gs_app_get_metadata_item (app, "appstream::source-file") == NULL) {
-#if AS_CHECK_VERSION(0,6,9)
-               AsFormat *format = as_app_get_format_by_kind (item, AS_FORMAT_KIND_DESKTOP);
-               if (format != NULL) {
-                       gs_app_set_metadata (app, "appstream::source-file",
-                                            as_format_get_filename (format));
-               }
-#else
-               gs_app_set_metadata (app, "appstream::source-file",
-                                    as_app_get_source_file (item));
-#endif
-       }
-
-       /* scope */
-       if (gs_app_get_scope (app) == AS_APP_SCOPE_UNKNOWN &&
-           as_app_get_scope (item) != AS_APP_SCOPE_UNKNOWN)
-               gs_app_set_scope (app, as_app_get_scope (item));
-
-       /* set branch */
-       if (as_app_get_branch (item) != NULL &&
-           gs_app_get_branch (app) == NULL)
-               gs_app_set_branch (app, as_app_get_branch (item));
-
-       /* set content rating */
-       array = as_app_get_content_ratings (item);
-       for (i = 0; i < array->len; i++) {
-               AsContentRating *cr = g_ptr_array_index (array, i);
-               if (g_strcmp0 (as_content_rating_get_kind (cr), "oars-1.0") == 0) {
-                       gs_app_set_content_rating (app, cr);
-                       break;
-               }
-       }
-
-       /* bundle-kind */
-       if (gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_UNKNOWN)
-               gs_app_set_bundle_kind (app, gs_appstream_get_bundle_kind (item));
-
-       /* set name */
-       tmp = as_app_get_name (item, NULL);
-       if (tmp != NULL)
-               gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, tmp);
-
-       /* set summary */
-       tmp = as_app_get_comment (item, NULL);
-       if (tmp != NULL) {
-               gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, tmp);
-       }
-
-       /* add urls */
-       urls = as_app_get_urls (item);
-       if (g_hash_table_size (urls) > 0 &&
-           gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL) {
-               GList *l;
-               g_autoptr(GList) keys = NULL;
-               keys = g_hash_table_get_keys (urls);
-               for (l = keys; l != NULL; l = l->next) {
-                       gs_app_set_url (app,
-                                       as_url_kind_from_string (l->data),
-                                       g_hash_table_lookup (urls, l->data));
-               }
-       }
-
-       /* set license */
-       if (as_app_get_project_license (item) != NULL && gs_app_get_license (app) == NULL)
-               gs_app_set_license (app,
-                                   GS_APP_QUALITY_HIGHEST,
-                                   as_app_get_project_license (item));
-
-       /* set keywords */
-       if (as_app_get_keywords (item, NULL) != NULL &&
-           gs_app_get_keywords (app) == NULL) {
-               gs_app_set_keywords (app, as_app_get_keywords (item, NULL));
-               gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS);
-       }
-
-       /* set origin */
-       if (as_app_get_origin (item) != NULL &&
-           gs_app_get_origin (app) == NULL ) {
-               tmp = as_app_get_unique_id (item);
-               if (tmp != NULL) {
-                       if (g_str_has_prefix (tmp, "user/flatpak/") ||
-                           g_str_has_prefix (tmp, "system/flatpak/"))
-                               gs_app_set_origin (app, as_app_get_origin (item));
-               }
-       }
-
-       /* set description */
-       tmp = as_app_get_description (item, NULL);
-       if (tmp != NULL) {
-               g_autofree gchar *from_xml = NULL;
-               from_xml = as_markup_convert_simple (tmp, error);
-               if (from_xml == NULL) {
-                       gs_utils_error_convert_appstream (error);
-                       g_prefix_error (error, "trying to parse '%s': ", tmp);
-                       return FALSE;
-               }
-               gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, from_xml);
-       }
-
-       /* set icon */
-       if (as_app_get_icon_default (item) != NULL &&
-           gs_app_get_icons(app)->len == 0)
-               gs_refine_item_icon (plugin, app, item);
-
-       /* set categories */
-       array = as_app_get_categories (item);
-       if (array != NULL && gs_app_get_categories (app)->len == 0) {
-               for (i = 0; i < array->len; i++) {
-                       tmp = g_ptr_array_index (array, i);
-                       gs_app_add_category (app, tmp);
-               }
-       }
-
-       /* set project group */
-       if (as_app_get_project_group (item) != NULL &&
-           gs_app_get_project_group (app) == NULL)
-               gs_app_set_project_group (app, as_app_get_project_group (item));
-
-       /*
-        * Set the core applications for the current desktop that cannot be
-        * removed -- but note: XDG_CURRENT_DESKTOP="GNOME" is different to
-        * XDG_CURRENT_DESKTOP="Ubuntu:GNOME" here.
-        *
-        * To define what is compulsory for the hybrid desktop either:
-        *
-        *  - Add an appstream merge file downstream with the tag
-        *    <compulsory_for_desktop>Ubuntu:GNOME</compulsory_for_desktop>
-        *
-        *  - Get upstream projects to add the <compulsory_for_desktop> tag
-        */
-       array = as_app_get_compulsory_for_desktops (item);
-       current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
-       for (i = 0; i < array->len; i++) {
-               tmp = g_ptr_array_index (array, i);
-               if (g_strcmp0 (current_desktop, tmp) == 0) {
-                       gs_app_add_quirk (app, AS_APP_QUIRK_COMPULSORY);
-                       break;
-               }
-       }
-
-       /* set id kind */
-       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN)
-               gs_app_set_kind (app, as_app_get_kind (item));
-
-       /* copy all the metadata */
-       gs_appstream_copy_metadata (app, item);
-
-       /* set package names */
-       pkgnames = as_app_get_pkgnames (item);
-       if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0)
-               gs_app_set_sources (app, pkgnames);
-
-       /* set addons */
-       if (!gs_appstream_refine_add_addons (plugin, app, item, error))
-               return FALSE;
-
-       /* set screenshots */
-       gs_appstream_refine_add_screenshots (app, item);
-
-       /* set reviews */
-       gs_appstream_refine_add_reviews (app, item);
-
-       /* set provides */
-       gs_appstream_refine_add_provides (app, item);
-
-       /* are the screenshots perfect */
-       if (gs_appstream_are_screenshots_perfect (item))
-               gs_app_add_kudo (app, GS_APP_KUDO_PERFECT_SCREENSHOTS);
-
-       /* was this application released recently */
-       if (gs_appstream_is_recent_release (item))
-               gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);
-
-       /* add kudos */
-       tmp = gs_plugin_get_locale (plugin);
-       if (!_gs_utils_locale_has_translations (tmp) ||
-           as_app_get_language (item, tmp) > 50)
-               gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);
-
-       /* add a kudo to featured and popular apps */
-       if (as_app_has_kudo (item, "GnomeSoftware::popular"))
-               gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
-       if (as_app_has_category (item, "featured"))
-               gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
-
-       /* add new-style kudos */
-       kudos = as_app_get_kudos (item);
-       for (i = 0; i < kudos->len; i++) {
-               tmp = g_ptr_array_index (kudos, i);
-               switch (as_kudo_kind_from_string (tmp)) {
-               case AS_KUDO_KIND_SEARCH_PROVIDER:
-                       gs_app_add_kudo (app, GS_APP_KUDO_SEARCH_PROVIDER);
-                       break;
-               case AS_KUDO_KIND_USER_DOCS:
-                       gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
-                       break;
-               case AS_KUDO_KIND_APP_MENU:
-                       gs_app_add_kudo (app, GS_APP_KUDO_USES_APP_MENU);
-                       break;
-               case AS_KUDO_KIND_MODERN_TOOLKIT:
-                       gs_app_add_kudo (app, GS_APP_KUDO_MODERN_TOOLKIT);
-                       break;
-               case AS_KUDO_KIND_NOTIFICATIONS:
-                       gs_app_add_kudo (app, GS_APP_KUDO_USES_NOTIFICATIONS);
-                       break;
-               case AS_KUDO_KIND_HIGH_CONTRAST:
-                       gs_app_add_kudo (app, GS_APP_KUDO_HIGH_CONTRAST);
-                       break;
-               case AS_KUDO_KIND_HI_DPI_ICON:
-                       gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON);
-                       break;
-               default:
-                       break;
-               }
-       }
-
-       /* we saved the origin hostname in the metadata */
-       tmp = as_app_get_metadata_item (item, "GnomeSoftware::OriginHostnameUrl");
-       if (tmp != NULL && gs_app_get_origin_hostname (app) == NULL)
-               gs_app_set_origin_hostname (app, tmp);
-
-       /* we have an origin in the XML */
-       if (gs_app_get_origin (app) == NULL &&
-           gs_appstream_origin_valid (as_app_get_origin (item)))
-               gs_app_set_origin (app, as_app_get_origin (item));
-
-       /* is there any update information */
-       if (!gs_appstream_refine_app_updates (plugin, app, item, error))
-               return FALSE;
-
-       return TRUE;
-}
-
-static gboolean
-gs_appstream_store_search_item (GsPlugin *plugin,
-                               AsApp *item,
-                               gchar **values,
-                               GsAppList *list,
-                               GCancellable *cancellable,
-                               GError **error)
-{
-       AsApp *item_tmp;
-       GPtrArray *addons;
-       guint i;
-       guint match_value;
-       g_autoptr(GsApp) app = NULL;
-
-       /* match against the app or any of the addons */
-       match_value = as_app_search_matches_all (item, values);
-       addons = as_app_get_addons (item);
-       for (i = 0; i < addons->len; i++) {
-               item_tmp = g_ptr_array_index (addons, i);
-               match_value |= as_app_search_matches_all (item_tmp, values);
-       }
-
-       /* no match */
-       if (match_value == 0)
-               return TRUE;
-
-       /* create app */
-       app = gs_appstream_create_app (plugin, item, error);
-       if (app == NULL)
-               return FALSE;
-       gs_app_set_match_value (app, match_value);
-       gs_app_list_add (list, app);
-       return TRUE;
-}
-
-gboolean
-gs_appstream_store_search (GsPlugin *plugin,
-                          AsStore *store,
-                          gchar **values,
-                          GsAppList *list,
-                          GCancellable *cancellable,
-                          GError **error)
-{
-       AsApp *item;
-       GPtrArray *array;
-       gboolean ret = TRUE;
-       guint i;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* search categories for the search term */
-       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
-                                         "appstream::search");
-       g_assert (ptask != NULL);
-       array = as_store_get_apps (store);
-       for (i = 0; i < array->len; i++) {
-               if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
-                       gs_utils_error_convert_gio (error);
-                       return FALSE;
-               }
-
-               item = g_ptr_array_index (array, i);
-               ret = gs_appstream_store_search_item (plugin, item,
-                                                     values, list,
-                                                     cancellable, error);
-               if (!ret)
-                       return FALSE;
-       }
-       return TRUE;
-}
-
-static gboolean
-_as_app_matches_desktop_group_set (AsApp *app, gchar **desktop_groups)
-{
-       guint i;
-       for (i = 0; desktop_groups[i] != NULL; i++) {
-               if (!as_app_has_category (app, desktop_groups[i]))
-                       return FALSE;
-       }
-       return TRUE;
-}
-
-static gboolean
-_as_app_matches_desktop_group (AsApp *app, const gchar *desktop_group)
-{
-       g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1);
-       return _as_app_matches_desktop_group_set (app, split);
-}
-
-static void
-gs_appstream_store_add_categories_for_app (GsCategory *parent, AsApp *app)
-{
-       GPtrArray *children;
-       GPtrArray *desktop_groups;
-       GsCategory *category;
-       guint i, j;
-
-       /* find all the sub-categories */
-       children = gs_category_get_children (parent);
-       for (j = 0; j < children->len; j++) {
-               gboolean matched = FALSE;
-               category = GS_CATEGORY (g_ptr_array_index (children, j));
-
-               /* do any desktop_groups match this application */
-               desktop_groups = gs_category_get_desktop_groups (category);
-               for (i = 0; i < desktop_groups->len; i++) {
-                       const gchar *desktop_group = g_ptr_array_index (desktop_groups, i);
-                       if (_as_app_matches_desktop_group (app, desktop_group)) {
-                               matched = TRUE;
-                               break;
-                       }
-               }
-               if (matched) {
-                       gs_category_increment_size (category);
-                       gs_category_increment_size (parent);
-               }
-       }
-}
-
-gboolean
-gs_appstream_store_add_category_apps (GsPlugin *plugin,
-                                     AsStore *store,
-                                     GsCategory *category,
-                                     GsAppList *list,
-                                     GCancellable *cancellable,
-                                     GError **error)
-{
-       GPtrArray *array;
-       GPtrArray *desktop_groups;
-       guint i;
-       guint j;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* just look at each app in turn */
-       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
-                                         "appstream::add-category-apps");
-       g_assert (ptask != NULL);
-       array = as_store_get_apps (store);
-       desktop_groups = gs_category_get_desktop_groups (category);
-       if (desktop_groups->len == 0) {
-               g_warning ("no desktop_groups for %s", gs_category_get_id (category));
-               return TRUE;
-       }
-       for (j = 0; j < desktop_groups->len; j++) {
-               const gchar *desktop_group = g_ptr_array_index (desktop_groups, j);
-               g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1);
-
-               /* match the app */
-               for (i = 0; i < array->len; i++) {
-                       AsApp *item;
-                       g_autoptr(GsApp) app = NULL;
-
-                       /* no ID is invalid */
-                       item = g_ptr_array_index (array, i);
-                       if (as_app_get_id (item) == NULL)
-                               continue;
-
-                       /* match all the desktop groups */
-                       if (!_as_app_matches_desktop_group_set (item, split))
-                               continue;
-
-                       /* add all the data we can */
-                       app = gs_appstream_create_app (plugin, item, error);
-                       if (app == NULL)
-                               return FALSE;
-                       gs_app_list_add (list, app);
-               }
-       }
-       return TRUE;
-}
-
-gboolean
-gs_appstream_store_add_categories (GsPlugin *plugin,
-                                  AsStore *store,
-                                  GPtrArray *list,
-                                  GCancellable *cancellable,
-                                  GError **error)
-{
-       AsApp *app;
-       GPtrArray *array;
-       guint i;
-       guint j;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* find out how many packages are in each category */
-       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
-                                         "appstream::add-categories");
-       g_assert (ptask != NULL);
-       array = as_store_get_apps (store);
-       for (i = 0; i < array->len; i++) {
-               app = g_ptr_array_index (array, i);
-               if (as_app_get_id (app) == NULL)
-                       continue;
-               if (as_app_get_priority (app) < 0)
-                       continue;
-               for (j = 0; j < list->len; j++) {
-                       GsCategory *parent = GS_CATEGORY (g_ptr_array_index (list, j));
-                       gs_appstream_store_add_categories_for_app (parent, app);
-               }
-       }
-       return TRUE;
-}
-
-gboolean
-gs_appstream_add_popular (GsPlugin *plugin,
-                         AsStore *store,
-                         GsAppList *list,
-                         GCancellable *cancellable,
-                         GError **error)
-{
-       AsApp *item;
-       GPtrArray *array;
-       guint i;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* find out how many packages are in each category */
-       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
-                                         "appstream::add-popular");
-       g_assert (ptask != NULL);
-       array = as_store_get_apps (store);
-       for (i = 0; i < array->len; i++) {
-               g_autoptr(GsApp) app = NULL;
-               item = g_ptr_array_index (array, i);
-               if (as_app_get_id (item) == NULL)
-                       continue;
-               if (!as_app_has_kudo (item, "GnomeSoftware::popular"))
-                       continue;
-               app = gs_app_new (as_app_get_id (item));
-               gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
-               gs_app_list_add (list, app);
-       }
-       return TRUE;
-}
-
-gboolean
-gs_appstream_add_featured (GsPlugin *plugin,
-                          AsStore *store,
-                          GsAppList *list,
-                          GCancellable *cancellable,
-                          GError **error)
-{
-       AsApp *item;
-       GPtrArray *array;
-       guint i;
-       g_autoptr(AsProfileTask) ptask = NULL;
-
-       /* find out how many packages are in each category */
-       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
-                                         "appstream::add-featured");
-       g_assert (ptask != NULL);
-       array = as_store_get_apps (store);
-       for (i = 0; i < array->len; i++) {
-               g_autoptr(GsApp) app = NULL;
-               item = g_ptr_array_index (array, i);
-               if (as_app_get_id (item) == NULL)
-                       continue;
-               if (as_app_get_metadata_item (item, "GnomeSoftware::FeatureTile-css") == NULL)
-                       continue;
-               app = gs_app_new (as_app_get_id (item));
-               gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
-               gs_app_list_add (list, app);
-       }
-       return TRUE;
-}
-
-void
-gs_appstream_add_extra_info (GsPlugin *plugin, AsApp *app)
-{
-       const gchar *tmp;
-
-       /* add more search terms */
-       switch (as_app_get_kind (app)) {
-       case AS_APP_KIND_WEB_APP:
-       case AS_APP_KIND_INPUT_METHOD:
-               tmp = as_app_kind_to_string (as_app_get_kind (app));
-               g_debug ("adding keyword '%s' to %s",
-                        tmp, as_app_get_unique_id (app));
-               as_app_add_keyword (app, NULL, tmp);
-               break;
-       default:
-               break;
-       }
-
-       /* fix up these */
-       if (as_app_get_kind (app) == AS_APP_KIND_LOCALIZATION &&
-           g_str_has_prefix (as_app_get_id (app),
-                             "org.fedoraproject.LangPack-")) {
-               g_autoptr(AsIcon) icon = NULL;
-
-               /* add icon */
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "accessories-dictionary-symbolic");
-               as_app_add_icon (app, icon);
-
-               /* add categories */
-               as_app_add_category (app, "Addons");
-               as_app_add_category (app, "Localization");
-       }
-
-       /* fix up drivers with our nonstandard groups */
-       if (as_app_get_kind (app) == AS_APP_KIND_DRIVER) {
-               as_app_add_category (app, "Addons");
-               as_app_add_category (app, "Drivers");
-       }
-}
diff --git a/plugins/gs-appstream.c b/plugins/gs-appstream.c
new file mode 120000
index 0000000..720e181
--- /dev/null
+++ b/plugins/gs-appstream.c
@@ -0,0 +1 @@
+core/gs-appstream.c
\ No newline at end of file
diff --git a/plugins/gs-appstream.h b/plugins/gs-appstream.h
deleted file mode 100644
index c4c6d98..0000000
--- a/plugins/gs-appstream.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
- *
- * Copyright (C) 2015-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 __APPSTREAM_COMMON_H
-#define __APPSTREAM_COMMON_H
-
-#include <gnome-software.h>
-
-G_BEGIN_DECLS
-
-GsApp          *gs_appstream_create_app                (GsPlugin       *plugin,
-                                                        AsApp          *item,
-                                                        GError         **error);
-gboolean        gs_appstream_refine_app                (GsPlugin       *plugin,
-                                                        GsApp          *app,
-                                                        AsApp          *item,
-                                                        GError         **error);
-GsApp          *gs_appstream_create_runtime            (GsPlugin       *plugin,
-                                                        GsApp          *parent,
-                                                        const gchar    *runtime);
-gboolean        gs_appstream_store_search              (GsPlugin       *plugin,
-                                                        AsStore        *store,
-                                                        gchar          **values,
-                                                        GsAppList      *list,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_appstream_store_add_categories      (GsPlugin       *plugin,
-                                                        AsStore        *store,
-                                                        GPtrArray      *list,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_appstream_store_add_category_apps   (GsPlugin       *plugin,
-                                                        AsStore        *store,
-                                                        GsCategory     *category,
-                                                        GsAppList      *list,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_appstream_add_popular               (GsPlugin       *plugin,
-                                                        AsStore        *store,
-                                                        GsAppList      *list,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_appstream_add_featured              (GsPlugin       *plugin,
-                                                        AsStore        *store,
-                                                        GsAppList      *list,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-void            gs_appstream_add_extra_info            (GsPlugin       *plugin,
-                                                        AsApp          *app);
-
-G_END_DECLS
-
-#endif /* __APPSTREAM_COMMON_H */
diff --git a/plugins/gs-appstream.h b/plugins/gs-appstream.h
new file mode 120000
index 0000000..f0785a6
--- /dev/null
+++ b/plugins/gs-appstream.h
@@ -0,0 +1 @@
+core/gs-appstream.h
\ No newline at end of file
diff --git a/plugins/gs-self-test.c b/plugins/gs-self-test.c
index e4ade25..9263bb0 100644
--- a/plugins/gs-self-test.c
+++ b/plugins/gs-self-test.c
@@ -25,371 +25,6 @@
 
 #include "gs-test.h"
 
-static guint _status_changed_cnt = 0;
-
-static void
-gs_plugin_loader_status_changed_cb (GsPluginLoader *plugin_loader,
-                                   GsApp *app,
-                                   GsPluginStatus status,
-                                   gpointer user_data)
-{
-       _status_changed_cnt++;
-}
-
-static void
-gs_plugin_loader_install_func (GsPluginLoader *plugin_loader)
-{
-       gboolean ret;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(GError) error = NULL;
-
-       /* install */
-       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_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
-
-       /* remove */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
-}
-
-static void
-gs_plugin_loader_error_func (GsPluginLoader *plugin_loader)
-{
-       GsPluginEvent *event;
-       const GError *app_error;
-       gboolean ret;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GPtrArray) events = NULL;
-       g_autoptr(GsApp) app = NULL;
-
-       /* drop all caches */
-       gs_plugin_loader_setup_again (plugin_loader);
-
-       /* 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_ACTION_UPDATE,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-
-       /* 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
-gs_plugin_loader_refine_func (GsPluginLoader *plugin_loader)
-{
-       gboolean ret;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(GError) error = NULL;
-
-       /* get the extra bits */
-       app = gs_app_new ("chiron.desktop");
-       gs_app_set_management_plugin (app, "dummy");
-       ret = gs_plugin_loader_app_refine (plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-
-       g_assert_cmpstr (gs_app_get_license (app), ==, "GPL-2.0+");
-       g_assert_cmpstr (gs_app_get_description (app), !=, NULL);
-       g_assert_cmpstr (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE), ==, "http://www.test.org/";);
-}
-
-static void
-gs_plugin_loader_key_colors_func (GsPluginLoader *plugin_loader)
-{
-       GPtrArray *array;
-       gboolean ret;
-       guint i;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(GError) error = NULL;
-
-       /* get the extra bits */
-       app = gs_app_new ("zeus.desktop");
-       ret = gs_plugin_loader_app_refine (plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-       array = gs_app_get_key_colors (app);
-       g_assert_cmpint (array->len, >=, 3);
-
-       /* check values are in range */
-       for (i = 0; i < array->len; i++) {
-               GdkRGBA *kc = g_ptr_array_index (array, i);
-               g_assert_cmpfloat (kc->red, >=, 0.f);
-               g_assert_cmpfloat (kc->red, <=, 1.f);
-               g_assert_cmpfloat (kc->green, >=, 0.f);
-               g_assert_cmpfloat (kc->green, <=, 1.f);
-               g_assert_cmpfloat (kc->blue, >=, 0.f);
-               g_assert_cmpfloat (kc->blue, <=, 1.f);
-               g_assert_cmpfloat (kc->alpha, >=, 0.f);
-               g_assert_cmpfloat (kc->alpha, <=, 1.f);
-       }
-}
-
-static void
-gs_plugin_loader_updates_func (GsPluginLoader *plugin_loader)
-{
-       GsApp *app;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsAppList) list = NULL;
-
-       /* get the updates list */
-       list = gs_plugin_loader_get_updates (plugin_loader,
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
-                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                            NULL,
-                                            &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list != NULL);
-
-       /* make sure there are two entries */
-       g_assert_cmpint (gs_app_list_length (list), ==, 3);
-       app = gs_app_list_index (list, 0);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "chiron.desktop");
-       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
-       g_assert_cmpstr (gs_app_get_update_details (app), ==, "Do not crash when using libvirt.");
-       g_assert_cmpint (gs_app_get_update_urgency (app), ==, AS_URGENCY_KIND_HIGH);
-
-       /* get the virtual non-apps OS update */
-       app = gs_app_list_index (list, 1);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "org.gnome.Software.OsUpdate");
-       g_assert_cmpstr (gs_app_get_name (app), ==, "OS Updates");
-       g_assert_cmpstr (gs_app_get_summary (app), ==, "Includes performance, stability and security 
improvements.");
-       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_OS_UPDATE);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
-       g_assert_cmpint (gs_app_get_related(app)->len, ==, 2);
-
-       /* get the virtual non-apps OS update */
-       app = gs_app_list_index (list, 2);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "proxy.desktop");
-       g_assert (gs_app_has_quirk (app, AS_APP_QUIRK_IS_PROXY));
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
-       g_assert_cmpint (gs_app_get_related(app)->len, ==, 2);
-}
-
-static void
-gs_plugin_loader_distro_upgrades_func (GsPluginLoader *plugin_loader)
-{
-       GsApp *app;
-       gboolean ret;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsAppList) list = NULL;
-
-       /* get the updates list */
-       list = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                    GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                    GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                    NULL,
-                                                    &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list != NULL);
-
-       /* make sure there is one entry */
-       g_assert_cmpint (gs_app_list_length (list), ==, 1);
-       app = gs_app_list_index (list, 0);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "org.fedoraproject.release-rawhide.upgrade");
-       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_OS_UPGRADE);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
-
-       /* this should be set with a higher priority by AppStream */
-       g_assert_cmpstr (gs_app_get_summary (app), ==, "Release specific tagline");
-
-       /* download the update */
-       ret = gs_plugin_loader_app_action (plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
-
-       /* trigger the update */
-       ret = gs_plugin_loader_app_action (plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
-}
-
-static void
-gs_plugin_loader_installed_func (GsPluginLoader *plugin_loader)
-{
-       GsApp *app;
-       GsApp *addon;
-       GPtrArray *addons;
-       guint64 kudos;
-       g_autofree gchar *menu_path = NULL;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsAppList) list = NULL;
-
-       /* get installed packages */
-       list = gs_plugin_loader_get_installed (plugin_loader,
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
-                                              GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                              NULL,
-                                              &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list != NULL);
-
-       /* make sure there is one entry */
-       g_assert_cmpint (gs_app_list_length (list), ==, 1);
-       app = gs_app_list_index (list, 0);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "zeus.desktop");
-       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
-       g_assert_cmpstr (gs_app_get_name (app), ==, "Zeus");
-       g_assert_cmpstr (gs_app_get_source_default (app), ==, "zeus");
-       g_assert (gs_app_get_pixbuf (app) != NULL);
-
-       /* check various bitfields */
-       g_assert (gs_app_has_quirk (app, AS_APP_QUIRK_PROVENANCE));
-       g_assert_cmpstr (gs_app_get_license (app), ==, "GPL-2.0+");
-       g_assert (gs_app_get_license_is_free (app));
-
-       /* check kudos */
-       kudos = gs_app_get_kudos (app);
-       g_assert (kudos & GS_APP_KUDO_MY_LANGUAGE);
-
-       /* check categories */
-       g_assert (gs_app_has_category (app, "Player"));
-       g_assert (gs_app_has_category (app, "AudioVideo"));
-       g_assert (!gs_app_has_category (app, "ImageProcessing"));
-       g_assert (gs_app_get_menu_path (app) != NULL);
-       menu_path = g_strjoinv ("->", gs_app_get_menu_path (app));
-       g_assert_cmpstr (menu_path, ==, "Audio & Video->Music Players");
-
-       /* check addon */
-       addons = gs_app_get_addons (app);
-       g_assert_cmpint (addons->len, ==, 1);
-       addon = g_ptr_array_index (addons, 0);
-       g_assert_cmpstr (gs_app_get_id (addon), ==, "zeus-spell.addon");
-       g_assert_cmpint (gs_app_get_kind (addon), ==, AS_APP_KIND_ADDON);
-       g_assert_cmpint (gs_app_get_state (addon), ==, AS_APP_STATE_AVAILABLE);
-       g_assert_cmpstr (gs_app_get_name (addon), ==, "Spell Check");
-       g_assert_cmpstr (gs_app_get_source_default (addon), ==, "zeus-spell");
-       g_assert_cmpstr (gs_app_get_license (addon), ==,
-                        "LicenseRef-free=https://www.debian.org/";);
-       g_assert (gs_app_get_pixbuf (addon) == NULL);
-}
-
-static void
-gs_plugin_loader_search_func (GsPluginLoader *plugin_loader)
-{
-       GsApp *app;
-       g_autofree gchar *menu_path = NULL;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsAppList) list = NULL;
-
-       /* get search result based on addon keyword */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "spell",
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list != NULL);
-
-       /* make sure there is one entry, the parent app */
-       g_assert_cmpint (gs_app_list_length (list), ==, 1);
-       app = gs_app_list_index (list, 0);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "zeus.desktop");
-       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
-}
-
-static void
-gs_plugin_loader_url_to_app_func (GsPluginLoader *plugin_loader)
-{
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsApp) app = NULL;
-
-       app = gs_plugin_loader_url_to_app (plugin_loader,
-                                          "dummy://chiron.desktop",
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (app != NULL);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "chiron.desktop");
-       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
-}
-
 static void
 gs_plugin_loader_modalias_func (GsPluginLoader *plugin_loader)
 {
@@ -1446,7 +1081,7 @@ gs_plugin_loader_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_assert (list_updates != NULL);
 
        /* make sure there are two entries */
-       g_assert_cmpint (gs_app_list_length (list_updates), >, 3);
+       g_assert_cmpint (gs_app_list_length (list_updates), ==, 1);
        for (guint i = 0; i < gs_app_list_length (list_updates); i++) {
                app_tmp = gs_app_list_index (list_updates, i);
                g_debug ("got update %s", gs_app_get_unique_id (app_tmp));
@@ -1537,132 +1172,6 @@ gs_plugin_loader_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (app_source), ==, AS_APP_STATE_AVAILABLE);
 }
 
-static void
-gs_plugin_loader_plugin_cache_func (GsPluginLoader *plugin_loader)
-{
-       GsApp *app1;
-       GsApp *app2;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsAppList) list1 = NULL;
-       g_autoptr(GsAppList) list2 = NULL;
-
-       /* ensure we get the same results back from calling the methods twice */
-       list1 = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                     NULL,
-                                                     &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list1 != NULL);
-       g_assert_cmpint (gs_app_list_length (list1), ==, 1);
-       app1 = gs_app_list_index (list1, 0);
-
-       list2 = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                     NULL,
-                                                     &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list2 != NULL);
-       g_assert_cmpint (gs_app_list_length (list2), ==, 1);
-       app2 = gs_app_list_index (list2, 0);
-
-       /* make sure there is one GObject */
-       g_assert_cmpstr (gs_app_get_id (app1), ==, gs_app_get_id (app2));
-       g_assert (app1 == app2);
-}
-
-static void
-gs_plugin_loader_authentication_func (GsPluginLoader *plugin_loader)
-{
-       GsAuth *auth;
-       gboolean ret;
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(AsReview) review = NULL;
-       g_autoptr(AsReview) review2 = NULL;
-
-       /* check initial state */
-       auth = gs_plugin_loader_get_auth_by_id (plugin_loader, "dummy");
-       g_assert (GS_IS_AUTH (auth));
-       g_assert_cmpint (gs_auth_get_flags (auth), ==, 0);
-
-       /* do an action that returns a URL */
-       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
-                                           GS_PLUGIN_ACTION_AUTH_REGISTER,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL, &error);
-       gs_test_flush_main_context ();
-       g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID);
-       g_assert (!ret);
-       g_clear_error (&error);
-       g_assert (!gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
-
-       /* do an action that requires a login */
-       app = gs_app_new (NULL);
-       review = as_review_new ();
-       ret = gs_plugin_loader_review_action (plugin_loader, app, review,
-                                             GS_PLUGIN_ACTION_REVIEW_REMOVE,
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             NULL, &error);
-       gs_test_flush_main_context ();
-       g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_REQUIRED);
-       g_assert (!ret);
-       g_clear_error (&error);
-
-       /* pretend to auth with no credentials */
-       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
-                                           GS_PLUGIN_ACTION_AUTH_LOGIN,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL, &error);
-       gs_test_flush_main_context ();
-       g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID);
-       g_assert (!ret);
-       g_clear_error (&error);
-       g_assert (!gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
-
-       /* auth again with correct credentials */
-       gs_auth_set_username (auth, "dummy");
-       gs_auth_set_password (auth, "dummy");
-       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
-                                           GS_PLUGIN_ACTION_AUTH_LOGIN,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL, &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-       g_assert (gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
-
-       /* do the action that requires a login */
-       review2 = as_review_new ();
-       ret = gs_plugin_loader_review_action (plugin_loader, app, review2,
-                                             GS_PLUGIN_ACTION_REVIEW_REMOVE,
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             NULL, &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (ret);
-}
-
-static void
-gs_plugin_loader_wildcard_func (GsPluginLoader *plugin_loader)
-{
-       g_autoptr(GError) error = NULL;
-       g_autoptr(GsAppList) list = NULL;
-
-       list = gs_plugin_loader_get_popular (plugin_loader,
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                            NULL,
-                                            &error);
-       gs_test_flush_main_context ();
-       g_assert_no_error (error);
-       g_assert (list != NULL);
-       g_assert_cmpint (gs_app_list_length (list), ==, 1);
-}
-
 int
 main (int argc, char **argv)
 {
@@ -1780,9 +1289,8 @@ main (int argc, char **argv)
 
        /* we can only load this once per process */
        plugin_loader = gs_plugin_loader_new ();
-       g_signal_connect (plugin_loader, "status-changed",
-                         G_CALLBACK (gs_plugin_loader_status_changed_cb), NULL);
        gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR);
+       gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR_CORE);
        ret = gs_plugin_loader_setup (plugin_loader,
                                      (gchar**) whitelist,
                                      NULL,
@@ -1794,18 +1302,8 @@ main (int argc, char **argv)
        g_assert (!gs_plugin_loader_get_enabled (plugin_loader, "notgoingtoexist"));
        g_assert (!gs_plugin_loader_get_enabled (plugin_loader, "packagekit"));
        g_assert (gs_plugin_loader_get_enabled (plugin_loader, "appstream"));
-       g_assert (gs_plugin_loader_get_enabled (plugin_loader, "dummy"));
 
        /* plugin tests go here */
-       g_test_add_data_func ("/gnome-software/plugin-loader{wildcard}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_wildcard_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{authentication}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_authentication_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{plugin-cache}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_plugin_cache_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{flatpak-app-with-runtime}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_flatpak_app_with_runtime_func);
@@ -1821,39 +1319,12 @@ main (int argc, char **argv)
        g_test_add_data_func ("/gnome-software/plugin-loader{flatpak-app-update-runtime}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_flatpak_app_update_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{key-colors}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_key_colors_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{webapps}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_webapps_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{modalias}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_modalias_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{search}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_search_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{url-to-app}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_url_to_app_func);
-       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);
-       g_test_add_data_func ("/gnome-software/plugin-loader{refine}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_refine_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{updates}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_updates_func);
-       g_test_add_data_func ("/gnome-software/plugin-loader{distro-upgrades}",
-                             plugin_loader,
-                             (GTestDataFunc) gs_plugin_loader_distro_upgrades_func);
 
        /* done last as it would otherwise try to do downloading in other
         * gs_plugin_file_to_app()-using tests */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f4ef7f7..b80d361 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -65,10 +65,10 @@ src/gs-updates-page.ui
 src/gs-upgrade-banner.c
 src/gs-upgrade-banner.ui
 src/org.gnome.Software.desktop.in
-plugins/gs-desktop-common.c
+plugins/core/gs-desktop-common.c
 plugins/external-appstream/gs-install-appstream.c
 plugins/gs-plugin-fedora-distro-upgrades.c
-plugins/gs-plugin-generic-updates.c
+plugins/core/gs-plugin-generic-updates.c
 plugins/org.gnome.Software.Plugin.Epiphany.metainfo.xml.in
 plugins/org.gnome.Software.Plugin.Flatpak.metainfo.xml.in
 plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in


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