[gnome-software] Move the AppStream cache functionality to it's own file



commit 43014ca4fd57376737a4afc2cc00de7bb8537973
Author: Richard Hughes <richard hughsie com>
Date:   Wed Sep 4 16:34:55 2013 +0100

    Move the AppStream cache functionality to it's own file
    
    Additionally support .xml and .xml.gz AppStream files. This gets us some degree
    of Ubuntu compatibility.

 src/plugins/Makefile.am           |    7 +-
 src/plugins/appstream-app.c       |  255 ++++++++++++++++
 src/plugins/appstream-app.h       |   81 +++++
 src/plugins/appstream-cache.c     |  594 +++++++++++++++++++++++++++++++++++++
 src/plugins/appstream-cache.h     |   75 +++++
 src/plugins/gs-appstream-item.c   |  256 ----------------
 src/plugins/gs-appstream-item.h   |   81 -----
 src/plugins/gs-plugin-appstream.c |  474 +++---------------------------
 8 files changed, 1053 insertions(+), 770 deletions(-)
---
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index c9f7ad4..2ca58e4 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -54,7 +54,12 @@ libgs_plugin_self_test_la_LIBADD = $(GS_PLUGIN_LIBS)
 libgs_plugin_self_test_la_LDFLAGS = -module -avoid-version
 libgs_plugin_self_test_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARNINGFLAGS_C)
 
-libgs_plugin_appstream_la_SOURCES = gs-plugin-appstream.c gs-appstream-item.c gs-appstream-item.h
+libgs_plugin_appstream_la_SOURCES =                    \
+       gs-plugin-appstream.c                           \
+       appstream-app.c                                 \
+       appstream-app.h                                 \
+       appstream-cache.c                               \
+       appstream-cache.h
 libgs_plugin_appstream_la_LIBADD = $(GS_PLUGIN_LIBS)
 libgs_plugin_appstream_la_LDFLAGS = -module -avoid-version
 libgs_plugin_appstream_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARNINGFLAGS_C)
diff --git a/src/plugins/appstream-app.c b/src/plugins/appstream-app.c
new file mode 100644
index 0000000..932f8ab
--- /dev/null
+++ b/src/plugins/appstream-app.c
@@ -0,0 +1,255 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include "appstream-app.h"
+
+struct AppstreamApp
+{
+       gchar                   *id;
+       gchar                   *pkgname;
+       gchar                   *name;
+       gchar                   *summary;
+       gchar                   *url;
+       gchar                   *description;
+       gchar                   *icon;
+       AppstreamAppIconKind     icon_kind;
+       GPtrArray               *appcategories;
+};
+
+/**
+ * appstream_app_free:
+ */
+void
+appstream_app_free (AppstreamApp *app)
+{
+       g_free (app->id);
+       g_free (app->pkgname);
+       g_free (app->name);
+       g_free (app->summary);
+       g_free (app->url);
+       g_free (app->description);
+       g_free (app->icon);
+       g_ptr_array_unref (app->appcategories);
+       g_slice_free (AppstreamApp, app);
+}
+
+/**
+ * appstream_app_new:
+ */
+AppstreamApp *
+appstream_app_new (void)
+{
+       AppstreamApp *app;
+       app = g_slice_new0 (AppstreamApp);
+       app->appcategories = g_ptr_array_new_with_free_func (g_free);
+       app->icon_kind = APPSTREAM_APP_ICON_KIND_UNKNOWN;
+       return app;
+}
+
+/**
+ * appstream_app_get_id:
+ */
+const gchar *
+appstream_app_get_id (AppstreamApp *app)
+{
+       return app->id;
+}
+
+/**
+ * appstream_app_get_pkgname:
+ */
+const gchar *
+appstream_app_get_pkgname (AppstreamApp *app)
+{
+       return app->pkgname;
+}
+
+/**
+ * appstream_app_get_name:
+ */
+const gchar *
+appstream_app_get_name (AppstreamApp *app)
+{
+       return app->name;
+}
+
+/**
+ * appstream_app_get_summary:
+ */
+const gchar *
+appstream_app_get_summary (AppstreamApp *app)
+{
+       return app->summary;
+}
+
+/**
+ * appstream_app_get_url:
+ */
+const gchar *
+appstream_app_get_url (AppstreamApp *app)
+{
+       return app->url;
+}
+
+/**
+ * appstream_app_get_description:
+ */
+const gchar *
+appstream_app_get_description (AppstreamApp *app)
+{
+       return app->description;
+}
+
+/**
+ * appstream_app_get_icon:
+ */
+const gchar *
+appstream_app_get_icon (AppstreamApp *app)
+{
+       return app->icon;
+}
+
+/**
+ * appstream_app_get_icon_kind:
+ */
+AppstreamAppIconKind 
+appstream_app_get_icon_kind (AppstreamApp *app)
+{
+       return app->icon_kind;
+}
+
+/**
+ * appstream_app_has_category:
+ */
+gboolean 
+appstream_app_has_category (AppstreamApp *app, const gchar *category)
+{
+       const gchar *tmp;
+       guint i;
+
+       for (i = 0; i < app->appcategories->len; i++) {
+               tmp = g_ptr_array_index (app->appcategories, i);
+               if (g_strcmp0 (tmp, category) == 0)
+                       return TRUE;
+       }
+       return FALSE;
+}
+
+/**
+ * appstream_app_set_id:
+ */
+void 
+appstream_app_set_id (AppstreamApp *app,
+                     const gchar *id,
+                     gsize length)
+{
+       app->id = g_strndup (id, length);
+}
+
+/**
+ * appstream_app_set_pkgname:
+ */
+void 
+appstream_app_set_pkgname (AppstreamApp *app,
+                          const gchar *pkgname,
+                          gsize length)
+{
+       app->pkgname = g_strndup (pkgname, length);
+}
+
+/**
+ * appstream_app_set_name:
+ */
+void 
+appstream_app_set_name (AppstreamApp *app,
+                       const gchar *name,
+                       gsize length)
+{
+       app->name = g_strndup (name, length);
+}
+
+/**
+ * appstream_app_set_summary:
+ */
+void 
+appstream_app_set_summary (AppstreamApp *app,
+                          const gchar *summary,
+                          gsize length)
+{
+       app->summary = g_strndup (summary, length);
+}
+
+/**
+ * appstream_app_set_url:
+ */
+void 
+appstream_app_set_url (AppstreamApp *app,
+                      const gchar *url,
+                      gsize length)
+{
+       app->url = g_strndup (url, length);
+}
+
+/**
+ * appstream_app_set_description:
+ */
+void 
+appstream_app_set_description (AppstreamApp *app,
+                              const gchar *description,
+                              gsize length)
+{
+       app->description = g_strndup (description, length);
+}
+
+/**
+ * appstream_app_set_icon:
+ */
+void 
+appstream_app_set_icon (AppstreamApp *app,
+                       const gchar *icon,
+                       gsize length)
+{
+       app->icon = g_strndup (icon, length);
+}
+
+/**
+ * appstream_app_add_category:
+ */
+void 
+appstream_app_add_category (AppstreamApp *app,
+                           const gchar *category,
+                           gsize length)
+{
+       g_ptr_array_add (app->appcategories,
+                        g_strndup (category, length));
+}
+
+/**
+ * appstream_app_set_icon_kind:
+ */
+void 
+appstream_app_set_icon_kind (AppstreamApp *app,
+                            AppstreamAppIconKind icon_kind)
+{
+       app->icon_kind = icon_kind;
+}
diff --git a/src/plugins/appstream-app.h b/src/plugins/appstream-app.h
new file mode 100644
index 0000000..fcd4d29
--- /dev/null
+++ b/src/plugins/appstream-app.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __APPSTREAM_APP_H
+#define __APPSTREAM_APP_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+       APPSTREAM_APP_ICON_KIND_STOCK,
+       APPSTREAM_APP_ICON_KIND_CACHED,
+       APPSTREAM_APP_ICON_KIND_UNKNOWN,
+       APPSTREAM_APP_ICON_KIND_LAST
+} AppstreamAppIconKind;
+
+typedef struct AppstreamApp    AppstreamApp;
+
+void            appstream_app_free                     (AppstreamApp   *app);
+AppstreamApp   *appstream_app_new                      (void);
+
+const gchar    *appstream_app_get_id                   (AppstreamApp   *app);
+const gchar    *appstream_app_get_pkgname              (AppstreamApp   *app);
+const gchar    *appstream_app_get_name                 (AppstreamApp   *app);
+const gchar    *appstream_app_get_summary              (AppstreamApp   *app);
+const gchar    *appstream_app_get_url                  (AppstreamApp   *app);
+const gchar    *appstream_app_get_description          (AppstreamApp   *app);
+const gchar    *appstream_app_get_icon                 (AppstreamApp   *app);
+gboolean        appstream_app_has_category             (AppstreamApp   *app,
+                                                        const gchar    *category);
+AppstreamAppIconKind   appstream_app_get_icon_kind     (AppstreamApp   *app);
+
+void            appstream_app_set_id                   (AppstreamApp   *app,
+                                                        const gchar    *id,
+                                                        gsize           length);
+void            appstream_app_set_pkgname              (AppstreamApp   *app,
+                                                        const gchar    *pkgname,
+                                                        gsize           length);
+void            appstream_app_set_name                 (AppstreamApp   *app,
+                                                        const gchar    *name,
+                                                        gsize           length);
+void            appstream_app_set_summary              (AppstreamApp   *app,
+                                                        const gchar    *summary,
+                                                        gsize           length);
+void            appstream_app_set_url                  (AppstreamApp   *app,
+                                                        const gchar    *summary,
+                                                        gsize           length);
+void            appstream_app_set_description          (AppstreamApp   *app,
+                                                        const gchar    *description,
+                                                        gsize           length);
+void            appstream_app_set_icon                 (AppstreamApp   *app,
+                                                        const gchar    *icon,
+                                                        gsize           length);
+void            appstream_app_add_category             (AppstreamApp   *app,
+                                                        const gchar    *category,
+                                                        gsize           length);
+void            appstream_app_set_icon_kind            (AppstreamApp   *app,
+                                                        AppstreamAppIconKind icon_kind);
+
+G_END_DECLS
+
+#endif /* __APPSTREAM_APP_H */
diff --git a/src/plugins/appstream-cache.c b/src/plugins/appstream-cache.c
new file mode 100644
index 0000000..6daeb24
--- /dev/null
+++ b/src/plugins/appstream-cache.c
@@ -0,0 +1,594 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include "appstream-cache.h"
+
+static void    appstream_cache_finalize        (GObject        *object);
+
+#define APPSTREAM_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), APPSTREAM_TYPE_CACHE, 
AppstreamCachePrivate))
+
+typedef enum {
+       APPSTREAM_CACHE_SECTION_UNKNOWN,
+       APPSTREAM_CACHE_SECTION_APPLICATIONS,
+       APPSTREAM_CACHE_SECTION_APPLICATION,
+       APPSTREAM_CACHE_SECTION_ID,
+       APPSTREAM_CACHE_SECTION_PKGNAME,
+       APPSTREAM_CACHE_SECTION_NAME,
+       APPSTREAM_CACHE_SECTION_SUMMARY,
+       APPSTREAM_CACHE_SECTION_DESCRIPTION,
+       APPSTREAM_CACHE_SECTION_URL,
+       APPSTREAM_CACHE_SECTION_ICON,
+       APPSTREAM_CACHE_SECTION_APPCATEGORIES,
+       APPSTREAM_CACHE_SECTION_APPCATEGORY,
+       APPSTREAM_CACHE_SECTION_LAST
+} AppstreamCacheSection;
+
+struct AppstreamCachePrivate
+{
+       GPtrArray               *array;         /* of AppstreamApp */
+       GHashTable              *hash_id;       /* of AppstreamApp{id} */
+       GHashTable              *hash_pkgname;  /* of AppstreamApp{pkgname} */
+};
+
+G_DEFINE_TYPE (AppstreamCache, appstream_cache, G_TYPE_OBJECT)
+
+/**
+ * appstream_cache_error_quark:
+ **/
+GQuark
+appstream_cache_error_quark (void)
+{
+       static GQuark quark = 0;
+       if (!quark)
+               quark = g_quark_from_static_string ("appstream_cache_error");
+       return quark;
+}
+
+/**
+ * appstream_cache_get_size:
+ */
+guint
+appstream_cache_get_size (AppstreamCache *cache)
+{
+       g_return_val_if_fail (APPSTREAM_IS_CACHE (cache), 0);
+       return cache->priv->array->len;
+}
+
+/**
+ * appstream_cache_get_items:
+ */
+GPtrArray *
+appstream_cache_get_items (AppstreamCache *cache)
+{
+       g_return_val_if_fail (APPSTREAM_IS_CACHE (cache), NULL);
+       return cache->priv->array;
+}
+
+/**
+ * appstream_cache_get_item_by_id:
+ */
+AppstreamApp *
+appstream_cache_get_item_by_id (AppstreamCache *cache, const gchar *id)
+{
+       g_return_val_if_fail (APPSTREAM_IS_CACHE (cache), NULL);
+       return g_hash_table_lookup (cache->priv->hash_id, id);
+}
+
+/**
+ * appstream_cache_get_item_by_pkgname:
+ */
+AppstreamApp *
+appstream_cache_get_item_by_pkgname (AppstreamCache *cache, const gchar *pkgname)
+{
+       g_return_val_if_fail (APPSTREAM_IS_CACHE (cache), NULL);
+       return g_hash_table_lookup (cache->priv->hash_pkgname, pkgname);
+}
+
+/**
+ * appstream_cache_selection_from_string:
+ */
+static AppstreamCacheSection
+appstream_cache_selection_from_string (const gchar *element_name)
+{
+       if (g_strcmp0 (element_name, "applications") == 0)
+               return APPSTREAM_CACHE_SECTION_APPLICATIONS;
+       if (g_strcmp0 (element_name, "application") == 0)
+               return APPSTREAM_CACHE_SECTION_APPLICATION;
+       if (g_strcmp0 (element_name, "id") == 0)
+               return APPSTREAM_CACHE_SECTION_ID;
+       if (g_strcmp0 (element_name, "pkgname") == 0)
+               return APPSTREAM_CACHE_SECTION_PKGNAME;
+       if (g_strcmp0 (element_name, "name") == 0)
+               return APPSTREAM_CACHE_SECTION_NAME;
+       if (g_strcmp0 (element_name, "summary") == 0)
+               return APPSTREAM_CACHE_SECTION_SUMMARY;
+       if (g_strcmp0 (element_name, "url") == 0)
+               return APPSTREAM_CACHE_SECTION_URL;
+       if (g_strcmp0 (element_name, "description") == 0)
+               return APPSTREAM_CACHE_SECTION_DESCRIPTION;
+       if (g_strcmp0 (element_name, "icon") == 0)
+               return APPSTREAM_CACHE_SECTION_ICON;
+       if (g_strcmp0 (element_name, "appcategories") == 0)
+               return  APPSTREAM_CACHE_SECTION_APPCATEGORIES;
+       if (g_strcmp0 (element_name, "appcategory") == 0)
+               return APPSTREAM_CACHE_SECTION_APPCATEGORY;
+       return APPSTREAM_CACHE_SECTION_UNKNOWN;
+}
+
+/**
+ * appstream_cache_selection_to_string:
+ */
+static const gchar *
+appstream_cache_selection_to_string (AppstreamCacheSection section)
+{
+       if (section == APPSTREAM_CACHE_SECTION_APPLICATIONS)
+               return "applications";
+       if (section == APPSTREAM_CACHE_SECTION_APPLICATION)
+               return "application";
+       if (section == APPSTREAM_CACHE_SECTION_ID)
+               return "id";
+       if (section == APPSTREAM_CACHE_SECTION_PKGNAME)
+               return "pkgname";
+       if (section == APPSTREAM_CACHE_SECTION_NAME)
+               return "name";
+       if (section == APPSTREAM_CACHE_SECTION_SUMMARY)
+               return "summary";
+       if (section == APPSTREAM_CACHE_SECTION_URL)
+               return "url";
+       if (section == APPSTREAM_CACHE_SECTION_DESCRIPTION)
+               return "description";
+       if (section == APPSTREAM_CACHE_SECTION_ICON)
+               return "icon";
+       if (section == APPSTREAM_CACHE_SECTION_APPCATEGORIES)
+               return "appcategories";
+       if (section == APPSTREAM_CACHE_SECTION_APPCATEGORY)
+               return "appcategory";
+       return NULL;
+}
+
+/**
+ * appstream_cache_icon_kind_from_string:
+ */
+static AppstreamAppIconKind
+appstream_cache_icon_kind_from_string (const gchar *kind_str)
+{
+       if (g_strcmp0 (kind_str, "stock") == 0)
+               return APPSTREAM_APP_ICON_KIND_STOCK;
+       if (g_strcmp0 (kind_str, "local") == 0 ||
+           g_strcmp0 (kind_str, "cached") == 0)
+               return APPSTREAM_APP_ICON_KIND_CACHED;
+       return APPSTREAM_APP_ICON_KIND_UNKNOWN;
+}
+
+typedef struct {
+       AppstreamApp            *item_temp;
+       AppstreamCache          *cache;
+       AppstreamCacheSection    section;
+} AppstreamCacheHelper;
+
+/**
+ * appstream_cache_start_element_cb:
+ */
+static void
+appstream_cache_start_element_cb (GMarkupParseContext *context,
+                                 const gchar *element_name,
+                                 const gchar **attribute_names,
+                                 const gchar **attribute_values,
+                                 gpointer user_data,
+                                 GError **error)
+{
+       AppstreamCacheHelper *helper = (AppstreamCacheHelper *) user_data;
+       AppstreamCacheSection section_new;
+       guint i;
+
+       /* process tag start */
+       section_new = appstream_cache_selection_from_string (element_name);
+       switch (section_new) {
+       case APPSTREAM_CACHE_SECTION_APPLICATIONS:
+       case APPSTREAM_CACHE_SECTION_APPCATEGORIES:
+       case APPSTREAM_CACHE_SECTION_APPCATEGORY:
+               /* ignore */
+               break;
+       case APPSTREAM_CACHE_SECTION_APPLICATION:
+               if (helper->item_temp != NULL ||
+                   helper->section != APPSTREAM_CACHE_SECTION_APPLICATIONS) {
+                       g_set_error (error,
+                                    APPSTREAM_CACHE_ERROR,
+                                    APPSTREAM_CACHE_ERROR_FAILED,
+                                    "XML start %s invalid, section %s",
+                                    element_name,
+                                    appstream_cache_selection_to_string (helper->section));
+                       return;
+               }
+               helper->item_temp = appstream_app_new ();
+               break;
+
+       case APPSTREAM_CACHE_SECTION_ICON:
+               /* get the icon kind */
+               for (i = 0; attribute_names[i] != NULL; i++) {
+                       if (g_strcmp0 (attribute_names[i], "type") == 0) {
+                               appstream_app_set_icon_kind (helper->item_temp,
+                                                            appstream_cache_icon_kind_from_string 
(attribute_values[i]));
+                               break;
+                       }
+               }
+               if (appstream_app_get_icon_kind (helper->item_temp) == APPSTREAM_APP_ICON_KIND_UNKNOWN) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "icon type not set");
+               }
+               break;
+       case APPSTREAM_CACHE_SECTION_ID:
+       case APPSTREAM_CACHE_SECTION_PKGNAME:
+       case APPSTREAM_CACHE_SECTION_NAME:
+       case APPSTREAM_CACHE_SECTION_SUMMARY:
+       case APPSTREAM_CACHE_SECTION_URL:
+       case APPSTREAM_CACHE_SECTION_DESCRIPTION:
+               if (helper->item_temp == NULL ||
+                   helper->section != APPSTREAM_CACHE_SECTION_APPLICATION) {
+                       g_set_error (error,
+                                    APPSTREAM_CACHE_ERROR,
+                                    APPSTREAM_CACHE_ERROR_FAILED,
+                                    "XML start %s invalid, section %s",
+                                    element_name,
+                                    appstream_cache_selection_to_string (helper->section));
+                       return;
+               }
+               break;
+       default:
+               /* ignore unknown entries */
+               break;
+       }
+
+       /* save */
+       helper->section = section_new;
+}
+
+/**
+ * appstream_cache_end_element_cb:
+ */
+static void
+appstream_cache_end_element_cb (GMarkupParseContext *context,
+                               const gchar *element_name,
+                               gpointer user_data,
+                               GError **error)
+{
+       const gchar *id;
+       AppstreamCacheHelper *helper = (AppstreamCacheHelper *) user_data;
+       AppstreamCachePrivate *priv = helper->cache->priv;
+       AppstreamCacheSection section_new;
+       AppstreamApp *item;
+
+       section_new = appstream_cache_selection_from_string (element_name);
+       switch (section_new) {
+       case APPSTREAM_CACHE_SECTION_APPLICATIONS:
+       case APPSTREAM_CACHE_SECTION_APPCATEGORY:
+               /* ignore */
+               break;
+       case APPSTREAM_CACHE_SECTION_APPLICATION:
+
+               /* have we recorded this before? */
+               id = appstream_app_get_id (helper->item_temp);
+               item = g_hash_table_lookup (priv->hash_id, id);
+               if (item != NULL) {
+                       g_set_error (error,
+                                    APPSTREAM_CACHE_ERROR,
+                                    APPSTREAM_CACHE_ERROR_FAILED,
+                                    "duplicate AppStream entry: %s", id);
+                       appstream_app_free (helper->item_temp);
+               } else {
+                       g_ptr_array_add (priv->array, helper->item_temp);
+                       g_hash_table_insert (priv->hash_id,
+                                            (gpointer) appstream_app_get_id (helper->item_temp),
+                                            helper->item_temp);
+                       g_hash_table_insert (priv->hash_pkgname,
+                                            (gpointer) appstream_app_get_pkgname (helper->item_temp),
+                                            helper->item_temp);
+               }
+               helper->item_temp = NULL;
+               helper->section = APPSTREAM_CACHE_SECTION_APPLICATIONS;
+               break;
+       case APPSTREAM_CACHE_SECTION_ID:
+       case APPSTREAM_CACHE_SECTION_PKGNAME:
+       case APPSTREAM_CACHE_SECTION_APPCATEGORIES:
+       case APPSTREAM_CACHE_SECTION_NAME:
+       case APPSTREAM_CACHE_SECTION_ICON:
+       case APPSTREAM_CACHE_SECTION_SUMMARY:
+       case APPSTREAM_CACHE_SECTION_URL:
+       case APPSTREAM_CACHE_SECTION_DESCRIPTION:
+               helper->section = APPSTREAM_CACHE_SECTION_APPLICATION;
+               break;
+       default:
+               /* ignore unknown entries */
+               helper->section = APPSTREAM_CACHE_SECTION_APPLICATION;
+               break;
+       }
+}
+
+/**
+ * appstream_cache_text_cb:
+ */
+static void
+appstream_cache_text_cb (GMarkupParseContext *context,
+                        const gchar *text,
+                        gsize text_len,
+                        gpointer user_data,
+                        GError **error)
+{
+       AppstreamCacheHelper *helper = (AppstreamCacheHelper *) user_data;
+
+       switch (helper->section) {
+       case APPSTREAM_CACHE_SECTION_UNKNOWN:
+       case APPSTREAM_CACHE_SECTION_APPLICATIONS:
+       case APPSTREAM_CACHE_SECTION_APPLICATION:
+       case APPSTREAM_CACHE_SECTION_APPCATEGORIES:
+               /* ignore */
+               break;
+       case APPSTREAM_CACHE_SECTION_APPCATEGORY:
+               if (helper->item_temp == NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp category invalid");
+                       return;
+               }
+               appstream_app_add_category (helper->item_temp, text, text_len);
+               break;
+       case APPSTREAM_CACHE_SECTION_ID:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_id (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp id invalid");
+                       return;
+               }
+               if (text_len < 9) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "desktop id invalid");
+                       return;
+               }
+               appstream_app_set_id (helper->item_temp, text, text_len - 8);
+               break;
+       case APPSTREAM_CACHE_SECTION_PKGNAME:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_pkgname (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp pkgname invalid");
+                       return;
+               }
+               appstream_app_set_pkgname (helper->item_temp, text, text_len);
+               break;
+       case APPSTREAM_CACHE_SECTION_NAME:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_name (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp name invalid");
+                       return;
+               }
+               appstream_app_set_name (helper->item_temp, text, text_len);
+               break;
+       case APPSTREAM_CACHE_SECTION_SUMMARY:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_summary (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp summary invalid");
+                       return;
+               }
+               appstream_app_set_summary (helper->item_temp, text, text_len);
+               break;
+       case APPSTREAM_CACHE_SECTION_URL:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_url (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp url invalid");
+                       return;
+               }
+               appstream_app_set_url (helper->item_temp, text, text_len);
+               break;
+       case APPSTREAM_CACHE_SECTION_DESCRIPTION:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_description (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp description invalid");
+                       return;
+               }
+               appstream_app_set_description (helper->item_temp, text, text_len);
+               break;
+       case APPSTREAM_CACHE_SECTION_ICON:
+               if (helper->item_temp == NULL ||
+                   appstream_app_get_icon (helper->item_temp) != NULL) {
+                       g_set_error_literal (error,
+                                            APPSTREAM_CACHE_ERROR,
+                                            APPSTREAM_CACHE_ERROR_FAILED,
+                                            "item_temp icon invalid");
+                       return;
+               }
+               appstream_app_set_icon (helper->item_temp, text, text_len);
+               break;
+       default:
+               /* ignore unknown entries */
+               break;
+       }
+}
+
+/**
+ * appstream_cache_parse_file:
+ */
+gboolean
+appstream_cache_parse_file (AppstreamCache *cache,
+                           GFile *file,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       const gchar *content_type = NULL;
+       gboolean ret = TRUE;
+       gchar *data = NULL;
+       GConverter *converter = NULL;
+       GFileInfo *info = NULL;
+       GInputStream *file_stream;
+       GInputStream *stream_data = NULL;
+       GMarkupParseContext *ctx = NULL;
+       AppstreamCacheHelper *helper = NULL;
+       gssize len;
+       const GMarkupParser parser = {
+               appstream_cache_start_element_cb,
+               appstream_cache_end_element_cb,
+               appstream_cache_text_cb,
+               NULL /* passthrough */,
+               NULL /* error */ };
+
+       g_return_val_if_fail (APPSTREAM_IS_CACHE (cache), FALSE);
+
+       file_stream = G_INPUT_STREAM (g_file_read (file, cancellable, error));
+       if (file_stream == NULL) {
+               ret = FALSE;
+               goto out;
+       }
+
+       /* what kind of file is this */
+       info = g_file_query_info (file,
+                                 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 cancellable,
+                                 error);
+       if (info == NULL) {
+               ret = FALSE;
+               goto out;
+       }
+
+       /* decompress if required */
+       content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+       if (g_strcmp0 (content_type, "application/gzip") == 0) {
+               converter = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
+               stream_data = g_converter_input_stream_new (file_stream, converter);
+       } else if (g_strcmp0 (content_type, "application/xml") == 0) {
+               stream_data = g_object_ref (file_stream);
+       } else {
+               ret = FALSE;
+               g_set_error (error,
+                            APPSTREAM_CACHE_ERROR,
+                            APPSTREAM_CACHE_ERROR_FAILED,
+                            "cannot process file of type %s",
+                            content_type);
+       }
+       helper = g_new0 (AppstreamCacheHelper, 1);
+       helper->cache = cache;
+       ctx = g_markup_parse_context_new (&parser,
+                                         G_MARKUP_PREFIX_ERROR_POSITION,
+                                         helper,
+                                         NULL);
+       data = g_malloc (32 * 1024);
+       while ((len = g_input_stream_read (stream_data, data, 32 * 1024, NULL, error)) > 0) {
+               ret = g_markup_parse_context_parse (ctx, data, len, error);
+               if (!ret)
+                       goto out;
+       }
+       if (len < 0)
+               ret = FALSE;
+out:
+       if (helper->item_temp)
+               appstream_app_free (helper->item_temp);
+       if (info != NULL)
+               g_object_unref (info);
+       g_free (helper);
+       g_free (data);
+       if (ctx != NULL)
+               g_markup_parse_context_free (ctx);
+       if (stream_data != NULL)
+               g_object_unref (stream_data);
+       if (converter != NULL)
+               g_object_unref (converter);
+       g_object_unref (file_stream);
+       return ret;
+}
+
+/**
+ * appstream_cache_class_init:
+ **/
+static void
+appstream_cache_class_init (AppstreamCacheClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = appstream_cache_finalize;
+       g_type_class_add_private (klass, sizeof (AppstreamCachePrivate));
+}
+
+/**
+ * appstream_cache_init:
+ **/
+static void
+appstream_cache_init (AppstreamCache *cache)
+{
+       AppstreamCachePrivate *priv;
+       priv = cache->priv = APPSTREAM_CACHE_GET_PRIVATE (cache);
+       priv->array = g_ptr_array_new_with_free_func ((GDestroyNotify) appstream_app_free);
+       priv->hash_id = g_hash_table_new_full (g_str_hash,
+                                              g_str_equal,
+                                              NULL,
+                                              NULL);
+       priv->hash_pkgname = g_hash_table_new_full (g_str_hash,
+                                                   g_str_equal,
+                                                   NULL,
+                                                   NULL);
+}
+
+/**
+ * appstream_cache_finalize:
+ **/
+static void
+appstream_cache_finalize (GObject *object)
+{
+       AppstreamCache *cache = APPSTREAM_CACHE (object);
+       AppstreamCachePrivate *priv = cache->priv;
+
+       g_ptr_array_unref (priv->array);
+       g_hash_table_unref (priv->hash_id);
+       g_hash_table_unref (priv->hash_pkgname);
+
+       G_OBJECT_CLASS (appstream_cache_parent_class)->finalize (object);
+}
+
+/**
+ * appstream_cache_new:
+ **/
+AppstreamCache *
+appstream_cache_new (void)
+{
+       AppstreamCache *cache;
+       cache = g_object_new (APPSTREAM_TYPE_CACHE, NULL);
+       return APPSTREAM_CACHE (cache);
+}
diff --git a/src/plugins/appstream-cache.h b/src/plugins/appstream-cache.h
new file mode 100644
index 0000000..e079707
--- /dev/null
+++ b/src/plugins/appstream-cache.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __APPSTREAM_CACHE_H
+#define __APPSTREAM_CACHE_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "appstream-app.h"
+
+G_BEGIN_DECLS
+
+#define APPSTREAM_TYPE_CACHE           (appstream_cache_get_type ())
+#define APPSTREAM_CACHE(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), APPSTREAM_TYPE_CACHE, 
AppstreamCache))
+#define APPSTREAM_CACHE_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST((k), APPSTREAM_TYPE_CACHE, 
AppstreamCacheClass))
+#define APPSTREAM_IS_CACHE(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), APPSTREAM_TYPE_CACHE))
+#define APPSTREAM_IS_CACHE_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), APPSTREAM_TYPE_CACHE))
+#define APPSTREAM_CACHE_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), APPSTREAM_TYPE_CACHE, 
AppstreamCacheClass))
+#define APPSTREAM_CACHE_ERROR          (appstream_cache_error_quark ())
+
+typedef struct AppstreamCachePrivate AppstreamCachePrivate;
+
+typedef struct
+{
+        GObject                         parent;
+        AppstreamCachePrivate          *priv;
+} AppstreamCache;
+
+typedef struct
+{
+       GObjectClass                     parent_class;
+} AppstreamCacheClass;
+
+typedef enum {
+       APPSTREAM_CACHE_ERROR_FAILED,
+       APPSTREAM_CACHE_ERROR_LAST
+} AppstreamCacheError;
+
+GQuark          appstream_cache_error_quark            (void);
+GType           appstream_cache_get_type               (void);
+
+AppstreamCache *appstream_cache_new                    (void);
+gboolean        appstream_cache_parse_file             (AppstreamCache *cache,
+                                                        GFile          *file,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+guint           appstream_cache_get_size               (AppstreamCache *cache);
+GPtrArray      *appstream_cache_get_items              (AppstreamCache *cache);
+AppstreamApp   *appstream_cache_get_item_by_id         (AppstreamCache *cache,
+                                                        const gchar    *id);
+AppstreamApp   *appstream_cache_get_item_by_pkgname    (AppstreamCache *cache,
+                                                        const gchar    *pkgname);
+
+G_END_DECLS
+
+#endif /* __APPSTREAM_CACHE_H */
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index 7b3d6e4..093bf1d 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -23,34 +23,15 @@
 
 #include <gs-plugin.h>
 
-#include "gs-appstream-item.h"
+#include "appstream-app.h"
+#include "appstream-cache.h"
 
 #define GS_PLUGIN_APPSTREAM_ICONS_HASH "81f92592ce464df3baf4c480c4a4cc6e18efee52"
 
-typedef enum {
-       GS_APPSTREAM_XML_SECTION_UNKNOWN,
-       GS_APPSTREAM_XML_SECTION_APPLICATIONS,
-       GS_APPSTREAM_XML_SECTION_APPLICATION,
-       GS_APPSTREAM_XML_SECTION_ID,
-       GS_APPSTREAM_XML_SECTION_PKGNAME,
-       GS_APPSTREAM_XML_SECTION_NAME,
-       GS_APPSTREAM_XML_SECTION_SUMMARY,
-       GS_APPSTREAM_XML_SECTION_DESCRIPTION,
-       GS_APPSTREAM_XML_SECTION_URL,
-       GS_APPSTREAM_XML_SECTION_ICON,
-       GS_APPSTREAM_XML_SECTION_APPCATEGORIES,
-       GS_APPSTREAM_XML_SECTION_APPCATEGORY,
-       GS_APPSTREAM_XML_SECTION_LAST
-} GsAppstreamXmlSection;
-
 struct GsPluginPrivate {
+       AppstreamCache          *cache;
        gchar                   *cachedir;
-       GsAppstreamXmlSection    section;
-       GsAppstreamItem         *item_temp;
-       GPtrArray               *array;         /* of GsAppstreamItem */
-       GHashTable              *hash_id;       /* of GsAppstreamItem{id} */
-       GHashTable              *hash_pkgname;  /* of GsAppstreamItem{pkgname} */
-       gsize                    done_init;
+       gsize                    done_init;
 };
 
 /**
@@ -135,328 +116,6 @@ out:
 }
 
 /**
- * gs_appstream_selection_from_text:
- */
-static GsAppstreamXmlSection
-gs_appstream_selection_from_text (const gchar *element_name)
-{
-       if (g_strcmp0 (element_name, "applications") == 0)
-               return GS_APPSTREAM_XML_SECTION_APPLICATIONS;
-       if (g_strcmp0 (element_name, "application") == 0)
-               return GS_APPSTREAM_XML_SECTION_APPLICATION;
-       if (g_strcmp0 (element_name, "id") == 0)
-               return GS_APPSTREAM_XML_SECTION_ID;
-       if (g_strcmp0 (element_name, "pkgname") == 0)
-               return GS_APPSTREAM_XML_SECTION_PKGNAME;
-       if (g_strcmp0 (element_name, "name") == 0)
-               return GS_APPSTREAM_XML_SECTION_NAME;
-       if (g_strcmp0 (element_name, "summary") == 0)
-               return GS_APPSTREAM_XML_SECTION_SUMMARY;
-       if (g_strcmp0 (element_name, "url") == 0)
-               return GS_APPSTREAM_XML_SECTION_URL;
-       if (g_strcmp0 (element_name, "description") == 0)
-               return GS_APPSTREAM_XML_SECTION_DESCRIPTION;
-       if (g_strcmp0 (element_name, "icon") == 0)
-               return GS_APPSTREAM_XML_SECTION_ICON;
-       if (g_strcmp0 (element_name, "appcategories") == 0)
-               return  GS_APPSTREAM_XML_SECTION_APPCATEGORIES;
-       if (g_strcmp0 (element_name, "appcategory") == 0)
-               return GS_APPSTREAM_XML_SECTION_APPCATEGORY;
-       return GS_APPSTREAM_XML_SECTION_UNKNOWN;
-}
-
-/**
- * gs_appstream_selection_to_text:
- */
-static const gchar *
-gs_appstream_selection_to_text (GsAppstreamXmlSection section)
-{
-       if (section == GS_APPSTREAM_XML_SECTION_APPLICATIONS)
-               return "applications";
-       if (section == GS_APPSTREAM_XML_SECTION_APPLICATION)
-               return "application";
-       if (section == GS_APPSTREAM_XML_SECTION_ID)
-               return "id";
-       if (section == GS_APPSTREAM_XML_SECTION_PKGNAME)
-               return "pkgname";
-       if (section == GS_APPSTREAM_XML_SECTION_NAME)
-               return "name";
-       if (section == GS_APPSTREAM_XML_SECTION_SUMMARY)
-               return "summary";
-       if (section == GS_APPSTREAM_XML_SECTION_URL)
-               return "url";
-       if (section == GS_APPSTREAM_XML_SECTION_DESCRIPTION)
-               return "description";
-       if (section == GS_APPSTREAM_XML_SECTION_ICON)
-               return "icon";
-       if (section == GS_APPSTREAM_XML_SECTION_APPCATEGORIES)
-               return "appcategories";
-       if (section == GS_APPSTREAM_XML_SECTION_APPCATEGORY)
-               return "appcategory";
-       return NULL;
-}
-
-/**
- * gs_appstream_icon_kind_from_text:
- */
-static GsAppstreamItemIconKind
-gs_appstream_icon_kind_from_text (const gchar *kind_str)
-{
-       if (g_strcmp0 (kind_str, "stock") == 0)
-               return GS_APPSTREAM_ITEM_ICON_KIND_STOCK;
-       if (g_strcmp0 (kind_str, "local") == 0 ||
-           g_strcmp0 (kind_str, "cached") == 0)
-               return GS_APPSTREAM_ITEM_ICON_KIND_CACHED;
-       g_warning ("icon type %s not recognised", kind_str);
-       return GS_APPSTREAM_ITEM_ICON_KIND_UNKNOWN;
-}
-
-/**
- * gs_appstream_start_element_cb:
- */
-static void
-gs_appstream_start_element_cb (GMarkupParseContext *context,
-                              const gchar *element_name,
-                              const gchar **attribute_names,
-                              const gchar **attribute_values,
-                              gpointer user_data,
-                              GError **error)
-{
-       GsPlugin *plugin = (GsPlugin *) user_data;
-       GsAppstreamXmlSection section_new;
-       guint i;
-
-       /* process tag start */
-       section_new = gs_appstream_selection_from_text (element_name);
-       switch (section_new) {
-       case GS_APPSTREAM_XML_SECTION_APPLICATIONS:
-       case GS_APPSTREAM_XML_SECTION_APPCATEGORIES:
-       case GS_APPSTREAM_XML_SECTION_APPCATEGORY:
-               /* ignore */
-               break;
-       case GS_APPSTREAM_XML_SECTION_APPLICATION:
-               if (plugin->priv->item_temp != NULL ||
-                   plugin->priv->section != GS_APPSTREAM_XML_SECTION_APPLICATIONS) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_FAILED,
-                                    "XML start %s invalid, section %s",
-                                    element_name,
-                                    gs_appstream_selection_to_text (plugin->priv->section));
-                       return;
-               }
-               plugin->priv->item_temp = gs_appstream_item_new ();
-               break;
-
-       case GS_APPSTREAM_XML_SECTION_ICON:
-               /* get the icon kind */
-               for (i = 0; attribute_names[i] != NULL; i++) {
-                       if (g_strcmp0 (attribute_names[i], "type") == 0) {
-                               gs_appstream_item_set_icon_kind (plugin->priv->item_temp,
-                                                                gs_appstream_icon_kind_from_text 
(attribute_values[i]));
-                               break;
-                       }
-               }
-               if (gs_appstream_item_get_icon_kind (plugin->priv->item_temp) == 
GS_APPSTREAM_ITEM_ICON_KIND_UNKNOWN) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "icon type not set");
-               }
-               break;
-       case GS_APPSTREAM_XML_SECTION_ID:
-       case GS_APPSTREAM_XML_SECTION_PKGNAME:
-       case GS_APPSTREAM_XML_SECTION_NAME:
-       case GS_APPSTREAM_XML_SECTION_SUMMARY:
-       case GS_APPSTREAM_XML_SECTION_URL:
-       case GS_APPSTREAM_XML_SECTION_DESCRIPTION:
-               if (plugin->priv->item_temp == NULL ||
-                   plugin->priv->section != GS_APPSTREAM_XML_SECTION_APPLICATION) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_FAILED,
-                                    "XML start %s invalid, section %s",
-                                    element_name,
-                                    gs_appstream_selection_to_text (plugin->priv->section));
-                       return;
-               }
-               break;
-       default:
-               /* ignore unknown entries */
-               break;
-       }
-
-       /* save */
-       plugin->priv->section = section_new;
-}
-
-/**
- * gs_appstream_end_element_cb:
- */
-static void
-gs_appstream_end_element_cb (GMarkupParseContext *context,
-                            const gchar *element_name,
-                            gpointer user_data,
-                            GError **error)
-{
-       GsPlugin *plugin = (GsPlugin *) user_data;
-       GsAppstreamXmlSection section_new;
-
-       section_new = gs_appstream_selection_from_text (element_name);
-       switch (section_new) {
-       case GS_APPSTREAM_XML_SECTION_APPLICATIONS:
-       case GS_APPSTREAM_XML_SECTION_APPCATEGORY:
-               /* ignore */
-               break;
-       case GS_APPSTREAM_XML_SECTION_APPLICATION:
-               /* save */
-               g_ptr_array_add (plugin->priv->array, plugin->priv->item_temp);
-               g_hash_table_insert (plugin->priv->hash_id,
-                                    (gpointer) gs_appstream_item_get_id (plugin->priv->item_temp),
-                                    plugin->priv->item_temp);
-               g_hash_table_insert (plugin->priv->hash_pkgname,
-                                    (gpointer) gs_appstream_item_get_pkgname (plugin->priv->item_temp),
-                                    plugin->priv->item_temp);
-               plugin->priv->item_temp = NULL;
-               plugin->priv->section = GS_APPSTREAM_XML_SECTION_APPLICATIONS;
-               break;
-       case GS_APPSTREAM_XML_SECTION_ID:
-       case GS_APPSTREAM_XML_SECTION_PKGNAME:
-       case GS_APPSTREAM_XML_SECTION_APPCATEGORIES:
-       case GS_APPSTREAM_XML_SECTION_NAME:
-       case GS_APPSTREAM_XML_SECTION_ICON:
-       case GS_APPSTREAM_XML_SECTION_SUMMARY:
-       case GS_APPSTREAM_XML_SECTION_URL:
-       case GS_APPSTREAM_XML_SECTION_DESCRIPTION:
-               plugin->priv->section = GS_APPSTREAM_XML_SECTION_APPLICATION;
-               break;
-       default:
-               /* ignore unknown entries */
-               plugin->priv->section = GS_APPSTREAM_XML_SECTION_APPLICATION;
-               break;
-       }
-}
-
-/**
- * gs_appstream_text_cb:
- */
-static void
-gs_appstream_text_cb (GMarkupParseContext *context,
-                     const gchar *text,
-                     gsize text_len,
-                     gpointer user_data,
-                     GError **error)
-{
-       GsPlugin *plugin = (GsPlugin *) user_data;
-
-       switch (plugin->priv->section) {
-       case GS_APPSTREAM_XML_SECTION_UNKNOWN:
-       case GS_APPSTREAM_XML_SECTION_APPLICATIONS:
-       case GS_APPSTREAM_XML_SECTION_APPLICATION:
-       case GS_APPSTREAM_XML_SECTION_APPCATEGORIES:
-               /* ignore */
-               break;
-       case GS_APPSTREAM_XML_SECTION_APPCATEGORY:
-               if (plugin->priv->item_temp == NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp category invalid");
-                       return;
-               }
-               gs_appstream_item_add_category (plugin->priv->item_temp, text, text_len);
-               break;
-       case GS_APPSTREAM_XML_SECTION_ID:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_id (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp id invalid");
-                       return;
-               }
-               if (text_len < 9) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "desktop id invalid");
-                       return;
-               }
-               gs_appstream_item_set_id (plugin->priv->item_temp, text, text_len - 8);
-               break;
-       case GS_APPSTREAM_XML_SECTION_PKGNAME:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_pkgname (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp pkgname invalid");
-                       return;
-               }
-               gs_appstream_item_set_pkgname (plugin->priv->item_temp, text, text_len);
-               break;
-       case GS_APPSTREAM_XML_SECTION_NAME:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_name (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp name invalid");
-                       return;
-               }
-               gs_appstream_item_set_name (plugin->priv->item_temp, text, text_len);
-               break;
-       case GS_APPSTREAM_XML_SECTION_SUMMARY:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_summary (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp summary invalid");
-                       return;
-               }
-               gs_appstream_item_set_summary (plugin->priv->item_temp, text, text_len);
-               break;
-       case GS_APPSTREAM_XML_SECTION_URL:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_url (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp url invalid");
-                       return;
-               }
-               gs_appstream_item_set_url (plugin->priv->item_temp, text, text_len);
-               break;
-       case GS_APPSTREAM_XML_SECTION_DESCRIPTION:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_description (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp description invalid");
-                       return;
-               }
-               gs_appstream_item_set_description (plugin->priv->item_temp, text, text_len);
-               break;
-       case GS_APPSTREAM_XML_SECTION_ICON:
-               if (plugin->priv->item_temp == NULL ||
-                   gs_appstream_item_get_icon (plugin->priv->item_temp) != NULL) {
-                       g_set_error_literal (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_FAILED,
-                                            "item_temp icon invalid");
-                       return;
-               }
-               gs_appstream_item_set_icon (plugin->priv->item_temp, text, text_len);
-               break;
-       default:
-               /* ignore unknown entries */
-               break;
-       }
-}
-
-/**
  * gs_plugin_parse_xml_file:
  */
 static gboolean
@@ -465,60 +124,19 @@ gs_plugin_parse_xml_file (GsPlugin *plugin,
                          const gchar *filename,
                          GError **error)
 {
-       gboolean ret = TRUE;
-       gchar *data = NULL;
+       gboolean ret;
        gchar *path;
-       GConverter *converter = NULL;
        GFile *file;
-       GInputStream *converter_stream = NULL;
-       GInputStream *file_stream;
-       GMarkupParseContext *ctx = NULL;
-       gssize len;
-       const GMarkupParser parser = {
-               gs_appstream_start_element_cb,
-               gs_appstream_end_element_cb,
-               gs_appstream_text_cb,
-               NULL /* passthrough */,
-               NULL /* error */ };
 
+       /* load this specific file */
        path  = g_build_filename (parent_dir, filename, NULL);
        file = g_file_new_for_path (path);
-       file_stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
-       if (file_stream == NULL) {
-               ret = FALSE;
+       ret = appstream_cache_parse_file (plugin->priv->cache, file, NULL, error);
+       if (!ret)
                goto out;
-       }
-       converter = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP));
-       converter_stream = g_converter_input_stream_new (file_stream, converter);
-       ctx = g_markup_parse_context_new (&parser,
-                                         G_MARKUP_PREFIX_ERROR_POSITION,
-                                         plugin,
-                                         NULL);
-       data = g_malloc (32 * 1024);
-       while ((len = g_input_stream_read (converter_stream, data, 32 * 1024, NULL, error)) > 0) {
-               ret = g_markup_parse_context_parse (ctx, data, len, error);
-               if (!ret)
-                       goto out;
-       }
-       if (len < 0)
-               ret = FALSE;
 out:
-       /* reset in case we failed parsing */
-       if (plugin->priv->item_temp) {
-               gs_appstream_item_free (plugin->priv->item_temp);
-               plugin->priv->item_temp = NULL;
-       }
-
-       g_free (data);
        g_free (path);
-       if (ctx != NULL)
-               g_markup_parse_context_free (ctx);
-       if (converter_stream != NULL)
-               g_object_unref (converter_stream);
-       if (converter != NULL)
-               g_object_unref (converter);
        g_object_unref (file);
-       g_object_unref (file_stream);
        return ret;
 }
 
@@ -560,15 +178,7 @@ gs_plugin_initialize (GsPlugin *plugin)
 {
        /* create private area */
        plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
-       plugin->priv->array = g_ptr_array_new_with_free_func ((GDestroyNotify) gs_appstream_item_free);
-       plugin->priv->hash_id = g_hash_table_new_full (g_str_hash,
-                                                      g_str_equal,
-                                                      NULL,
-                                                      NULL);
-       plugin->priv->hash_pkgname = g_hash_table_new_full (g_str_hash,
-                                                           g_str_equal,
-                                                           NULL,
-                                                           NULL);
+       plugin->priv->cache = appstream_cache_new ();
 
        /* this is per-user */
        plugin->priv->cachedir = g_build_filename (g_get_user_cache_dir(),
@@ -592,9 +202,7 @@ void
 gs_plugin_destroy (GsPlugin *plugin)
 {
        g_free (plugin->priv->cachedir);
-       g_ptr_array_unref (plugin->priv->array);
-       g_hash_table_unref (plugin->priv->hash_id);
-       g_hash_table_unref (plugin->priv->hash_pkgname);
+       g_object_unref (plugin->priv->cache);
 }
 
 /**
@@ -619,7 +227,7 @@ gs_plugin_startup (GsPlugin *plugin, GError **error)
        if (!ret)
                goto out;
        g_debug ("Parsed %i entries of XML\t:%.1fms",
-                plugin->priv->array->len,
+                appstream_cache_get_size (plugin->priv->cache),
                 g_timer_elapsed (timer, NULL) * 1000);
 
 out:
@@ -634,7 +242,7 @@ out:
 static gboolean
 gs_plugin_refine_item (GsPlugin *plugin,
                       GsApp *app,
-                      GsAppstreamItem *item,
+                      AppstreamApp *item,
                       GError **error)
 {
        gboolean ret = TRUE;
@@ -649,38 +257,38 @@ gs_plugin_refine_item (GsPlugin *plugin,
                gs_app_set_kind (app, GS_APP_KIND_NORMAL);
 
        /* set id */
-       if (gs_appstream_item_get_id (item) != NULL && gs_app_get_id (app) == NULL)
-               gs_app_set_id (app, gs_appstream_item_get_id (item));
+       if (appstream_app_get_id (item) != NULL && gs_app_get_id (app) == NULL)
+               gs_app_set_id (app, appstream_app_get_id (item));
 
        /* set name */
-       if (gs_appstream_item_get_name (item) != NULL && gs_app_get_name (app) == NULL)
-               gs_app_set_name (app, gs_appstream_item_get_name (item));
+       if (appstream_app_get_name (item) != NULL && gs_app_get_name (app) == NULL)
+               gs_app_set_name (app, appstream_app_get_name (item));
 
        /* set summary */
-       if (gs_appstream_item_get_summary (item) != NULL && gs_app_get_summary (app) == NULL)
-               gs_app_set_summary (app, gs_appstream_item_get_summary (item));
+       if (appstream_app_get_summary (item) != NULL && gs_app_get_summary (app) == NULL)
+               gs_app_set_summary (app, appstream_app_get_summary (item));
 
        /* set url */
-       if (gs_appstream_item_get_url (item) != NULL && gs_app_get_url (app) == NULL)
-               gs_app_set_url (app, gs_appstream_item_get_url (item));
+       if (appstream_app_get_url (item) != NULL && gs_app_get_url (app) == NULL)
+               gs_app_set_url (app, appstream_app_get_url (item));
 
        /* set description */
-       if (gs_appstream_item_get_description (item) != NULL && gs_app_get_description (app) == NULL)
-               gs_app_set_description (app, gs_appstream_item_get_description (item));
+       if (appstream_app_get_description (item) != NULL && gs_app_get_description (app) == NULL)
+               gs_app_set_description (app, appstream_app_get_description (item));
 
        /* set icon */
-       if (gs_appstream_item_get_icon (item) != NULL && gs_app_get_pixbuf (app) == NULL) {
-               if (gs_appstream_item_get_icon_kind (item) == GS_APPSTREAM_ITEM_ICON_KIND_STOCK) {
+       if (appstream_app_get_icon (item) != NULL && gs_app_get_pixbuf (app) == NULL) {
+               if (appstream_app_get_icon_kind (item) == APPSTREAM_APP_ICON_KIND_STOCK) {
                        pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
-                                                          gs_appstream_item_get_icon (item),
+                                                          appstream_app_get_icon (item),
                                                           plugin->pixbuf_size,
                                                           GTK_ICON_LOOKUP_USE_BUILTIN |
                                                           GTK_ICON_LOOKUP_FORCE_SIZE,
                                                           error);
-               } else if (gs_appstream_item_get_icon_kind (item) == GS_APPSTREAM_ITEM_ICON_KIND_CACHED) {
+               } else if (appstream_app_get_icon_kind (item) == APPSTREAM_APP_ICON_KIND_CACHED) {
                        icon_path = g_strdup_printf ("%s/%s.png",
                                                     plugin->priv->cachedir,
-                                                    gs_appstream_item_get_icon (item));
+                                                    appstream_app_get_icon (item));
                        pixbuf = gdk_pixbuf_new_from_file_at_size (icon_path,
                                                                   plugin->pixbuf_size,
                                                                   plugin->pixbuf_size,
@@ -696,8 +304,8 @@ gs_plugin_refine_item (GsPlugin *plugin,
        }
 
        /* set package name */
-       if (gs_appstream_item_get_pkgname (item) != NULL && gs_app_get_metadata_item (app, "package-name") == 
NULL)
-               gs_app_set_metadata (app, "package-name", gs_appstream_item_get_pkgname (item));
+       if (appstream_app_get_pkgname (item) != NULL && gs_app_get_metadata_item (app, "package-name") == 
NULL)
+               gs_app_set_metadata (app, "package-name", appstream_app_get_pkgname (item));
 out:
        g_free (icon_path);
        if (pixbuf != NULL)
@@ -715,13 +323,13 @@ gs_plugin_refine_from_id (GsPlugin *plugin,
 {
        const gchar *id;
        gboolean ret = TRUE;
-       GsAppstreamItem *item;
+       AppstreamApp *item;
 
        /* find anything that matches the ID */
        id = gs_app_get_id (app);
        if (id == NULL)
                goto out;
-       item = g_hash_table_lookup (plugin->priv->hash_id, id);
+       item = appstream_cache_get_item_by_id (plugin->priv->cache, id);
        if (item == NULL) {
                g_debug ("no AppStream match for [id] %s", id);
                goto out;
@@ -745,13 +353,13 @@ gs_plugin_refine_from_pkgname (GsPlugin *plugin,
 {
        const gchar *pkgname;
        gboolean ret = TRUE;
-       GsAppstreamItem *item;
+       AppstreamApp *item;
 
        /* find anything that matches the ID */
        pkgname = gs_app_get_metadata_item (app, "package-name");
        if (pkgname == NULL)
                goto out;
-       item = g_hash_table_lookup (plugin->priv->hash_pkgname, pkgname);
+       item = appstream_cache_get_item_by_pkgname (plugin->priv->cache, pkgname);
        if (item == NULL) {
                g_debug ("no AppStream match for {pkgname} %s", pkgname);
                goto out;
@@ -817,8 +425,9 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
        const gchar *search_id2 = NULL;
        gboolean ret = TRUE;
        GsApp *app;
-       GsAppstreamItem *item;
+       AppstreamApp *item;
        GsCategory *parent;
+       GPtrArray *array;
        guint i;
 
        /* load XML files */
@@ -842,17 +451,18 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
        }
 
        /* just look at each app in turn */
-       for (i = 0; i < plugin->priv->array->len; i++) {
-               item = g_ptr_array_index (plugin->priv->array, i);
-               if (gs_appstream_item_get_id (item) == NULL)
+       array = appstream_cache_get_items (plugin->priv->cache);
+       for (i = 0; i < array->len; i++) {
+               item = g_ptr_array_index (array, i);
+               if (appstream_app_get_id (item) == NULL)
                        continue;
-               if (!gs_appstream_item_has_category (item, search_id1))
+               if (!appstream_app_has_category (item, search_id1))
                        continue;
-               if (search_id2 != NULL && !gs_appstream_item_has_category (item, search_id2))
+               if (search_id2 != NULL && !appstream_app_has_category (item, search_id2))
                        continue;
 
                /* got a search match, so add all the data we can */
-               app = gs_app_new (gs_appstream_item_get_id (item));
+               app = gs_app_new (appstream_app_get_id (item));
                ret = gs_plugin_refine_item (plugin, app, item, error);
                if (!ret)
                        goto out;


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