[gnome-software] Add an AppStream plugin



commit c6cefe6a510d36458792f4d1c4813ed828294e2f
Author: Richard Hughes <richard hughsie com>
Date:   Sun Sep 1 16:35:33 2013 +0100

    Add an AppStream plugin
    
    This allows offline applications to be shown in the software center.

 src/gs-application.c              |    3 +-
 src/plugins/Makefile.am           |    6 +
 src/plugins/README.md             |    9 +
 src/plugins/gs-plugin-appstream.c |  729 +++++++++++++++++++++++++++++++++++++
 4 files changed, 746 insertions(+), 1 deletions(-)
---
diff --git a/src/gs-application.c b/src/gs-application.c
index 6817610..7a42056 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -158,7 +158,8 @@ gs_application_startup (GApplication *application)
         gs_plugin_loader_set_enabled (app->plugin_loader, "hardcoded-menu-spec", TRUE);
         gs_plugin_loader_set_enabled (app->plugin_loader, "local-ratings", TRUE);
         gs_plugin_loader_set_enabled (app->plugin_loader, "packagekit", TRUE);
-        gs_plugin_loader_set_enabled (app->plugin_loader, "packagekit-refine", TRUE);
+//        gs_plugin_loader_set_enabled (app->plugin_loader, "packagekit-refine", TRUE);
+        gs_plugin_loader_set_enabled (app->plugin_loader, "appstream", TRUE);
         gs_plugin_loader_set_enabled (app->plugin_loader, "desktopdb", TRUE);
         gs_plugin_loader_set_enabled (app->plugin_loader, "datadir-apps", TRUE);
         gs_plugin_loader_set_enabled (app->plugin_loader, "datadir-filename", TRUE);
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 47da7a5..348d081 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -26,6 +26,7 @@ dist_resources_DATA =                                 \
 
 plugindir = $(libdir)/gs-plugins
 plugin_LTLIBRARIES =                                   \
+       libgs_plugin_appstream.la                       \
        libgs_plugin_datadir_apps.la                    \
        libgs_plugin_datadir_filename.la                \
        libgs_plugin_desktopdb.la                       \
@@ -46,6 +47,11 @@ libgs_plugin_dummy_la_LIBADD = $(GS_PLUGIN_LIBS)
 libgs_plugin_dummy_la_LDFLAGS = -module -avoid-version
 libgs_plugin_dummy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARNINGFLAGS_C)
 
+libgs_plugin_appstream_la_SOURCES = gs-plugin-appstream.c
+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)
+
 libgs_plugin_hardcoded_kind_la_SOURCES = gs-plugin-hardcoded-kind.c
 libgs_plugin_hardcoded_kind_la_LIBADD = $(GS_PLUGIN_LIBS)
 libgs_plugin_hardcoded_kind_la_LDFLAGS = -module -avoid-version
diff --git a/src/plugins/README.md b/src/plugins/README.md
index 8b80a20..ddcc1c8 100644
--- a/src/plugins/README.md
+++ b/src/plugins/README.md
@@ -128,6 +128,15 @@ Methods:     | `nothing`
 Requires:    | `nothing`
 Refines:     | `{package-name}->{datadir-desktop-filename}`
 
+### appstream ###
+Uses offline AppStream data to refine package results.
+
+Overview:    | <p>
+-------------|---
+Methods:     | `AddCategoryApps`
+Requires:    | `nothing`
+Refines:     | `{package-name}->[name,summary,pixbuf,id,kind]`
+
 ### datadir-apps ###
 Uses the files in /usr/share/applications to provide icons and, translations for
 installed applications.
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
new file mode 100644
index 0000000..95cf783
--- /dev/null
+++ b/src/plugins/gs-plugin-appstream.c
@@ -0,0 +1,729 @@
+/* -*- 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 <gs-plugin.h>
+
+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_ICON,
+       GS_APPSTREAM_XML_SECTION_APPCATEGORIES,
+       GS_APPSTREAM_XML_SECTION_APPCATEGORY,
+       GS_APPSTREAM_XML_SECTION_LAST
+} GsAppstreamXmlSection;
+
+typedef struct {
+       gchar           *id;
+       gchar           *pkgname;
+       gchar           *name;
+       gchar           *summary;
+       gchar           *icon;
+} GsAppstreamItem;
+
+struct GsPluginPrivate {
+       GMutex                   mutex;
+       gchar                   *cachedir;
+       GsAppstreamXmlSection    section;
+       GsAppstreamItem         *item_temp;
+       GPtrArray               *array;         /* of GsAppstreamItem */
+       GHashTable              *hash_id;       /* of GsAppstreamItem{id} */
+       GHashTable              *hash_pkgname;  /* of GsAppstreamItem{pkgname} */
+       gboolean                 done_init;
+};
+
+/**
+ * gs_appstream_item_free:
+ */
+static void
+gs_appstream_item_free (gpointer data)
+{
+       GsAppstreamItem *item = (GsAppstreamItem *) data;
+       g_free (item->id);
+       g_free (item->pkgname);
+       g_free (item->name);
+       g_free (item->summary);
+       g_free (item->icon);
+       g_free (item);
+}
+
+/**
+ * gs_plugin_get_name:
+ */
+const gchar *
+gs_plugin_get_name (void)
+{
+       return "appstream";
+}
+
+/**
+ * gs_plugin_decompress_icons:
+ */
+static gboolean
+gs_plugin_decompress_icons (GsPlugin *plugin, GError **error)
+{
+       const gchar *argv[6];
+       gboolean ret = TRUE;
+       gint exit_status = 0;
+
+       /* cache already exists [FIXME: How to test for new enough...] */
+       if (g_file_test (plugin->priv->cachedir, G_FILE_TEST_EXISTS))
+               goto out;
+
+       /* create directory */
+       exit_status = g_mkdir_with_parents (plugin->priv->cachedir, 0700);
+       if (exit_status != 0) {
+               ret = FALSE;
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Failed to create %s",
+                            plugin->priv->cachedir);
+               goto out;
+       }
+
+       /* decompress */
+       argv[0] = "tar";
+       argv[1] = "-zxvf";
+       argv[2] = DATADIR "/gnome-software/appstream-icons.tar.gz";
+       argv[3] = "-C";
+       argv[4] = plugin->priv->cachedir;
+       argv[5] = NULL;
+       g_debug ("Decompressing %s to %s", argv[2], plugin->priv->cachedir);
+       ret = g_spawn_sync (NULL,
+                           (gchar **) argv,
+                           NULL,
+                           G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
+                           NULL, NULL,
+                           NULL,
+                           NULL,
+                           &exit_status,
+                           error);
+       if (!ret)
+               goto out;
+       if (exit_status != 0) {
+               ret = FALSE;
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Failed to extract icon data to %s [%i]",
+                            plugin->priv->cachedir, exit_status);
+               goto out;
+       }
+out:
+       return ret;
+}
+
+/**
+ * gs_plugin_decompress_xml:
+ */
+static gboolean
+gs_plugin_decompress_xml (GsPlugin *plugin, GError **error)
+{
+       const gchar *argv[6];
+       const gchar *tmpdir = "/tmp";
+       gboolean ret = TRUE;
+       gint exit_status = 0;
+       gchar *data = NULL;
+       gsize len;
+
+       /* already exists */
+       if (g_file_test ("/tmp/appstream.xml.gz", G_FILE_TEST_EXISTS))
+               goto out;
+
+       /* copy to /tmp */
+       ret = g_file_get_contents (DATADIR "/gnome-software/appstream.xml.gz",
+                                  &data,
+                                  &len,
+                                  error);
+       if (!ret)
+               goto out;
+       ret = g_file_set_contents ("/tmp/appstream.xml.gz",
+                                  data,
+                                  len,
+                                  error);
+       if (!ret)
+               goto out;
+
+       /* decompress */
+       argv[0] = "gunzip";
+       argv[1] = "/tmp/appstream.xml.gz";
+       argv[2] = NULL;
+       g_debug ("Decompressing %s to %s", argv[2], tmpdir);
+       ret = g_spawn_sync (tmpdir,
+                           (gchar **) argv,
+                           NULL,
+                           G_SPAWN_SEARCH_PATH,
+                           NULL, NULL,
+                           NULL,
+                           NULL,
+                           &exit_status,
+                           error);
+       if (!ret)
+               goto out;
+       if (exit_status != 0) {
+               ret = FALSE;
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Failed to unzip XML data to %s [%i]",
+                            tmpdir, exit_status);
+               goto out;
+       }
+out:
+       g_free (data);
+       return ret;
+}
+
+/**
+ * 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, "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_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_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;
+
+       /* 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 = g_new0 (GsAppstreamItem, 1);
+               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_ICON:
+               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_APPCATEGORIES:
+       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) plugin->priv->item_temp->id,
+                                    plugin->priv->item_temp);
+               g_hash_table_insert (plugin->priv->hash_pkgname,
+                                    (gpointer) plugin->priv->item_temp->pkgname,
+                                    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_NAME:
+       case GS_APPSTREAM_XML_SECTION_ICON:
+       case GS_APPSTREAM_XML_SECTION_SUMMARY:
+               plugin->priv->section = GS_APPSTREAM_XML_SECTION_APPLICATION;
+               break;
+       default:
+               /* ignore unknown entries */
+               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:
+       case GS_APPSTREAM_XML_SECTION_APPCATEGORY:
+               /* ignore */
+               break;
+       case GS_APPSTREAM_XML_SECTION_ID:
+               if (plugin->priv->item_temp == NULL ||
+                   plugin->priv->item_temp->id != 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;
+               }
+               plugin->priv->item_temp->id = g_strndup (text, text_len - 8);
+               break;
+       case GS_APPSTREAM_XML_SECTION_PKGNAME:
+               if (plugin->priv->item_temp == NULL ||
+                   plugin->priv->item_temp->pkgname != NULL) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "item_temp pkgname invalid");
+                       return;
+               }
+               plugin->priv->item_temp->pkgname = g_strndup (text, text_len);
+               break;
+       case GS_APPSTREAM_XML_SECTION_NAME:
+               if (plugin->priv->item_temp == NULL ||
+                   plugin->priv->item_temp->name != NULL) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "item_temp name invalid");
+                       return;
+               }
+               plugin->priv->item_temp->name = g_strndup (text, text_len);
+               break;
+       case GS_APPSTREAM_XML_SECTION_SUMMARY:
+               if (plugin->priv->item_temp == NULL ||
+                   plugin->priv->item_temp->summary != NULL) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "item_temp summary invalid");
+                       return;
+               }
+               plugin->priv->item_temp->summary = g_strndup (text, text_len);
+               break;
+       case GS_APPSTREAM_XML_SECTION_ICON:
+               if (plugin->priv->item_temp == NULL ||
+                   plugin->priv->item_temp->icon != NULL) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "item_temp icon invalid");
+                       return;
+               }
+               plugin->priv->item_temp->icon = g_strndup (text, text_len);
+               break;
+       default:
+               /* ignore unknown entries */
+               break;
+       }
+}
+
+/**
+ * gs_plugin_parse_xml:
+ */
+static gboolean
+gs_plugin_parse_xml (GsPlugin *plugin, GError **error)
+{
+       gboolean ret;
+       GMarkupParseContext *ctx;
+       gchar *data;
+       gsize len;
+       const GMarkupParser parser = {
+               gs_appstream_start_element_cb,
+               gs_appstream_end_element_cb,
+               gs_appstream_text_cb,
+               NULL /* passthrough */,
+               NULL /* error */ };
+       ctx = g_markup_parse_context_new (&parser,
+                                         G_MARKUP_PREFIX_ERROR_POSITION,
+                                         plugin,
+                                         NULL);
+       ret = g_file_get_contents ("/tmp/appstream.xml", &data, &len, error);
+       if (!ret)
+               goto out;
+       ret = g_markup_parse_context_parse (ctx, data, len, error);
+       if (!ret)
+               goto out;
+out:
+       g_markup_parse_context_free (ctx);
+       g_free (data);
+       return ret;
+}
+
+/**
+ * gs_plugin_initialize:
+ */
+void
+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 (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);
+
+       /* this is per-user */
+       plugin->priv->cachedir = g_build_filename (g_get_user_cache_dir(),
+                                                  "gnome-software",
+                                                  NULL);
+
+       /* can only load from one thread */
+       g_mutex_init (&plugin->priv->mutex);
+}
+
+/**
+ * gs_plugin_get_priority:
+ */
+gdouble
+gs_plugin_get_priority (GsPlugin *plugin)
+{
+       return 1.0f;
+}
+
+/**
+ * gs_plugin_destroy:
+ */
+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_mutex_clear (&plugin->priv->mutex);
+}
+
+
+/**
+ * gs_plugin_startup:
+ */
+static gboolean
+gs_plugin_startup (GsPlugin *plugin, GError **error)
+{
+       gboolean ret = TRUE;
+       GTimer *timer = NULL;
+
+       /* protect */
+       g_mutex_lock (&plugin->priv->mutex);
+
+       /* already done */
+       if (plugin->priv->done_init)
+               goto out;
+
+       /* get the icons ready for use */
+       timer = g_timer_new ();
+       ret = gs_plugin_decompress_icons (plugin, error);
+       if (!ret)
+               goto out;
+       g_debug ("Decompressing icons\t:%.1fms", g_timer_elapsed (timer, NULL) * 1000);
+
+       /* Decompress the XML ready for use */
+       g_timer_reset (timer);
+       ret = gs_plugin_decompress_xml (plugin, error);
+       if (!ret)
+               goto out;
+       g_debug ("Decompressing XML\t:%.1fms",
+                g_timer_elapsed (timer, NULL) * 1000);
+
+       /* Parse the XML */
+       g_timer_reset (timer);
+       ret = gs_plugin_parse_xml (plugin, error);
+       if (!ret)
+               goto out;
+       g_debug ("Parsed %i entries of XML\t:%.1fms",
+                plugin->priv->array->len,
+                g_timer_elapsed (timer, NULL) * 1000);
+
+       /* done */
+       plugin->priv->done_init = TRUE;
+out:
+       if (timer != NULL)
+               g_timer_destroy (timer);
+       g_mutex_unlock (&plugin->priv->mutex);
+       return ret;
+}
+
+/**
+ * gs_plugin_refine_item:
+ */
+static gboolean
+gs_plugin_refine_item (GsPlugin *plugin,
+                      GsApp *app,
+                      GsAppstreamItem *item,
+                      GError **error)
+{
+       gboolean ret = TRUE;
+       gchar *icon_path = NULL;
+       GdkPixbuf *pixbuf = NULL;
+
+       g_debug ("AppStream: Refining %s", gs_app_get_id (app));
+
+       /* is an app */
+       if (gs_app_get_kind (app) == GS_APP_KIND_UNKNOWN ||
+           gs_app_get_kind (app) == GS_APP_KIND_PACKAGE)
+               gs_app_set_kind (app, GS_APP_KIND_NORMAL);
+
+       /* set id */
+       if (item->id != NULL && gs_app_get_id (app) == NULL)
+               gs_app_set_id (app, item->id);
+
+       /* set name */
+       if (item->name != NULL && gs_app_get_name (app) == NULL)
+               gs_app_set_name (app, item->name);
+
+       /* set summary */
+       if (item->summary != NULL && gs_app_get_summary (app) == NULL)
+               gs_app_set_summary (app, item->summary);
+
+       /* set icon */
+       if (item->icon != NULL && gs_app_get_pixbuf (app) == NULL) {
+               icon_path = g_strdup_printf ("%s/%s.png",
+                                            plugin->priv->cachedir,
+                                            item->icon);
+               pixbuf = gdk_pixbuf_new_from_file_at_size (icon_path,
+                                                          plugin->pixbuf_size,
+                                                          plugin->pixbuf_size,
+                                                          error);
+               if (pixbuf == NULL) {
+                       ret = FALSE;
+                       goto out;
+               }
+               gs_app_set_pixbuf (app, pixbuf);
+       }
+
+       /* set package name */
+       if (item->pkgname != NULL && gs_app_get_metadata_item (app, "package-name") == NULL)
+               gs_app_set_metadata (app, "package-name", item->pkgname);
+out:
+       g_free (icon_path);
+       if (pixbuf != NULL)
+               g_object_unref (pixbuf);
+       return ret;
+}
+
+/**
+ * gs_plugin_refine_from_id:
+ */
+static gboolean
+gs_plugin_refine_from_id (GsPlugin *plugin,
+                         GsApp *app,
+                         GError **error)
+{
+       const gchar *id;
+       gboolean ret = TRUE;
+       GsAppstreamItem *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);
+       if (item == NULL) {
+               g_debug ("no AppStream match for [id] %s", id);
+               goto out;
+       }
+
+       /* set new properties */
+       ret = gs_plugin_refine_item (plugin, app, item, error);
+       if (!ret)
+               goto out;
+out:
+       return ret;
+}
+
+/**
+ * gs_plugin_refine_from_pkgname:
+ */
+static gboolean
+gs_plugin_refine_from_pkgname (GsPlugin *plugin,
+                              GsApp *app,
+                              GError **error)
+{
+       const gchar *pkgname;
+       gboolean ret = TRUE;
+       GsAppstreamItem *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);
+       if (item == NULL) {
+               g_debug ("no AppStream match for {pkgname} %s", pkgname);
+               goto out;
+       }
+
+       /* set new properties */
+       ret = gs_plugin_refine_item (plugin, app, item, error);
+       if (!ret)
+               goto out;
+out:
+       return ret;
+}
+
+/**
+ * gs_plugin_refine:
+ */
+gboolean
+gs_plugin_refine (GsPlugin *plugin,
+                 GList *list,
+                 GCancellable *cancellable,
+                 GError **error)
+{
+       gboolean ret;
+       GList *l;
+       GsApp *app;
+
+       /* load XML files */
+       ret = gs_plugin_startup (plugin, error);
+       if (!ret)
+               goto out;
+
+       for (l = list; l != NULL; l = l->next) {
+               app = GS_APP (l->data);
+               ret = gs_plugin_refine_from_id (plugin, app, error);
+               if (!ret)
+                       goto out;
+               ret = gs_plugin_refine_from_pkgname (plugin, app, error);
+               if (!ret)
+                       goto out;
+       }
+
+       /* sucess */
+       ret = TRUE;
+out:
+       return ret;
+}


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