[gnome-software/wip/hughsie/libxmlb: 4/10] Use libxmlb to parse AppStream XML



commit d36482522990faef4d212bd716826d7f830347de
Author: Richard Hughes <richard hughsie com>
Date:   Mon Sep 24 13:35:31 2018 +0100

    Use libxmlb to parse AppStream XML
    
    The libxmlb library is much faster to load and query as it does not require
    the client to parse all the XML metadata at startup. It's a zero-copy mmap
    design that is more modern and less clunky. In case of memory pressure on low
    RAM machines, the compressed XML is evicted from memory and automatically
    reloaded when required.
    
    For a single operation, startup time has decreased by 40%, and RSS usage is
    down by a massive 78%. The GUI process has similar wins, with a reduction in
    startup time by 13% and RSS usage is down by 45%. This now means the idle
    process uses less than 50Mb with all plugins enabled.

 .gitlab-ci.yml                                     |    1 +
 meson.build                                        |    1 +
 plugins/core/gs-appstream.c                        | 1734 ++++++++++++--------
 plugins/core/gs-appstream.h                        |   38 +-
 plugins/core/gs-plugin-appstream.c                 |  997 ++++++-----
 plugins/core/gs-self-test.c                        |    9 +-
 plugins/core/meson.build                           |    6 +-
 plugins/dummy/gs-plugin-dummy.c                    |    1 +
 plugins/dummy/gs-self-test.c                       |   11 +-
 plugins/epiphany/gs-self-test.c                    |    2 +
 plugins/external-appstream/gs-install-appstream.c  |   48 +-
 plugins/external-appstream/meson.build             |    5 +-
 plugins/flatpak/gs-flatpak.c                       |  715 ++++----
 plugins/flatpak/gs-self-test.c                     |   56 +-
 plugins/flatpak/meson.build                        |    6 +-
 .../share/appdata/org.test.Chiron.appdata.xml      |    2 +-
 .../share/appdata/org.test.Chiron.appdata.xml      |    2 +-
 plugins/modalias/gs-self-test.c                    |    3 +
 .../shell-extensions/gs-plugin-shell-extensions.c  |  164 +-
 plugins/shell-extensions/gs-self-test.c            |   17 +-
 plugins/shell-extensions/meson.build               |    6 +-
 21 files changed, 2262 insertions(+), 1562 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b1bdc853..d3d95554 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,6 +7,7 @@ before_script:
   # Update and use base build deps
   - dnf update -y && dnf -y install dnf-plugins-core @buildsys-build
   - dnf install -y 
https://people.freedesktop.org/~hughsient/temp/libappstream-glib-0.7.14-0.641.20181015git.fc29.x86_64.rpm 
https://people.freedesktop.org/~hughsient/temp/libappstream-glib-devel-0.7.14-0.641.20181015git.fc29.x86_64.rpm
+  - dnf install -y 
https://people.freedesktop.org/~hughsient/temp/libxmlb-0.1.2-0.49.20181016git.fc29.x86_64.rpm 
https://people.freedesktop.org/~hughsient/temp/libxmlb-devel-0.1.2-0.49.20181016git.fc29.x86_64.rpm
   - dnf --enablerepo updates-testing -y builddep gnome-software
   # Some deps may not be sync'd
   - dnf -y install rpm-ostree-devel
diff --git a/meson.build b/meson.build
index 9d81e502..567d4a70 100644
--- a/meson.build
+++ b/meson.build
@@ -98,6 +98,7 @@ conf.set('HAVE_LINUX_UNISTD_H', cc.has_header('linux/unistd.h'))
 
 appstream_glib = dependency('appstream-glib', version : '>= 0.7.14')
 gdk_pixbuf = dependency('gdk-pixbuf-2.0', version : '>= 2.32.0')
+libxmlb = dependency('xmlb', version : '>= 0.1.2')
 gio_unix = dependency('gio-unix-2.0', version : '>= 2.56.0')
 gmodule = dependency('gmodule-2.0')
 gtk = dependency('gtk+-3.0', version : '>= 3.22.4')
diff --git a/plugins/core/gs-appstream.c b/plugins/core/gs-appstream.c
index a9c7f945..bbc47e9f 100644
--- a/plugins/core/gs-appstream.c
+++ b/plugins/core/gs-appstream.c
@@ -28,95 +28,207 @@
 #define        GS_APPSTREAM_MAX_SCREENSHOTS    5
 
 GsApp *
-gs_appstream_create_app (GsPlugin *plugin, AsApp *item, GError **error)
+gs_appstream_create_app (GsPlugin *plugin, XbSilo *silo, XbNode *component, GError **error)
 {
-       const gchar *unique_id = as_app_get_unique_id (item);
-       GsApp *app = gs_plugin_cache_lookup (plugin, unique_id);
-
-       /* if the app we found has the "is-wildcard" quirk and our item does
-        * not, then we create a new one because ours will be "complete", and
-        * using the mentioned quirk will lead to a different behavior (e.g. it'll
-        * be refined using refine_wildcard, it won't allow a management plugin to
-        * be set, etc.)  */
-       if (app != NULL && gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD) &&
-           !as_app_has_quirk (item, GS_APP_QUIRK_IS_WILDCARD)) {
-               g_debug ("Looking for %s, got %s but has 'is-wildcard' quirk "
-                        "so we create a new one instead.",
-                        unique_id, gs_app_get_unique_id (app));
-               g_clear_object (&app);
-       }
-
-       if (app == NULL) {
-               app = gs_app_new (NULL);
-               gs_app_set_from_unique_id (app, unique_id);
-               /* clear origin set from unique_id: appstream origin goes to
-                * GsApp's origin-appstream field instead */
-               gs_app_set_origin (app, NULL);
-               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;
+       GsApp *app;
+       g_autoptr(GsApp) app_new = gs_app_new (NULL);
+
+       /* refine enough to get the unique ID */
+       if (!gs_appstream_refine_app (plugin, app_new, silo, component,
+                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                     error))
+               return NULL;
+
+       /* look for existing object */
+       app = gs_plugin_cache_lookup (plugin, gs_app_get_unique_id (app_new));
+       if (app != NULL)
+               return app;
+
+       /* use the temp object we just created */
+       gs_app_set_metadata (app_new, "GnomeSoftware::Creator",
+                            gs_plugin_get_name (plugin));
+       gs_plugin_cache_add (plugin, NULL, app_new);
+       return g_steal_pointer (&app_new);
+}
+
+static gchar *
+gs_appstream_format_description (XbNode *root, GError **error)
+{
+       g_autoptr(GString) str = g_string_new (NULL);
+       g_autoptr(XbNode) n = xb_node_get_child (root);
+
+       while (n != NULL) {
+               g_autoptr(XbNode) n2 = NULL;
+
+               /* support <p>, <ul>, <ol> and <li>, ignore all else */
+               if (g_strcmp0 (xb_node_get_element (n), "p") == 0) {
+                       g_string_append_printf (str, "%s\n\n", xb_node_get_text (n));
+               } else if (g_strcmp0 (xb_node_get_element (n), "ul") == 0) {
+                       g_autoptr(GPtrArray) children = xb_node_get_children (n);
+                       for (guint i = 0; i < children->len; i++) {
+                               XbNode *nc = g_ptr_array_index (children, i);
+                               if (g_strcmp0 (xb_node_get_element (nc), "li") == 0) {
+                                       g_string_append_printf (str, " • %s\n",
+                                                               xb_node_get_text (nc));
+                               }
+                       }
+                       g_string_append (str, "\n");
+               } else if (g_strcmp0 (xb_node_get_element (n), "ol") == 0) {
+                       g_autoptr(GPtrArray) children = xb_node_get_children (n);
+                       for (guint i = 0; i < children->len; i++) {
+                               XbNode *nc = g_ptr_array_index (children, i);
+                               if (g_strcmp0 (xb_node_get_element (nc), "li") == 0) {
+                                       g_string_append_printf (str, " %u. %s\n",
+                                                               i + 1,
+                                                               xb_node_get_text (nc));
+                               }
+                       }
+                       g_string_append (str, "\n");
                }
-               gs_plugin_cache_add (plugin, unique_id, app);
+
+               n2 = xb_node_get_next (n);
+               g_set_object (&n, n2);
        }
-       return app;
+
+       /* remove extra newlines */
+       while (str->len > 0 && str->str[str->len - 1] == '\n')
+               g_string_truncate (str, str->len - 1);
+
+       /* success */
+       return g_string_free (g_steal_pointer (&str), FALSE);
+}
+
+static gchar *
+gs_appstream_build_icon_prefix (XbNode *component)
+{
+       const gchar *origin;
+       const gchar *tmp;
+       gint npath;
+       g_auto(GStrv) path = NULL;
+       g_autoptr(XbNode) components = NULL;
+
+       /* no parent, e.g. AppData */
+       components = xb_node_get_parent (component);
+       if (components == NULL)
+               return NULL;
+
+       /* set explicitly */
+       tmp = xb_node_query_text (components, "info/icon-prefix", NULL);
+       if (tmp != NULL)
+               return g_strdup (tmp);
+
+       /* fall back to origin */
+       origin = xb_node_get_attr (components, "origin");
+       if (origin == NULL)
+               return NULL;
+
+       /* no metadata */
+       tmp = xb_node_query_text (components, "info/filename", NULL);
+       if (tmp == NULL)
+               return NULL;
+
+       /* check format */
+       path = g_strsplit (tmp, "/", -1);
+       npath = g_strv_length (path);
+       if (npath < 3 || g_strcmp0 (path[npath-2], "xmls") != 0)
+               return NULL;
+
+       /* fix the new path */
+       g_free (path[npath-1]);
+       g_free (path[npath-2]);
+       path[npath-1] = g_strdup (origin);
+       path[npath-2] = g_strdup ("icons");
+       return g_strjoinv ("/", path);
 }
 
 static AsIcon *
-gs_appstream_get_icon_by_kind (AsApp *app, AsIconKind icon_kind)
+gs_appstream_new_icon (XbNode *component, XbNode *n, AsIconKind icon_kind, guint sz)
 {
-       GPtrArray *icons = as_app_get_icons (app);
-       for (guint i = 0; i < icons->len; i++) {
-               AsIcon *icon = g_ptr_array_index (icons, i);
-               if (as_icon_get_kind (icon) == icon_kind)
-                       return icon;
+       AsIcon *icon = as_icon_new ();
+       g_autofree gchar *icon_path = NULL;
+       as_icon_set_kind (icon, icon_kind);
+       switch (icon_kind) {
+       case AS_ICON_KIND_REMOTE:
+               as_icon_set_url (icon, xb_node_get_text (n));
+               break;
+       default:
+               as_icon_set_name (icon, xb_node_get_text (n));
+       }
+       if (sz == 0)
+               sz = xb_node_get_attr_as_uint (n, "width");
+       if (sz > 0) {
+               as_icon_set_width (icon, sz);
+               as_icon_set_height (icon, sz);
        }
-       return NULL;
+       icon_path = gs_appstream_build_icon_prefix (component);
+       if (icon_path != NULL)
+               as_icon_set_prefix (icon, icon_path);
+       return icon;
+}
+
+static AsIcon *
+gs_appstream_get_icon_by_kind (XbNode *component, AsIconKind icon_kind)
+{
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(XbNode) icon = NULL;
+
+       xpath = g_strdup_printf ("icon[@type='%s']",
+                                as_icon_kind_to_string (icon_kind));
+       icon = xb_node_query_first (component, xpath, NULL);
+       if (icon == NULL)
+               return NULL;
+       return gs_appstream_new_icon (component, icon, icon_kind, 0);
 }
 
 static AsIcon *
-gs_appstream_get_icon_by_kind_and_size (AsApp *app, AsIconKind icon_kind, guint sz)
+gs_appstream_get_icon_by_kind_and_size (XbNode *component, AsIconKind icon_kind, guint sz)
 {
-       GPtrArray *icons = as_app_get_icons (app);
-       for (guint 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;
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(XbNode) icon = NULL;
+
+       xpath = g_strdup_printf ("icon[@type='%s'][@height='%u'][@width='%u']",
+                                as_icon_kind_to_string (icon_kind), sz, sz);
+       icon = xb_node_query_first (component, xpath, NULL);
+       if (icon == NULL)
+               return NULL;
+       return gs_appstream_new_icon (component, icon, icon_kind, sz);
 }
 
 static void
-gs_refine_item_icon (GsPlugin *plugin, GsApp *app, AsApp *item)
+gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
 {
-       AsIcon *icon;
+       g_autoptr(AsIcon) icon = NULL;
+       g_autoptr(XbNode) n = NULL;
 
        /* try a stock icon first */
-       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_STOCK);
-       if (icon != NULL)
+       icon = gs_appstream_get_icon_by_kind (component, AS_ICON_KIND_STOCK);
+       if (icon != NULL) {
                gs_app_add_icon (app, icon);
+               return;
+       }
 
        /* if HiDPI get a 128px cached icon */
        if (gs_plugin_get_scale (plugin) == 2) {
-               icon = gs_appstream_get_icon_by_kind_and_size (item,
+               icon = gs_appstream_get_icon_by_kind_and_size (component,
                                                               AS_ICON_KIND_CACHED,
                                                               128);
-               if (icon != NULL)
+               if (icon != NULL) {
                        gs_app_add_icon (app, icon);
+                       return;
+               }
        }
 
        /* non-HiDPI cached icon */
-       icon = gs_appstream_get_icon_by_kind_and_size (item,
+       icon = gs_appstream_get_icon_by_kind_and_size (component,
                                                       AS_ICON_KIND_CACHED,
                                                       64);
-       if (icon != NULL)
+       if (icon != NULL) {
                gs_app_add_icon (app, icon);
+               return;
+       }
 
        /* prefer local */
-       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_LOCAL);
+       icon = gs_appstream_get_icon_by_kind (component, 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 &&
@@ -126,191 +238,264 @@ gs_refine_item_icon (GsPlugin *plugin, GsApp *app, AsApp *item)
                        as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
                }
                gs_app_add_icon (app, icon);
+               return;
        }
 
-       /* remote as a last resort */
-       icon = gs_appstream_get_icon_by_kind (item, AS_ICON_KIND_REMOTE);
-       if (icon != NULL)
+       /* remote URL */
+       icon = gs_appstream_get_icon_by_kind (component, AS_ICON_KIND_REMOTE);
+       if (icon != NULL) {
+               gs_app_add_icon (app, icon);
+               return;
+       }
+
+       /* assume a stock icon */
+       n = xb_node_query_first (component, "icon", NULL);
+       if (n != NULL) {
+               icon = gs_appstream_new_icon (component, n, AS_ICON_KIND_STOCK, 0);
                gs_app_add_icon (app, icon);
+       }
 }
 
 static gboolean
 gs_appstream_refine_add_addons (GsPlugin *plugin,
                                GsApp *app,
-                               AsApp *item,
+                               XbSilo *silo,
                                GError **error)
 {
-       GPtrArray *addons;
-
-       /* we only care about addons to desktop apps */
-       if (gs_app_get_kind (app) != AS_APP_KIND_DESKTOP)
-               return TRUE;
-
-       addons = as_app_get_addons (item);
-       if (addons == NULL)
-               return TRUE;
-
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) addons = NULL;
+
+       /* get all components */
+       xpath = g_strdup_printf ("components/component/extends[text()='%s']/..",
+                                gs_app_get_id (app));
+       addons = xb_silo_query (silo, xpath, 0, &error_local);
+       if (addons == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint 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))
+               XbNode *addon = g_ptr_array_index (addons, i);
+               g_autoptr(GsApp) app2 = NULL;
+               app2 = gs_appstream_create_app (plugin, silo, addon, error);
+               if (app2 == NULL)
                        return FALSE;
-               gs_app_add_addon (app, addon);
+               gs_app_add_addon (app, app2);
        }
        return TRUE;
 }
 
-static void
-gs_appstream_refine_add_screenshots (GsApp *app, AsApp *item)
+static gboolean
+gs_appstream_refine_add_images (GsApp *app, AsScreenshot *ss, XbNode *screenshot, GError **error)
 {
-       GPtrArray *screenshots_as;
-
-       /* do we have any to add */
-       screenshots_as = as_app_get_screenshots (item);
-       if (screenshots_as->len == 0)
-               return;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) images = NULL;
+
+       /* get all components */
+       images = xb_node_query (screenshot, "image", 0, &error_local);
+       if (images == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < images->len; i++) {
+               XbNode *image = g_ptr_array_index (images, i);
+               g_autoptr(AsImage) im = as_image_new ();
+               as_image_set_height (im, xb_node_get_attr_as_uint (image, "height"));
+               as_image_set_width (im, xb_node_get_attr_as_uint (image, "width"));
+               as_image_set_kind (im, as_image_kind_from_string (xb_node_get_attr (image, "type")));
+               as_image_set_url (im, xb_node_get_text (image));
+               as_screenshot_add_image (ss, im);
+       }
 
-       /* 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;
+       /* success */
+       return TRUE;
+}
 
-       /* add any we know */
-       for (guint i = 0; i < screenshots_as->len &&
-                         i < GS_APPSTREAM_MAX_SCREENSHOTS; i++) {
-               AsScreenshot *ss = g_ptr_array_index (screenshots_as, i);
-               GPtrArray *images_as = as_screenshot_get_images (ss);
-               if (images_as->len == 0)
-                       continue;
-               if (as_screenshot_get_kind (ss) == AS_SCREENSHOT_KIND_UNKNOWN)
-                       continue;
+static gboolean
+gs_appstream_refine_add_screenshots (GsApp *app, XbNode *component, GError **error)
+{
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) screenshots = NULL;
+
+       /* get all components */
+       screenshots = xb_node_query (component, "screenshots/screenshot", 0, &error_local);
+       if (screenshots == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < screenshots->len; i++) {
+               XbNode *screenshot = g_ptr_array_index (screenshots, i);
+               g_autoptr(AsScreenshot) ss = as_screenshot_new ();
+               if (!gs_appstream_refine_add_images (app, ss, screenshot, error))
+                       return FALSE;
                gs_app_add_screenshot (app, ss);
        }
-}
 
-static void
-gs_appstream_refine_add_reviews (GsApp *app, AsApp *item)
-{
-       GPtrArray *reviews;
+       /* FIXME: move into no refine flags section? */
+       if (screenshots ->len > 0)
+               gs_app_add_kudo (app, GS_APP_KUDO_HAS_SCREENSHOTS);
 
-       /* do we have any to add */
-       if (gs_app_get_reviews(app)->len > 0)
-               return;
-       reviews = as_app_get_reviews (item);
-       for (guint i = 0; i < reviews->len; i++) {
-               AsReview *review = g_ptr_array_index (reviews, i);
-               gs_app_add_review (app, review);
-       }
+       /* success */
+       return TRUE;
 }
 
-static void
-gs_appstream_refine_add_provides (GsApp *app, AsApp *item)
+static gboolean
+gs_appstream_refine_add_provides (GsApp *app, XbNode *component, GError **error)
 {
-       GPtrArray *provides;
-
-       /* do we have any to add */
-       if (gs_app_get_provides(app)->len > 0)
-               return;
-       provides = as_app_get_provides (item);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) provides = NULL;
+
+       /* get all components */
+       provides = xb_node_query (component, "provides/*", 0, &error_local);
+       if (provides == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint i = 0; i < provides->len; i++) {
-               AsProvide *provide = g_ptr_array_index (provides, i);
-               gs_app_add_provide (app, provide);
+               XbNode *provide = g_ptr_array_index (provides, i);
+               g_autoptr(AsProvide) pr = as_provide_new ();
+               as_provide_set_kind (pr, as_provide_kind_from_string (xb_node_get_element (provide)));
+               as_provide_set_value (pr, xb_node_get_text (provide));
+               gs_app_add_provide (app, pr);
        }
+
+       /* success */
+       return TRUE;
 }
 
 static gboolean
-gs_appstream_is_recent_release (AsApp *app)
+gs_appstream_is_recent_release (XbNode *component)
 {
-       AsRelease *release;
-       GPtrArray *releases;
+       guint64 ts;
        guint64 secs;
 
        /* get newest release */
-       releases = as_app_get_releases (app);
-       if (releases->len == 0)
+       ts = xb_node_query_attr_as_uint (component, "releases/release", "timestamp", NULL);
+       if (ts == G_MAXUINT64)
                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);
+       secs = ((guint64) g_get_real_time () / G_USEC_PER_SEC) - ts;
        return secs / (60 * 60 * 24) < 365;
 }
 
-static void
-gs_appstream_copy_metadata (GsApp *app, AsApp *item)
+static gboolean
+gs_appstream_copy_metadata (GsApp *app, XbNode *component, GError **error)
 {
-       GHashTable *hash = as_app_get_metadata (item);
-       g_autoptr(GList) keys = g_hash_table_get_keys (hash);
-       for (GList *l = keys; l != NULL; l = l->next) {
-               const gchar *key = l->data;
-               const gchar *value = g_hash_table_lookup (hash, key);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) values = NULL;
+
+       /* get all components */
+       values = xb_node_query (component, "custom/value", 0, &error_local);
+       if (values == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < values->len; i++) {
+               XbNode *value = g_ptr_array_index (values, i);
+               const gchar *key = xb_node_get_attr (value, "key");
+               if (key == NULL)
+                       continue;
                if (gs_app_get_metadata_item (app, key) != NULL)
                        continue;
-               gs_app_set_metadata (app, key, value);
-       }
-}
-
-static void
-gs_refine_item_management_plugin (GsPlugin *plugin, GsApp *app, AsApp *item)
-{
-       GPtrArray *bundles;
-       const gchar *management_plugin = NULL;
-
-       /* 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 (guint i = 0; i < bundles->len; i++) {
-               AsBundle *bundle = g_ptr_array_index (bundles, i);
-               gs_app_add_source (app, as_bundle_get_id (bundle));
+               gs_app_set_metadata (app, key, xb_node_get_text (value));
        }
+       return TRUE;
 }
 
 static gboolean
 gs_appstream_refine_app_updates (GsPlugin *plugin,
                                 GsApp *app,
-                                AsApp *item,
+                                XbSilo *silo,
+                                XbNode *component,
                                 GError **error)
 {
        AsUrgencyKind urgency_best = AS_URGENCY_KIND_UNKNOWN;
-       GPtrArray *releases;
-       g_autoptr(GPtrArray) updates_list = NULL;
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GHashTable) installed = g_hash_table_new (g_str_hash, g_str_equal);
+       g_autoptr(GPtrArray) releases_inst = NULL;
+       g_autoptr(GPtrArray) releases = NULL;
+       g_autoptr(GPtrArray) updates_list = g_ptr_array_new ();
 
        /* 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);
+       /* find out which releases are already installed */
+       xpath = g_strdup_printf ("component/id[text()='%s']/../releases/*[@version]",
+                                gs_app_get_id (app));
+       releases_inst = xb_silo_query (silo, xpath, 0, &error_local);
+       if (releases_inst == NULL) {
+               if (!g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
+                   !g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) {
+                       g_propagate_error (error, g_steal_pointer (&error_local));
+                       return FALSE;
+               }
+       } else {
+               for (guint i = 0; i < releases_inst->len; i++) {
+                       XbNode *release = g_ptr_array_index (releases_inst, i);
+                       g_hash_table_insert (installed,
+                                            (gpointer) xb_node_get_attr (release, "version"),
+                                            (gpointer) release);
+               }
+       }
+       g_clear_error (&error_local);
+
+       /* get all components */
+       releases = xb_node_query (component, "releases/*", 0, &error_local);
+       if (releases == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint i = 0; i < releases->len; i++) {
-               AsRelease *rel = g_ptr_array_index (releases, i);
+               XbNode *release = g_ptr_array_index (releases, i);
+               const gchar *version = xb_node_get_attr (release, "version");
+               g_autoptr(XbNode) description = NULL;
+               AsUrgencyKind urgency_tmp;
+
+               /* ignore releases with no version */
+               if (version == NULL)
+                       continue;
 
                /* 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)
+               if (g_hash_table_lookup (installed, version) != NULL)
                        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);
+               urgency_tmp = as_urgency_kind_from_string (xb_node_get_attr (release, "urgency"));
+               if (urgency_tmp > urgency_best)
+                       urgency_best = urgency_tmp;
 
                /* add updates with a description */
-               if (as_release_get_description (rel, NULL) == NULL)
+               description = xb_node_query_first (release, "description", NULL);
+               if (description == NULL)
                        continue;
-               g_ptr_array_add (updates_list, rel);
+               g_ptr_array_add (updates_list, release);
        }
 
        /* only set if known */
@@ -319,33 +504,26 @@ gs_appstream_refine_app_updates (GsPlugin *plugin,
 
        /* no prefix on each release */
        if (updates_list->len == 1) {
+               XbNode *release = g_ptr_array_index (updates_list, 0);
+               g_autoptr(XbNode) n = NULL;
                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;
-               }
+               n = xb_node_query_first (release, "description", NULL);
+               desc = gs_appstream_format_description (n, NULL);
                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++) {
+                       XbNode *release = g_ptr_array_index (updates_list, 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_autoptr(XbNode) n = NULL;
+
+                       n = xb_node_query_first (release, "description", NULL);
+                       desc = gs_appstream_format_description (n, NULL);
                        g_string_append_printf (update_desc,
                                                "Version %s:\n%s\n\n",
-                                               as_release_get_version (rel),
+                                               xb_node_get_attr (release, "version"),
                                                desc);
                }
 
@@ -356,10 +534,10 @@ gs_appstream_refine_app_updates (GsPlugin *plugin,
        }
 
        /* 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));
+       if (gs_app_get_update_version (app) == NULL &&
+           updates_list->len > 0) {
+               XbNode *release = g_ptr_array_index (updates_list, 0);
+               gs_app_set_update_version (app, xb_node_get_attr (release, "version"));
        }
 
        /* success */
@@ -386,29 +564,6 @@ _gs_utils_locale_has_translations (const gchar *locale)
        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)
 {
@@ -420,46 +575,101 @@ gs_appstream_origin_valid (const gchar *origin)
 }
 
 static gboolean
-gs_appstream_is_valid_project_group (AsApp *item)
+gs_appstream_is_valid_project_group (const gchar *project_group)
 {
-       const gchar *project_group = as_app_get_project_group (item);
        if (project_group == NULL)
                return FALSE;
        return as_utils_is_environment_id (project_group);
 }
 
+static gboolean
+gs_appstream_refine_app_content_rating (GsPlugin *plugin,
+                                       GsApp *app,
+                                       XbNode *content_rating,
+                                       GError **error)
+{
+       g_autoptr(AsContentRating) cr = as_content_rating_new ();
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) content_attributes = NULL;
+
+       /* get kind */
+       as_content_rating_set_kind (cr, xb_node_get_attr (content_rating, "type"));
+
+       /* get attributes */
+       content_attributes = xb_node_query (content_rating, "content_attribute", 0, &error_local);
+       if (content_attributes == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < content_attributes->len; i++) {
+               XbNode *content_attribute = g_ptr_array_index (content_attributes, i);
+               as_content_rating_add_attribute (cr,
+                                                xb_node_get_attr (content_attribute, "id"),
+                                                as_content_rating_value_from_string (xb_node_get_text 
(content_attribute)));
+       }
+
+       /* we only really expect OARS 1.0 and 1.1 */
+       if (g_str_has_prefix (as_content_rating_get_kind (cr), "oars-1."))
+               gs_app_set_content_rating (app, cr);
+       return TRUE;
+}
+
+static gboolean
+gs_appstream_refine_app_content_ratings (GsPlugin *plugin,
+                                        GsApp *app,
+                                        XbNode *component,
+                                        GError **error)
+{
+       g_autoptr(GPtrArray) content_ratings = NULL;
+       g_autoptr(GError) error_local = NULL;
+
+       /* find any content ratings */
+       content_ratings = xb_node_query (component, "content_rating", 0, &error_local);
+       if (content_ratings == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < content_ratings->len; i++) {
+               XbNode *content_rating = g_ptr_array_index (content_ratings, i);
+               if (!gs_appstream_refine_app_content_rating (plugin, app, content_rating, error))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
 gboolean
 gs_appstream_refine_app (GsPlugin *plugin,
                         GsApp *app,
-                        AsApp *item,
+                        XbSilo *silo,
+                        XbNode *component,
+                        GsPluginRefineFlags refine_flags,
                         GError **error)
 {
-       AsRequire *req;
-       g_autoptr(GError) error_local = NULL;
-       GHashTable *urls;
-       GPtrArray *launchables;
-       GPtrArray *array;
-       GPtrArray *pkgnames;
-       GPtrArray *kudos;
        const gchar *tmp;
-
-       /* 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));
-       }
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) bundles = NULL;
+       g_autoptr(GPtrArray) launchables = NULL;
+       g_autoptr(GPtrArray) pkgnames = NULL;
+       g_autoptr(XbNode) req = NULL;
 
        /* is compatible */
-       req = as_app_get_require_by_value (item,
-                                          AS_REQUIRE_KIND_ID,
-                                          "org.gnome.Software.desktop");
+       req = xb_node_query_first (component,
+                                  "requires/id[@type='id']"
+                                  "[text()='org.gnome.Software.desktop']", NULL);
        if (req != NULL) {
-               if (!as_require_version_compare (req, PACKAGE_VERSION, &error_local)) {
+               if (as_utils_vercmp (xb_node_get_attr (req, "version"), PACKAGE_VERSION) > 0) {
                        g_set_error (error,
                                     GS_PLUGIN_ERROR,
                                     GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "not for this gnome-software: %s",
-                                    error_local->message);
+                                    "not for this gnome-software");
                        return FALSE;
                }
        }
@@ -493,362 +703,385 @@ gs_appstream_refine_app (GsPlugin *plugin,
                        gs_app_remove_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
        }
 
-       /* 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) {
-               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));
-               }
-       }
-
-       /* 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));
+       tmp = xb_node_query_text (component, "id", NULL);
+       if (tmp != NULL && gs_app_get_id (app) == NULL)
+               gs_app_set_id (app, tmp);
 
        /* set content rating */
-       array = as_app_get_content_ratings (item);
-       for (guint i = 0; i < array->len; i++) {
-               AsContentRating *cr = g_ptr_array_index (array, i);
-               if (g_str_has_prefix (as_content_rating_get_kind (cr), "oars-1.")) {
-                       gs_app_set_content_rating (app, cr);
-                       break;
-               }
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CONTENT_RATING) {
+               if (!gs_appstream_refine_app_content_ratings (plugin, app, component, error))
+                       return FALSE;
        }
 
-       /* 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);
+       tmp = xb_node_query_text (component, "name", 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) {
+       tmp = xb_node_query_text (component, "summary", 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) {
-               g_autoptr(GList) keys = NULL;
-               keys = g_hash_table_get_keys (urls);
-               for (GList *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));
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL) {
+               g_autoptr(GPtrArray) urls = NULL;
+               urls = xb_node_query (component, "url", 0, NULL);
+               if (urls != NULL) {
+                       for (guint i = 0; i < urls->len; i++) {
+                               XbNode *url = g_ptr_array_index (urls, i);
+                               const gchar *kind = xb_node_get_attr (url, "type");
+                               if (kind == NULL)
+                                       continue;
+                               gs_app_set_url (app,
+                                               as_url_kind_from_string (kind),
+                                               xb_node_get_text (url));
+                       }
                }
        }
 
        /* add launchables */
-       launchables = as_app_get_launchables (item);
-       for (guint i = 0; i < launchables->len; i++) {
-               AsLaunchable *launchable = g_ptr_array_index (launchables, i);
-               switch (as_launchable_get_kind (launchable)) {
-               case AS_LAUNCHABLE_KIND_DESKTOP_ID:
-                       gs_app_set_launchable (app,
-                                              AS_LAUNCHABLE_KIND_DESKTOP_ID,
-                                              as_launchable_get_value (launchable));
-                       break;
-               case AS_LAUNCHABLE_KIND_SERVICE:
-                       gs_app_set_launchable (app,
-                                              AS_LAUNCHABLE_KIND_SERVICE,
-                                              as_launchable_get_value (launchable));
-                       break;
-               case AS_LAUNCHABLE_KIND_COCKPIT_MANIFEST:
-                       gs_app_set_launchable (app,
-                                              AS_LAUNCHABLE_KIND_COCKPIT_MANIFEST,
-                                              as_launchable_get_value (launchable));
-                       break;
-               case AS_LAUNCHABLE_KIND_URL:
-                       gs_app_set_launchable (app,
-                                              AS_LAUNCHABLE_KIND_URL,
-                                              as_launchable_get_value (launchable));
-                       break;
-               default:
-                       break;
+       launchables = xb_node_query (component, "launchable", 0, NULL);
+       if (launchables != NULL) {
+               for (guint i = 0; i < launchables->len; i++) {
+                       XbNode *launchable = g_ptr_array_index (launchables, i);
+                       const gchar *kind = xb_node_get_attr (launchable, "type");
+                       if (g_strcmp0 (kind, "desktop-id") == 0) {
+                               gs_app_set_launchable (app,
+                                                      AS_LAUNCHABLE_KIND_DESKTOP_ID,
+                                                      xb_node_get_text (launchable));
+                               break;
+                       } else if (g_strcmp0 (kind, "url") == 0) {
+                               gs_app_set_launchable (app,
+                                                      AS_LAUNCHABLE_KIND_URL,
+                                                      xb_node_get_text (launchable));
+                       }
                }
        }
 
        /* 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_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));
-               }
+       if ((refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) > 0 &&
+           gs_app_get_license (app) == NULL) {
+               tmp = xb_node_query_text (component, "project_license", NULL);
+               if (tmp != NULL)
+                       gs_app_set_license (app, GS_APP_QUALITY_HIGHEST, tmp);
        }
 
        /* 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);
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION) {
+               g_autofree gchar *description = NULL;
+               g_autoptr(XbNode) n = xb_node_query_first (component, "description", NULL);
+               if (n != NULL)
+                       description = gs_appstream_format_description (n, NULL);
+               if (description != NULL)
+                       gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, description);
        }
 
        /* set icon */
-       if (as_app_get_icon_default (item) != NULL &&
+       if ((refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON) > 0 &&
            gs_app_get_icons(app)->len == 0)
-               gs_refine_item_icon (plugin, app, item);
+               gs_appstream_refine_icon (plugin, app, component);
 
        /* set categories */
-       array = as_app_get_categories (item);
-       if (array != NULL && gs_app_get_categories (app)->len == 0) {
-               for (guint i = 0; i < array->len; i++) {
-                       tmp = g_ptr_array_index (array, i);
-                       gs_app_add_category (app, tmp);
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CATEGORIES) {
+               g_autoptr(GPtrArray) categories = NULL;
+               categories = xb_node_query (component, "categories/category", 0, NULL);
+               if (categories != NULL) {
+                       for (guint i = 0; i < categories->len; i++) {
+                               XbNode *category = g_ptr_array_index (categories, i);
+                               gs_app_add_category (app, xb_node_get_text (category));
+                       }
                }
        }
 
        /* set project group */
-       if (gs_app_get_project_group (app) == NULL &&
-           gs_appstream_is_valid_project_group (item))
-               gs_app_set_project_group (app, as_app_get_project_group (item));
+       if ((refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROJECT_GROUP) > 0 &&
+           gs_app_get_project_group (app) == NULL) {
+               tmp = xb_node_query_text (component, "project_group", NULL);
+               if (tmp != NULL && gs_appstream_is_valid_project_group (tmp))
+                       gs_app_set_project_group (app, tmp);
+       }
 
        /* set developer name */
-       if (gs_app_get_developer_name (app) == NULL &&
-           as_app_get_developer_name (item, NULL) != NULL)
-               gs_app_set_developer_name (app, as_app_get_developer_name (item, NULL));
+       if ((refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DEVELOPER_NAME) > 0 &&
+           gs_app_get_developer_name (app) == NULL) {
+               tmp = xb_node_query_text (component, "developer_name", NULL);
+               if (tmp != NULL)
+                       gs_app_set_developer_name (app, tmp);
+       }
 
        /* set id kind */
-       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN)
-               gs_app_set_kind (app, as_app_get_kind (item));
+       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN ||
+           gs_app_get_kind (app) == AS_APP_KIND_GENERIC) {
+               tmp = xb_node_get_attr (component, "type");
+               gs_app_set_kind (app, as_app_kind_from_string (tmp));
+       }
 
        /* copy all the metadata */
-       gs_appstream_copy_metadata (app, item);
+       if (!gs_appstream_copy_metadata (app, component, error))
+               return FALSE;
 
-       /* 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);
+       /* add package names */
+       pkgnames = xb_node_query (component, "pkgname", 0, NULL);
+       if (pkgnames != NULL && gs_app_get_sources(app)->len == 0) {
+               for (guint i = 0; i < pkgnames->len; i++) {
+                       XbNode *pkgname = g_ptr_array_index (pkgnames, i);
+                       tmp = xb_node_get_text (pkgname);
+                       if (tmp != NULL && tmp[0] != '\0')
+                               gs_app_add_source (app, tmp);
+               }
+               gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
+       }
+
+       /* add bundles */
+       bundles = xb_node_query (component, "bundle", 0, NULL);
+       if (bundles != NULL && gs_app_get_sources(app)->len == 0) {
+               for (guint i = 0; i < bundles->len; i++) {
+                       XbNode *bundle = g_ptr_array_index (bundles, i);
+                       const gchar *kind = xb_node_get_attr (bundle, "type");
+                       gs_app_add_source (app, xb_node_get_text (bundle));
+                       gs_app_set_bundle_kind (app, as_bundle_kind_from_string (kind));
+               }
+       }
 
        /* set addons */
-       if (!gs_appstream_refine_add_addons (plugin, app, item, error))
-               return FALSE;
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS) {
+               if (!gs_appstream_refine_add_addons (plugin, app, silo, error))
+                       return FALSE;
+       }
 
        /* set screenshots */
-       gs_appstream_refine_add_screenshots (app, item);
-
-       /* set reviews */
-       gs_appstream_refine_add_reviews (app, item);
+       if ((refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS) > 0 &&
+           gs_app_get_screenshots(app)->len == 0) {
+               if (!gs_appstream_refine_add_screenshots (app, component, error))
+                       return FALSE;
+       }
 
        /* set provides */
-       gs_appstream_refine_add_provides (app, item);
-
-       /* was this application released recently */
-       if (gs_appstream_is_recent_release (item))
-               gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);
+       if (!gs_appstream_refine_add_provides (app, component, error))
+               return FALSE;
 
        /* 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 (guint 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_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:
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_KUDOS) {
+               g_autoptr(GPtrArray) kudos = NULL;
+               tmp = gs_plugin_get_locale (plugin);
+               if (!_gs_utils_locale_has_translations (tmp)) {
+                       gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);
+               } else {
+                       g_autofree gchar *xpath = NULL;
+                       xpath = g_strdup_printf ("languages/lang[text()='%s'][@percentage>50]", tmp);
+                       if (xb_node_query_text (component, xpath, NULL) != NULL)
+                               gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);
+               }
+
+               /* any keywords */
+               if (xb_node_query_text (component, "keywords/keyword", NULL) != NULL)
+                       gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS);
+
+               /* HiDPI icon */
+               if (xb_node_query_text (component, "icon[@width='128']", NULL) != NULL)
                        gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON);
-                       break;
-               default:
-                       break;
+
+               /* was this application released recently */
+               if (gs_appstream_is_recent_release (component))
+                       gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);
+
+               /* add a kudo to featured and popular apps */
+               if (xb_node_query_text (component, "kudos/kudo[text()='GnomeSoftware::popular']", NULL) != 
NULL)
+                       gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
+               if (xb_node_query_text (component, "categories/category[text()='featured']", NULL) != NULL)
+                       gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
+
+               /* add new-style kudos */
+               kudos = xb_node_query (component, "kudos/kudo", 0, NULL);
+               for (guint i = 0; kudos != NULL && i < kudos->len; i++) {
+                       XbNode *kudo = g_ptr_array_index (kudos, i);
+                       switch (as_kudo_kind_from_string (xb_node_get_text (kudo))) {
+                       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_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_appstream (app, as_app_get_origin (item));
+       if ((refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN) > 0 &&
+           gs_app_get_origin_appstream (app) == NULL) {
+               g_autoptr(XbNode) parent = xb_node_get_parent (component);
+               if (parent != NULL) {
+                       tmp = xb_node_get_attr (parent, "origin");
+                       if (gs_appstream_origin_valid (tmp))
+                               gs_app_set_origin_appstream (app, tmp);
+               }
+       }
 
        /* is there any update information */
-       if (!gs_appstream_refine_app_updates (plugin, app, item, error))
-               return FALSE;
+       if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS) {
+               if (!gs_appstream_refine_app_updates (plugin,
+                                                     app,
+                                                     silo,
+                                                     component,
+                                                     error))
+                       return FALSE;
+       }
 
        return TRUE;
 }
 
-static gboolean
-gs_appstream_store_search_item (GsPlugin *plugin,
-                               AsApp *item,
-                               gchar **values,
-                               GsAppList *list,
-                               GCancellable *cancellable,
-                               GError **error)
-{
-       GPtrArray *addons;
-       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 (guint i = 0; i < addons->len; i++) {
-               AsApp *item_tmp = g_ptr_array_index (addons, i);
-               match_value |= as_app_search_matches_all (item_tmp, values);
-       }
+typedef struct {
+       AsAppSearchMatch         match_value;
+       gchar                   *xpath;
+} GsAppstreamSearchHelper;
 
-       /* 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;
+static void
+gs_appstream_search_helper_free (GsAppstreamSearchHelper *helper)
+{
+       g_free (helper->xpath);
+       g_free (helper);
 }
 
-gboolean
-gs_appstream_store_search (GsPlugin *plugin,
-                          AsStore *store,
-                          gchar **values,
-                          GsAppList *list,
-                          GCancellable *cancellable,
-                          GError **error)
+static guint16
+gs_appstream_silo_search_component2 (XbNode *component, const gchar *search)
 {
-       GPtrArray *array;
-       gboolean ret = TRUE;
-
-       array = as_store_get_apps (store);
+       GsAppstreamSearchHelper *helper;
+       guint16 match_value = 0;
+       g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func ((GDestroyNotify) 
gs_appstream_search_helper_free);
+
+       /* mimetype */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_MIMETYPE;
+       helper->xpath = g_strdup_printf ("mimetypes/mimetype[type()~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* package name */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_PKGNAME;
+       helper->xpath = g_strdup_printf ("pkgname[text()~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* summary */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_COMMENT;
+       helper->xpath = g_strdup_printf ("summary[text()~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* name */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_NAME;
+       helper->xpath = g_strdup_printf ("name[text()~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* keywords */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_KEYWORD;
+       helper->xpath = g_strdup_printf ("keywords/keyword[text()~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* AppStream ID */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_ID;
+       helper->xpath = g_strdup_printf ("id[text()~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* origin */
+       helper = g_new0 (GsAppstreamSearchHelper, 1);
+       helper->match_value = AS_APP_SEARCH_MATCH_ORIGIN;
+       helper->xpath = g_strdup_printf ("../components[@origin~='%s']", search);
+       g_ptr_array_add (array, helper);
+
+       /* do searches */
        for (guint i = 0; i < array->len; i++) {
-               AsApp *item = g_ptr_array_index (array, i);
-               if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
-                       gs_utils_error_convert_gio (error);
-                       return FALSE;
-               }
-               ret = gs_appstream_store_search_item (plugin, item,
-                                                     values, list,
-                                                     cancellable, error);
-               if (!ret)
-                       return FALSE;
+               g_autoptr(XbNode) n = NULL;
+               helper = g_ptr_array_index (array, i);
+               n = xb_node_query_first (component, helper->xpath, NULL);
+               if (n != NULL)
+                       match_value |= helper->match_value;
        }
-       return TRUE;
+       return match_value;
 }
 
-static gboolean
-_as_app_matches_desktop_group_set (AsApp *app, gchar **desktop_groups)
+static guint16
+gs_appstream_silo_search_component (XbNode *component, gchar **search)
 {
-       for (guint i = 0; desktop_groups[i] != NULL; i++) {
-               if (!as_app_has_category (app, desktop_groups[i]))
-                       return FALSE;
+       guint16 matches_sum = 0;
+
+       /* do *all* search keywords match */
+       for (guint i = 0; search[i] != NULL; i++) {
+               guint tmp = gs_appstream_silo_search_component2 (component, search[i]);
+               if (tmp == 0)
+                       return 0;
+               matches_sum |= tmp;
        }
-       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);
+       return matches_sum;
 }
 
-static void
-gs_appstream_store_add_categories_for_app (GsCategory *parent, AsApp *app)
+gboolean
+gs_appstream_search (GsPlugin *plugin,
+                    XbSilo *silo,
+                    gchar **values,
+                    GsAppList *list,
+                    GCancellable *cancellable,
+                    GError **error)
 {
-       GPtrArray *children;
-       GPtrArray *desktop_groups;
-
-       /* find all the sub-categories */
-       children = gs_category_get_children (parent);
-       for (guint j = 0; j < children->len; j++) {
-               gboolean matched = FALSE;
-               GsCategory *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 (guint 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);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) components = NULL;
+       g_autoptr(GTimer) timer = g_timer_new ();
+
+       /* get all components */
+       components = xb_silo_query (silo, "components/component", 0, &error_local);
+       if (components == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < components->len; i++) {
+               XbNode *component = g_ptr_array_index (components, i);
+               guint16 match_value = gs_appstream_silo_search_component (component, values);
+               if (match_value != 0) {
+                       g_autoptr(GsApp) app = gs_appstream_create_app (plugin, silo, component, error);
+                       if (app == NULL)
+                               return FALSE;
+                       g_debug ("add %s", gs_app_get_id (app));
+                       gs_app_set_match_value (app, match_value);
+                       gs_app_list_add (list, app);
                }
        }
+       g_debug ("search took %fms", g_timer_elapsed (timer, NULL) * 1000);
+       return TRUE;
 }
 
 gboolean
-gs_appstream_store_add_category_apps (GsPlugin *plugin,
-                                     AsStore *store,
-                                     GsCategory *category,
-                                     GsAppList *list,
-                                     GCancellable *cancellable,
-                                     GError **error)
+gs_appstream_add_category_apps (GsPlugin *plugin,
+                               XbSilo *silo,
+                               GsCategory *category,
+                               GsAppList *list,
+                               GCancellable *cancellable,
+                               GError **error)
 {
-       GPtrArray *array;
        GPtrArray *desktop_groups;
+       g_autoptr(GError) error_local = NULL;
 
-       /* just look at each app in turn */
-       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));
@@ -856,113 +1089,173 @@ gs_appstream_store_add_category_apps (GsPlugin *plugin,
        }
        for (guint j = 0; j < desktop_groups->len; j++) {
                const gchar *desktop_group = g_ptr_array_index (desktop_groups, j);
+               g_autofree gchar *xpath = NULL;
                g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1);
+               g_autoptr(GPtrArray) components = NULL;
+
+               /* generate query */
+               if (g_strv_length (split) == 1) {
+                       xpath = g_strdup_printf ("components/component/categories/"
+                                                "category[text()='%s']/../..",
+                                                split[0]);
+               } else if (g_strv_length (split) == 2) {
+                       xpath = g_strdup_printf ("components/component/categories/"
+                                                "category[text()='%s']/../"
+                                                "category[text()='%s']/../..",
+                                                split[0], split[1]);
+               }
+               components = xb_silo_query (silo, xpath, 0, &error_local);
+               if (components == NULL) {
+                       if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                               return TRUE;
+                       if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                               return TRUE;
+                       g_propagate_error (error, g_steal_pointer (&error_local));
+                       return FALSE;
+               }
 
-               /* match the app */
-               for (guint i = 0; i < array->len; i++) {
-                       AsApp *item;
+               /* create app */
+               for (guint i = 0; i < components->len; i++) {
+                       XbNode *component = g_ptr_array_index (components, i);
                        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);
+                       app = gs_appstream_create_app (plugin, silo, component, 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)
+static guint
+gs_appstream_count_component_for_groups (GsPlugin *plugin, XbSilo *silo, const gchar *desktop_group)
 {
-       GPtrArray *array;
+       guint limit = 10;
+       g_autofree gchar *xpath = NULL;
+       g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1);
+       g_autoptr(GPtrArray) array = NULL;
+       g_autoptr(GError) error_local = NULL;
 
-       /* find out how many packages are in each category */
-       array = as_store_get_apps (store);
-       for (guint i = 0; i < array->len; i++) {
-               AsApp *app = g_ptr_array_index (array, i);
-               if (as_app_get_id (app) == NULL)
-                       continue;
-               if (as_app_get_priority (app) < 0)
-                       continue;
-               for (guint 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);
+       if (g_strv_length (split) != 2)
+               return 0;
+
+       xpath = g_strdup_printf ("components/component/categories/"
+                                "category[text()='%s']/../"
+                                "category[text()='%s']/../..",
+                                split[0], split[1]);
+       array = xb_silo_query (silo, xpath, limit, &error_local);
+       if (array == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return 0;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return 0;
+               g_warning ("%s", error_local->message);
+               return 0;
+       }
+       return array->len;
+}
+
+/* we're not actually adding categories here, we're just setting the number of
+ * applications available in each category */
+gboolean
+gs_appstream_add_categories (GsPlugin *plugin,
+                                 XbSilo *silo,
+                                 GPtrArray *list,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       for (guint j = 0; j < list->len; j++) {
+               GsCategory *parent = GS_CATEGORY (g_ptr_array_index (list, j));
+               GPtrArray *children = gs_category_get_children (parent);
+               g_autofree gchar *xpath = NULL;
+               g_autoptr(GError) error_local = NULL;
+               g_autoptr(GPtrArray) array = NULL;
+
+               for (guint i = 1; i < children->len; i++) { /* 1 to ignore all */
+                       GsCategory *cat = g_ptr_array_index (children, i);
+                       GPtrArray *groups = gs_category_get_desktop_groups (cat);
+                       for (guint k = 0; k < groups->len; k++) {
+                               const gchar *group = g_ptr_array_index (groups, k);
+                               guint cnt = gs_appstream_count_component_for_groups (plugin, silo, group);
+                               for (guint l = 0; l < cnt; l++) {
+                                       gs_category_increment_size (parent);
+                                       gs_category_increment_size (cat);
+                               }
+                       }
                }
+               continue;
        }
        return TRUE;
 }
 
 gboolean
 gs_appstream_add_popular (GsPlugin *plugin,
-                         AsStore *store,
+                         XbSilo *silo,
                          GsAppList *list,
                          GCancellable *cancellable,
                          GError **error)
 {
-       GPtrArray *array = as_store_get_apps (store);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) array = NULL;
+
+       /* find out how many packages are in each category */
+       array = xb_silo_query (silo,
+                              "components/component/kudos/"
+                              "kudo[text()='GnomeSoftware::popular']/../..",
+                              0, &error_local);
+       if (array == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint i = 0; i < array->len; i++) {
                g_autoptr(GsApp) app = NULL;
-               AsApp *item = g_ptr_array_index (array, i);
-               if (as_app_get_id (item) == NULL)
+               XbNode *component = g_ptr_array_index (array, i);
+               const gchar *component_id = xb_node_query_text (component, "id", NULL);
+               if (component_id == NULL)
                        continue;
-               if (!as_app_has_kudo (item, "GnomeSoftware::popular"))
-                       continue;
-               app = gs_app_new (as_app_get_id (item));
+               app = gs_app_new (component_id);
                gs_app_add_quirk (app, GS_APP_QUIRK_IS_WILDCARD);
                gs_app_list_add (list, app);
        }
        return TRUE;
 }
 
-static gboolean
-_as_app_is_recent (AsApp *app, guint64 age)
-{
-       AsRelease *rel;
-       guint64 ts;
-       guint64 now;
-
-       rel = as_app_get_release_default (app);
-       if (rel == NULL)
-               return FALSE;
-       ts = as_release_get_timestamp (rel);
-       if (ts == 0)
-               return FALSE;
-       now = (guint64) g_get_real_time () / G_USEC_PER_SEC;
-       return (now - ts) < age;
-}
-
 gboolean
 gs_appstream_add_recent (GsPlugin *plugin,
-                        AsStore *store,
+                        XbSilo *silo,
                         GsAppList *list,
                         guint64 age,
                         GCancellable *cancellable,
                         GError **error)
 {
-       GPtrArray *array = as_store_get_apps (store);
+       guint64 now = (guint64) g_get_real_time () / G_USEC_PER_SEC;
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) array = NULL;
+
+       /* use predicate conditions to the max */
+       xpath = g_strdup_printf ("components/component/releases/"
+                                "release[@timestamp>%" G_GUINT64_FORMAT "]/../..",
+                                now - (30 * 24 * 60 * 60));
+       array = xb_silo_query (silo, xpath, 0, &error_local);
+       if (array == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint i = 0; i < array->len; i++) {
-               g_autoptr(GsApp) app = NULL;
-               AsApp *item = g_ptr_array_index (array, i);
-               if (as_app_get_id (item) == NULL)
-                       continue;
-               if (!_as_app_is_recent (item, age))
-                       continue;
-               app = gs_appstream_create_app (plugin, item, error);
+               XbNode *component = g_ptr_array_index (array, i);
+               g_autoptr(GsApp) app = gs_appstream_create_app (plugin, silo, component, error);
                if (app == NULL)
                        return FALSE;
                gs_app_list_add (list, app);
@@ -970,101 +1263,54 @@ gs_appstream_add_recent (GsPlugin *plugin,
        return TRUE;
 }
 
-static void
-_g_ptr_array_add_str_uniq (GPtrArray *array, const gchar *str)
-{
-       if (str == NULL)
-               return;
-       for (guint i = 0; i < array->len; i++) {
-               const gchar *str_tmp = g_ptr_array_index (array, i);
-               if (g_strcmp0 (str, str_tmp) == 0)
-                       return;
-       }
-       g_ptr_array_add (array, g_strdup (str));
-}
-
-/* add the component ID for any matching <provide><id/></provide> value */
-static void
-gs_appstream_add_alternates_new_id (GPtrArray *array, AsApp *item, const gchar *id)
-{
-       GPtrArray *provides = as_app_get_provides (item);
-       for (guint i = 0; i < provides->len; i++) {
-               AsProvide *provide = g_ptr_array_index (provides, i);
-               if (as_provide_get_kind (provide) == AS_PROVIDE_KIND_ID &&
-                   g_strcmp0 (as_provide_get_value (provide), id) == 0) {
-                       _g_ptr_array_add_str_uniq (array, as_app_get_id (item));
-                       break;
-               }
-       }
-
-}
-
-/* add all <provide><id/></provide> values for a matching component ID */
-static void
-gs_appstream_add_alternates_old_id (GPtrArray *array, AsApp *item, const gchar *id)
-{
-       GPtrArray *provides;
-       if (g_strcmp0 (as_app_get_id (item), id) != 0)
-               return;
-       provides = as_app_get_provides (item);
-       for (guint i = 0; i < provides->len; i++) {
-               AsProvide *provide = g_ptr_array_index (provides, i);
-               if (as_provide_get_kind (provide) == AS_PROVIDE_KIND_ID)
-                       _g_ptr_array_add_str_uniq (array, as_provide_get_value (provide));
-       }
-}
-
-/* find any matching package names */
-static void
-gs_appstream_add_alternates_source (GPtrArray *array, AsApp *item, const gchar *source)
-{
-       GPtrArray *item_pkgnames = as_app_get_pkgnames (item);
-       for (guint i = 0; i < item_pkgnames->len; i++) {
-               const gchar *pkgname = g_ptr_array_index (item_pkgnames, i);
-               if (g_strcmp0 (pkgname, source) == 0) {
-                       _g_ptr_array_add_str_uniq (array, as_app_get_id (item));
-                       break;
-               }
-       }
-}
-
 gboolean
 gs_appstream_add_alternates (GsPlugin *plugin,
-                            AsStore *store,
+                            XbSilo *silo,
                             GsApp *app,
                             GsAppList *list,
                             GCancellable *cancellable,
                             GError **error)
 {
-       GPtrArray *apps = as_store_get_apps (store);
-       g_autoptr(GPtrArray) ids = g_ptr_array_new_with_free_func (g_free);
-
-       /* find apps that provide the new name */
-       for (guint i = 0; i < apps->len; i++) {
-               AsApp *item = g_ptr_array_index (apps, i);
-               GPtrArray *sources = gs_app_get_sources (app);
-
-               /* actual ID */
-               if (g_strcmp0 (as_app_get_id (item), gs_app_get_id (app)) == 0)
-                       _g_ptr_array_add_str_uniq (ids, as_app_get_id (item));
-
-               /* new ID -> old ID */
-               gs_appstream_add_alternates_old_id (ids, item, gs_app_get_id (app));
-
-               /* old ID -> new ID */
-               gs_appstream_add_alternates_new_id (ids, item, gs_app_get_id (app));
-
-               /* find apps that use the same pkgname */
-               for (guint j = 0; j < sources->len; j++) {
-                       const gchar *source = g_ptr_array_index (sources, j);
-                       gs_appstream_add_alternates_source (ids, item, source);
-               }
+       GPtrArray *sources = gs_app_get_sources (app);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) ids = NULL;
+       g_autoptr(GString) xpath = g_string_new (NULL);
+
+       /* actual ID */
+       xb_string_append_union (xpath, "components/component/id[text()='%s']",
+                               gs_app_get_id (app));
+
+       /* new ID -> old ID */
+       xb_string_append_union (xpath, "components/component/id[text()='%s']/../provides/id",
+                               gs_app_get_id (app));
+
+       /* old ID -> new ID */
+       xb_string_append_union (xpath, "components/component/provides/id[text()='%s']/../../id",
+                               gs_app_get_id (app));
+
+       /* find apps that use the same pkgname */
+       for (guint j = 0; j < sources->len; j++) {
+               const gchar *source = g_ptr_array_index (sources, j);
+               g_autofree gchar *source_safe = xb_string_escape (source);
+               xb_string_append_union (xpath,
+                                       "components/component/pkgname[text()='%s']/../id",
+                                       source_safe);
        }
 
-       /* add all results */
+       /* do a big query, and return all the unique results */
+       ids = xb_silo_query (silo, xpath->str, 0, &error_local);
+       if (ids == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint i = 0; i < ids->len; i++) {
-               const gchar *id = g_ptr_array_index (ids, i);
-               g_autoptr(GsApp) app2 = gs_app_new (id);
+               XbNode *n = g_ptr_array_index (ids, i);
+               g_autoptr(GsApp) app2 = NULL;
+               app2 = gs_app_new (xb_node_get_text (n));
                gs_app_add_quirk (app2, GS_APP_QUIRK_IS_WILDCARD);
                gs_app_list_add (list, app2);
        }
@@ -1073,100 +1319,138 @@ gs_appstream_add_alternates (GsPlugin *plugin,
 
 gboolean
 gs_appstream_add_featured (GsPlugin *plugin,
-                          AsStore *store,
+                          XbSilo *silo,
                           GsAppList *list,
                           GCancellable *cancellable,
                           GError **error)
 {
-       GPtrArray *array = as_store_get_apps (store);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) array = NULL;
+
+       /* find out how many packages are in each category */
+       array = xb_silo_query (silo,
+                              "components/component/custom/"
+                              "value[@key='GnomeSoftware::FeatureTile-css']/../..",
+                              0, &error_local);
+       if (array == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
        for (guint i = 0; i < array->len; i++) {
                g_autoptr(GsApp) app = NULL;
-               AsApp *item = g_ptr_array_index (array, i);
-               if (as_app_get_id (item) == NULL)
+               XbNode *component = g_ptr_array_index (array, i);
+               const gchar *component_id = xb_node_query_text (component, "id", NULL);
+               if (component_id == NULL)
                        continue;
-               if (as_app_get_metadata_item (item, "GnomeSoftware::FeatureTile-css") == NULL)
-                       continue;
-               app = gs_app_new (as_app_get_id (item));
+               app = gs_app_new (component_id);
                gs_app_add_quirk (app, GS_APP_QUIRK_IS_WILDCARD);
-               gs_appstream_copy_metadata (app, item);
+               if (!gs_appstream_copy_metadata (app, component, error))
+                       return FALSE;
                gs_app_list_add (list, app);
        }
        return TRUE;
 }
 
 void
-gs_appstream_add_extra_info (GsPlugin *plugin, AsApp *app)
+gs_appstream_component_add_keyword (XbBuilderNode *component, const gchar *str)
 {
-       const gchar *tmp;
-       g_autoptr(AsIcon) icon = NULL;
+       g_autoptr(XbBuilderNode) keyword = NULL;
+       g_autoptr(XbBuilderNode) keywords = NULL;
+
+       /* create <keywords> if it does not already exist */
+       keywords = xb_builder_node_get_child (component, "keywords", NULL);
+       if (keywords == NULL)
+               keywords = xb_builder_node_insert (component, "keywords", NULL);
+
+       /* create <keyword>str</keyword> if it does not already exist */
+       keyword = xb_builder_node_get_child (keywords, "keyword", str);
+       if (keyword == NULL) {
+               keyword = xb_builder_node_insert (keywords, "keyword", NULL);
+               xb_builder_node_set_text (keyword, str, -1);
+       }
+}
 
-       /* 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;
+void
+gs_appstream_component_add_category (XbBuilderNode *component, const gchar *str)
+{
+       g_autoptr(XbBuilderNode) category = NULL;
+       g_autoptr(XbBuilderNode) categories = NULL;
+
+       /* create <categories> if it does not already exist */
+       categories = xb_builder_node_get_child (component, "categories", NULL);
+       if (categories == NULL)
+               categories = xb_builder_node_insert (component, "categories", NULL);
+
+       /* create <category>str</category> if it does not already exist */
+       category = xb_builder_node_get_child (categories, "category", str);
+       if (category == NULL) {
+               category = xb_builder_node_insert (categories, "category", NULL);
+               xb_builder_node_set_text (category, str, -1);
        }
+}
+
+void
+gs_appstream_component_add_icon (XbBuilderNode *component, const gchar *str)
+{
+       g_autoptr(XbBuilderNode) icon = NULL;
+
+       /* create <icon>str</icon> if it does not already exist */
+       icon = xb_builder_node_get_child (component, "icon", NULL);
+       if (icon == NULL) {
+               icon = xb_builder_node_insert (component, "icon",
+                                              "type", "stock",
+                                              NULL);
+               xb_builder_node_set_text (icon, str, -1);
+       }
+}
+
+void
+gs_appstream_component_add_extra_info (GsPlugin *plugin, XbBuilderNode *component)
+{
+       const gchar *kind = xb_builder_node_get_attr (component, "type");
 
        /* add the gnome-software-specific 'Addon' group and ensure they
         * all have an icon set */
-       switch (as_app_get_kind (app)) {
+       switch (as_app_kind_from_string (kind)) {
+       case AS_APP_KIND_WEB_APP:
+               gs_appstream_component_add_keyword (component, kind);
+               break;
        case AS_APP_KIND_FONT:
-               as_app_add_category (app, "Addon");
-               as_app_add_category (app, "Font");
+               gs_appstream_component_add_category (component, "Addon");
+               gs_appstream_component_add_category (component, "Font");
                break;
        case AS_APP_KIND_SHELL_EXTENSION:
-               as_app_add_category (app, "Addon");
-               as_app_add_category (app, "ShellExtension");
-               if (g_hash_table_size (as_app_get_comments (app)) == 0)
-                       as_app_set_comment (app, NULL, "GNOME Shell Extension");
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "application-x-addon-symbolic");
-               as_app_add_icon (app, icon);
+               gs_appstream_component_add_category (component, "Addon");
+               gs_appstream_component_add_category (component, "ShellExtension");
+               gs_appstream_component_add_icon (component, "application-x-addon-symbolic");
                break;
        case AS_APP_KIND_DRIVER:
-               as_app_add_category (app, "Addon");
-               as_app_add_category (app, "Driver");
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "application-x-firmware-symbolic");
-               as_app_add_icon (app, icon);
+               gs_appstream_component_add_category (component, "Addon");
+               gs_appstream_component_add_category (component, "Driver");
+               gs_appstream_component_add_icon (component, "application-x-firmware-symbolic");
                break;
        case AS_APP_KIND_LOCALIZATION:
-               as_app_add_category (app, "Addon");
-               as_app_add_category (app, "Localization");
-               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);
+               gs_appstream_component_add_category (component, "Addon");
+               gs_appstream_component_add_category (component, "Localization");
+               gs_appstream_component_add_icon (component, "accessories-dictionary-symbolic");
                break;
        case AS_APP_KIND_CODEC:
-               as_app_add_category (app, "Addon");
-               as_app_add_category (app, "Codec");
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "application-x-addon");
-               as_app_add_icon (app, icon);
+               gs_appstream_component_add_category (component, "Addon");
+               gs_appstream_component_add_category (component, "Codec");
+               gs_appstream_component_add_icon (component, "application-x-addon");
                break;
        case AS_APP_KIND_INPUT_METHOD:
-               as_app_add_category (app, "Addon");
-               as_app_add_category (app, "InputSource");
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "system-run-symbolic");
-               as_app_add_icon (app, icon);
+               gs_appstream_component_add_keyword (component, kind);
+               gs_appstream_component_add_category (component, "Addon");
+               gs_appstream_component_add_category (component, "InputSource");
+               gs_appstream_component_add_icon (component, "system-run-symbolic");
                break;
        case AS_APP_KIND_FIRMWARE:
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "system-run-symbolic");
-               as_app_add_icon (app, icon);
+               gs_appstream_component_add_icon (component, "system-run-symbolic");
                break;
        default:
                break;
diff --git a/plugins/core/gs-appstream.h b/plugins/core/gs-appstream.h
index 29ccc11a..44ba1132 100644
--- a/plugins/core/gs-appstream.h
+++ b/plugins/core/gs-appstream.h
@@ -23,57 +23,67 @@
 #define __APPSTREAM_COMMON_H
 
 #include <gnome-software.h>
+#include <xmlb.h>
 
 G_BEGIN_DECLS
 
 GsApp          *gs_appstream_create_app                (GsPlugin       *plugin,
-                                                        AsApp          *item,
+                                                        XbSilo         *silo,
+                                                        XbNode         *component,
                                                         GError         **error);
 gboolean        gs_appstream_refine_app                (GsPlugin       *plugin,
                                                         GsApp          *app,
-                                                        AsApp          *item,
+                                                        XbSilo         *silo,
+                                                        XbNode         *component,
+                                                        GsPluginRefineFlags flags,
                                                         GError         **error);
-gboolean        gs_appstream_store_search              (GsPlugin       *plugin,
-                                                        AsStore        *store,
+gboolean        gs_appstream_search                    (GsPlugin       *plugin,
+                                                        XbSilo         *silo,
                                                         gchar          **values,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-gboolean        gs_appstream_store_add_categories      (GsPlugin       *plugin,
-                                                        AsStore        *store,
+gboolean        gs_appstream_add_categories            (GsPlugin       *plugin,
+                                                        XbSilo         *silo,
                                                         GPtrArray      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-gboolean        gs_appstream_store_add_category_apps   (GsPlugin       *plugin,
-                                                        AsStore        *store,
+gboolean        gs_appstream_add_category_apps         (GsPlugin       *plugin,
+                                                        XbSilo         *silo,
                                                         GsCategory     *category,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
 gboolean        gs_appstream_add_popular               (GsPlugin       *plugin,
-                                                        AsStore        *store,
+                                                        XbSilo         *silo,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
 gboolean        gs_appstream_add_featured              (GsPlugin       *plugin,
-                                                        AsStore        *store,
+                                                        XbSilo         *silo,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
 gboolean        gs_appstream_add_alternates            (GsPlugin       *plugin,
-                                                        AsStore        *store,
+                                                        XbSilo         *silo,
                                                         GsApp          *app,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
 gboolean        gs_appstream_add_recent                (GsPlugin       *plugin,
-                                                        AsStore        *store,
+                                                        XbSilo         *silo,
                                                         GsAppList      *list,
                                                         guint64         age,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-void            gs_appstream_add_extra_info            (GsPlugin       *plugin,
-                                                        AsApp          *app);
+void            gs_appstream_component_add_extra_info  (GsPlugin       *plugin,
+                                                        XbBuilderNode  *component);
+void            gs_appstream_component_add_keyword     (XbBuilderNode  *component,
+                                                        const gchar    *str);
+void            gs_appstream_component_add_category    (XbBuilderNode  *component,
+                                                        const gchar    *str);
+void            gs_appstream_component_add_icon        (XbBuilderNode  *component,
+                                                        const gchar    *str);
 
 G_END_DECLS
 
diff --git a/plugins/core/gs-plugin-appstream.c b/plugins/core/gs-plugin-appstream.c
index b38dd1e8..61b8a416 100644
--- a/plugins/core/gs-plugin-appstream.c
+++ b/plugins/core/gs-plugin-appstream.c
@@ -24,6 +24,7 @@
 
 #include <glib/gi18n.h>
 #include <gnome-software.h>
+#include <xmlb.h>
 
 #include "gs-appstream.h"
 
@@ -39,255 +40,472 @@
  */
 
 struct GsPluginData {
-       AsStore                 *store;
-       GHashTable              *app_hash_old;
-       guint                    store_changed_id;
+       XbSilo                  *silo;
        GSettings               *settings;
 };
 
-#define GS_PLUGIN_NUMBER_CHANGED_RELOAD        10
-
-static GHashTable *
-gs_plugin_appstream_create_app_hash (AsStore *store)
+void
+gs_plugin_initialize (GsPlugin *plugin)
 {
-       GHashTable *hash;
-       GPtrArray *apps;
-       guint i;
+       GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
 
-       hash = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                     g_free, (GDestroyNotify) g_object_unref);
-       apps = as_store_get_apps (store);
-       for (i = 0; i < apps->len; i++) {
-               AsApp *app = g_ptr_array_index (apps, i);
-               gchar *key = g_strdup (as_app_get_id (app));
-               g_hash_table_insert (hash, key, g_object_ref (app));
-       }
-       return hash;
+       /* need package name */
+       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dpkg");
+
+       /* require settings */
+       priv->settings = g_settings_new ("org.gnome.software");
 }
 
-static void
-gs_plugin_detect_reload_apps (GsPlugin *plugin)
+void
+gs_plugin_destroy (GsPlugin *plugin)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       AsApp *item;
-       GsApp *app;
-       guint cnt = 0;
-       g_autoptr(GHashTable) app_hash = NULL;
-       g_autoptr(GList) keys = NULL;
-       g_autoptr(GList) keys_old = NULL;
-
-       /* find packages that have been added */
-       app_hash = gs_plugin_appstream_create_app_hash (priv->store);
-       keys = g_hash_table_get_keys (app_hash);
-       for (GList *l = keys; l != NULL; l = l->next) {
-               const gchar *key = l->data;
-               item = g_hash_table_lookup (priv->app_hash_old, key);
-               if (item == NULL) {
-                       item = g_hash_table_lookup (app_hash, key);
-                       app = gs_plugin_cache_lookup (plugin,
-                                                     as_app_get_unique_id (item));
-                       if (app != NULL)
-                               g_debug ("added GsApp %s", gs_app_get_id (app));
-                       cnt++;
+       g_object_unref (priv->silo);
+       g_object_unref (priv->settings);
+}
+
+static gboolean
+gs_plugin_appstream_upgrade_cb (XbBuilderSource *self,
+                               XbBuilderNode *bn,
+                               gpointer user_data,
+                               GError **error)
+{
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "application") == 0) {
+               g_autoptr(XbBuilderNode) id = xb_builder_node_get_child (bn, "id", NULL);
+               g_autofree gchar *kind = NULL;
+               if (id != NULL) {
+                       kind = g_strdup (xb_builder_node_get_attr (id, "type"));
+                       xb_builder_node_remove_attr (id, "type");
                }
+               if (kind != NULL)
+                       xb_builder_node_set_attr (bn, "type", kind);
+               xb_builder_node_set_element (bn, "component");
+       } else if (g_strcmp0 (xb_builder_node_get_element (bn), "metadata") == 0) {
+               xb_builder_node_set_element (bn, "custom");
        }
+       return TRUE;
+}
+
+static gboolean
+gs_plugin_appstream_add_pkgname_cb (XbBuilderSource *self,
+                                   XbBuilderNode *bn,
+                                   gpointer user_data,
+                                   GError **error)
+{
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0)
+               xb_builder_node_insert_text (bn, "pkgname", "", NULL);
+       return TRUE;
+}
+
+static gboolean
+gs_plugin_appstream_add_icons_cb (XbBuilderSource *self,
+                                 XbBuilderNode *bn,
+                                 gpointer user_data,
+                                 GError **error)
+{
+       GsPlugin *plugin = GS_PLUGIN (user_data);
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "component") != 0)
+               return TRUE;
+       gs_appstream_component_add_extra_info (plugin, bn);
+       return TRUE;
+}
 
-       /* find packages that have been removed */
-       keys_old = g_hash_table_get_keys (priv->app_hash_old);
-       for (GList *l = keys_old; l != NULL; l = l->next) {
-               const gchar *key = l->data;
-               item = g_hash_table_lookup (app_hash, key);
-               if (item == NULL) {
-                       item = g_hash_table_lookup (priv->app_hash_old, key);
-                       app = gs_plugin_cache_lookup (plugin,
-                                                     as_app_get_unique_id (item));
-                       if (app != NULL)
-                               g_debug ("removed GsApp %s", gs_app_get_id (app));
-                       cnt++;
+static gboolean
+gs_plugin_appstream_add_origin_keyword_cb (XbBuilderSource *self,
+                                          XbBuilderNode *bn,
+                                          gpointer user_data,
+                                          GError **error)
+{
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "components") == 0) {
+               const gchar *origin = xb_builder_node_get_attr (bn, "origin");
+               GPtrArray *components = xb_builder_node_get_children (bn);
+               if (origin == NULL || origin[0] == '\0')
+                       return TRUE;
+               g_debug ("origin %s has %u components", origin, components->len);
+               if (components->len < 200) {
+                       for (guint i = 0; i < components->len; i++) {
+                               XbBuilderNode *component = g_ptr_array_index (components, i);
+                               gs_appstream_component_add_keyword (component, origin);
+                       }
                }
        }
+       return TRUE;
+}
+
+static gboolean
+gs_plugin_appstream_load_appdata_fn (GsPlugin *plugin,
+                                    XbBuilder *builder,
+                                    const gchar *filename,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       g_autoptr(GFile) file = g_file_new_for_path (filename);
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
 
-       /* replace if any changes */
-       if (cnt > 0) {
-               if (priv->app_hash_old != NULL)
-                       g_hash_table_unref (priv->app_hash_old);
-               priv->app_hash_old = g_hash_table_ref (app_hash);
+       /* add source */
+       if (!xb_builder_source_load_file (source, file,
+                                         XB_BUILDER_SOURCE_FLAG_WATCH_FILE,
+                                         cancellable,
+                                         error)) {
+               return FALSE;
        }
 
-       /* invalidate all if a large number of apps changed */
-       if (cnt > GS_PLUGIN_NUMBER_CHANGED_RELOAD) {
-               g_debug ("%u is more than %i AsApps changed",
-                        cnt, GS_PLUGIN_NUMBER_CHANGED_RELOAD);
-               gs_plugin_reload (plugin);
+       /* fix up any legacy installed files */
+       xb_builder_source_add_node_func (source, "AppStreamUpgrade",
+                                        gs_plugin_appstream_upgrade_cb,
+                                        plugin, NULL);
+
+       /* success */
+       xb_builder_import_source (builder, source);
+       return TRUE;
+}
+
+static gboolean
+gs_plugin_appstream_load_appdata (GsPlugin *plugin,
+                                 XbBuilder *builder,
+                                 const gchar *path,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       const gchar *fn;
+       g_autoptr(GDir) dir = g_dir_open (path, 0, error);
+       g_autoptr(GFile) parent = g_file_new_for_path (path);
+       if (!g_file_query_exists (parent, cancellable))
+               return TRUE;
+       if (dir == NULL)
+               return FALSE;
+       while ((fn = g_dir_read_name (dir)) != NULL) {
+               if (g_str_has_suffix (fn, ".appdata.xml") ||
+                   g_str_has_suffix (fn, ".metainfo.xml")) {
+                       g_autofree gchar *filename = g_build_filename (path, fn, NULL);
+                       g_autoptr(GError) error_local = NULL;
+                       if (!gs_plugin_appstream_load_appdata_fn (plugin,
+                                                                 builder,
+                                                                 filename,
+                                                                 cancellable,
+                                                                 &error_local)) {
+                               g_debug ("ignoring %s: %s", filename, error_local->message);
+                               continue;
+                       }
+               }
        }
+
+       /* success */
+       return TRUE;
 }
 
-static void
-gs_plugin_appstream_store_changed_cb (AsStore *store, GsPlugin *plugin)
+static GInputStream *
+gs_plugin_appstream_load_desktop_cb (XbBuilderSource *self,
+                                    GFile *file,
+                                    gpointer user_data,
+                                    GCancellable *cancellable,
+                                    GError **error)
 {
-       g_debug ("AppStream metadata changed");
+       g_autofree gchar *fn = g_file_get_path (file);
+       g_autoptr(AsApp) app = as_app_new ();
+       GString *xml;
+       if (!as_app_parse_file (app, fn, AS_APP_PARSE_FLAG_USE_FALLBACKS, error))
+               return NULL;
+       xml = as_app_to_xml (app, error);
+       if (xml == NULL)
+               return NULL;
+       return g_memory_input_stream_new_from_data (g_string_free (xml, FALSE), -1, g_free);
+}
 
-       /* send ::reload-apps */
-       gs_plugin_detect_reload_apps (plugin);
+static gboolean
+gs_plugin_appstream_load_desktop_fn (GsPlugin *plugin,
+                                    XbBuilder *builder,
+                                    const gchar *filename,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       g_autoptr(GFile) file = g_file_new_for_path (filename);
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+
+       /* add support for desktop files */
+       xb_builder_source_add_converter (source,
+                                        "application/x-desktop",
+                                        gs_plugin_appstream_load_desktop_cb,
+                                        NULL, NULL);
+
+       /* add a dummy package name */
+       xb_builder_source_add_node_func (source, "AddDesktopPackageName",
+                                        gs_plugin_appstream_add_pkgname_cb,
+                                        plugin, NULL);
+
+       /* add source */
+       if (!xb_builder_source_load_file (source, file,
+                                         XB_BUILDER_SOURCE_FLAG_WATCH_FILE,
+                                         cancellable,
+                                         error)) {
+               return FALSE;
+       }
 
-       /* all the UI is reloaded as something external has happened */
-       if (!gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_RUNNING_OTHER))
-               gs_plugin_reload (plugin);
+       /* success */
+       xb_builder_import_source (builder, source);
+       return TRUE;
 }
 
-static void
-gs_plugin_appstream_store_app_added_cb (AsStore *store,
-                                       AsApp *app,
-                                       GsPlugin *plugin)
+static gboolean
+gs_plugin_appstream_load_desktop (GsPlugin *plugin,
+                                 XbBuilder *builder,
+                                 const gchar *path,
+                                 GCancellable *cancellable,
+                                 GError **error)
 {
-       gs_appstream_add_extra_info (plugin, app);
+       const gchar *fn;
+       g_autoptr(GDir) dir = g_dir_open (path, 0, error);
+       g_autoptr(GFile) parent = g_file_new_for_path (path);
+       if (!g_file_query_exists (parent, cancellable))
+               return TRUE;
+       if (dir == NULL)
+               return FALSE;
+       while ((fn = g_dir_read_name (dir)) != NULL) {
+               if (g_str_has_suffix (fn, ".desktop")) {
+                       g_autofree gchar *filename = g_build_filename (path, fn, NULL);
+                       g_autoptr(GError) error_local = NULL;
+                       if (g_strcmp0 (fn, "mimeinfo.cache") == 0)
+                               continue;
+                       if (!gs_plugin_appstream_load_desktop_fn (plugin,
+                                                                 builder,
+                                                                 filename,
+                                                                 cancellable,
+                                                                 &error_local)) {
+                               g_debug ("ignoring %s: %s", filename, error_local->message);
+                               continue;
+                       }
+               }
+       }
+
+       /* success */
+       return TRUE;
 }
 
-static void
-gs_plugin_appstream_store_app_removed_cb (AsStore *store,
-                                         AsApp *app,
-                                         GsPlugin *plugin)
+static GInputStream *
+gs_plugin_appstream_load_dep11_cb (XbBuilderSource *self,
+                                  GFile *file,
+                                  gpointer user_data,
+                                  GCancellable *cancellable,
+                                  GError **error)
 {
-       g_debug ("AppStream app was removed, doing delete from global cache");
-       gs_plugin_cache_remove (plugin, as_app_get_unique_id (app));
+       GString *xml;
+       g_autoptr(AsStore) store = as_store_new ();
+       if (!as_store_from_file (store, file, NULL, cancellable, error))
+               return FALSE;
+       xml = as_store_to_xml (store, AS_NODE_INSERT_FLAG_NONE);
+       if (xml == NULL)
+               return NULL;
+       return g_memory_input_stream_new_from_data (g_string_free (xml, FALSE), -1, g_free);
 }
 
-void
-gs_plugin_initialize (GsPlugin *plugin)
+static gboolean
+gs_plugin_appstream_load_appstream_fn (GsPlugin *plugin,
+                                      XbBuilder *builder,
+                                      const gchar *filename,
+                                      GCancellable *cancellable,
+                                      GError **error)
 {
-       GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
-       priv->store = as_store_new ();
-       g_signal_connect (priv->store, "app-added",
-                         G_CALLBACK (gs_plugin_appstream_store_app_added_cb),
-                         plugin);
-       g_signal_connect (priv->store, "app-removed",
-                         G_CALLBACK (gs_plugin_appstream_store_app_removed_cb),
-                         plugin);
-       as_store_set_add_flags (priv->store,
-                               AS_STORE_ADD_FLAG_USE_UNIQUE_ID |
-                               AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS |
-                               AS_STORE_ADD_FLAG_USE_MERGE_HEURISTIC);
-       as_store_set_watch_flags (priv->store,
-                                 AS_STORE_WATCH_FLAG_ADDED |
-                                 AS_STORE_WATCH_FLAG_REMOVED);
-       as_store_set_search_match (priv->store,
-                                  AS_APP_SEARCH_MATCH_MIMETYPE |
-                                  AS_APP_SEARCH_MATCH_PKGNAME |
-                                  AS_APP_SEARCH_MATCH_COMMENT |
-                                  AS_APP_SEARCH_MATCH_NAME |
-                                  AS_APP_SEARCH_MATCH_KEYWORD |
-                                  AS_APP_SEARCH_MATCH_ID);
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GFile) file = g_file_new_for_path (filename);
+       g_autoptr(XbBuilderNode) info = NULL;
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+
+       /* add support for DEP-11 files */
+       xb_builder_source_add_converter (source,
+                                        "application/x-yaml",
+                                        gs_plugin_appstream_load_dep11_cb,
+                                        NULL, NULL);
+
+       /* add source */
+       if (!xb_builder_source_load_file (source, file,
+                                         XB_BUILDER_SOURCE_FLAG_WATCH_FILE |
+                                         XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT,
+                                         cancellable,
+                                         error)) {
+               return FALSE;
+       }
 
-       /* need package name */
-       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "dpkg");
+       /* add metadata */
+       info = xb_builder_node_insert (NULL, "info", NULL);
+       xb_builder_node_insert_text (info, "scope", "system", NULL);
+       xb_builder_node_insert_text (info, "filename", filename, NULL);
+       xb_builder_source_set_info (source, info);
 
-       /* require settings */
-       priv->settings = g_settings_new ("org.gnome.software");
+       /* add missing icons as required */
+       xb_builder_source_add_node_func (source, "AddIcons",
+                                        gs_plugin_appstream_add_icons_cb,
+                                        plugin, NULL);
+
+       /* fix up any legacy installed files */
+       xb_builder_source_add_node_func (source, "AppStreamUpgrade",
+                                        gs_plugin_appstream_upgrade_cb,
+                                        plugin, NULL);
+
+       /* add the origin as a search keyword for small repos */
+       xb_builder_source_add_node_func (source, "AddOriginKeyword",
+                                        gs_plugin_appstream_add_origin_keyword_cb,
+                                        plugin, NULL);
+
+       /* success */
+       xb_builder_import_source (builder, source);
+       return TRUE;
 }
 
-void
-gs_plugin_destroy (GsPlugin *plugin)
+static gboolean
+gs_plugin_appstream_load_appstream (GsPlugin *plugin,
+                                   XbBuilder *builder,
+                                   const gchar *path,
+                                   GCancellable *cancellable,
+                                   GError **error)
 {
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-       if (priv->store_changed_id != 0)
-               g_signal_handler_disconnect (priv->store, priv->store_changed_id);
-       if (priv->app_hash_old != NULL)
-               g_hash_table_unref (priv->app_hash_old);
-       g_object_unref (priv->store);
-       g_object_unref (priv->settings);
-}
+       const gchar *fn;
+       g_autoptr(GDir) dir = NULL;
+       g_autoptr(GFile) parent = g_file_new_for_path (path);
 
-/*
- * Returns: A hash table with a string key of the application origin and a
- * value of the guint percentage of the store is made up by that origin.
- */
-static GHashTable *
-gs_plugin_appstream_get_origins_hash (GPtrArray *array)
-{
-       AsApp *app;
-       GHashTable *origins = NULL;
-       const gchar *tmp;
-       gdouble perc;
-       guint *cnt;
-       guint i;
-       g_autoptr(GList) keys = NULL;
-
-       /* create a hash table with origin:cnt */
-       origins = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                        g_free, g_free);
-       for (i = 0; i < array->len; i++) {
-               app = g_ptr_array_index (array, i);
-               tmp = as_app_get_origin (app);
-               if (tmp == NULL)
-                       continue;
-               cnt = g_hash_table_lookup (origins, tmp);
-               if (cnt == NULL) {
-                       cnt = g_new0 (guint, 1);
-                       g_hash_table_insert (origins, g_strdup (tmp), cnt);
+       /* parent patch does not exist */
+       if (!g_file_query_exists (parent, cancellable))
+               return TRUE;
+       dir = g_dir_open (path, 0, error);
+       if (dir == NULL)
+               return FALSE;
+       while ((fn = g_dir_read_name (dir)) != NULL) {
+               if (g_str_has_suffix (fn, ".xml") ||
+                   g_str_has_suffix (fn, ".yml") ||
+                   g_str_has_suffix (fn, ".xml.gz")) {
+                       g_autofree gchar *filename = g_build_filename (path, fn, NULL);
+                       g_autoptr(GError) error_local = NULL;
+                       if (!gs_plugin_appstream_load_appstream_fn (plugin,
+                                                                   builder,
+                                                                   filename,
+                                                                   cancellable,
+                                                                   &error_local)) {
+                               g_debug ("ignoring %s: %s", filename, error_local->message);
+                               continue;
+                       }
                }
-               (*cnt)++;
-       }
-
-       /* convert the cnt to a percentage */
-       keys = g_hash_table_get_keys (origins);
-       for (GList *l = keys; l != NULL; l = l->next) {
-               tmp = l->data;
-               if (tmp == NULL || tmp[0] == '\0')
-                       continue;
-               cnt = g_hash_table_lookup (origins, tmp);
-               perc = (100.f / (gdouble) array->len) * (gdouble) (*cnt);
-               g_debug ("origin %s provides %u apps (%.0f%%)", tmp, *cnt, perc);
-               *cnt = (guint) perc;
        }
 
-       return origins;
+       /* success */
+       return TRUE;
 }
 
-gboolean
-gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+static gboolean
+gs_plugin_appstream_check_silo (GsPlugin *plugin,
+                               GCancellable *cancellable,
+                               GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       GPtrArray *items;
-       gboolean ret;
-       const gchar *tmp;
+       const gchar *locale;
        const gchar *test_xml;
-       const gchar *test_icon_root;
-       gboolean all_origin_keywords = g_getenv ("GS_SELF_TEST_ALL_ORIGIN_KEYWORDS") != NULL;
-       guint *perc;
-       guint i;
-       g_autoptr(GHashTable) origins = NULL;
-
-       /* Parse the XML */
-       if (g_getenv ("GNOME_SOFTWARE_PREFER_LOCAL") != NULL) {
-               as_store_set_add_flags (priv->store,
-                                       AS_STORE_ADD_FLAG_PREFER_LOCAL);
+       g_autofree gchar *blobfn = NULL;
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+       g_autoptr(XbNode) n = NULL;
+       g_autoptr(GFile) file = NULL;
+       g_autoptr(GPtrArray) parent_appdata = g_ptr_array_new_with_free_func (g_free);
+       g_autoptr(GPtrArray) parent_appstream = g_ptr_array_new_with_free_func (g_free);
+
+       /* everything is okay */
+       if (priv->silo != NULL && xb_silo_is_valid (priv->silo))
+               return TRUE;
+
+       /* drat! silo needs regenerating */
+       g_clear_object (&priv->silo);
+
+       /* verbose profiling */
+       if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+               xb_builder_set_profile_flags (builder,
+                                             XB_SILO_PROFILE_FLAG_XPATH |
+                                             XB_SILO_PROFILE_FLAG_DEBUG);
+       }
+
+       /* add current locales */
+       locale = g_getenv ("GS_SELF_TEST_LOCALE");
+       if (locale == NULL) {
+               const gchar *const *locales = g_get_language_names ();
+               for (guint i = 0; locales[i] != NULL; i++)
+                       xb_builder_add_locale (builder, locales[i]);
+       } else {
+               xb_builder_add_locale (builder, locale);
        }
 
        /* only when in self test */
        test_xml = g_getenv ("GS_SELF_TEST_APPSTREAM_XML");
        if (test_xml != NULL) {
-               test_icon_root = g_getenv ("GS_SELF_TEST_APPSTREAM_ICON_ROOT");
-               g_debug ("using self test data of %s... with icon root %s",
-                        test_xml, test_icon_root);
-               if (!as_store_from_xml (priv->store, test_xml, test_icon_root, error))
+               g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+               if (!xb_builder_source_load_xml (source, test_xml,
+                                                XB_BUILDER_SOURCE_FLAG_NONE,
+                                                error))
                        return FALSE;
+               xb_builder_source_add_node_func (source, "AddOriginKeywords",
+                                                gs_plugin_appstream_add_origin_keyword_cb,
+                                                plugin, NULL);
+               xb_builder_source_add_node_func (source, "AddIcons",
+                                                gs_plugin_appstream_add_icons_cb,
+                                                plugin, NULL);
+               xb_builder_import_source (builder, source);
        } else {
-               ret = as_store_load (priv->store,
-                                    AS_STORE_LOAD_FLAG_IGNORE_INVALID |
-                                    AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM |
-                                    AS_STORE_LOAD_FLAG_APP_INFO_USER |
-                                    AS_STORE_LOAD_FLAG_APPDATA |
-                                    AS_STORE_LOAD_FLAG_DESKTOP |
-                                    AS_STORE_LOAD_FLAG_APP_INSTALL,
-                                    cancellable,
-                                    error);
-               if (!ret) {
-                       gs_utils_error_convert_appstream (error);
+               /* add search paths */
+               g_ptr_array_add (parent_appstream,
+                                g_build_filename ("/usr/share", "app-info", "xmls", NULL));
+               g_ptr_array_add (parent_appstream,
+                                g_build_filename ("/usr/share", "app-info", "yaml", NULL));
+               g_ptr_array_add (parent_appdata,
+                                g_build_filename ("/usr/share", "appdata", NULL));
+               g_ptr_array_add (parent_appdata,
+                                g_build_filename ("/usr/share", "metainfo", NULL));
+
+               /* import all files */
+               for (guint i = 0; i < parent_appstream->len; i++) {
+                       const gchar *fn = g_ptr_array_index (parent_appstream, i);
+                       if (!gs_plugin_appstream_load_appstream (plugin, builder, fn,
+                                                                cancellable, error))
+                               return FALSE;
+               }
+               for (guint i = 0; i < parent_appdata->len; i++) {
+                       const gchar *fn = g_ptr_array_index (parent_appdata, i);
+                       if (!gs_plugin_appstream_load_appdata (plugin, builder, fn,
+                                                              cancellable, error))
+                               return FALSE;
+               }
+               if (!gs_plugin_appstream_load_desktop (plugin, builder,
+                                                      "/usr/share/applications",
+                                                      cancellable, error)) {
                        return FALSE;
                }
        }
-       items = as_store_get_apps (priv->store);
-       if (items->len == 0) {
+
+       /* create per-user cache */
+       blobfn = gs_utils_get_cache_filename ("appstream", "components.xmlb",
+                                             GS_UTILS_CACHE_FLAG_WRITEABLE,
+                                             error);
+       if (blobfn == NULL)
+               return FALSE;
+       file = g_file_new_for_path (blobfn);
+       g_debug ("ensuring %s", blobfn);
+       priv->silo = xb_builder_ensure (builder, file,
+                                       XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID |
+                                       XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+                                       NULL, error);
+       if (priv->silo == NULL)
+               return FALSE;
+
+       /* watch all directories too */
+       for (guint i = 0; i < parent_appstream->len; i++) {
+               const gchar *fn = g_ptr_array_index (parent_appstream, i);
+               g_autoptr(GFile) file_tmp = g_file_new_for_path (fn);
+               if (!xb_silo_watch_file (priv->silo, file_tmp, cancellable, error))
+                       return FALSE;
+       }
+       for (guint i = 0; i < parent_appdata->len; i++) {
+               const gchar *fn = g_ptr_array_index (parent_appdata, i);
+               g_autoptr(GFile) file_tmp = g_file_new_for_path (fn);
+               if (!xb_silo_watch_file (priv->silo, file_tmp, cancellable, error))
+                       return FALSE;
+       }
+
+       /* test we found something */
+       n = xb_silo_query_first (priv->silo, "components/component", NULL);
+       if (n == NULL) {
                g_warning ("No AppStream data, try 'make install-sample-data' in data/");
                g_set_error (error,
                             GS_PLUGIN_ERROR,
@@ -296,36 +514,17 @@ gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
                return FALSE;
        }
 
-       /* prime the cache */
-       priv->app_hash_old = gs_plugin_appstream_create_app_hash (priv->store);
-
-       /* watch for changes */
-       priv->store_changed_id =
-               g_signal_connect (priv->store, "changed",
-                                 G_CALLBACK (gs_plugin_appstream_store_changed_cb),
-                                 plugin);
-
-       /* add search terms for apps not in the main source */
-       origins = gs_plugin_appstream_get_origins_hash (items);
-       for (i = 0; i < items->len; i++) {
-               AsApp *app = g_ptr_array_index (items, i);
-               tmp = as_app_get_origin (app);
-               if (tmp == NULL || tmp[0] == '\0')
-                       continue;
-               perc = g_hash_table_lookup (origins, tmp);
-               if (*perc < 10 || all_origin_keywords) {
-                       g_debug ("adding keyword '%s' to %s",
-                                tmp, as_app_get_id (app));
-                       as_app_set_search_match (app,
-                                                as_store_get_search_match (priv->store) |
-                                                AS_APP_SEARCH_MATCH_ORIGIN);
-               }
-       }
-
-       /* rely on the store keeping itself updated */
+       /* success */
        return TRUE;
 }
 
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+       /* set up silo, compiling if required */
+       return gs_plugin_appstream_check_silo (plugin, cancellable, error);
+}
+
 gboolean
 gs_plugin_url_to_app (GsPlugin *plugin,
                      GsAppList *list,
@@ -334,10 +533,15 @@ gs_plugin_url_to_app (GsPlugin *plugin,
                      GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       AsApp *item;
        g_autofree gchar *path = NULL;
        g_autofree gchar *scheme = NULL;
+       g_autofree gchar *xpath = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(XbNode) component = NULL;
+
+       /* check silo is valid */
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
 
        /* not us */
        scheme = gs_utils_get_url_scheme (url);
@@ -346,20 +550,22 @@ gs_plugin_url_to_app (GsPlugin *plugin,
 
        /* create app */
        path = gs_utils_get_url_path (url);
-       item = as_store_get_app_by_id (priv->store, path);
-       if (item == NULL)
+       xpath = g_strdup_printf ("components/component/id[text()='%s']", path);
+       component = xb_silo_query_first (priv->silo, xpath, NULL);
+       if (component == NULL)
                return TRUE;
-       app = gs_appstream_create_app (plugin, item, error);
+       app = gs_appstream_create_app (plugin, priv->silo, component, error);
        if (app == NULL)
                return FALSE;
+       gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
        gs_app_list_add (list, app);
        return TRUE;
 }
 
 static void
-gs_plugin_appstream_set_compulsory_quirk (GsApp *app, AsApp *item)
+gs_plugin_appstream_set_compulsory_quirk (GsApp *app, XbNode *component)
 {
-       GPtrArray *array;
+       g_autoptr(GPtrArray) array = NULL;
        const gchar *current_desktop;
 
        /*
@@ -377,12 +583,15 @@ gs_plugin_appstream_set_compulsory_quirk (GsApp *app, AsApp *item)
         * compulsory apps for such compound desktops if they want.
         *
         */
-       array = as_app_get_compulsory_for_desktops (item);
+       array = xb_node_query (component, "compulsory_for_desktop", 0, NULL);
+       if (array == NULL)
+               return;
        current_desktop = g_getenv ("XDG_CURRENT_DESKTOP");
        if (current_desktop != NULL) {
                g_auto(GStrv) xdg_current_desktops = g_strsplit (current_desktop, ":", 0);
                for (guint i = 0; i < array->len; i++) {
-                       const gchar *tmp = g_ptr_array_index (array, i);
+                       XbNode *n = g_ptr_array_index (array, i);
+                       const gchar *tmp = xb_node_get_text (n);
                        /* if the value has a :, check the whole string */
                        if (g_strstr_len (tmp, -1, ":")) {
                                if (g_strcmp0 (current_desktop, tmp) == 0) {
@@ -398,56 +607,73 @@ gs_plugin_appstream_set_compulsory_quirk (GsApp *app, AsApp *item)
        }
 }
 
+static gboolean
+gs_plugin_appstream_refine_state (GsPlugin *plugin, GsApp *app, GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(XbNode) component = NULL;
+
+       xpath = g_strdup_printf ("component/id[text()='%s']", gs_app_get_id (app));
+       component = xb_silo_query_first (priv->silo, xpath, &error_local);
+       if (component == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       return TRUE;
+}
+
 static gboolean
 gs_plugin_refine_from_id (GsPlugin *plugin,
                          GsApp *app,
+                         GsPluginRefineFlags flags,
                          gboolean *found,
                          GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       const gchar *unique_id;
-       AsApp *item;
-
-       /* unfound */
-       *found = FALSE;
+       const gchar *id;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GString) xpath = g_string_new (NULL);
+       g_autoptr(GPtrArray) components = NULL;
 
-       /* find anything that matches the ID */
-       unique_id = gs_app_get_unique_id (app);
-       if (unique_id == NULL)
+       /* not enough info to find */
+       id = gs_app_get_id (app);
+       if (id == NULL)
                return TRUE;
 
-       /* nothing found */
-       g_debug ("searching appstream for %s", unique_id);
-       item = as_store_get_app_by_unique_id (priv->store, unique_id,
-                                             AS_STORE_SEARCH_FLAG_USE_WILDCARDS);
-       if (item == NULL) {
-               GPtrArray *apps = as_store_get_apps (priv->store);
-               g_debug ("no app with ID %s found in system appstream", unique_id);
-               for (guint i = 0; i < apps->len; i++) {
-                       item = g_ptr_array_index (apps, i);
-                       if (g_strcmp0 (as_app_get_id (item), gs_app_get_id (app)) != 0)
-                               continue;
-                       g_debug ("possible match: %s",
-                                as_app_get_unique_id (item));
-               }
-
-               /* fall back to trying to get a merge app */
-               apps = as_store_get_apps_by_id_merge (priv->store, gs_app_get_id (app));
-               if (apps != NULL) {
-                       for (guint i = 0; i < apps->len; i++) {
-                               item = g_ptr_array_index (apps, i);
-                               if (!gs_appstream_refine_app (plugin, app, item, error))
-                                       return FALSE;
-                               gs_plugin_appstream_set_compulsory_quirk (app, item);
-                       }
-               }
-               return TRUE;
+       /* look in AppStream then fall back to AppData */
+       xb_string_append_union (xpath, "components/component/id[text()='%s']/../pkgname/..", id);
+       xb_string_append_union (xpath, "component/id[text()='%s']/../pkgname/..", id);
+       components = xb_silo_query (priv->silo, xpath->str, 0, &error_local);
+       if (components == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < components->len; i++) {
+               XbNode *component = g_ptr_array_index (components, i);
+               if (!gs_appstream_refine_app (plugin, app, priv->silo,
+                                             component, flags, error))
+                       return FALSE;
+               gs_plugin_appstream_set_compulsory_quirk (app, component);
        }
 
-       /* set new properties */
-       if (!gs_appstream_refine_app (plugin, app, item, error))
-               return FALSE;
+       /* if an installed desktop or appdata file exists set to installed */
+       if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
+               if (!gs_plugin_appstream_refine_state (plugin, app, error))
+                       return FALSE;
+       }
 
+       /* success */
        *found = TRUE;
        return TRUE;
 }
@@ -455,56 +681,45 @@ gs_plugin_refine_from_id (GsPlugin *plugin,
 static gboolean
 gs_plugin_refine_from_pkgname (GsPlugin *plugin,
                               GsApp *app,
+                              GsPluginRefineFlags flags,
                               GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       AsApp *item = NULL;
-       GPtrArray *sources;
-
-       /* find anything that matches the ID */
-       sources = gs_app_get_sources (app);
-       for (guint i = 0; i < sources->len && item == NULL; i++) {
-               const gchar *pkgname = g_ptr_array_index (sources, i);
-               item = as_store_get_app_by_pkgname (priv->store, pkgname);
-               if (item == NULL)
-                       g_debug ("no AppStream match for {pkgname} %s", pkgname);
-       }
+       GPtrArray *sources = gs_app_get_sources (app);
+       g_autoptr(GError) error_local = NULL;
 
-       /* nothing found */
-       if (item == NULL)
+       /* not enough info to find */
+       if (sources->len == 0)
                return TRUE;
 
-       /* set new properties */
-       return gs_appstream_refine_app (plugin, app, item, error);
-}
-
-gboolean
-gs_plugin_add_distro_upgrades (GsPlugin *plugin,
-                              GsAppList *list,
-                              GCancellable *cancellable,
-                              GError **error)
-{
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-       AsApp *item;
-       GPtrArray *array;
-       guint i;
-
-       /* find any upgrades */
-       array = as_store_get_apps (priv->store);
-       for (i = 0; i < array->len; i++) {
-               g_autoptr(GsApp) app = NULL;
-               item = g_ptr_array_index (array, i);
-               if (as_app_get_kind (item) != AS_APP_KIND_OS_UPDATE)
-                       continue;
-
-               /* create */
-               app = gs_appstream_create_app (plugin, item, error);
-               if (app == NULL)
+       /* find all apps when matching any prefixes */
+       for (guint j = 0; j < sources->len; j++) {
+               const gchar *pkgname = g_ptr_array_index (sources, j);
+               g_autofree gchar *xpath = NULL;
+               g_autoptr(GPtrArray) components = NULL;
+
+               g_debug ("searching appstream for pkg %s", pkgname);
+               xpath = g_strdup_printf ("components/component/pkgname[text()='%s']/..",
+                                        pkgname);
+               components = xb_silo_query (priv->silo, xpath, 0, &error_local);
+               if (components == NULL) {
+                       if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                               continue;
+                       if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                               continue;
+                       g_propagate_error (error, g_steal_pointer (&error_local));
                        return FALSE;
-               gs_app_set_kind (app, AS_APP_KIND_OS_UPGRADE);
-               gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-               gs_app_list_add (list, app);
+               }
+               for (guint i = 0; i < components->len; i++) {
+                       XbNode *component = g_ptr_array_index (components, i);
+                       if (!gs_appstream_refine_app (plugin, app, priv->silo,
+                                                     component, flags, error))
+                               return FALSE;
+                       gs_plugin_appstream_set_compulsory_quirk (app, component);
+               }
        }
+
+       /* success */
        return TRUE;
 }
 
@@ -519,16 +734,18 @@ gs_plugin_refine_app (GsPlugin *plugin,
 
        /* not us */
        if (gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_PACKAGE &&
-           gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_UNKNOWN) {
-               g_debug ("not a package, ignoring");
+           gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_UNKNOWN)
                return TRUE;
-       }
 
-       /* find by ID then package name */
-       if (!gs_plugin_refine_from_id (plugin, app, &found, error))
+       /* check silo is valid */
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+
+       /* find by ID then fall back to package name */
+       if (!gs_plugin_refine_from_id (plugin, app, flags, &found, error))
                return FALSE;
        if (!found) {
-               if (!gs_plugin_refine_from_pkgname (plugin, app, error))
+               if (!gs_plugin_refine_from_pkgname (plugin, app, flags, error))
                        return FALSE;
        }
 
@@ -540,58 +757,50 @@ gboolean
 gs_plugin_refine_wildcard (GsPlugin *plugin,
                           GsApp *app,
                           GsAppList *list,
-                          GsPluginRefineFlags flags,
+                          GsPluginRefineFlags refine_flags,
                           GCancellable *cancellable,
                           GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
        const gchar *id;
-       guint i;
-       g_autoptr(GPtrArray) items = NULL;
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) components = NULL;
+
+       /* check silo is valid */
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
 
        /* not enough info to find */
        id = gs_app_get_id (app);
        if (id == NULL)
                return TRUE;
 
-       /* find all apps when matching any prefixes */
-       items = as_store_get_apps_by_id (priv->store, id);
-       for (i = 0; i < items->len; i++) {
-               AsApp *item = g_ptr_array_index (items, i);
+       /* find all app with package names when matching any prefixes */
+       xpath = g_strdup_printf ("components/component/id[text()='%s']/../pkgname/..", id);
+       components = xb_silo_query (priv->silo, xpath, 0, &error_local);
+       if (components == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < components->len; i++) {
+               XbNode *component = g_ptr_array_index (components, i);
                g_autoptr(GsApp) new = NULL;
 
-               /* is compatible */
-               if (!as_utils_unique_id_match (gs_app_get_unique_id (app),
-                                              as_app_get_unique_id (item),
-                                              AS_UNIQUE_ID_MATCH_FLAG_SCOPE |
-                                              AS_UNIQUE_ID_MATCH_FLAG_BUNDLE_KIND |
-                                              /* don't match origin as AsApp appstream
-                                               * origin can differ from package origin */
-                                              AS_UNIQUE_ID_MATCH_FLAG_KIND |
-                                              AS_UNIQUE_ID_MATCH_FLAG_ID |
-                                              AS_UNIQUE_ID_MATCH_FLAG_BRANCH)) {
-                       g_debug ("does not match unique ID constraints: %s, %s",
-                                gs_app_get_unique_id (app),
-                                as_app_get_unique_id (item));
-                       continue;
-               }
-
-               /* does the app have an installation method */
-               if (as_app_get_pkgname_default (item) == NULL &&
-                   as_app_get_bundle_default (item) == NULL) {
-                       g_debug ("not using %s for wildcard as "
-                                "no bundle or pkgname",
-                                as_app_get_id (item));
-                       continue;
-               }
-
                /* new app */
-               g_debug ("found %s for wildcard %s",
-                        as_app_get_id (item), id);
-               new = gs_appstream_create_app (plugin, item, error);
+               g_debug ("found component for wildcard %s", id);
+               new = gs_appstream_create_app (plugin, priv->silo, component, error);
                if (new == NULL)
                        return FALSE;
+               gs_app_set_scope (new, AS_APP_SCOPE_SYSTEM);
                gs_app_subsume_metadata (new, app);
+               if (!gs_appstream_refine_app (plugin, new, priv->silo, component,
+                                             refine_flags, error))
+                       return FALSE;
                gs_app_list_add (list, new);
        }
 
@@ -607,12 +816,14 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
                             GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_store_add_category_apps (plugin,
-                                                    priv->store,
-                                                    category,
-                                                    list,
-                                                    cancellable,
-                                                    error);
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_category_apps (plugin,
+                                              priv->silo,
+                                              category,
+                                              list,
+                                              cancellable,
+                                              error);
 }
 
 gboolean
@@ -623,12 +834,14 @@ gs_plugin_add_search (GsPlugin *plugin,
                      GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_store_search (plugin,
-                                         priv->store,
-                                         values,
-                                         list,
-                                         cancellable,
-                                         error);
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_search (plugin,
+                                   priv->silo,
+                                   values,
+                                   list,
+                                   cancellable,
+                                   error);
 }
 
 gboolean
@@ -638,19 +851,24 @@ gs_plugin_add_installed (GsPlugin *plugin,
                         GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       GPtrArray *array;
-
-       /* search categories for the search term */
-       array = as_store_get_apps (priv->store);
-       for (guint i = 0; i < array->len; i++) {
-               AsApp *item = g_ptr_array_index (array, i);
-               if (as_app_get_state (item) == AS_APP_STATE_INSTALLED) {
-                       g_autoptr(GsApp) app = NULL;
-                       app = gs_appstream_create_app (plugin, item, error);
-                       if (app == NULL)
-                               return FALSE;
-                       gs_app_list_add (list, app);
-               }
+       g_autoptr(GPtrArray) components = NULL;
+
+       /* check silo is valid */
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+
+       /* get all installed appdata files (notice no 'components/' prefix...) */
+       components = xb_silo_query (priv->silo, "component", 0, NULL);
+       if (components == NULL)
+               return TRUE;
+       for (guint i = 0; i < components->len; i++) {
+               XbNode *component = g_ptr_array_index (components, i);
+               g_autoptr(GsApp) app = gs_appstream_create_app (plugin, priv->silo, component, error);
+               if (app == NULL)
+                       return FALSE;
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+               gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+               gs_app_list_add (list, app);
        }
        return TRUE;
 }
@@ -662,8 +880,10 @@ gs_plugin_add_categories (GsPlugin *plugin,
                          GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_store_add_categories (plugin, priv->store, list,
-                                                 cancellable, error);
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_categories (plugin, priv->silo, list,
+                                           cancellable, error);
 }
 
 gboolean
@@ -673,8 +893,9 @@ gs_plugin_add_popular (GsPlugin *plugin,
                       GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_add_popular (plugin, priv->store, list, cancellable,
-                                        error);
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_popular (plugin, priv->silo, list, cancellable, error);
 }
 
 gboolean
@@ -684,8 +905,9 @@ gs_plugin_add_featured (GsPlugin *plugin,
                        GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_add_featured (plugin, priv->store, list, cancellable,
-                                         error);
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_featured (plugin, priv->silo, list, cancellable, error);
 }
 
 gboolean
@@ -696,7 +918,9 @@ gs_plugin_add_recent (GsPlugin *plugin,
                      GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_add_recent (plugin, priv->store, list, age,
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_recent (plugin, priv->silo, list, age,
                                        cancellable, error);
 }
 
@@ -708,7 +932,10 @@ gs_plugin_add_alternates (GsPlugin *plugin,
                          GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       return gs_appstream_add_alternates (plugin, priv->store, app, list, cancellable, error);
+       if (!gs_plugin_appstream_check_silo (plugin, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_alternates (plugin, priv->silo, app, list,
+                                           cancellable, error);
 }
 
 gboolean
@@ -717,11 +944,5 @@ gs_plugin_refresh (GsPlugin *plugin,
                   GCancellable *cancellable,
                   GError **error)
 {
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-
-       /* ensure the token cache */
-       if (cache_age == G_MAXUINT)
-               as_store_load_search_cache (priv->store);
-
-       return TRUE;
+       return gs_plugin_appstream_check_silo (plugin, cancellable, error);
 }
diff --git a/plugins/core/gs-self-test.c b/plugins/core/gs-self-test.c
index 37f08686..3ca6adcc 100644
--- a/plugins/core/gs-self-test.c
+++ b/plugins/core/gs-self-test.c
@@ -21,6 +21,8 @@
 
 #include "config.h"
 
+#include <glib/gstdio.h>
+
 #include "gnome-software-private.h"
 
 #include "gs-appstream.h"
@@ -37,6 +39,7 @@ gs_plugins_core_search_repo_name_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* force this app to be installed */
@@ -70,6 +73,7 @@ gs_plugins_core_os_release_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* refine system application */
@@ -120,6 +124,7 @@ gs_plugins_core_generic_updates_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsAppList) list_wildcard = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* create a list with generic apps */
@@ -204,7 +209,7 @@ main (int argc, char **argv)
 
        g_test_init (&argc, &argv, NULL);
        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
-       g_setenv ("GS_SELF_TEST_CORE_DATADIR", tmp_root, TRUE);
+       g_setenv ("GS_SELF_TEST_CACHEDIR", tmp_root, TRUE);
 
        os_release_filename = gs_test_get_filename (TESTDATADIR, "os-release");
        g_assert (os_release_filename != NULL);
@@ -235,10 +240,10 @@ main (int argc, char **argv)
                "  <component type=\"os-upgrade\">\n"
                "    <id>org.fedoraproject.Fedora-25</id>\n"
                "    <summary>Fedora Workstation</summary>\n"
+               "    <pkgname>fedora-release</pkgname>\n"
                "  </component>\n"
                "</components>\n";
        g_setenv ("GS_SELF_TEST_APPSTREAM_XML", xml, TRUE);
-       g_setenv ("GS_SELF_TEST_ALL_ORIGIN_KEYWORDS", "1", TRUE);
 
        /* only critical and error are fatal */
        g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
diff --git a/plugins/core/meson.build b/plugins/core/meson.build
index 92ec67a5..599d2bb2 100644
--- a/plugins/core/meson.build
+++ b/plugins/core/meson.build
@@ -92,7 +92,10 @@ shared_module(
   install : true,
   install_dir: plugin_dir,
   c_args : cargs,
-  dependencies : plugin_libs
+  dependencies : [
+    plugin_libs,
+    libxmlb,
+  ],
 )
 
 shared_module(
@@ -208,6 +211,7 @@ if get_option('tests')
     ],
     dependencies : [
       plugin_libs,
+      libxmlb,
     ],
     link_with : [
       libgnomesoftware
diff --git a/plugins/dummy/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c
index 003b398d..dd657027 100644
--- a/plugins/dummy/gs-plugin-dummy.c
+++ b/plugins/dummy/gs-plugin-dummy.c
@@ -804,6 +804,7 @@ gs_plugin_add_distro_upgrades (GsPlugin *plugin,
        app = gs_app_new ("org.fedoraproject.release-rawhide.upgrade");
        gs_app_set_scope (app, AS_APP_SCOPE_USER);
        gs_app_set_kind (app, AS_APP_KIND_OS_UPGRADE);
+       gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_PACKAGE);
        gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
        gs_app_set_name (app, GS_APP_QUALITY_LOWEST, "Fedora");
        gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c
index f9101dc2..1708a0b5 100644
--- a/plugins/dummy/gs-self-test.c
+++ b/plugins/dummy/gs-self-test.c
@@ -21,6 +21,8 @@
 
 #include "config.h"
 
+#include <glib/gstdio.h>
+
 #include "gnome-software-private.h"
 
 #include "gs-test.h"
@@ -104,6 +106,7 @@ gs_plugins_dummy_error_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* update, which should cause an error to be emitted */
@@ -426,7 +429,7 @@ gs_plugins_dummy_search_func (GsPluginLoader *plugin_loader)
 
        /* get search result based on addon keyword */
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
-                                        "search", "spell",
+                                        "search", "zeus",
                                         "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                         NULL);
        list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
@@ -480,6 +483,7 @@ gs_plugins_dummy_hang_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* get search result based on addon keyword */
@@ -736,6 +740,7 @@ gs_plugins_dummy_limit_parallel_ops_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsDummyTestHelper) helper3 = gs_dummy_test_helper_new ();
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* get the updates list */
@@ -827,6 +832,7 @@ gs_plugins_dummy_limit_parallel_ops_func (GsPluginLoader *plugin_loader)
 int
 main (int argc, char **argv)
 {
+       const gchar *tmp_root = "/var/tmp/self-test";
        gboolean ret;
        g_autofree gchar *xml = NULL;
        g_autoptr(GError) error = NULL;
@@ -847,6 +853,7 @@ main (int argc, char **argv)
 
        g_test_init (&argc, &argv, NULL);
        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+       g_setenv ("GS_XMLB_VERBOSE", "1", TRUE);
 
        /* set all the things required as a dummy test harness */
        g_setenv ("GS_SELF_TEST_LOCALE", "en_GB", TRUE);
@@ -855,6 +862,7 @@ main (int argc, char **argv)
        g_setenv ("GS_SELF_TEST_PROVENANCE_LICENSE_SOURCES", "london*,boston", TRUE);
        g_setenv ("GS_SELF_TEST_PROVENANCE_LICENSE_URL", "https://www.debian.org/";, TRUE);
        g_setenv ("GNOME_SOFTWARE_POPULAR", "", TRUE);
+       g_setenv ("GS_SELF_TEST_CACHEDIR", tmp_root, TRUE);
 
        xml = g_strdup ("<?xml version=\"1.0\"?>\n"
                "<components version=\"0.9\">\n"
@@ -900,6 +908,7 @@ main (int argc, char **argv)
                "  <component type=\"os-upgrade\">\n"
                "    <id>org.fedoraproject.release-rawhide.upgrade</id>\n"
                "    <summary>Release specific tagline</summary>\n"
+               "    <pkgname>fedora-release</pkgname>\n"
                "  </component>\n"
                "</components>\n");
        g_setenv ("GS_SELF_TEST_APPSTREAM_XML", xml, TRUE);
diff --git a/plugins/epiphany/gs-self-test.c b/plugins/epiphany/gs-self-test.c
index e2190d08..47013486 100644
--- a/plugins/epiphany/gs-self-test.c
+++ b/plugins/epiphany/gs-self-test.c
@@ -70,6 +70,7 @@ main (int argc, char **argv)
 
        g_test_init (&argc, &argv, NULL);
        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+       g_setenv ("GS_XMLB_VERBOSE", "1", TRUE);
 
        fn = gs_test_get_filename (TESTDATADIR, "icons/hicolor/scalable/org.gnome.Software.svg");
        g_assert (fn != NULL);
@@ -78,6 +79,7 @@ main (int argc, char **argv)
                "  <component type=\"webapp\">\n"
                "    <id>arachne.desktop</id>\n"
                "    <name>test</name>\n"
+               "    <pkgname>test</pkgname>\n"
                "    <icon type=\"remote\">file://%s</icon>\n"
                "  </component>\n"
                "</components>\n", fn);
diff --git a/plugins/external-appstream/gs-install-appstream.c 
b/plugins/external-appstream/gs-install-appstream.c
index d4a430c9..7f5eb92d 100644
--- a/plugins/external-appstream/gs-install-appstream.c
+++ b/plugins/external-appstream/gs-install-appstream.c
@@ -24,10 +24,8 @@
 #include <locale.h>
 #include <stdlib.h>
 
-#include <appstream-glib.h>
-#include <gio/gio.h>
+#include <xmlb.h>
 #include <glib/gi18n.h>
-#include <glib-object.h>
 
 #include "gs-external-appstream-utils.h"
 
@@ -59,8 +57,12 @@ static gboolean
 gs_install_appstream_check_content_type (GFile *file, GError **error)
 {
        const gchar *type;
-       g_autoptr(AsStore) store = NULL;
+       g_autoptr(GError) error_local = NULL;
        g_autoptr(GFileInfo) info = NULL;
+       g_autoptr(GPtrArray) components = NULL;
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+       g_autoptr(XbSilo) silo = NULL;
 
        /* check is correct type */
        info = g_file_query_info (file,
@@ -80,14 +82,38 @@ gs_install_appstream_check_content_type (GFile *file, GError **error)
        }
 
        /* check is an AppStream file */
-       store = as_store_new ();
-       if (!as_store_from_file (store, file, NULL, NULL, error))
+       if (!xb_builder_source_load_file (source, file,
+                                         XB_BUILDER_SOURCE_FLAG_NONE,
+                                         NULL, &error_local)) {
+               g_set_error (error,
+                            G_IO_ERROR,
+                            G_IO_ERROR_INVALID_DATA,
+                            "Failed to import XML: %s", error_local->message);
                return FALSE;
-       if (as_store_get_size (store) == 0) {
-               g_set_error_literal (error,
-                                    G_IO_ERROR,
-                                    G_IO_ERROR_INVALID_DATA,
-                                    "No applications found in the AppStream XML");
+       }
+       xb_builder_import_source (builder, source);
+       silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE,
+                                  NULL, &error_local);
+       if (silo == NULL) {
+               g_set_error (error,
+                            G_IO_ERROR,
+                            G_IO_ERROR_INVALID_DATA,
+                            "Failed to parse XML: %s", error_local->message);
+               return FALSE;
+       }
+       components = xb_silo_query (silo, "components/component", 0, &error_local);
+       if (components == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+                       g_set_error_literal (error,
+                                            G_IO_ERROR,
+                                            G_IO_ERROR_INVALID_DATA,
+                                            "No applications found in the AppStream XML");
+                       return FALSE;
+               }
+               g_set_error (error,
+                            G_IO_ERROR,
+                            G_IO_ERROR_INVALID_DATA,
+                            "Failed to query XML: %s", error_local->message);
                return FALSE;
        }
 
diff --git a/plugins/external-appstream/meson.build b/plugins/external-appstream/meson.build
index 6e2f3dfa..95897252 100644
--- a/plugins/external-appstream/meson.build
+++ b/plugins/external-appstream/meson.build
@@ -9,7 +9,10 @@ executable(
   include_directories : [
     include_directories('../..'),
   ],
-  dependencies : [gio_unix, appstream_glib],
+  dependencies : [
+    gio_unix,
+    libxmlb,
+  ],
   c_args : cargs,
   install : true,
   install_dir : get_option('libexecdir')
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
index 8b145158..b24bf8c2 100644
--- a/plugins/flatpak/gs-flatpak.c
+++ b/plugins/flatpak/gs-flatpak.c
@@ -30,6 +30,7 @@
 #include <config.h>
 
 #include <glib/gi18n.h>
+#include <xmlb.h>
 
 #include "gs-appstream.h"
 #include "gs-flatpak-app.h"
@@ -44,7 +45,7 @@ struct _GsFlatpak {
        GFileMonitor            *monitor;
        AsAppScope               scope;
        GsPlugin                *plugin;
-       AsStore                 *store;
+       XbSilo                  *silo;
        gchar                   *id;
        guint                    changed_id;
 };
@@ -55,16 +56,6 @@ static gboolean
 gs_flatpak_refresh_appstream (GsFlatpak *self, guint cache_age,
                              GCancellable *cancellable, GError **error);
 
-static gchar *
-gs_flatpak_build_id (FlatpakRef *xref)
-{
-       if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_APP) {
-               return g_strdup_printf ("%s.desktop",
-                                       flatpak_ref_get_name (xref));
-       }
-       return g_strdup (flatpak_ref_get_name (xref));
-}
-
 static void
 gs_plugin_refine_item_scope (GsFlatpak *self, GsApp *app)
 {
@@ -145,12 +136,10 @@ static GsApp *
 gs_flatpak_create_app (GsFlatpak *self, FlatpakRef *xref)
 {
        GsApp *app_cached;
-       g_autofree gchar *id = NULL;
        g_autoptr(GsApp) app = NULL;
 
        /* create a temp GsApp */
-       id = gs_flatpak_build_id (xref);
-       app = gs_app_new (id);
+       app = gs_app_new (flatpak_ref_get_name (xref));
        gs_flatpak_set_metadata (self, app, xref);
 
        /* return the ref'd cached copy */
@@ -205,11 +194,6 @@ gs_plugin_flatpak_changed_cb (GFileMonitor *monitor,
                              GsFlatpak *self)
 {
        g_autoptr(GError) error = NULL;
-       g_autoptr(GError) error_md = NULL;
-
-       /* don't refresh when it's us ourselves doing the change */
-       if (gs_plugin_has_flags (self->plugin, GS_PLUGIN_FLAGS_RUNNING_SELF))
-               return;
 
        /* manually drop the cache */
        if (!flatpak_installation_drop_caches (self->installation,
@@ -217,49 +201,96 @@ gs_plugin_flatpak_changed_cb (GFileMonitor *monitor,
                g_warning ("failed to drop cache: %s", error->message);
                return;
        }
+}
+
+static gboolean
+gs_flatpak_add_flatpak_keyword_cb (XbBuilderSource *self,
+                                  XbBuilderNode *bn,
+                                  gpointer user_data,
+                                  GError **error)
+{
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0)
+               gs_appstream_component_add_keyword (bn, "flatpak");
+       return TRUE;
+}
 
-       /* if this is a new remote, get the AppStream data */
-       if (!gs_flatpak_refresh_appstream (self, G_MAXUINT, NULL, &error_md)) {
-               g_warning ("failed to get initial available data: %s",
-                          error_md->message);
+static gboolean
+gs_flatpak_set_origin_cb (XbBuilderSource *self,
+                         XbBuilderNode *bn,
+                         gpointer user_data,
+                         GError **error)
+{
+       FlatpakRemote *xremote = FLATPAK_REMOTE (user_data);
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "components") == 0) {
+               xb_builder_node_set_attr (bn, "origin",
+                                         flatpak_remote_get_name (xremote));
        }
+       return TRUE;
 }
 
-static void
-gs_flatpak_remove_prefixed_names (AsApp *app)
+static gboolean
+gs_flatpak_filter_default_branch_cb (XbBuilderSource *self,
+                                    XbBuilderNode *bn,
+                                    gpointer user_data,
+                                    GError **error)
 {
-       GHashTable *names;
-       g_autoptr(GList) keys = NULL;
+       const gchar *default_branch = (const gchar *) user_data;
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+               g_autoptr(XbBuilderNode) bc = xb_builder_node_get_child (bn, "bundle", NULL);
+               g_auto(GStrv) split = NULL;
+               if (bc == NULL) {
+                       g_debug ("no bundle for component");
+                       return TRUE;
+               }
+               split = g_strsplit (xb_builder_node_get_text (bc), "/", -1);
+               if (split == NULL || g_strv_length (split) != 4)
+                       return TRUE;
+               if (g_strcmp0 (split[3], default_branch) != 0) {
+                       g_debug ("not adding app with branch %s as filtering to %s",
+                                split[3], default_branch);
+                       xb_builder_node_add_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE);
+               }
+       }
+       return TRUE;
+}
 
-       names = as_app_get_names (app);
-       keys = g_hash_table_get_keys (names);
-       for (GList *l = keys; l != NULL; l = l->next) {
-               const gchar *locale = l->data;
-               const gchar *value = g_hash_table_lookup (names, locale);
-               if (value == NULL)
-                       continue;
-               if (!g_str_has_prefix (value, "(Nightly) "))
-                       continue;
-               as_app_set_name (app, locale, value + 10);
+static gboolean
+gs_flatpak_filter_noenumerate_cb (XbBuilderSource *self,
+                                 XbBuilderNode *bn,
+                                 gpointer user_data,
+                                 GError **error)
+{
+       const gchar *only_app_id = (const gchar *) user_data;
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "component") == 0) {
+               g_autoptr(XbBuilderNode) bc = xb_builder_node_get_child (bn, "id", NULL);
+               if (bc == NULL) {
+                       g_debug ("no ID for component");
+                       return TRUE;
+               }
+               if (g_strcmp0 (xb_builder_node_get_text (bc), only_app_id) != 0) {
+                       g_debug ("not adding app %s as filtering to %s",
+                                xb_builder_node_get_text (bc), only_app_id);
+                       xb_builder_node_add_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE);
+               }
        }
+       return TRUE;
 }
 
 static gboolean
 gs_flatpak_add_apps_from_xremote (GsFlatpak *self,
+                                 XbBuilder *builder,
                                  FlatpakRemote *xremote,
                                  GCancellable *cancellable,
                                  GError **error)
 {
-       GPtrArray *apps;
        g_autofree gchar *appstream_dir_fn = NULL;
        g_autofree gchar *appstream_fn = NULL;
-       g_autofree gchar *default_branch = NULL;
-       g_autofree gchar *only_app_id = NULL;
-       g_autoptr(AsStore) store = NULL;
+       g_autofree gchar *icon_prefix = NULL;
        g_autoptr(GFile) appstream_dir = NULL;
-       g_autoptr(GFile) file = NULL;
+       g_autoptr(GFile) file_xml = NULL;
        g_autoptr(GSettings) settings = NULL;
-       g_autoptr(GPtrArray) app_filtered = NULL;
+       g_autoptr(XbBuilderNode) info = NULL;
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
 
        /* get the AppStream data location */
        appstream_dir = flatpak_remote_get_appstream_dir (xremote, NULL);
@@ -269,100 +300,134 @@ gs_flatpak_add_apps_from_xremote (GsFlatpak *self,
                return TRUE;
        }
 
-       /* load the file into a temp store */
+       /* load the file into a temp silo */
        appstream_dir_fn = g_file_get_path (appstream_dir);
-       appstream_fn = g_build_filename (appstream_dir_fn,
-                                        "appstream.xml.gz", NULL);
+       appstream_fn = g_build_filename (appstream_dir_fn, "appstream.xml.gz", NULL);
        if (!g_file_test (appstream_fn, G_FILE_TEST_EXISTS)) {
                g_debug ("no %s appstream metadata found: %s",
                         flatpak_remote_get_name (xremote),
                         appstream_fn);
                return TRUE;
        }
-       file = g_file_new_for_path (appstream_fn);
-       store = as_store_new ();
-       as_store_set_add_flags (store,
-                               AS_STORE_ADD_FLAG_USE_UNIQUE_ID |
-                               AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS);
-       as_store_set_search_match (store,
-                                  AS_APP_SEARCH_MATCH_MIMETYPE |
-                                  AS_APP_SEARCH_MATCH_PKGNAME |
-                                  AS_APP_SEARCH_MATCH_COMMENT |
-                                  AS_APP_SEARCH_MATCH_NAME |
-                                  AS_APP_SEARCH_MATCH_KEYWORD |
-                                  AS_APP_SEARCH_MATCH_ORIGIN |
-                                  AS_APP_SEARCH_MATCH_ID);
-       if (!as_store_from_file (store, file, NULL, cancellable, error)) {
-               gs_utils_error_convert_appstream (error);
+
+       /* add source */
+       file_xml = g_file_new_for_path (appstream_fn);
+       if (!xb_builder_source_load_file (source, file_xml,
+                                         XB_BUILDER_SOURCE_FLAG_WATCH_FILE |
+                                         XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT,
+                                         cancellable,
+                                         error))
                return FALSE;
-       }
+
+       /* add the flatpak search keyword */
+       xb_builder_source_add_node_func (source, "AddKeywordFlatpak",
+                                        gs_flatpak_add_flatpak_keyword_cb,
+                                        self, NULL);
 
        /* override the *AppStream* origin */
-       apps = as_store_get_apps (store);
-       for (guint i = 0; i < apps->len; i++) {
-               AsApp *app = g_ptr_array_index (apps, i);
-               as_app_set_origin (app, flatpak_remote_get_name (xremote));
-       }
+       xb_builder_source_add_node_func (source, "SetOrigin",
+                                        gs_flatpak_set_origin_cb,
+                                        xremote, NULL);
+
+       /* add metadata */
+       icon_prefix = g_build_filename (appstream_dir_fn, "icons", NULL);
+       info = xb_builder_node_insert (NULL, "info", NULL);
+       xb_builder_node_insert_text (info, "scope", as_app_scope_to_string (self->scope), NULL);
+       xb_builder_node_insert_text (info, "icon-prefix", icon_prefix, NULL);
+       xb_builder_source_set_info (source, info);
 
        /* only add the specific app for noenumerate=true */
        if (flatpak_remote_get_noenumerate (xremote)) {
                g_autofree gchar *tmp = NULL;
                tmp = g_strdup (flatpak_remote_get_name (xremote));
                g_strdelimit (tmp, "-", '\0');
-               only_app_id = g_strdup_printf ("%s.desktop", tmp);
+               xb_builder_source_add_node_func (source, "FilterNoEnumerate",
+                                                gs_flatpak_filter_noenumerate_cb,
+                                                g_strdup (tmp),
+                                                g_free);
        }
 
        /* do we want to filter to the default branch */
        settings = g_settings_new ("org.gnome.software");
-       if (g_settings_get_boolean (settings, "filter-default-branch"))
-               default_branch = flatpak_remote_get_default_branch (xremote);
-
-       /* get all the apps and fix them up */
-       app_filtered = g_ptr_array_new ();
-       for (guint i = 0; i < apps->len; i++) {
-               AsApp *app = g_ptr_array_index (apps, i);
-
-               /* filter to app */
-               if (only_app_id != NULL &&
-                   g_strcmp0 (as_app_get_id (app), only_app_id) != 0) {
-                       as_app_set_kind (app, AS_APP_KIND_UNKNOWN);
-                       continue;
-               }
+       if (g_settings_get_boolean (settings, "filter-default-branch") &&
+           flatpak_remote_get_default_branch (xremote) != NULL) {
+               xb_builder_source_add_node_func (source, "FilterDefaultbranch",
+                                                gs_flatpak_filter_default_branch_cb,
+                                                flatpak_remote_get_default_branch (xremote),
+                                                g_free);
+       }
 
-               /* filter by branch */
-               if (default_branch != NULL &&
-                   g_strcmp0 (as_app_get_branch (app), default_branch) != 0) {
-                       g_debug ("not adding app with branch %s as filtering to %s",
-                                as_app_get_branch (app), default_branch);
-                       continue;
-               }
+       /* success */
+       xb_builder_import_source (builder, source);
+       return TRUE;
+}
 
-               /* fix the names when using old versions of appstream-compose */
-               gs_flatpak_remove_prefixed_names (app);
+static GInputStream *
+gs_plugin_appstream_load_desktop_cb (XbBuilderSource *self,
+                                    GFile *file,
+                                    gpointer user_data,
+                                    GCancellable *cancellable,
+                                    GError **error)
+{
+       g_autofree gchar *fn = g_file_get_path (file);
+       g_autoptr(AsApp) app = as_app_new ();
+       GString *xml;
+       if (!as_app_parse_file (app, fn, AS_APP_PARSE_FLAG_USE_FALLBACKS, error))
+               return NULL;
+       xml = as_app_to_xml (app, error);
+       if (xml == NULL)
+               return NULL;
+       return g_memory_input_stream_new_from_data (g_string_free (xml, FALSE), -1, g_free);
+}
 
-               /* add */
-               as_app_set_scope (app, self->scope);
-               as_app_set_origin (app, flatpak_remote_get_name (xremote));
-               as_app_add_keyword (app, NULL, "flatpak");
-               g_debug ("adding %s", as_app_get_unique_id (app));
-               g_ptr_array_add (app_filtered, app);
+static gboolean
+gs_flatpak_load_desktop_fn (GsFlatpak *self,
+                           XbBuilder *builder,
+                           const gchar *filename,
+                           const gchar *icon_prefix,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       g_autoptr(GFile) file = g_file_new_for_path (filename);
+       g_autoptr(XbBuilderNode) info = NULL;
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+
+       /* add support for desktop files */
+       xb_builder_source_add_converter (source,
+                                        "application/x-desktop",
+                                        gs_plugin_appstream_load_desktop_cb,
+                                        NULL, NULL);
+
+       /* add the flatpak search keyword */
+       xb_builder_source_add_node_func (source, "AddKeywordFlatpak",
+                                        gs_flatpak_add_flatpak_keyword_cb,
+                                        self, NULL);
+
+       /* set the component metadata */
+       info = xb_builder_node_insert (NULL, "info", NULL);
+       xb_builder_node_insert_text (info, "scope", as_app_scope_to_string (self->scope), NULL);
+       xb_builder_node_insert_text (info, "icon-prefix", icon_prefix, NULL);
+       xb_builder_source_set_info (source, info);
+
+       /* add source */
+       if (!xb_builder_source_load_file (source, file,
+                                         XB_BUILDER_SOURCE_FLAG_WATCH_FILE,
+                                         cancellable,
+                                         error)) {
+               return FALSE;
        }
 
-       /* add them to the main store */
-       as_store_add_apps (self->store, app_filtered);
-
-       /* ensure the token cache for all apps */
-       as_store_load_search_cache (store);
-
+       /* success */
+       xb_builder_import_source (builder, source);
        return TRUE;
 }
 
 static void
 gs_flatpak_rescan_installed (GsFlatpak *self,
+                            XbBuilder *builder,
                             GCancellable *cancellable,
                             GError **error)
 {
-       GPtrArray *icons;
        const gchar *fn;
        g_autoptr(GFile) path = NULL;
        g_autoptr(GDir) dir = NULL;
@@ -379,68 +444,24 @@ gs_flatpak_rescan_installed (GsFlatpak *self,
        if (dir == NULL)
                return;
        while ((fn = g_dir_read_name (dir)) != NULL) {
-               g_autofree gchar *fn_desktop = NULL;
+               g_autofree gchar *filename = NULL;
                g_autoptr(GError) error_local = NULL;
-               g_autoptr(AsApp) app = NULL;
-               g_autoptr(AsFormat) format = as_format_new ();
-               g_autoptr(FlatpakInstalledRef) app_ref = NULL;
 
                /* ignore */
                if (g_strcmp0 (fn, "mimeinfo.cache") == 0)
                        continue;
 
                /* parse desktop files */
-               app = as_app_new ();
-               fn_desktop = g_build_filename (path_apps, fn, NULL);
-               if (!as_app_parse_file (app, fn_desktop, 0, &error_local)) {
-                       g_warning ("failed to parse %s: %s",
-                                  fn_desktop, error_local->message);
-                       continue;
-               }
-
-               /* fix up icons */
-               icons = as_app_get_icons (app);
-               for (guint i = 0; i < icons->len; i++) {
-                       AsIcon *ic = g_ptr_array_index (icons, i);
-                       if (as_icon_get_kind (ic) == AS_ICON_KIND_UNKNOWN) {
-                               as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-                               as_icon_set_prefix (ic, path_exports);
-                       }
-               }
-
-               /* fix the names when using old versions of appstream-compose */
-               gs_flatpak_remove_prefixed_names (app);
-
-               /* add */
-               as_app_set_state (app, AS_APP_STATE_INSTALLED);
-               as_app_set_scope (app, self->scope);
-               as_format_set_kind (format, AS_FORMAT_KIND_DESKTOP);
-               as_format_set_filename (format, fn_desktop);
-               as_app_add_format (app, format);
-
-               app_ref = flatpak_installation_get_current_installed_app (self->installation,
-                                                                         as_app_get_id (app),
-                                                                         cancellable,
-                                                                         &error_local);
-               if (app_ref == NULL) {
-                       g_warning ("Could not get app (from ID '%s') for installed desktop "
-                                  "file %s: %s", as_app_get_id (app), fn_desktop, error_local->message);
+               filename = g_build_filename (path_apps, fn, NULL);
+               if (!gs_flatpak_load_desktop_fn (self,
+                                                builder,
+                                                filename,
+                                                path_exports,
+                                                cancellable,
+                                                &error_local)) {
+                       g_debug ("ignoring %s: %s", filename, error_local->message);
                        continue;
                }
-
-               /* add the bundle info */
-               if (as_app_get_bundle_default (app) == NULL) {
-                       g_autoptr(AsBundle) bundle = as_bundle_new ();
-                       g_autofree gchar *ref = flatpak_ref_format_ref (FLATPAK_REF (app_ref));
-                       as_bundle_set_kind (bundle, AS_BUNDLE_KIND_FLATPAK);
-                       as_bundle_set_id (bundle, ref);
-                       as_app_add_bundle (app, bundle);
-               }
-
-               as_app_set_branch (app, flatpak_ref_get_branch (FLATPAK_REF (app_ref)));
-               as_app_set_icon_path (app, path_exports);
-               as_app_add_keyword (app, NULL, "flatpak");
-               as_store_add_app (self->store, app);
        }
 }
 
@@ -449,10 +470,29 @@ gs_flatpak_rescan_appstream_store (GsFlatpak *self,
                                   GCancellable *cancellable,
                                   GError **error)
 {
+       const gchar *const *locales = g_get_language_names ();
+       g_autofree gchar *blobfn = NULL;
+       g_autoptr(GFile) file = NULL;
        g_autoptr(GPtrArray) xremotes = NULL;
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+
+       /* everything is okay */
+       if (self->silo != NULL && xb_silo_is_valid (self->silo))
+               return TRUE;
 
-       /* remove all components */
-       as_store_remove_all (self->store);
+       /* drat! silo needs regenerating */
+       g_clear_object (&self->silo);
+
+       /* verbose profiling */
+       if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+               xb_builder_set_profile_flags (builder,
+                                             XB_SILO_PROFILE_FLAG_XPATH |
+                                             XB_SILO_PROFILE_FLAG_DEBUG);
+       }
+
+       /* add current locales */
+       for (guint i = 0; locales[i] != NULL; i++)
+               xb_builder_add_locale (builder, locales[i]);
 
        /* go through each remote adding metadata */
        xremotes = flatpak_installation_list_remotes (self->installation,
@@ -468,13 +508,30 @@ gs_flatpak_rescan_appstream_store (GsFlatpak *self,
                        continue;
                g_debug ("found remote %s",
                         flatpak_remote_get_name (xremote));
-               if (!gs_flatpak_add_apps_from_xremote (self, xremote, cancellable, error))
+               if (!gs_flatpak_add_apps_from_xremote (self, builder, xremote, cancellable, error))
                        return FALSE;
        }
 
        /* add any installed files without AppStream info */
-       gs_flatpak_rescan_installed (self, cancellable, error);
+       gs_flatpak_rescan_installed (self, builder, cancellable, error);
+
+       /* create per-user cache */
+       blobfn = gs_utils_get_cache_filename (gs_flatpak_get_id (self),
+                                             "components.xmlb",
+                                             GS_UTILS_CACHE_FLAG_WRITEABLE,
+                                             error);
+       if (blobfn == NULL)
+               return FALSE;
+       file = g_file_new_for_path (blobfn);
+       g_debug ("ensuring %s", blobfn);
+       self->silo = xb_builder_ensure (builder, file,
+                                       XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID |
+                                       XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+                                       NULL, error);
+       if (self->silo == NULL)
+               return FALSE;
 
+       /* success */
        return TRUE;
 }
 
@@ -608,7 +665,6 @@ gs_flatpak_refresh_appstream (GsFlatpak *self, guint cache_age,
                              GCancellable *cancellable, GError **error)
 {
        gboolean ret;
-       gboolean something_changed = FALSE;
        g_autoptr(GPtrArray) xremotes = NULL;
 
        /* get remotes */
@@ -680,21 +736,15 @@ gs_flatpak_refresh_appstream (GsFlatpak *self, guint cache_age,
                        continue;
                }
 
-               /* add the new AppStream repo to the shared store */
+               /* add the new AppStream repo to the shared silo */
                file = flatpak_remote_get_appstream_dir (xremote, NULL);
                appstream_fn = g_file_get_path (file);
                g_debug ("using AppStream metadata found at: %s", appstream_fn);
-
-               /* trigger the symlink rebuild */
-               something_changed = TRUE;
        }
 
-       /* ensure the AppStream store is up to date */
-       if (something_changed ||
-           as_store_get_size (self->store) == 0) {
-               if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
-                       return FALSE;
-       }
+       /* ensure the AppStream silo is up to date */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
 
        return TRUE;
 }
@@ -832,6 +882,10 @@ gs_flatpak_add_sources (GsFlatpak *self, GsAppList *list,
        g_autoptr(GPtrArray) xrefs = NULL;
        g_autoptr(GPtrArray) xremotes = NULL;
 
+       /* refresh */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+
        /* get installed apps and runtimes */
        xrefs = flatpak_installation_list_installed_refs (self->installation,
                                                          cancellable,
@@ -1046,12 +1100,6 @@ gs_flatpak_app_install_source (GsFlatpak *self, GsApp *app,
                return FALSE;
        }
 
-       /* refresh the AppStream data manually */
-       if (!gs_flatpak_add_apps_from_xremote (self, xremote, cancellable, error)) {
-               g_prefix_error (error, "cannot refresh remote AppStream: ");
-               return FALSE;
-       }
-
        /* success */
        gs_app_set_state (app, AS_APP_STATE_INSTALLED);
        return TRUE;
@@ -1139,6 +1187,10 @@ gs_flatpak_add_updates (GsFlatpak *self, GsAppList *list,
 {
        g_autoptr(GPtrArray) xrefs = NULL;
 
+       /* ensure valid */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+
        /* get all the updatable apps and runtimes */
        xrefs = flatpak_installation_list_installed_refs_for_update (self->installation,
                                                                     cancellable,
@@ -1238,8 +1290,20 @@ gs_flatpak_refresh (GsFlatpak *self,
                return FALSE;
        }
 
+       /* manually do this in case we created the first appstream file */
+       if (self->silo != NULL)
+               xb_silo_invalidate (self->silo);
+
        /* update AppStream metadata */
-       return gs_flatpak_refresh_appstream (self, cache_age, cancellable, error);
+       if (!gs_flatpak_refresh_appstream (self, cache_age, cancellable, error))
+               return FALSE;
+
+       /* ensure valid */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+
+       /* success */
+       return TRUE;
 }
 
 static gboolean
@@ -1429,6 +1493,10 @@ gs_flatpak_refine_app_state (GsFlatpak *self,
        g_autoptr(FlatpakInstalledRef) ref = NULL;
        g_autoptr(GError) error_local = NULL;
 
+       /* ensure valid */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+
        /* already found */
        if (gs_app_get_state (app) != AS_APP_STATE_UNKNOWN)
                return TRUE;
@@ -1822,56 +1890,51 @@ gs_plugin_refine_item_size (GsFlatpak *self,
 }
 
 static void
-gs_flatpak_refine_appstream_release (AsApp *item, GsApp *app)
+gs_flatpak_refine_appstream_release (XbNode *component, GsApp *app)
 {
-       AsRelease *rel = as_app_get_release_default (item);
-       if (rel == NULL)
-               return;
-       if (as_release_get_version (rel) == NULL)
+       const gchar *version;
+
+       /* get first release */
+       version = xb_node_query_attr (component, "releases/release", "version", NULL);
+       if (version == NULL)
                return;
        switch (gs_app_get_state (app)) {
        case AS_APP_STATE_INSTALLED:
        case AS_APP_STATE_AVAILABLE:
        case AS_APP_STATE_AVAILABLE_LOCAL:
-               gs_app_set_version (app, as_release_get_version (rel));
+               gs_app_set_version (app, version);
                break;
        default:
                g_debug ("%s is not installed, so ignoring version of %s",
-                        as_app_get_id (item), as_release_get_version (rel));
+                        gs_app_get_unique_id (app), version);
                break;
        }
 }
 
 static gboolean
-gs_flatpak_refine_appstream (GsFlatpak *self, GsApp *app, GError **error)
+gs_flatpak_refine_appstream (GsFlatpak *self,
+                            GsApp *app,
+                            XbSilo *silo,
+                            GsPluginRefineFlags flags,
+                            GError **error)
 {
-       AsApp *item;
-       const gchar *unique_id = gs_app_get_unique_id (app);
+       const gchar *id = gs_app_get_id (app);
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(XbNode) component = NULL;
 
-       if (unique_id == NULL)
-               return TRUE;
-       item = as_store_get_app_by_unique_id (self->store,
-                                             unique_id,
-                                             AS_STORE_SEARCH_FLAG_USE_WILDCARDS);
-       if (item == NULL) {
-               g_autoptr(GPtrArray) apps = NULL;
-               apps = as_store_get_apps_by_id (self->store, gs_app_get_id (app));
-               if (apps->len > 0) {
-                       g_debug ("potential matches for %s:", unique_id);
-                       for (guint i = 0; i < apps->len; i++) {
-                               AsApp *app_tmp = g_ptr_array_index (apps, i);
-                               g_debug ("- %s", as_app_get_unique_id (app_tmp));
-                       }
-               }
+       if (id == NULL)
                return TRUE;
-       }
 
-       if (!gs_appstream_refine_app (self->plugin, app, item, error))
+       /* find using ID */
+       xpath = g_strdup_printf ("components/component/id[text()='%s']/..", id);
+       component = xb_silo_query_first (silo, xpath, NULL);
+       if (component == NULL)
+               return TRUE;
+       if (!gs_appstream_refine_app (self->plugin, app, silo, component, flags, error))
                return FALSE;
 
        /* use the default release as the version number */
-       gs_flatpak_refine_appstream_release (item, app);
-
+       gs_flatpak_refine_appstream_release (component, app);
        return TRUE;
 }
 
@@ -1884,8 +1947,16 @@ gs_flatpak_refine_app (GsFlatpak *self,
 {
        AsAppState old_state = gs_app_get_state (app);
 
+       /* not us */
+       if (gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_FLATPAK)
+               return TRUE;
+
+       /* ensure valid */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+
        /* always do AppStream properties */
-       if (!gs_flatpak_refine_appstream (self, app, error))
+       if (!gs_flatpak_refine_appstream (self, app, self->silo, flags, error))
                return FALSE;
 
        /* AppStream sets the source to appname/arch/branch */
@@ -1906,7 +1977,7 @@ gs_flatpak_refine_app (GsFlatpak *self,
 
        /* if the state was changed, perhaps set the version from the release */
        if (old_state != gs_app_get_state (app)) {
-               if (!gs_flatpak_refine_appstream (self, app, error))
+               if (!gs_flatpak_refine_appstream (self, app, self->silo, flags, error))
                        return FALSE;
        }
 
@@ -1953,50 +2024,49 @@ gs_flatpak_refine_app (GsFlatpak *self,
 
 gboolean
 gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app,
-                           GsAppList *list, GsPluginRefineFlags flags,
+                           GsAppList *list, GsPluginRefineFlags refine_flags,
                            GCancellable *cancellable, GError **error)
 {
        const gchar *id;
-       guint i;
-       g_autoptr(GPtrArray) items = NULL;
+       g_autofree gchar *xpath = NULL;
+       g_autoptr(GError) error_local = NULL;
+       g_autoptr(GPtrArray) components = NULL;
 
-       /* not valid */
+       /* not enough info to find */
        id = gs_app_get_id (app);
        if (id == NULL)
                return TRUE;
 
+       /* ensure valid */
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+
        /* find all apps when matching any prefixes */
-       items = as_store_get_apps_by_id (self->store, id);
-       for (i = 0; i < items->len; i++) {
-               AsApp *item = g_ptr_array_index (items, i);
+       xpath = g_strdup_printf ("components/component/id[text()='%s']/..", id);
+       components = xb_silo_query (self->silo, xpath, 0, &error_local);
+       if (components == NULL) {
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return TRUE;
+               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return TRUE;
+               g_propagate_error (error, g_steal_pointer (&error_local));
+               return FALSE;
+       }
+       for (guint i = 0; i < components->len; i++) {
+               XbNode *component = g_ptr_array_index (components, i);
                g_autoptr(GsApp) new = NULL;
-
-               /* is compatible */
-               if (!as_utils_unique_id_equal (gs_app_get_unique_id (app),
-                                              as_app_get_unique_id (item))) {
-                       g_debug ("does not match unique ID constraints");
-                       continue;
-               }
-
-               /* does the app have an installation method */
-               if (as_app_get_bundle_default (item) == NULL) {
-                       g_debug ("not using %s for wildcard as no bundle",
-                                as_app_get_id (item));
-                       continue;
-               }
-
-               /* new app */
-               g_debug ("found %s for wildcard %s",
-                        as_app_get_unique_id (item), id);
-               new = gs_appstream_create_app (self->plugin, item, NULL);
+               g_debug ("found component for wildcard %s", id);
+               new = gs_appstream_create_app (self->plugin, self->silo, component, error);
                if (new == NULL)
                        return FALSE;
                gs_flatpak_claim_app (self, new);
-               if (!gs_flatpak_refine_app (self, new, flags, cancellable, error))
+               if (!gs_flatpak_refine_app (self, new, refine_flags, cancellable, error))
                        return FALSE;
                gs_app_subsume_metadata (new, app);
                gs_app_list_add (list, new);
        }
+
+       /* success */
        return TRUE;
 }
 
@@ -2116,13 +2186,17 @@ gs_flatpak_file_to_app_bundle (GsFlatpak *self,
        /* load AppStream */
        appstream_gz = flatpak_bundle_ref_get_appstream (xref_bundle);
        if (appstream_gz != NULL) {
-               g_autoptr(GZlibDecompressor) decompressor = NULL;
-               g_autoptr(GInputStream) stream_gz = NULL;
-               g_autoptr(GInputStream) stream_data = NULL;
+               g_autofree gchar *xpath = NULL;
                g_autoptr(GBytes) appstream = NULL;
-               g_autoptr(AsStore) store = NULL;
-               g_autofree gchar *id = NULL;
-               AsApp *item;
+               g_autoptr(GError) error_local = NULL;
+               g_autoptr(GInputStream) stream_data = NULL;
+               g_autoptr(GInputStream) stream_gz = NULL;
+               g_autoptr(GZlibDecompressor) decompressor = NULL;
+               g_autoptr(XbBuilder) builder = xb_builder_new ();
+               g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
+               g_autoptr(XbNode) component = NULL;
+               g_autoptr(XbNode) n = NULL;
+               g_autoptr(XbSilo) silo = NULL;
 
                /* decompress data */
                decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
@@ -2140,47 +2214,57 @@ gs_flatpak_file_to_app_bundle (GsFlatpak *self,
                        gs_flatpak_error_convert (error);
                        return NULL;
                }
-               store = as_store_new ();
-               if (!as_store_from_bytes (store, appstream, cancellable, error)) {
-                       gs_flatpak_error_convert (error);
-                       return NULL;
-               }
 
-               /* allow peeking into this for debugging */
-               if (g_getenv ("GS_FLATPAK_DEBUG_APPSTREAM") != NULL) {
-                       g_autoptr(GString) str = NULL;
-                       str = as_store_to_xml (store,
-                                              AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE |
-                                              AS_NODE_TO_XML_FLAG_FORMAT_INDENT);
-                       g_debug ("showing AppStream data: %s", str->str);
+               /* build silo */
+               if (!xb_builder_source_load_bytes (source, appstream,
+                                                  XB_BUILDER_SOURCE_FLAG_NONE,
+                                                  error))
+                       return NULL;
+               xb_builder_import_source (builder, source);
+               silo = xb_builder_compile (builder,
+                                          XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+                                          cancellable,
+                                          error);
+               if (silo == NULL)
+                       return NULL;
+               if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+                       g_autofree gchar *xml = NULL;
+                       xml = xb_silo_export (silo,
+                                             XB_NODE_EXPORT_FLAG_FORMAT_INDENT |
+                                             XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE,
+                                             NULL);
+                       g_debug ("showing AppStream data: %s", xml);
                }
 
                /* check for sanity */
-               if (as_store_get_size (store) == 0) {
+               n = xb_silo_query_first (silo, "components/component", NULL);
+               if (n == NULL) {
                        g_set_error_literal (error,
                                             GS_PLUGIN_ERROR,
                                             GS_PLUGIN_ERROR_NOT_SUPPORTED,
                                             "no apps found in AppStream data");
                        return NULL;
                }
-               g_debug ("%u applications found in AppStream data",
-                        as_store_get_size (store));
 
                /* find app */
-               id = g_strdup_printf ("%s.desktop", gs_flatpak_app_get_ref_name (app));
-               item = as_store_get_app_by_id (store, id);
-               if (item == NULL) {
+               xpath = g_strdup_printf ("components/component/id[text()='%s']",
+                                        gs_flatpak_app_get_ref_name (app));
+               component = xb_silo_query_first (silo, xpath, NULL);
+               if (component == NULL) {
                        g_set_error (error,
                                     GS_PLUGIN_ERROR,
                                     GS_PLUGIN_ERROR_INVALID_FORMAT,
                                     "application %s not found",
-                                    id);
+                                    gs_flatpak_app_get_ref_name (app));
                        return NULL;
                }
 
                /* copy details from AppStream to app */
-               if (!gs_appstream_refine_app (self->plugin, app, item, error))
+               if (!gs_appstream_refine_app (self->plugin, app, silo, component,
+                                             GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                             error))
                        return NULL;
+
        } else {
                g_warning ("no appstream metadata in file");
                gs_app_set_name (app, GS_APP_QUALITY_LOWEST,
@@ -2227,15 +2311,18 @@ gs_flatpak_file_to_app_ref (GsFlatpak *self,
                            GError **error)
 {
        GsApp *runtime;
+       const gchar *const *locales = g_get_language_names ();
        const gchar *remote_name;
        gsize len = 0;
        g_autofree gchar *contents = NULL;
        g_autoptr(FlatpakRemoteRef) xref = NULL;
+       g_autoptr(FlatpakRemote) xremote = NULL;
        g_autoptr(GBytes) ref_file_data = NULL;
        g_autoptr(GError) error_local = NULL;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(FlatpakRemote) xremote = NULL;
        g_autoptr(GKeyFile) kf = NULL;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+       g_autoptr(XbSilo) silo = NULL;
        g_autofree gchar *origin_url = NULL;
        g_autofree gchar *ref_comment = NULL;
        g_autofree gchar *ref_description = NULL;
@@ -2244,6 +2331,10 @@ gs_flatpak_file_to_app_ref (GsFlatpak *self,
        g_autofree gchar *ref_title = NULL;
        g_autofree gchar *ref_name = NULL;
 
+       /* add current locales */
+       for (guint i = 0; locales[i] != NULL; i++)
+               xb_builder_add_locale (builder, locales[i]);
+
        /* get file data */
        if (!g_file_load_contents (file,
                                   cancellable,
@@ -2372,11 +2463,29 @@ gs_flatpak_file_to_app_ref (GsFlatpak *self,
        }
 
        /* parse it */
-       if (!gs_flatpak_add_apps_from_xremote (self, xremote, cancellable, error))
+       if (!gs_flatpak_add_apps_from_xremote (self, builder, xremote, cancellable, error))
                return NULL;
 
+       /* build silo */
+       silo = xb_builder_compile (builder,
+                                  XB_BUILDER_COMPILE_FLAG_SINGLE_LANG,
+                                  cancellable,
+                                  error);
+       if (silo == NULL)
+               return NULL;
+       if (g_getenv ("GS_XMLB_VERBOSE") != NULL) {
+               g_autofree gchar *xml = NULL;
+               xml = xb_silo_export (silo,
+                                     XB_NODE_EXPORT_FLAG_FORMAT_INDENT |
+                                     XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE,
+                                     NULL);
+               g_debug ("showing AppStream data: %s", xml);
+       }
+
        /* get extra AppStream data if available */
-       if (!gs_flatpak_refine_appstream (self, app, error))
+       if (!gs_flatpak_refine_appstream (self, app, silo,
+                                         G_MAXUINT64,
+                                         error))
                return NULL;
 
        /* success */
@@ -2391,8 +2500,10 @@ gs_flatpak_search (GsFlatpak *self,
                   GError **error)
 {
        g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
-       if (!gs_appstream_store_search (self->plugin, self->store, values, list_tmp,
-                                       cancellable, error))
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       if (!gs_appstream_search (self->plugin, self->silo, values, list_tmp,
+                                 cancellable, error))
                return FALSE;
        gs_flatpak_claim_app_list (self, list_tmp);
        gs_app_list_add_list (list, list_tmp);
@@ -2407,9 +2518,11 @@ gs_flatpak_add_category_apps (GsFlatpak *self,
                              GError **error)
 {
        g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
-       if (!gs_appstream_store_add_category_apps (self->plugin, self->store,
-                                                  category, list_tmp,
-                                                  cancellable, error))
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       if (!gs_appstream_add_category_apps (self->plugin, self->silo,
+                                            category, list_tmp,
+                                            cancellable, error))
                return FALSE;
        gs_flatpak_claim_app_list (self, list_tmp);
        gs_app_list_add_list (list, list_tmp);
@@ -2422,8 +2535,10 @@ gs_flatpak_add_categories (GsFlatpak *self,
                           GCancellable *cancellable,
                           GError **error)
 {
-       return gs_appstream_store_add_categories (self->plugin, self->store,
-                                                 list, cancellable, error);
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       return gs_appstream_add_categories (self->plugin, self->silo,
+                                           list, cancellable, error);
 }
 
 gboolean
@@ -2433,10 +2548,11 @@ gs_flatpak_add_popular (GsFlatpak *self,
                        GError **error)
 {
        g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
-       if (!gs_appstream_add_popular (self->plugin, self->store, list_tmp,
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       if (!gs_appstream_add_popular (self->plugin, self->silo, list_tmp,
                                       cancellable, error))
                return FALSE;
-       gs_flatpak_claim_app_list (self, list_tmp);
        gs_app_list_add_list (list, list_tmp);
        return TRUE;
 }
@@ -2448,10 +2564,11 @@ gs_flatpak_add_featured (GsFlatpak *self,
                         GError **error)
 {
        g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
-       if (!gs_appstream_add_featured (self->plugin, self->store, list_tmp,
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       if (!gs_appstream_add_featured (self->plugin, self->silo, list_tmp,
                                        cancellable, error))
                return FALSE;
-       gs_flatpak_claim_app_list (self, list_tmp);
        gs_app_list_add_list (list, list_tmp);
        return TRUE;
 }
@@ -2464,7 +2581,9 @@ gs_flatpak_add_alternates (GsFlatpak *self,
                           GError **error)
 {
        g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
-       if (!gs_appstream_add_alternates (self->plugin, self->store, app, list_tmp,
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       if (!gs_appstream_add_alternates (self->plugin, self->silo, app, list_tmp,
                                          cancellable, error))
                return FALSE;
        gs_app_list_add_list (list, list_tmp);
@@ -2479,7 +2598,9 @@ gs_flatpak_add_recent (GsFlatpak *self,
                       GError **error)
 {
        g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
-       if (!gs_appstream_add_recent (self->plugin, self->store, list_tmp, age,
+       if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
+               return FALSE;
+       if (!gs_appstream_add_recent (self->plugin, self->silo, list_tmp, age,
                                      cancellable, error))
                return FALSE;
        gs_flatpak_claim_app_list (self, list_tmp);
@@ -2487,24 +2608,11 @@ gs_flatpak_add_recent (GsFlatpak *self,
        return TRUE;
 }
 
-static void
-gs_flatpak_store_app_added_cb (AsStore *store, AsApp *app, GsFlatpak *self)
-{
-       gs_appstream_add_extra_info (self->plugin, app);
-}
-
-static void
-gs_flatpak_store_app_removed_cb (AsStore *store, AsApp *app, GsFlatpak *self)
-{
-       g_debug ("AppStream app was removed, doing delete from global cache");
-       gs_plugin_cache_remove (self->plugin, as_app_get_unique_id (app));
-}
-
 const gchar *
 gs_flatpak_get_id (GsFlatpak *self)
 {
        if (self->id == NULL) {
-               GString *str = g_string_new ("GsFlatpak");
+               GString *str = g_string_new ("flatpak");
                g_string_append_printf (str, "-%s",
                                        as_app_scope_to_string (self->scope));
                if (flatpak_installation_get_id (self->installation) != NULL) {
@@ -2541,11 +2649,12 @@ gs_flatpak_finalize (GObject *object)
                g_signal_handler_disconnect (self->monitor, self->changed_id);
                self->changed_id = 0;
        }
+       if (self->silo != NULL)
+               g_object_unref (self->silo);
 
        g_free (self->id);
        g_object_unref (self->installation);
        g_object_unref (self->plugin);
-       g_object_unref (self->store);
        g_hash_table_unref (self->broken_remotes);
 
        G_OBJECT_CLASS (gs_flatpak_parent_class)->finalize (object);
@@ -2563,22 +2672,6 @@ gs_flatpak_init (GsFlatpak *self)
 {
        self->broken_remotes = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                      g_free, NULL);
-       self->store = as_store_new ();
-       g_signal_connect (self->store, "app-added",
-                         G_CALLBACK (gs_flatpak_store_app_added_cb),
-                         self);
-       g_signal_connect (self->store, "app-removed",
-                         G_CALLBACK (gs_flatpak_store_app_removed_cb),
-                         self);
-       as_store_set_add_flags (self->store, AS_STORE_ADD_FLAG_USE_UNIQUE_ID);
-       as_store_set_watch_flags (self->store, AS_STORE_WATCH_FLAG_REMOVED);
-       as_store_set_search_match (self->store,
-                                  AS_APP_SEARCH_MATCH_MIMETYPE |
-                                  AS_APP_SEARCH_MATCH_PKGNAME |
-                                  AS_APP_SEARCH_MATCH_COMMENT |
-                                  AS_APP_SEARCH_MATCH_NAME |
-                                  AS_APP_SEARCH_MATCH_KEYWORD |
-                                  AS_APP_SEARCH_MATCH_ID);
 }
 
 GsFlatpak *
diff --git a/plugins/flatpak/gs-self-test.c b/plugins/flatpak/gs-self-test.c
index 03a771df..933d70e0 100644
--- a/plugins/flatpak/gs-self-test.c
+++ b/plugins/flatpak/gs-self-test.c
@@ -22,6 +22,8 @@
 
 #include "config.h"
 
+#include <glib/gstdio.h>
+
 #include "gnome-software-private.h"
 
 #include "gs-flatpak-app.h"
@@ -230,6 +232,8 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* no flatpak, abort */
@@ -323,6 +327,8 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
                                         "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
                                                         GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
                                                         GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_KUDOS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
                                                         GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                         NULL);
        list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
@@ -332,7 +338,7 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        /* make sure there is one entry, the flatpak 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), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
        g_assert_cmpint ((gint64) gs_app_get_kudos (app), ==,
@@ -496,6 +502,8 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* no flatpak, abort */
@@ -552,7 +560,7 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        /* make sure there is one entry, the flatpak 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), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
 
        /* install, also installing runtime */
@@ -642,6 +650,8 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* write a flatpakrepo file */
@@ -675,9 +685,9 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        g_assert (app != NULL);
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE_LOCAL);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert (as_utils_unique_id_equal (gs_app_get_unique_id (app),
-                       "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron.desktop/master"));
+                       "user/flatpak/*/desktop/org.test.Chiron/master"));
        g_assert (gs_app_get_local_file (app) != NULL);
 
        /* get runtime */
@@ -777,6 +787,8 @@ gs_plugins_flatpak_runtime_repo_redundant_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* write a flatpakrepo file */
@@ -839,9 +851,9 @@ gs_plugins_flatpak_runtime_repo_redundant_func (GsPluginLoader *plugin_loader)
        g_assert (app != NULL);
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE_LOCAL);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert (as_utils_unique_id_equal (gs_app_get_unique_id (app),
-                       "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron.desktop/master"));
+                       "user/flatpak/*/desktop/org.test.Chiron/master"));
        g_assert (gs_app_get_local_file (app) != NULL);
 
        /* get runtime */
@@ -930,6 +942,8 @@ gs_plugins_flatpak_broken_remote_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* no flatpak, abort */
@@ -974,9 +988,9 @@ gs_plugins_flatpak_broken_remote_func (GsPluginLoader *plugin_loader)
        g_assert (app != NULL);
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE_LOCAL);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert (as_utils_unique_id_equal (gs_app_get_unique_id (app),
-                       "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron.desktop/master"));
+                       "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron/master"));
        g_assert_cmpstr (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE), ==, "http://127.0.0.1/";);
        g_assert_cmpstr (gs_app_get_name (app), ==, "Chiron");
        g_assert_cmpstr (gs_app_get_summary (app), ==, "Single line synopsis");
@@ -1018,6 +1032,8 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_autoptr(GString) str = g_string_new (NULL);
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* no flatpak, abort */
@@ -1093,6 +1109,8 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
                                         "file", file,
                                         "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
                                                         GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
                                         NULL);
        app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
@@ -1100,9 +1118,9 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert (app != NULL);
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE_LOCAL);
-       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert (as_utils_unique_id_equal (gs_app_get_unique_id (app),
-                       "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron.desktop/master"));
+                       "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron/master"));
        g_assert_cmpstr (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE), ==, "http://127.0.0.1/";);
        g_assert_cmpstr (gs_app_get_name (app), ==, "Chiron");
        g_assert_cmpstr (gs_app_get_summary (app), ==, "Single line synopsis");
@@ -1140,7 +1158,7 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert (search1 != NULL);
        g_assert_cmpint (gs_app_list_length (search1), ==, 1);
        app_tmp = gs_app_list_index (search1, 0);
-       g_assert_cmpstr (gs_app_get_id (app_tmp), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app_tmp), ==, "org.test.Chiron");
 
        /* convert it to a GsApp again, and get the installed thing */
        g_object_unref (plugin_job);
@@ -1154,7 +1172,7 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert (app2 != NULL);
        g_assert_cmpint (gs_app_get_state (app2), ==, AS_APP_STATE_INSTALLED);
        g_assert (as_utils_unique_id_equal (gs_app_get_unique_id (app2),
-                 "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron.desktop/master"));
+                 "user/flatpak/org.test.Chiron-origin/desktop/org.test.Chiron/master"));
 
        /* remove app */
        g_object_unref (plugin_job);
@@ -1237,6 +1255,8 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE);
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* no flatpak, abort */
@@ -1301,7 +1321,7 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        /* make sure there is one entry, the flatpak 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), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
 
        /* install, also installing runtime */
@@ -1351,7 +1371,7 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        }
 
        /* check they are the same GObject */
-       app_tmp = gs_app_list_lookup (list_updates, "*/flatpak/test/*/org.test.Chiron.desktop/*");
+       app_tmp = gs_app_list_lookup (list_updates, "*/flatpak/test/*/org.test.Chiron/*");
        g_assert (app_tmp == app);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
        g_assert_cmpstr (gs_app_get_update_details (app), ==, "Version 1.2.4:\nThis is best.\n\nVersion 
1.2.3:\nThis is better.");
@@ -1484,6 +1504,8 @@ gs_plugins_flatpak_runtime_extension_func (GsPluginLoader *plugin_loader)
        g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE);
 
        /* drop all caches */
+       g_unlink ("/var/tmp/self-test/flatpak-user/components.xmlb");
+       g_unlink ("/var/tmp/self-test/appstream/components.xmlb");
        gs_plugin_loader_setup_again (plugin_loader);
 
        /* no flatpak, abort */
@@ -1545,7 +1567,7 @@ gs_plugins_flatpak_runtime_extension_func (GsPluginLoader *plugin_loader)
        /* make sure there is one entry, the flatpak 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), ==, "org.test.Chiron.desktop");
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron");
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
 
        /* install, also installing runtime and suggested extensions */
@@ -1601,7 +1623,7 @@ gs_plugins_flatpak_runtime_extension_func (GsPluginLoader *plugin_loader)
        g_assert_null (app_tmp);
 
        /* check that the app has an update (it's affected by the extension's update) */
-       app_tmp = gs_app_list_lookup (list_updates, "*/flatpak/test/*/org.test.Chiron.desktop/*");
+       app_tmp = gs_app_list_lookup (list_updates, "*/flatpak/test/*/org.test.Chiron/*");
        g_assert (app_tmp == app);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
 
@@ -1703,6 +1725,8 @@ main (int argc, char **argv)
 
        g_test_init (&argc, &argv, NULL);
        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+       g_setenv ("GS_XMLB_VERBOSE", "1", TRUE);
+       g_setenv ("GS_SELF_TEST_CACHEDIR", tmp_root, TRUE);
        g_setenv ("GS_SELF_TEST_FLATPAK_DATADIR", tmp_root, TRUE);
        g_setenv ("GS_SELF_TEST_PLUGIN_ERROR_FAIL_HARD", "1", TRUE);
 
diff --git a/plugins/flatpak/meson.build b/plugins/flatpak/meson.build
index 25b853db..ac82bfc0 100644
--- a/plugins/flatpak/meson.build
+++ b/plugins/flatpak/meson.build
@@ -19,7 +19,11 @@ shared_module(
   install : true,
   install_dir: plugin_dir,
   c_args : cargs,
-  dependencies : [ plugin_libs, flatpak ]
+  dependencies : [
+    plugin_libs,
+    flatpak,
+    libxmlb,
+  ],
 )
 metainfo = 'org.gnome.Software.Plugin.Flatpak.metainfo.xml'
 
diff --git a/plugins/flatpak/tests/app-update/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml 
b/plugins/flatpak/tests/app-update/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml
index b6d51386..74eb9db9 100644
--- a/plugins/flatpak/tests/app-update/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml
+++ b/plugins/flatpak/tests/app-update/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Copyright 2016 Richard Hughes <richard hughsie com> -->
 <component type="desktop">
-  <id>org.test.Chiron.desktop</id>
+  <id>org.test.Chiron</id>
   <metadata_license>CC0-1.0</metadata_license>
   <project_license>GPL-2.0+</project_license>
   <name>Chiron</name>
diff --git 
a/plugins/flatpak/tests/app-with-runtime/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml 
b/plugins/flatpak/tests/app-with-runtime/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml
index 0d912a89..58af0827 100644
--- a/plugins/flatpak/tests/app-with-runtime/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml
+++ b/plugins/flatpak/tests/app-with-runtime/org.test.Chiron/files/share/appdata/org.test.Chiron.appdata.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Copyright 2016 Richard Hughes <richard hughsie com> -->
 <component type="desktop">
-  <id>org.test.Chiron.desktop</id>
+  <id>org.test.Chiron</id>
   <metadata_license>CC0-1.0</metadata_license>
   <project_license>GPL-2.0+</project_license>
   <name>Chiron</name>
diff --git a/plugins/modalias/gs-self-test.c b/plugins/modalias/gs-self-test.c
index dbe628a7..80257b49 100644
--- a/plugins/modalias/gs-self-test.c
+++ b/plugins/modalias/gs-self-test.c
@@ -56,6 +56,7 @@ gs_plugins_modalias_func (GsPluginLoader *plugin_loader)
 int
 main (int argc, char **argv)
 {
+       const gchar *tmp_root = "/var/tmp/self-test";
        gboolean ret;
        g_autofree gchar *xml = NULL;
        g_autoptr(GError) error = NULL;
@@ -77,12 +78,14 @@ main (int argc, char **argv)
                "    <id>com.hughski.ColorHug2.driver</id>\n"
                "    <name>ColorHug2</name>\n"
                "    <summary>ColorHug2 Colorimeter Driver</summary>\n"
+               "    <pkgname>colorhug-client</pkgname>\n"
                "    <provides>\n"
                "      <modalias>pci:*</modalias>\n"
                "    </provides>\n"
                "  </component>\n"
                "</components>\n");
        g_setenv ("GS_SELF_TEST_APPSTREAM_XML", xml, TRUE);
+       g_setenv ("GS_SELF_TEST_CACHEDIR", tmp_root, TRUE);
 
        /* only critical and error are fatal */
        g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
diff --git a/plugins/shell-extensions/gs-plugin-shell-extensions.c 
b/plugins/shell-extensions/gs-plugin-shell-extensions.c
index 5bf2ee71..cf69c298 100644
--- a/plugins/shell-extensions/gs-plugin-shell-extensions.c
+++ b/plugins/shell-extensions/gs-plugin-shell-extensions.c
@@ -26,6 +26,7 @@
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
 #include <json-glib/json-glib.h>
+#include <xmlb.h>
 
 #include <gnome-software.h>
 
@@ -437,7 +438,8 @@ gs_plugin_refine_app (GsPlugin *plugin,
 
 static gboolean
 gs_plugin_shell_extensions_parse_version (GsPlugin *plugin,
-                                         AsApp *app,
+                                         const gchar *component_id,
+                                         XbBuilderNode *app,
                                          JsonObject *ver_map,
                                          GError **error)
 {
@@ -445,7 +447,7 @@ gs_plugin_shell_extensions_parse_version (GsPlugin *plugin,
        JsonObject *json_ver = NULL;
        gint64 version;
        g_autofree gchar *shell_version = NULL;
-       g_autoptr(AsRelease) release = NULL;
+       g_autoptr(XbBuilderNode) release = NULL;
 
        /* look for version, major.minor.micro */
        if (json_object_has_member (ver_map, priv->shell_version)) {
@@ -468,7 +470,7 @@ gs_plugin_shell_extensions_parse_version (GsPlugin *plugin,
        /* FIXME: mark as incompatible? */
        if (json_ver == NULL) {
                g_debug ("no version_map for %s: %s",
-                        as_app_get_id (app),
+                        component_id,
                         priv->shell_version);
                return TRUE;
        }
@@ -485,108 +487,106 @@ gs_plugin_shell_extensions_parse_version (GsPlugin *plugin,
        shell_version = g_strdup_printf ("%" G_GINT64_FORMAT, version);
 
        /* add a dummy release */
-       release = as_release_new ();
-       as_release_set_version (release, shell_version);
-       as_app_add_release (app, release);
+       xb_builder_node_insert_text (app, "release", NULL,
+                                    "version", shell_version,
+                                    NULL);
        return TRUE;
 }
 
-static AsApp *
+
+
+static XbBuilderNode *
 gs_plugin_shell_extensions_parse_app (GsPlugin *plugin,
                                      JsonObject *json_app,
                                      GError **error)
 {
-       g_autoptr(AsApp) app = NULL;
        JsonObject *json_ver_map;
        const gchar *tmp;
+       g_autofree gchar *component_id = NULL;
+       g_autoptr(XbBuilderNode) app = NULL;
+       g_autoptr(XbBuilderNode) metadata = NULL;
 
-       app = as_app_new ();
-       as_app_set_kind (app, AS_APP_KIND_SHELL_EXTENSION);
-       as_app_set_project_license (app, "GPL-2.0+");
+       app = xb_builder_node_new ("component");
+       xb_builder_node_set_attr (app, "kind", "shell-extension");
+       xb_builder_node_insert_text (app, "project_license", "GPL-2.0+", NULL);
+       metadata = xb_builder_node_insert (app, "metadata", NULL);
 
        tmp = json_object_get_string_member (json_app, "description");
        if (tmp != NULL) {
-               g_autofree gchar *desc = NULL;
-               desc = as_markup_import (tmp, AS_MARKUP_CONVERT_FORMAT_SIMPLE, error);
-               if (desc == NULL) {
-                       gs_utils_error_convert_appstream (error);
-                       return NULL;
-               }
-               as_app_set_description (app, NULL, desc);
+               g_auto(GStrv) paras = g_strsplit (tmp, "\n", -1);
+               g_autoptr(XbBuilderNode) desc = xb_builder_node_insert (app, "description", NULL);
+               for (guint i = 0; paras[i] != NULL; i++)
+                       xb_builder_node_insert_text (desc, "p", paras[i], NULL);
        }
        tmp = json_object_get_string_member (json_app, "screenshot");
        if (tmp != NULL) {
-               g_autoptr(AsScreenshot) ss = NULL;
-               g_autoptr(AsImage) im = NULL;
+               g_autoptr(XbBuilderNode) screenshots = NULL;
+               g_autoptr(XbBuilderNode) screenshot = NULL;
                g_autofree gchar *uri = NULL;
+               screenshots = xb_builder_node_insert (app, "screenshots", NULL);
+               screenshot = xb_builder_node_insert (screenshots, "screenshot",
+                                                    "kind", "default",
+                                                    NULL);
                uri = g_build_path ("/", SHELL_EXTENSIONS_API_URI, tmp, NULL);
-               im = as_image_new ();
-               as_image_set_kind (im, AS_IMAGE_KIND_SOURCE);
-               as_image_set_url (im, uri);
-               ss = as_screenshot_new ();
-               as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_DEFAULT);
-               as_screenshot_add_image (ss, im);
-               as_app_add_screenshot (app, ss);
+               xb_builder_node_insert_text (screenshot, "image", uri,
+                                            "kind", "source",
+                                            NULL);
        }
        tmp = json_object_get_string_member (json_app, "name");
        if (tmp != NULL)
-               as_app_set_name (app, NULL, tmp);
+               xb_builder_node_insert_text (app, "name", tmp, NULL);
        tmp = json_object_get_string_member (json_app, "uuid");
        if (tmp != NULL) {
-               g_autofree gchar *id = NULL;
-               id = as_utils_appstream_id_build (tmp);
-               as_app_set_id (app, id);
-               as_app_add_metadata (app, "shell-extensions::uuid", tmp);
+               component_id = as_utils_appstream_id_build (tmp);
+               xb_builder_node_insert_text (app, "id", component_id, NULL);
+               xb_builder_node_insert_text (metadata, "value", tmp,
+                                            "key", "shell-extensions::uuid",
+                                            NULL);
        }
        tmp = json_object_get_string_member (json_app, "link");
        if (tmp != NULL) {
                g_autofree gchar *uri = NULL;
                uri = g_build_filename (SHELL_EXTENSIONS_API_URI, tmp, NULL);
-               as_app_add_url (app, AS_URL_KIND_HOMEPAGE, uri);
+               xb_builder_node_insert_text (app, "url", uri,
+                                            "type", "homepage",
+                                            NULL);
        }
        tmp = json_object_get_string_member (json_app, "icon");
        if (tmp != NULL) {
-               g_autoptr(AsIcon) ic = NULL;
                /* just use a stock icon as the remote icons are
                 * sometimes missing, poor quality and low resolution */
-               ic = as_icon_new ();
-               as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-               as_icon_set_name (ic, "application-x-addon-symbolic");
-               as_app_add_icon (app, ic);
+               xb_builder_node_insert_text (app, "icon",
+                                            "application-x-addon-symbolic",
+                                            "type", "stock",
+                                            NULL);
        }
 
        /* try to get version */
        json_ver_map = json_object_get_object_member (json_app, "shell_version_map");
        if (json_ver_map != NULL) {
                if (!gs_plugin_shell_extensions_parse_version (plugin,
+                                                              component_id,
                                                               app,
                                                               json_ver_map,
                                                               error))
                        return NULL;
        }
 
-       /* we have no data :/ */
-       as_app_add_metadata (app, "GnomeSoftware::Plugin",
-                            gs_plugin_get_name (plugin));
-       as_app_add_metadata (app, "GnomeSoftware::OriginHostnameUrl",
-                            SHELL_EXTENSIONS_API_URI);
-
        return g_steal_pointer (&app);
 }
 
-static GPtrArray *
+static XbBuilderNode *
 gs_plugin_shell_extensions_parse_apps (GsPlugin *plugin,
                                       const gchar *data,
                                       gssize data_len,
                                       GError **error)
 {
-       GPtrArray *apps;
        JsonArray *json_extensions_array;
        JsonNode *json_extensions;
        JsonNode *json_root;
        JsonObject *json_item;
-       guint i;
        g_autoptr(JsonParser) json_parser = NULL;
+       g_autoptr(XbBuilderNode) apps = NULL;
 
        /* nothing */
        if (data == NULL) {
@@ -628,7 +628,7 @@ gs_plugin_shell_extensions_parse_apps (GsPlugin *plugin,
        }
 
        /* load extensions */
-       apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+       apps = xb_builder_node_new ("components");
        json_extensions = json_object_get_member (json_item, "extensions");
        if (json_extensions == NULL) {
                g_set_error_literal (error,
@@ -647,36 +647,36 @@ gs_plugin_shell_extensions_parse_apps (GsPlugin *plugin,
        }
 
        /* parse each app */
-       for (i = 0; i < json_array_get_length (json_extensions_array); i++) {
-               AsApp *app;
+       for (guint i = 0; i < json_array_get_length (json_extensions_array); i++) {
+               XbBuilderNode *component;
                JsonNode *json_extension;
                JsonObject *json_extension_obj;
                json_extension = json_array_get_element (json_extensions_array, i);
                json_extension_obj = json_node_get_object (json_extension);
-               app = gs_plugin_shell_extensions_parse_app (plugin,
+               component = gs_plugin_shell_extensions_parse_app (plugin,
                                                            json_extension_obj,
                                                            error);
-               if (app == NULL)
+               if (component == NULL)
                        return NULL;
-               g_ptr_array_add (apps, app);
+               xb_builder_node_add_child (apps, component);
        }
 
-       return apps;
+       return g_steal_pointer (&apps);
 }
 
-static GPtrArray *
+static XbBuilderNode *
 gs_plugin_shell_extensions_get_apps (GsPlugin *plugin,
                                     guint cache_age,
                                     GCancellable *cancellable,
                                     GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       GPtrArray *apps;
        g_autofree gchar *cachefn = NULL;
        g_autofree gchar *uri = NULL;
-       g_autoptr(GFile) cachefn_file = NULL;
        g_autoptr(GBytes) data = NULL;
+       g_autoptr(GFile) cachefn_file = NULL;
        g_autoptr(GsApp) app_dl = gs_app_new (gs_plugin_get_name (plugin));
+       g_autoptr(XbBuilderNode) apps = NULL;
 
        /* look in the cache */
        cachefn = gs_utils_get_cache_filename ("shell-extensions",
@@ -730,7 +730,7 @@ gs_plugin_shell_extensions_get_apps (GsPlugin *plugin,
                                  error))
                return NULL;
 
-       return apps;
+       return g_steal_pointer (&apps);
 }
 
 static gboolean
@@ -740,25 +740,26 @@ gs_plugin_shell_extensions_refresh (GsPlugin *plugin,
                                    GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       AsApp *app;
        gboolean repo_enabled;
        const gchar *fn_test;
-       guint i;
        g_autofree gchar *fn = NULL;
-       g_autoptr(GPtrArray) apps = NULL;
-       g_autoptr(AsStore) store = NULL;
+       g_autoptr(GError) error_local = NULL;
        g_autoptr(GFile) file = NULL;
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+       g_autoptr(XbBuilderNode) apps = NULL;
+       g_autoptr(XbSilo) silo = NULL;
 
        /* check age */
        fn_test = g_getenv ("GS_SELF_TEST_SHELL_EXTENSIONS_XML_FN");
        if (fn_test != NULL) {
                fn = g_strdup (fn_test);
        } else {
-               fn = g_build_filename (g_get_user_data_dir (),
-                                      "app-info",
-                                      "xmls",
-                                      "extensions-web.xml",
-                                      NULL);
+               fn = gs_utils_get_cache_filename ("shell-extensions",
+                                                 "extensions-web.xmlb",
+                                                 GS_UTILS_CACHE_FLAG_WRITEABLE,
+                                                 error);
+               if (fn == NULL)
+                       return FALSE;
        }
 
        /* remove old appstream data if the repo is disabled */
@@ -785,25 +786,22 @@ gs_plugin_shell_extensions_refresh (GsPlugin *plugin,
        if (apps == NULL)
                return FALSE;
 
-       /* add to local store */
-       store = as_store_new ();
-       as_store_set_origin (store, "extensions-web");
-       for (i = 0; i < apps->len; i++) {
-               app = g_ptr_array_index (apps, i);
-               g_debug ("adding to local store %s", as_app_get_id (app));
-               as_store_add_app (store, app);
-       }
+       /* add to builder */
+       xb_builder_import_node (builder, apps);
 
        /* save to disk */
-       if (!gs_mkdir_parent (fn, error))
+       silo = xb_builder_ensure (builder, file,
+                                 XB_BUILDER_COMPILE_FLAG_NONE,
+                                 cancellable, &error_local);
+       if (silo == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "failed to compile %s",
+                            error_local->message);
                return FALSE;
-       g_debug ("saving to %s", fn);
-       return as_store_to_file (store, file,
-                                AS_NODE_TO_XML_FLAG_ADD_HEADER |
-                                AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
-                                AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE,
-                                cancellable,
-                                error);
+       }
+       return TRUE;
 }
 
 gboolean
diff --git a/plugins/shell-extensions/gs-self-test.c b/plugins/shell-extensions/gs-self-test.c
index b0fb993d..29db7106 100644
--- a/plugins/shell-extensions/gs-self-test.c
+++ b/plugins/shell-extensions/gs-self-test.c
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include <glib/gstdio.h>
+#include <xmlb.h>
 
 #include "gnome-software-private.h"
 
@@ -77,12 +78,13 @@ gs_plugins_shell_extensions_installed_func (GsPluginLoader *plugin_loader)
 static void
 gs_plugins_shell_extensions_remote_func (GsPluginLoader *plugin_loader)
 {
-       const gchar *xml_fn = "/var/tmp/self-test/extensions-web.xml";
+       const gchar *xml_fn = "/var/tmp/self-test/extensions-web.xmlb";
        gboolean ret;
        g_autoptr(GError) error = NULL;
        g_autoptr(GFile) file = NULL;
+       g_autoptr(GPtrArray) components = NULL;
        g_autoptr(GsPluginJob) plugin_job = NULL;
-       g_autoptr(AsStore) store = NULL;
+       g_autoptr(XbSilo) silo = NULL;
 
        /* no shell-extensions, abort */
        if (!gs_plugin_loader_get_enabled (plugin_loader, "shell-extensions")) {
@@ -103,12 +105,17 @@ gs_plugins_shell_extensions_remote_func (GsPluginLoader *plugin_loader)
        g_assert (ret);
 
        /* ensure file was populated */
-       store = as_store_new ();
+       silo = xb_silo_new ();
        file = g_file_new_for_path (xml_fn);
-       ret = as_store_from_file (store, file, NULL, NULL, &error);
+       ret = xb_silo_load_from_file (silo, file,
+                                     XB_SILO_LOAD_FLAG_NONE,
+                                     NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
-       g_assert_cmpint (as_store_get_size (store), >, 20);
+       components = xb_silo_query (silo, "components/component", 0, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (components);
+       g_assert_cmpint (components->len, >, 20);
 }
 
 int
diff --git a/plugins/shell-extensions/meson.build b/plugins/shell-extensions/meson.build
index 655562bc..dec9d97d 100644
--- a/plugins/shell-extensions/meson.build
+++ b/plugins/shell-extensions/meson.build
@@ -11,7 +11,10 @@ sources : 'gs-plugin-shell-extensions.c',
   install : true,
   install_dir: plugin_dir,
   c_args : cargs,
-  dependencies : plugin_libs
+  dependencies : [
+    plugin_libs,
+    libxmlb,
+  ],
 )
 
 if get_option('tests')
@@ -27,6 +30,7 @@ if get_option('tests')
     ],
     dependencies : [
       plugin_libs,
+      libxmlb,
     ],
     link_with : [
       libgnomesoftware



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