[gnome-software/wip/hughsie/editor: 16/16] Convert the editor to use xmlb rather than appstream-glib



commit d02ef0398e8f9ed7d7ac220175f9c8a51011b154
Author: Richard Hughes <richard hughsie com>
Date:   Wed Jul 17 17:02:42 2019 +0100

    Convert the editor to use xmlb rather than appstream-glib
    
    This also allows us to actually import and export the reference XML correctly.

 src/gs-editor.c | 761 +++++++++++++++++++++++++++++++++++---------------------
 src/meson.build |   3 +-
 2 files changed, 478 insertions(+), 286 deletions(-)
---
diff --git a/src/gs-editor.c b/src/gs-editor.c
index 9bd06294..d5b3f236 100644
--- a/src/gs-editor.c
+++ b/src/gs-editor.c
@@ -10,6 +10,7 @@
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 #include <locale.h>
+#include <xmlb.h>
 
 #include "gs-common.h"
 #include "gs-css.h"
@@ -17,21 +18,150 @@
 #include "gs-summary-tile.h"
 #include "gs-upgrade-banner.h"
 
+#include "gnome-software-private.h"
+
 typedef struct {
        GCancellable            *cancellable;
        GtkApplication          *application;
        GtkBuilder              *builder;
        GtkWidget               *featured_tile1;
        GtkWidget               *upgrade_banner;
-       AsStore                 *store;
-       AsStore                 *store_global;
-       AsApp                   *selected_item;
-       AsApp                   *deleted_item;
+       GsPluginLoader          *plugin_loader;
+       GsAppList               *store;
+       GsApp                   *selected_app;
+       GsApp                   *deleted_app;
        gboolean                 is_in_refresh;
        gboolean                 pending_changes;
        guint                    refresh_details_delayed_id;
+       guint                    autosave_id;
+       GFile                   *autosave_file;
 } GsEditor;
 
+static void
+gs_editor_error_message (GsEditor *self, const gchar *title, const gchar *message)
+{
+       GtkWidget *dialog;
+       GtkWindow *window;
+       window = GTK_WINDOW (gtk_builder_get_object (self->builder, "window_main"));
+       dialog = gtk_message_dialog_new (window,
+                                        GTK_DIALOG_MODAL |
+                                        GTK_DIALOG_DESTROY_WITH_PARENT,
+                                        GTK_MESSAGE_WARNING,
+                                        GTK_BUTTONS_OK,
+                                        "%s", title);
+       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+                                                 "%s", message);
+       gtk_dialog_run (GTK_DIALOG (dialog));
+       gtk_widget_destroy (dialog);
+}
+
+static GsApp *
+gs_editor_node_to_app (XbNode *component)
+{
+       g_autoptr(GsApp) app = gs_app_new (NULL);
+       const gchar *tmp;
+       const gchar *keys[] = {
+               "GnomeSoftware::AppTile-css",
+               "GnomeSoftware::FeatureTile-css",
+               "GnomeSoftware::UpgradeBanner-css",
+               NULL };
+
+       /* <id> */
+       tmp = xb_node_query_text (component, "id", NULL);
+       if (tmp == NULL)
+               return NULL;
+       gs_app_set_id (app, tmp);
+
+       /* <kudos><kudo>foo</kudo></kudos> */
+       if (xb_node_query_text (component, "kudos/kudo[text()='GnomeSoftware::popular']", NULL) != NULL)
+               gs_app_add_kudo (app, GS_APP_KUDO_POPULAR);
+
+       /* <categories><category>Featured</category></categories> */
+       if (xb_node_query_text (component, "categories/category[text()='Featured']", NULL) != NULL)
+               gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
+
+       /* <custom><value key="foo">bar</value></custom> */
+       for (guint j = 0; keys[j] != NULL; j++) {
+               g_autofree gchar *xpath = g_strdup_printf ("custom/value[@key='%s']", keys[j]);
+               tmp = xb_node_query_text (component, xpath, NULL);
+               if (tmp != NULL)
+                       gs_app_set_metadata (app, keys[j], tmp);
+       }
+       return g_steal_pointer (&app);
+}
+
+static XbBuilderNode *
+gs_editor_app_to_node (GsApp *app)
+{
+       g_autoptr(XbBuilderNode) component =  xb_builder_node_new ("component");
+       g_autoptr(XbBuilderNode) custom = NULL;
+       const gchar *keys[] = {
+               "GnomeSoftware::AppTile-css",
+               "GnomeSoftware::FeatureTile-css",
+               "GnomeSoftware::UpgradeBanner-css",
+               NULL };
+
+       /* <id> */
+       xb_builder_node_insert_text (component, "id", gs_app_get_id (app), NULL);
+
+       /* <categories><category>Featured</category></categories> */
+       if (gs_app_has_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED)) {
+               g_autoptr(XbBuilderNode) cats = NULL;
+               cats = xb_builder_node_insert (component, "categories", NULL);
+               xb_builder_node_insert_text (cats, "category", "Featured", NULL);
+       }
+
+       /* <kudos><kudo>foo</kudo></kudos> */
+       if (gs_app_has_kudo (app, GS_APP_KUDO_POPULAR)) {
+               g_autoptr(XbBuilderNode) kudos = NULL;
+               kudos = xb_builder_node_insert (component, "kudos", NULL);
+               xb_builder_node_insert_text (kudos, "category", "GnomeSoftware::popular", NULL);
+       }
+
+       /* <custom><value key="foo">bar</value></custom> */
+       custom = xb_builder_node_insert (component, "custom", NULL);
+       for (guint j = 0; keys[j] != NULL; j++) {
+               g_autoptr(XbBuilderNode) value = xb_builder_node_new ("value");
+               const gchar *tmp = gs_app_get_metadata_item (app, keys[j]);
+               if (tmp == NULL)
+                       continue;
+
+               /* add literal text */
+               xb_builder_node_add_flag (value, XB_BUILDER_NODE_FLAG_LITERAL_TEXT);
+               xb_builder_node_set_text (value, tmp, -1);
+               xb_builder_node_set_attr (value, "key", keys[j]);
+               xb_builder_node_add_child (custom, value);
+       }
+
+       return g_steal_pointer (&component);
+}
+
+static void
+gs_editor_add_nodes_from_silo (GsEditor *self, XbSilo *silo)
+{
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GPtrArray) components = NULL;
+
+       components = xb_silo_query (silo, "components/component", 0, &error);
+       if (components == NULL) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+                       return;
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
+                       return;
+               /* TRANSLATORS: error dialog title */
+               gs_editor_error_message (self, _("Failed to load components"), error->message);
+               return;
+       }
+       for (guint i = 0; i < components->len; i++) {
+               g_autoptr(GsApp) app = NULL;
+               XbNode *component = g_ptr_array_index (components, i);
+               app = gs_editor_node_to_app (component);
+               if (app == NULL)
+                       continue;
+               gs_app_list_add (self->store, app);
+       }
+}
+
 static gchar *
 gs_editor_css_download_resources (GsEditor *self, const gchar *css, GError **error)
 {
@@ -66,123 +196,31 @@ gs_design_validate_css (GsEditor *self, const gchar *markup, GError **error)
 }
 
 static void
-gs_editor_refine_app_pixbuf (GsApp *app)
+gs_editor_copy_from_global_app (GsApp *app, GsApp *global_app)
 {
-       GPtrArray *icons;
-       if (gs_app_get_pixbuf (app) != NULL)
+       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+
+       /* nothing found */
+       if (global_app == NULL) {
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "Application");
+               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "Description");
+               gs_app_set_description (app, GS_APP_QUALITY_NORMAL, "A multiline description");
+               gs_app_set_version (app, "3.28");
+               gs_app_set_pixbuf (app, NULL);
+               gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
                return;
-       icons = gs_app_get_icons (app);
-       for (guint i = 0; i < icons->len; i++) {
-               AsIcon *ic = g_ptr_array_index (icons, i);
-               g_autoptr(GError) error = NULL;
-               if (as_icon_get_kind (ic) == AS_ICON_KIND_STOCK) {
-
-                       g_autoptr(GdkPixbuf) pb = NULL;
-                       pb = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
-                                                      as_icon_get_name (ic),
-                                                      64,
-                                                      GTK_ICON_LOOKUP_FORCE_SIZE,
-                                                      &error);
-                       if (pb == NULL) {
-                               g_warning ("failed to load icon: %s", error->message);
-                               continue;
-                       }
-                       gs_app_set_pixbuf (app, pb);
-               } else {
-                       if (!as_icon_load (ic, AS_ICON_LOAD_FLAG_SEARCH_SIZE, &error)) {
-                               g_warning ("failed to load icon: %s", error->message);
-                               continue;
-                       }
-                       gs_app_set_pixbuf (app, as_icon_get_pixbuf (ic));
-               }
-               break;
-       }
-}
-
-static GsApp *
-gs_editor_convert_app (GsEditor *self, AsApp *item)
-{
-       AsApp *item_global;
-       AsAppState item_state;
-       GsApp *app;
-       const gchar *keys[] = {
-               "GnomeSoftware::AppTile-css",
-               "GnomeSoftware::FeatureTile-css",
-               "GnomeSoftware::UpgradeBanner-css",
-               NULL };
-
-       /* copy name, summary and description */
-       app = gs_app_new (as_app_get_id (item));
-       item_global = as_store_get_app_by_id (self->store_global, as_app_get_id (item));
-       if (item_global == NULL) {
-               const gchar *tmp;
-               g_autoptr(AsIcon) ic = NULL;
-               g_debug ("no app found for %s, using fallback", as_app_get_id (item));
-
-               /* copy from AsApp, falling back to something sane */
-               tmp = as_app_get_name (item, NULL);
-               if (tmp == NULL)
-                       tmp = "Application";
-               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, tmp);
-               tmp = as_app_get_comment (item, NULL);
-               if (tmp == NULL)
-                       tmp = "Description";
-               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, tmp);
-               tmp = as_app_get_description (item, NULL);
-               if (tmp == NULL)
-                       tmp = "A multiline description";
-               gs_app_set_description (app, GS_APP_QUALITY_NORMAL, tmp);
-               ic = as_icon_new ();
-               as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-               as_icon_set_name (ic, "application-x-executable");
-               gs_app_add_icon (app, ic);
-               item_state = as_app_get_state (item);
-       } else {
-               GPtrArray *icons;
-               g_debug ("found global app for %s", as_app_get_id (item));
-               gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
-                                as_app_get_name (item_global, NULL));
-               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
-                                   as_app_get_comment (item_global, NULL));
-               gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
-                                       as_app_get_description (item_global, NULL));
-               icons = as_app_get_icons (item_global);
-               for (guint i = 0; i < icons->len; i++) {
-                       AsIcon *icon = g_ptr_array_index (icons, i);
-                       gs_app_add_icon (app, icon);
-               }
-               item_state = as_app_get_state (item_global);
        }
 
        /* copy state */
-       if (item_state == AS_APP_STATE_UNKNOWN)
-               item_state = AS_APP_STATE_AVAILABLE;
-       gs_app_set_state (app, item_state);
-
-       /* copy version */
-       gs_app_set_version (app, "3.28");
-
-       /* load pixbuf */
-       gs_editor_refine_app_pixbuf (app);
-
-       /* copy metadata */
-       for (guint i = 0; keys[i] != NULL; i++) {
-               g_autoptr(GError) error = NULL;
-               const gchar *markup = as_app_get_metadata_item (item, keys[i]);
-               if (markup != NULL) {
-                       g_autofree gchar *css_new = NULL;
-                       css_new = gs_editor_css_download_resources (self, markup, &error);
-                       if (css_new == NULL) {
-                               g_warning ("%s", error->message);
-                               gs_app_set_metadata (app, keys[i], markup);
-                       } else {
-                               gs_app_set_metadata (app, keys[i], css_new);
-                       }
-               } else {
-                       gs_app_set_metadata (app, keys[i], NULL);
-               }
-       }
-       return app;
+       gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
+                        gs_app_get_name (global_app));
+       gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
+                           gs_app_get_summary (global_app));
+       gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+                               gs_app_get_description (global_app));
+       gs_app_set_version (app, gs_app_get_version (global_app));
+       gs_app_set_state (app, gs_app_get_state (global_app));
+       gs_app_set_pixbuf (app, gs_app_get_pixbuf (global_app));
 }
 
 static void
@@ -192,25 +230,18 @@ gs_editor_refresh_details (GsEditor *self)
        GtkWidget *widget;
        const gchar *css = NULL;
        g_autoptr(GError) error = NULL;
-       g_autoptr(GsApp) app = NULL;
 
        /* ignore changed events */
        self->is_in_refresh = TRUE;
 
-       /* create a GsApp for the AsApp */
-       if (self->selected_item != NULL) {
-               app = gs_editor_convert_app (self, self->selected_item);
-               g_debug ("refreshing details for %s", gs_app_get_id (app));
-       }
-
        /* get kind */
-       if (self->selected_item != NULL)
-               app_kind = as_app_get_kind (self->selected_item);
+       if (self->selected_app != NULL)
+               app_kind = gs_app_get_kind (self->selected_app);
 
        /* feature tiles */
        if (app_kind != AS_APP_KIND_OS_UPGRADE) {
-               if (self->selected_item != NULL) {
-                       gs_app_tile_set_app (GS_APP_TILE (self->featured_tile1), app);
+               if (self->selected_app != NULL) {
+                       gs_app_tile_set_app (GS_APP_TILE (self->featured_tile1), self->selected_app);
                        gtk_widget_set_sensitive (self->featured_tile1, TRUE);
                } else {
                        gtk_widget_set_sensitive (self->featured_tile1, FALSE);
@@ -222,8 +253,8 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* upgrade banner */
        if (app_kind == AS_APP_KIND_OS_UPGRADE) {
-               if (self->selected_item != NULL) {
-                       gs_upgrade_banner_set_app (GS_UPGRADE_BANNER (self->upgrade_banner), app);
+               if (self->selected_app != NULL) {
+                       gs_upgrade_banner_set_app (GS_UPGRADE_BANNER (self->upgrade_banner), 
self->selected_app);
                        gtk_widget_set_sensitive (self->upgrade_banner, TRUE);
                } else {
                        gtk_widget_set_sensitive (self->upgrade_banner, FALSE);
@@ -235,11 +266,11 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* name */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_name"));
-       if (self->selected_item != NULL) {
+       if (self->selected_app != NULL) {
                const gchar *tmp;
                gtk_widget_set_visible (widget, app_kind == AS_APP_KIND_OS_UPGRADE);
                widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_name"));
-               tmp = as_app_get_name (self->selected_item, NULL);
+               tmp = gs_app_get_name (self->selected_app);
                if (tmp != NULL)
                        gtk_entry_set_text (GTK_ENTRY (widget), tmp);
        } else {
@@ -248,11 +279,11 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* summary */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_summary"));
-       if (self->selected_item != NULL) {
+       if (self->selected_app != NULL) {
                const gchar *tmp;
                gtk_widget_set_visible (widget, app_kind == AS_APP_KIND_OS_UPGRADE);
                widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_summary"));
-               tmp = as_app_get_comment (self->selected_item, NULL);
+               tmp = gs_app_get_summary (self->selected_app);
                if (tmp != NULL)
                        gtk_entry_set_text (GTK_ENTRY (widget), tmp);
        } else {
@@ -261,7 +292,7 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* kudos */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_kudos"));
-       if (self->selected_item != NULL) {
+       if (self->selected_app != NULL) {
                gtk_widget_set_visible (widget, app_kind != AS_APP_KIND_OS_UPGRADE);
        } else {
                gtk_widget_set_visible (widget, TRUE);
@@ -269,10 +300,10 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* category featured */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "checkbutton_category_featured"));
-       if (self->selected_item != NULL) {
+       if (self->selected_app != NULL) {
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
-                                             as_app_has_category (self->selected_item,
-                                                                  "Featured"));
+                                             gs_app_has_kudo (self->selected_app,
+                                                              GS_APP_KUDO_FEATURED_RECOMMENDED));
                gtk_widget_set_sensitive (widget, TRUE);
        } else {
                gtk_widget_set_sensitive (widget, FALSE);
@@ -280,10 +311,10 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* kudo popular */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "checkbutton_editors_pick"));
-       if (self->selected_item != NULL) {
+       if (self->selected_app != NULL) {
                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
-                                             as_app_has_kudo (self->selected_item,
-                                                              "GnomeSoftware::popular"));
+                                             gs_app_has_kudo (self->selected_app,
+                                                              GS_APP_KUDO_POPULAR));
                gtk_widget_set_sensitive (widget, TRUE);
        } else {
                gtk_widget_set_sensitive (widget, FALSE);
@@ -291,17 +322,17 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* featured */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "textview_css"));
-       if (self->selected_item != NULL) {
+       if (self->selected_app != NULL) {
                GtkTextBuffer *buffer;
                GtkTextIter iter_end;
                GtkTextIter iter_start;
                g_autofree gchar *css_existing = NULL;
 
                if (app_kind == AS_APP_KIND_OS_UPGRADE) {
-                       css = as_app_get_metadata_item (self->selected_item,
+                       css = gs_app_get_metadata_item (self->selected_app,
                                                        "GnomeSoftware::UpgradeBanner-css");
                } else {
-                       css = as_app_get_metadata_item (self->selected_item,
+                       css = gs_app_get_metadata_item (self->selected_app,
                                                        "GnomeSoftware::FeatureTile-css");
                }
                if (css == NULL)
@@ -318,8 +349,8 @@ gs_editor_refresh_details (GsEditor *self)
 
        /* desktop ID */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_desktop_id"));
-       if (self->selected_item != NULL) {
-               const gchar *id = as_app_get_id (self->selected_item);
+       if (self->selected_app != NULL) {
+               const gchar *id = gs_app_get_id (self->selected_app);
                if (id == NULL)
                        id = "";
                gtk_entry_set_text (GTK_ENTRY (widget), id);
@@ -362,6 +393,41 @@ gs_design_dialog_refresh_details_delayed_cb (gpointer user_data)
        return FALSE;
 }
 
+static XbSilo *
+gs_editor_build_silo_from_apps (GsEditor *self)
+{
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+       g_autoptr(XbBuilderNode) components = xb_builder_node_new ("components");
+
+       /* add all apps */
+       for (guint i = 0; i < gs_app_list_length (self->store); i++) {
+               GsApp *app = gs_app_list_index (self->store, i);
+               g_autoptr(XbBuilderNode) component = gs_editor_app_to_node (app);
+               xb_builder_node_add_child (components, component);
+       }
+       xb_builder_import_node (builder, components);
+
+       return xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, NULL);
+}
+
+static void
+gs_editor_autosave (GsEditor *self)
+{
+       g_autoptr(GError) error = NULL;
+       g_autoptr(XbSilo) silo = gs_editor_build_silo_from_apps (self);
+       g_debug ("autosaving silo");
+       if (!xb_silo_save_to_file (silo, self->autosave_file, NULL, &error))
+               g_warning ("failed to autosave: %s", error->message);
+}
+
+static gboolean
+gs_editor_autosave_cb (gpointer user_data)
+{
+       GsEditor *self = (GsEditor *) user_data;
+       gs_editor_autosave (self);
+       return TRUE;
+}
+
 static void
 gs_design_dialog_refresh_details_delayed (GsEditor *self)
 {
@@ -385,12 +451,12 @@ gs_design_dialog_buffer_changed_cb (GtkTextBuffer *buffer, GsEditor *self)
        gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
        css = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, FALSE);
        g_debug ("CSS now '%s'", css);
-       if (as_app_get_kind (self->selected_item) == AS_APP_KIND_OS_UPGRADE) {
-               as_app_add_metadata (self->selected_item, "GnomeSoftware::UpgradeBanner-css", NULL);
-               as_app_add_metadata (self->selected_item, "GnomeSoftware::UpgradeBanner-css", css);
+       if (gs_app_get_kind (self->selected_app) == AS_APP_KIND_OS_UPGRADE) {
+               gs_app_set_metadata (self->selected_app, "GnomeSoftware::UpgradeBanner-css", NULL);
+               gs_app_set_metadata (self->selected_app, "GnomeSoftware::UpgradeBanner-css", css);
        } else {
-               as_app_add_metadata (self->selected_item, "GnomeSoftware::FeatureTile-css", NULL);
-               as_app_add_metadata (self->selected_item, "GnomeSoftware::FeatureTile-css", css);
+               gs_app_set_metadata (self->selected_app, "GnomeSoftware::FeatureTile-css", NULL);
+               gs_app_set_metadata (self->selected_app, "GnomeSoftware::FeatureTile-css", css);
        }
        self->pending_changes = TRUE;
        gs_design_dialog_refresh_details_delayed (self);
@@ -452,13 +518,7 @@ static void
 gs_editor_app_tile_clicked_cb (GsAppTile *tile, GsEditor *self)
 {
        GsApp *app = gs_app_tile_get_app (tile);
-       AsApp *item = as_store_get_app_by_id (self->store, gs_app_get_id (app));
-       if (item == NULL) {
-               g_warning ("failed to find %s", gs_app_get_id (app));
-               return;
-       }
-       g_set_object (&self->selected_item, item);
-
+       g_set_object (&self->selected_app, app);
        gs_editor_refresh_details (self);
        gs_editor_set_page (self, "details");
 }
@@ -466,21 +526,15 @@ gs_editor_app_tile_clicked_cb (GsAppTile *tile, GsEditor *self)
 static void
 gs_editor_refresh_choice (GsEditor *self)
 {
-       GPtrArray *apps;
        GtkContainer *container;
 
        /* add all apps */
        container = GTK_CONTAINER (gtk_builder_get_object (self->builder,
                                                           "flowbox_main"));
        gs_container_remove_all (GTK_CONTAINER (container));
-       apps = as_store_get_apps (self->store);
-       for (guint i = 0; i < apps->len; i++) {
-               AsApp *item = g_ptr_array_index (apps, i);
-               GtkWidget *tile = NULL;
-               g_autoptr(GsApp) app = NULL;
-
-               app = gs_editor_convert_app (self, item);
-               tile = gs_summary_tile_new (app);
+       for (guint i = 0; i < gs_app_list_length (self->store); i++) {
+               GsApp *app = gs_app_list_index (self->store, i);
+               GtkWidget *tile = gs_summary_tile_new (app);
                g_signal_connect (tile, "clicked",
                                  G_CALLBACK (gs_editor_app_tile_clicked_cb),
                                  self);
@@ -493,28 +547,10 @@ gs_editor_refresh_choice (GsEditor *self)
        }
 }
 
-static void
-gs_editor_error_message (GsEditor *self, const gchar *title, const gchar *message)
-{
-       GtkWidget *dialog;
-       GtkWindow *window;
-       window = GTK_WINDOW (gtk_builder_get_object (self->builder, "window_main"));
-       dialog = gtk_message_dialog_new (window,
-                                        GTK_DIALOG_MODAL |
-                                        GTK_DIALOG_DESTROY_WITH_PARENT,
-                                        GTK_MESSAGE_WARNING,
-                                        GTK_BUTTONS_OK,
-                                        "%s", title);
-       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
-                                                 "%s", message);
-       gtk_dialog_run (GTK_DIALOG (dialog));
-       gtk_widget_destroy (dialog);
-}
-
 static void
 gs_editor_button_back_clicked_cb (GtkWidget *widget, GsEditor *self)
 {
-       gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
+       gs_editor_set_page (self, gs_app_list_length (self->store) == 0 ? "none" : "choice");
 }
 
 static void
@@ -539,26 +575,110 @@ gs_editor_refresh_file (GsEditor *self, GFile *file)
        }
 }
 
+static void
+gs_search_page_app_search_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = GS_APP (user_data);
+       g_autoptr(GsAppList) list = NULL;
+
+       list = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (source_object), res, &error);
+       if (list == NULL) {
+               if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
+                       return;
+               g_warning ("failed to get search app: %s", error->message);
+               return;
+       }
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
+               GsApp *app_tmp = gs_app_list_index (list, i);
+               if (g_strcmp0 (gs_app_get_id (app), gs_app_get_id (app_tmp)) == 0) {
+                       gs_editor_copy_from_global_app (app, app_tmp);
+                       return;
+               }
+       }
+       if (gs_app_list_length (list) > 0) {
+               GsApp *app_tmp = gs_app_list_index (list, 0);
+               gs_editor_copy_from_global_app (app, app_tmp);
+       }
+}
+
+static void
+gs_editor_plugin_app_search (GsEditor *self, GsApp *app)
+{
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", gs_app_get_id (app),
+                                        "max-results", 20,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, NULL,
+                                           gs_search_page_app_search_cb,
+                                           g_object_ref (app));
+}
+
+static gboolean
+gs_editor_appstream_upgrade_cb (XbBuilderFixup *self,
+                               XbBuilderNode *bn,
+                               gpointer user_data,
+                               GError **error)
+{
+       if (g_strcmp0 (xb_builder_node_get_element (bn), "metadata") == 0)
+               xb_builder_node_set_element (bn, "custom");
+       return TRUE;
+}
+
 static void
 gs_editor_button_import_file (GsEditor *self, GFile *file)
 {
        g_autoptr(GError) error = NULL;
+       g_autoptr(XbSilo) silo = NULL;
+       g_autoptr(XbBuilder) builder = xb_builder_new ();
+       g_autoptr(XbBuilderFixup) fixup = NULL;
+       g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
 
        /* load new file */
-       if (!as_store_from_file (self->store, file, NULL, NULL, &error)) {
+       if (!xb_builder_source_load_file (source, file,
+                                         XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT,
+                                         NULL, &error)) {
                /* TRANSLATORS: error dialog title */
                gs_editor_error_message (self, _("Failed to load file"), error->message);
                return;
        }
 
+       /* fix up any legacy installed files */
+       fixup = xb_builder_fixup_new ("AppStreamUpgrade",
+                                     gs_editor_appstream_upgrade_cb,
+                                     self, NULL);
+       xb_builder_fixup_set_max_depth (fixup, 3);
+       xb_builder_source_add_fixup (source, fixup);
+
+       xb_builder_import_source (builder, source);
+       silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error);
+       if (silo == NULL) {
+               /* TRANSLATORS: error dialog title */
+               gs_editor_error_message (self, _("Failed to load file"), error->message);
+               return;
+       }
+
+       /* add applications */
+       gs_editor_add_nodes_from_silo (self, silo);
+
        /* update listview */
        gs_editor_refresh_choice (self);
        gs_editor_refresh_file (self, file);
 
+       /* set the global app state */
+       for (guint i = 0; i < gs_app_list_length (self->store); i++) {
+               GsApp *app = gs_app_list_index (self->store, i);
+               gs_editor_plugin_app_search (self, app);
+       }
+
        /* set the appropriate page */
-       gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
+       gs_editor_set_page (self, gs_app_list_length (self->store) == 0 ? "none" : "choice");
 
        /* reset */
+       gs_editor_autosave (self);
        self->pending_changes = FALSE;
 }
 
@@ -574,7 +694,7 @@ gs_editor_button_import_clicked_cb (GtkApplication *application, GsEditor *self)
        /* import warning */
        window = GTK_WINDOW (gtk_builder_get_object (self->builder,
                                                     "window_main"));
-       if (as_store_get_size (self->store) > 0) {
+       if (gs_app_list_length (self->store) > 0) {
                dialog = gtk_message_dialog_new (window,
                                                 GTK_DIALOG_MODAL |
                                                 GTK_DIALOG_DESTROY_WITH_PARENT,
@@ -599,7 +719,7 @@ gs_editor_button_import_clicked_cb (GtkApplication *application, GsEditor *self)
                if (res == GTK_RESPONSE_CANCEL)
                        return;
                if (res == GTK_RESPONSE_YES)
-                       as_store_remove_all (self->store);
+                       gs_app_list_remove_all (self->store);
        }
 
        /* import the new file */
@@ -631,11 +751,12 @@ gs_editor_button_save_clicked_cb (GtkApplication *application, GsEditor *self)
        gint res;
        g_autoptr(GError) error = NULL;
        g_autoptr(GFile) file = NULL;
+       g_autoptr(XbSilo) silo = NULL;
 
        /* export a new file */
        window = GTK_WINDOW (gtk_builder_get_object (self->builder,
                                                     "window_main"));
-       dialog = gtk_file_chooser_dialog_new (_("Open AppStream File"),
+       dialog = gtk_file_chooser_dialog_new (_("Save AppStream File"),
                                              window,
                                              GTK_FILE_CHOOSER_ACTION_SAVE,
                                              _("_Cancel"), GTK_RESPONSE_CANCEL,
@@ -651,17 +772,19 @@ gs_editor_button_save_clicked_cb (GtkApplication *application, GsEditor *self)
        }
        file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
        gtk_widget_destroy (dialog);
-       if (!as_store_to_file (self->store,
-                              file,
-                              AS_NODE_TO_XML_FLAG_ADD_HEADER |
-                              AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE |
-                              AS_NODE_TO_XML_FLAG_FORMAT_INDENT,
-                              self->cancellable,
-                              &error)) {
+
+       /* export as XML */
+       silo = gs_editor_build_silo_from_apps (self);
+       if (!xb_silo_export_file (silo, file,
+                                 XB_NODE_EXPORT_FLAG_ADD_HEADER |
+                                 XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE |
+                                 XB_NODE_EXPORT_FLAG_FORMAT_INDENT,
+                                 NULL, &error)) {
                /* TRANSLATORS: error dialog title */
                gs_editor_error_message (self, _("Failed to save file"), error->message);
                return;
        }
+
        self->pending_changes = FALSE;
        gs_editor_refresh_file (self, file);
        gs_editor_refresh_details (self);
@@ -695,13 +818,13 @@ gs_editor_button_notification_dismiss_clicked_cb (GtkWidget *widget, GsEditor *s
 static void
 gs_editor_button_undo_remove_clicked_cb (GtkWidget *widget, GsEditor *self)
 {
-       if (self->deleted_item == NULL)
+       if (self->deleted_app == NULL)
                return;
 
        /* add this back to the store and set it as current */
-       as_store_add_app (self->store, self->deleted_item);
-       g_set_object (&self->selected_item, self->deleted_item);
-       g_clear_object (&self->deleted_item);
+       gs_app_list_add (self->store, self->deleted_app);
+       g_set_object (&self->selected_app, self->deleted_app);
+       g_clear_object (&self->deleted_app);
 
        /* hide notification */
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "revealer_notification"));
@@ -719,17 +842,11 @@ gs_editor_button_remove_clicked_cb (GtkWidget *widget, GsEditor *self)
        const gchar *name;
        g_autofree gchar *msg = NULL;
 
-       if (self->selected_item == NULL)
+       if (self->selected_app == NULL)
                return;
 
        /* send notification */
-       name = as_app_get_name (self->selected_item, NULL);
-       if (name == NULL) {
-               AsApp *item_global = as_store_get_app_by_id (self->store_global,
-                                                            as_app_get_id (self->selected_item));
-               if (item_global != NULL)
-                       name = as_app_get_name (item_global, NULL);
-       }
+       name = gs_app_get_name (self->selected_app);
        if (name != NULL) {
                g_autofree gchar *name_markup = NULL;
                name_markup = g_strdup_printf ("<b>%s</b>", name);
@@ -742,14 +859,14 @@ gs_editor_button_remove_clicked_cb (GtkWidget *widget, GsEditor *self)
        gs_editor_show_notification (self, msg);
 
        /* save this so we can undo */
-       g_set_object (&self->deleted_item, self->selected_item);
+       g_set_object (&self->deleted_app, self->selected_app);
 
-       as_store_remove_app_by_id (self->store, as_app_get_id (self->selected_item));
+       gs_app_list_remove (self->store, self->selected_app);
        self->pending_changes = TRUE;
        gs_editor_refresh_choice (self);
 
        /* set the appropriate page */
-       gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
+       gs_editor_set_page (self, gs_app_list_length (self->store) == 0 ? "none" : "choice");
 }
 
 static void
@@ -758,13 +875,13 @@ gs_editor_checkbutton_editors_pick_cb (GtkToggleButton *widget, GsEditor *self)
        /* ignore, self change */
        if (self->is_in_refresh)
                return;
-       if (self->selected_item == NULL)
+       if (self->selected_app == NULL)
                return;
 
        if (gtk_toggle_button_get_active (widget)) {
-               as_app_add_kudo (self->selected_item, "GnomeSoftware::popular");
+               gs_app_add_kudo (self->selected_app, GS_APP_KUDO_POPULAR);
        } else {
-               as_app_remove_kudo (self->selected_item, "GnomeSoftware::popular");
+               gs_app_remove_kudo (self->selected_app, GS_APP_KUDO_POPULAR);
        }
        self->pending_changes = TRUE;
        gs_editor_refresh_details (self);
@@ -776,33 +893,108 @@ gs_editor_checkbutton_category_featured_cb (GtkToggleButton *widget, GsEditor *s
        /* ignore, self change */
        if (self->is_in_refresh)
                return;
-       if (self->selected_item == NULL)
+       if (self->selected_app == NULL)
                return;
 
        if (gtk_toggle_button_get_active (widget)) {
-               as_app_add_category (self->selected_item, "Featured");
+               gs_app_add_kudo (self->selected_app, GS_APP_KUDO_FEATURED_RECOMMENDED);
        } else {
-               as_app_remove_category (self->selected_item, "Featured");
+               gs_app_remove_kudo (self->selected_app, GS_APP_KUDO_FEATURED_RECOMMENDED);
        }
        self->pending_changes = TRUE;
        gs_editor_refresh_details (self);
 }
 
+static gboolean
+gs_editor_get_search_filter_id_prefix_cb (GsApp *app, gpointer user_data)
+{
+       const gchar *id = (const gchar *) user_data;
+       return g_str_has_prefix (gs_app_get_id (app), id);
+}
+
+static void
+gs_editor_get_search_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+       GsEditor *self = (GsEditor *) user_data;
+       GtkListStore *store;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get apps */
+       list = gs_plugin_loader_job_process_finish (self->plugin_loader, res, &error);
+       if (list == NULL) {
+               if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
+                       return;
+               g_warning ("failed to get search apps: %s", error->message);
+               return;
+       }
+
+       /* only include the apps with the right ID prefix */
+       gs_app_list_filter (list,
+                           gs_editor_get_search_filter_id_prefix_cb,
+                           gs_app_get_id (self->selected_app));
+
+       /* load all the IDs into the completion model */
+       store = GTK_LIST_STORE (gtk_builder_get_object (self->builder, "liststore_ids"));
+       gtk_list_store_clear (store);
+       if (gs_app_list_length (list) == 1) {
+               GsApp *app = gs_app_list_index (list, 0);
+               GtkWidget *w = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_desktop_id"));
+               g_debug ("forcing completion: %s", gs_app_get_id (app));
+               gtk_entry_set_text (GTK_ENTRY (w), gs_app_get_id (app));
+       } else if (gs_app_list_length (list) > 0) {
+               for (guint i = 0; i < gs_app_list_length (list); i++) {
+                       GsApp *app = gs_app_list_index (list, i);
+                       GtkTreeIter iter;
+                       gtk_list_store_append (store, &iter);
+                       g_debug ("adding completion: %s", gs_app_get_id (app));
+                       gtk_list_store_set (store, &iter, 0, gs_app_get_id (app), -1);
+               }
+       } else {
+               g_debug ("nothing found");
+       }
+
+       /* get the "best" application for the icon and description */
+       if (gs_app_list_length (list) > 0) {
+               GsApp *app = gs_app_list_index (list, 0);
+               g_debug ("setting global app %s", gs_app_get_unique_id (app));
+               gs_editor_copy_from_global_app (self->selected_app, app);
+       } else {
+               gs_editor_copy_from_global_app (self->selected_app, NULL);
+       }
+       gs_editor_refresh_details (self);
+}
+
 static void
 gs_editor_entry_desktop_id_notify_cb (GtkEntry *entry, GParamSpec *pspec, GsEditor *self)
 {
+       const gchar *tmp;
+
        /* ignore, self change */
        if (self->is_in_refresh)
                return;
-       if (self->selected_item == NULL)
+       if (self->selected_app == NULL)
                return;
 
-       /* check the name does not already exist */
+       /* get the new list of possible apps */
+       tmp = gtk_entry_get_text (entry);
+       if (tmp != NULL && strlen (tmp) > 3) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                                "search", tmp,
+                                                "max-results", 20,
+                                                "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                                GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                                NULL);
+               gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, NULL,
+                                                   gs_editor_get_search_cb,
+                                                   self);
+       }
+
+       /* check the ID does not already exist */
        //FIXME
 
-       as_store_remove_app (self->store, self->selected_item);
-       as_app_set_id (self->selected_item, gtk_entry_get_text (entry));
-       as_store_add_app (self->store, self->selected_item);
+       gs_app_set_id (self->selected_app, tmp);
 
        self->pending_changes = TRUE;
        gs_editor_refresh_choice (self);
@@ -815,10 +1007,10 @@ gs_editor_entry_name_notify_cb (GtkEntry *entry, GParamSpec *pspec, GsEditor *se
        /* ignore, self change */
        if (self->is_in_refresh)
                return;
-       if (self->selected_item == NULL)
+       if (self->selected_app == NULL)
                return;
 
-       as_app_set_name (self->selected_item, NULL, gtk_entry_get_text (entry));
+       gs_app_set_name (self->selected_app, GS_APP_QUALITY_NORMAL, gtk_entry_get_text (entry));
 
        self->pending_changes = TRUE;
        gs_editor_refresh_choice (self);
@@ -831,10 +1023,10 @@ gs_editor_entry_summary_notify_cb (GtkEntry *entry, GParamSpec *pspec, GsEditor
        /* ignore, self change */
        if (self->is_in_refresh)
                return;
-       if (self->selected_item == NULL)
+       if (self->selected_app == NULL)
                return;
 
-       as_app_set_comment (self->selected_item, NULL, gtk_entry_get_text (entry));
+       gs_app_set_summary (self->selected_app, GS_APP_QUALITY_NORMAL, gtk_entry_get_text (entry));
 
        self->pending_changes = TRUE;
        gs_editor_refresh_choice (self);
@@ -880,39 +1072,23 @@ gs_editor_flow_box_sort_cb (GtkFlowBoxChild *row1, GtkFlowBoxChild *row2, gpoint
                          gs_app_get_name (gs_app_tile_get_app (tile2)));
 }
 
-static void
-gs_editor_load_completion_model (GsEditor *self)
-{
-       GPtrArray *apps;
-       GtkListStore *store;
-       GtkTreeIter iter;
-
-       store = GTK_LIST_STORE (gtk_builder_get_object (self->builder, "liststore_ids"));
-       apps = as_store_get_apps (self->store_global);
-       for (guint i = 0; i < apps->len; i++) {
-               AsApp *item = g_ptr_array_index (apps, i);
-               gtk_list_store_append (store, &iter);
-               gtk_list_store_set (store, &iter, 0, as_app_get_id (item), -1);
-       }
-}
-
 static void
 gs_editor_button_new_feature_clicked_cb (GtkApplication *application, GsEditor *self)
 {
        g_autofree gchar *id = NULL;
-       g_autoptr(AsApp) item = as_app_new ();
+       g_autoptr(GsApp) app = gs_app_new (NULL);
        const gchar *css = "border: 1px solid #808080;\nbackground: #eee;\ncolor: #000;";
 
        /* add new app */
-       as_app_set_kind (item, AS_APP_KIND_DESKTOP);
+       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
        id = g_strdup_printf ("example-%04x.desktop",
                              (guint) g_random_int_range (0x0000, 0xffff));
-       as_app_set_id (item, id);
-       as_app_add_metadata (item, "GnomeSoftware::FeatureTile-css", css);
-       as_app_add_kudo (item, "GnomeSoftware::popular");
-       as_app_add_category (item, "Featured");
-       as_store_add_app (self->store, item);
-       g_set_object (&self->selected_item, item);
+       gs_app_set_id (app, id);
+       gs_app_set_metadata (app, "GnomeSoftware::FeatureTile-css", css);
+       gs_app_add_kudo (app, GS_APP_KUDO_POPULAR);
+       gs_app_add_kudo (app, GS_APP_KUDO_FEATURED_RECOMMENDED);
+       gs_app_list_add (self->store, app);
+       g_set_object (&self->selected_app, app);
 
        self->pending_changes = TRUE;
        gs_editor_refresh_choice (self);
@@ -923,18 +1099,19 @@ gs_editor_button_new_feature_clicked_cb (GtkApplication *application, GsEditor *
 static void
 gs_editor_button_new_os_upgrade_clicked_cb (GtkApplication *application, GsEditor *self)
 {
-       g_autoptr(AsApp) item = as_app_new ();
+       g_autoptr(GsApp) app = gs_app_new (NULL);
        const gchar *css = "border: 1px solid #808080;\nbackground: #fffeee;\ncolor: #000;";
 
        /* add new app */
-       as_app_set_kind (item, AS_APP_KIND_OS_UPGRADE);
-       as_app_set_state (item, AS_APP_STATE_AVAILABLE);
-       as_app_set_id (item, "org.gnome.release");
-       as_app_set_name (item, NULL, "GNOME");
-       as_app_set_comment (item, NULL, "A major upgrade, with new features and added polish.");
-       as_app_add_metadata (item, "GnomeSoftware::UpgradeBanner-css", css);
-       as_store_add_app (self->store, item);
-       g_set_object (&self->selected_item, item);
+       gs_app_set_kind (app, AS_APP_KIND_OS_UPGRADE);
+       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       gs_app_set_id (app, "org.gnome.release");
+       gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "GNOME");
+       gs_app_set_version (app, "3.40");
+       gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "A major upgrade, with new features and added 
polish.");
+       gs_app_set_metadata (app, "GnomeSoftware::UpgradeBanner-css", css);
+       gs_app_list_add (self->store, app);
+       g_set_object (&self->selected_app, app);
 
        self->pending_changes = TRUE;
        gs_editor_refresh_choice (self);
@@ -955,8 +1132,8 @@ gs_editor_startup_cb (GtkApplication *application, GsEditor *self)
        GtkTextBuffer *buffer;
        GtkWidget *main_window;
        GtkWidget *widget;
-       gboolean ret;
        guint retval;
+       g_autofree gchar *fn = NULL;
        g_autoptr(GError) error = NULL;
 
        /* get UI */
@@ -968,23 +1145,14 @@ gs_editor_startup_cb (GtkApplication *application, GsEditor *self)
                return;
        }
 
-       /* load all system appstream */
-       as_store_set_add_flags (self->store_global, AS_STORE_ADD_FLAG_USE_MERGE_HEURISTIC);
-       ret = as_store_load (self->store_global,
-                            AS_STORE_LOAD_FLAG_IGNORE_INVALID |
-                            AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM |
-                            AS_STORE_LOAD_FLAG_APPDATA |
-                            AS_STORE_LOAD_FLAG_DESKTOP,
-                            self->cancellable,
-                            &error);
-       if (!ret) {
-               g_warning ("failed to load global store: %s", error->message);
+       self->plugin_loader = gs_plugin_loader_new ();
+       if (g_file_test (LOCALPLUGINDIR, G_FILE_TEST_EXISTS))
+               gs_plugin_loader_add_location (self->plugin_loader, LOCALPLUGINDIR);
+       if (!gs_plugin_loader_setup (self->plugin_loader, NULL, NULL, NULL, &error)) {
+               g_warning ("Failed to setup plugins: %s", error->message);
                return;
        }
 
-       /* load all the IDs into the completion model */
-       gs_editor_load_completion_model (self);
-
        self->featured_tile1 = gs_feature_tile_new (NULL);
        self->upgrade_banner = gs_upgrade_banner_new ();
        widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_featured"));
@@ -1066,13 +1234,34 @@ gs_editor_startup_cb (GtkApplication *application, GsEditor *self)
        g_signal_connect (widget, "delete_event",
                          G_CALLBACK (gs_editor_delete_event_cb), self);
 
+       /* load last saved state */
+       fn = gs_utils_get_cache_filename ("editor", "autosave.xmlb",
+                                         GS_UTILS_CACHE_FLAG_WRITEABLE, &error);
+       if (fn == NULL) {
+               g_warning ("Failed to get cache location: %s", error->message);
+               return;
+       }
+       self->autosave_file = g_file_new_for_path (fn);
+       if (g_file_query_exists (self->autosave_file, NULL)) {
+               g_autoptr(XbSilo) silo = xb_silo_new ();
+               g_debug ("loading apps from %s", fn);
+               if (!xb_silo_load_from_file (silo, self->autosave_file,
+                                            XB_SILO_LOAD_FLAG_NONE, NULL, &error))
+                       g_warning ("Failed to load silo: %s", error->message);
+               gs_editor_add_nodes_from_silo (self, silo);
+               for (guint i = 0; i < gs_app_list_length (self->store); i++) {
+                       GsApp *app = gs_app_list_index (self->store, i);
+                       gs_editor_plugin_app_search (self, app);
+               }
+       }
+
        /* clear entries */
        gs_editor_refresh_choice (self);
        gs_editor_refresh_details (self);
        gs_editor_refresh_file (self, NULL);
 
        /* set the appropriate page */
-       gs_editor_set_page (self, "none");
+       gs_editor_set_page (self, gs_app_list_length (self->store) == 0 ? "none" : "choice");
 
        main_window = GTK_WIDGET (gtk_builder_get_object (self->builder, "window_main"));
        gtk_application_add_window (application, GTK_WINDOW (main_window));
@@ -1120,15 +1309,19 @@ gs_editor_commandline_cb (GApplication *application,
 static void
 gs_editor_self_free (GsEditor *self)
 {
-       if (self->selected_item != NULL)
-               g_object_unref (self->selected_item);
-       if (self->deleted_item != NULL)
-               g_object_unref (self->deleted_item);
+       if (self->autosave_file != NULL)
+               g_object_unref (self->autosave_file);
+       if (self->selected_app != NULL)
+               g_object_unref (self->selected_app);
+       if (self->deleted_app != NULL)
+               g_object_unref (self->deleted_app);
        if (self->refresh_details_delayed_id != 0)
                g_source_remove (self->refresh_details_delayed_id);
+       if (self->autosave_id != 0)
+               g_source_remove (self->autosave_id);
+       g_object_unref (self->plugin_loader);
        g_object_unref (self->cancellable);
        g_object_unref (self->store);
-       g_object_unref (self->store_global);
        g_object_unref (self->builder);
        g_free (self);
 }
@@ -1151,10 +1344,8 @@ main (int argc, char *argv[])
        self = g_new0 (GsEditor, 1);
        self->cancellable = g_cancellable_new ();
        self->builder = gtk_builder_new ();
-       self->store = as_store_new ();
-       as_store_set_add_flags (self->store, AS_STORE_ADD_FLAG_USE_UNIQUE_ID);
-       self->store_global = as_store_new ();
-       as_store_set_add_flags (self->store_global, AS_STORE_ADD_FLAG_USE_UNIQUE_ID);
+       self->store = gs_app_list_new ();
+       self->autosave_id = g_timeout_add_seconds (5, gs_editor_autosave_cb, self);
 
        /* are we already activated? */
        self->application = gtk_application_new ("org.gnome.Software.Editor",
diff --git a/src/meson.build b/src/meson.build
index e63fe104..ea8900ba 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -77,7 +77,8 @@ gnome_software_dependencies = [
   gtk,
   json_glib,
   libm,
-  libsoup
+  libsoup,
+  libxmlb,
 ]
 
 if get_option('packagekit')



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