[gnome-software] trivial: Split out the AppStream refine refine functionality again



commit 6cd11b1d4fdf23c7823c9bb236117161003ea8d8
Author: Richard Hughes <richard hughsie com>
Date:   Fri Feb 26 13:29:38 2016 +0000

    trivial: Split out the AppStream refine refine functionality again

 src/plugins/Makefile.am           |    5 +-
 src/plugins/gs-appstream.c        |  568 +++++++++++++++++++++++++++++++++++++
 src/plugins/gs-appstream.h        |   40 +++
 src/plugins/gs-plugin-appstream.c |  555 +-----------------------------------
 4 files changed, 620 insertions(+), 548 deletions(-)
---
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 5156bb7..d91c69e 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -103,7 +103,10 @@ 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) $(WARN_CFLAGS)
 
-libgs_plugin_appstream_la_SOURCES = gs-plugin-appstream.c
+libgs_plugin_appstream_la_SOURCES =                    \
+       gs-appstream.c                                  \
+       gs-appstream.h                                  \
+       gs-plugin-appstream.c
 libgs_plugin_appstream_la_LIBADD = $(GS_PLUGIN_LIBS) $(APPSTREAM_LIBS)
 libgs_plugin_appstream_la_LDFLAGS = -module -avoid-version
 libgs_plugin_appstream_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
diff --git a/src/plugins/gs-appstream.c b/src/plugins/gs-appstream.c
new file mode 100644
index 0000000..5e526a0
--- /dev/null
+++ b/src/plugins/gs-appstream.c
@@ -0,0 +1,568 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015-2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <gs-plugin.h>
+#include <gs-utils.h>
+
+#include "gs-appstream.h"
+
+#define        GS_APPSTREAM_MAX_SCREENSHOTS    5
+
+/**
+ * _as_app_has_compulsory_for_desktop:
+ */
+static gboolean
+_as_app_has_compulsory_for_desktop (AsApp *app, const gchar *compulsory_for_desktop)
+{
+       GPtrArray *compulsory_for_desktops;
+       const gchar *tmp;
+       guint i;
+
+       compulsory_for_desktops = as_app_get_compulsory_for_desktops (app);
+       for (i = 0; i < compulsory_for_desktops->len; i++) {
+               tmp = g_ptr_array_index (compulsory_for_desktops, i);
+               if (g_strcmp0 (tmp, compulsory_for_desktop) == 0)
+                       return TRUE;
+       }
+       return FALSE;
+}
+
+/**
+ * gs_refine_item_pixbuf:
+ */
+static void
+gs_refine_item_pixbuf (GsPlugin *plugin, GsApp *app, AsApp *item)
+{
+       AsIcon *icon;
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autofree gchar *fn = NULL;
+       g_autofree gchar *cachedir = NULL;
+
+       icon = as_app_get_icon_default (item);
+       switch (as_icon_get_kind (icon)) {
+       case AS_ICON_KIND_REMOTE:
+               gs_app_set_icon (app, icon);
+               if (as_icon_get_filename (icon) == NULL) {
+                       cachedir = gs_utils_get_cachedir ("icons", NULL);
+                       fn = g_build_filename (cachedir, as_icon_get_name (icon), NULL);
+                       as_icon_set_filename (icon, fn);
+                       as_icon_set_prefix (icon, cachedir);
+               }
+               if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
+                       as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
+                       ret = gs_app_load_icon (app, plugin->scale, &error);
+                       if (!ret) {
+                               g_warning ("failed to load icon %s: %s",
+                                          as_icon_get_name (icon),
+                                          error->message);
+                               return;
+                       }
+               }
+               break;
+       case AS_ICON_KIND_STOCK:
+       case AS_ICON_KIND_LOCAL:
+               gs_app_set_icon (app, icon);
+
+               /* does not exist, so try to find using the icon theme */
+               if (as_icon_get_kind (icon) == AS_ICON_KIND_LOCAL &&
+                   as_icon_get_filename (icon) == NULL)
+                       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+
+               /* load */
+               ret = gs_app_load_icon (app, plugin->scale, &error);
+               if (!ret) {
+                       g_warning ("failed to load %s icon %s: %s",
+                                  as_icon_kind_to_string (as_icon_get_kind (icon)),
+                                  as_icon_get_name (icon),
+                                  error->message);
+                               return;
+               }
+               break;
+       case AS_ICON_KIND_CACHED:
+               if (plugin->scale == 2)
+                       icon = as_app_get_icon_for_size (item, 128, 128);
+               if (icon == NULL)
+                       icon = as_app_get_icon_for_size (item, 64, 64);
+               if (icon == NULL) {
+                       g_warning ("failed to find cached icon %s",
+                                  as_icon_get_name (icon));
+                       return;
+               }
+               if (!as_icon_load (icon, AS_ICON_LOAD_FLAG_SEARCH_SIZE, &error)) {
+                       g_warning ("failed to load cached icon %s: %s",
+                                  as_icon_get_name (icon), error->message);
+                               return;
+               }
+               gs_app_set_pixbuf (app, as_icon_get_pixbuf (icon));
+               break;
+       default:
+               g_warning ("icon kind unknown for %s", as_app_get_id (item));
+               break;
+       }
+}
+
+/**
+ * gs_appstream_refine_add_addons:
+ */
+static void
+gs_appstream_refine_add_addons (GsPlugin *plugin, GsApp *app, AsApp *item)
+{
+       GPtrArray *addons;
+       guint i;
+
+       addons = as_app_get_addons (item);
+       if (addons == NULL)
+               return;
+
+       for (i = 0; i < addons->len; i++) {
+               AsApp *as_addon = g_ptr_array_index (addons, i);
+               g_autoptr(GError) error = NULL;
+               g_autoptr(GsApp) addon = NULL;
+
+               addon = gs_app_new (as_app_get_id (as_addon));
+
+               /* add all the data we can */
+               if (!gs_appstream_refine_app (plugin, addon, as_addon, &error)) {
+                       g_warning ("failed to refine addon: %s", error->message);
+                       continue;
+               }
+               gs_app_add_addon (app, addon);
+       }
+}
+/**
+ * gs_appstream_refine_add_screenshots:
+ */
+static void
+gs_appstream_refine_add_screenshots (GsApp *app, AsApp *item)
+{
+       AsScreenshot *ss;
+       GPtrArray *images_as;
+       GPtrArray *screenshots_as;
+       guint i;
+
+       /* do we have any to add */
+       screenshots_as = as_app_get_screenshots (item);
+       if (screenshots_as->len == 0)
+               return;
+
+       /* does the app already have some */
+       gs_app_add_kudo (app, GS_APP_KUDO_HAS_SCREENSHOTS);
+       if (gs_app_get_screenshots(app)->len > 0)
+               return;
+
+       /* add any we know */
+       for (i = 0; i < screenshots_as->len &&
+                   i < GS_APPSTREAM_MAX_SCREENSHOTS; i++) {
+               ss = g_ptr_array_index (screenshots_as, i);
+               images_as = as_screenshot_get_images (ss);
+               if (images_as->len == 0)
+                       continue;
+               if (as_screenshot_get_kind (ss) == AS_SCREENSHOT_KIND_UNKNOWN)
+                       continue;
+               gs_app_add_screenshot (app, ss);
+       }
+}
+
+/**
+ * gs_appstream_is_recent_release:
+ */
+static gboolean
+gs_appstream_is_recent_release (AsApp *app)
+{
+       AsRelease *release;
+       GPtrArray *releases;
+       guint secs;
+
+       /* get newest release */
+       releases = as_app_get_releases (app);
+       if (releases->len == 0)
+               return FALSE;
+       release = g_ptr_array_index (releases, 0);
+
+       /* is last build less than one year ago? */
+       secs = (g_get_real_time () / G_USEC_PER_SEC) -
+               as_release_get_timestamp (release);
+       return secs / (60 * 60 * 24) < 365;
+}
+
+/**
+ * gs_appstream_are_screenshots_perfect:
+ */
+static gboolean
+gs_appstream_are_screenshots_perfect (AsApp *app)
+{
+       AsImage *image;
+       AsScreenshot *screenshot;
+       GPtrArray *screenshots;
+       guint height;
+       guint i;
+       guint width;
+
+       screenshots = as_app_get_screenshots (app);
+       if (screenshots->len == 0)
+               return FALSE;
+       for (i = 0; i < screenshots->len; i++) {
+
+               /* get the source image as the thumbs will be resized & padded */
+               screenshot = g_ptr_array_index (screenshots, i);
+               image = as_screenshot_get_source (screenshot);
+               if (image == NULL)
+                       return FALSE;
+
+               width = as_image_get_width (image);
+               height = as_image_get_height (image);
+
+               /* too small */
+               if (width < AS_IMAGE_LARGE_WIDTH || height < AS_IMAGE_LARGE_HEIGHT)
+                       return FALSE;
+
+               /* too large */
+               if (width > AS_IMAGE_LARGE_WIDTH * 2 || height > AS_IMAGE_LARGE_HEIGHT * 2)
+                       return FALSE;
+
+               /* not 16:9 */
+               if ((width / 16) * 9 != height)
+                       return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * gs_appstream_copy_metadata:
+ */
+static void
+gs_appstream_copy_metadata (GsApp *app, AsApp *item)
+{
+       GHashTable *hash;
+       GList *l;
+       g_autoptr(GList) keys = NULL;
+
+       hash = as_app_get_metadata (item);
+       keys = g_hash_table_get_keys (hash);
+       for (l = keys; l != NULL; l = l->next) {
+               const gchar *key = l->data;
+               const gchar *value = g_hash_table_lookup (hash, key);
+               gs_app_set_metadata (app, key, value);
+       }
+}
+
+/**
+ * gs_appstream_create_runtime:
+ */
+GsApp *
+gs_appstream_create_runtime (GsApp *parent, const gchar *runtime)
+{
+       const gchar *id_parent;
+       g_autofree gchar *id = NULL;
+       g_autofree gchar *source = NULL;
+       g_auto(GStrv) id_split = NULL;
+       g_auto(GStrv) runtime_split = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* get the name/arch/branch */
+       runtime_split = g_strsplit (runtime, "/", -1);
+       if (g_strv_length (runtime_split) != 3)
+               return NULL;
+
+       /* find the parent app ID prefix */
+       id_parent = gs_app_get_id (parent);
+       if (id_parent == NULL)
+               return NULL;
+       id_split = g_strsplit (id_parent, ":", 2);
+       if (g_strv_length (id_split) == 2) {
+               id = g_strdup_printf ("%s:%s.runtime",
+                                     id_split[0],
+                                     runtime_split[0]);
+       } else {
+               id = g_strdup_printf ("%s.runtime", runtime_split[0]);
+       }
+
+       /* create the complete GsApp from the single string */
+       app = gs_app_new (id);
+       source = g_strdup_printf ("runtime/%s", runtime);
+       gs_app_add_source (app, source);
+       gs_app_set_kind (app, AS_APP_KIND_RUNTIME);
+       gs_app_set_version (app, id_split[2]);
+
+       return g_steal_pointer (&app);
+}
+
+/**
+ * gs_refine_item_management_plugin:
+ */
+static void
+gs_refine_item_management_plugin (GsApp *app, AsApp *item)
+{
+       GPtrArray *bundles;
+       const gchar *management_plugin = NULL;
+       const gchar *runtime = NULL;
+       guint i;
+
+       /* find the default bundle kind */
+       bundles = as_app_get_bundles (item);
+       for (i = 0; i < bundles->len; i++) {
+               AsBundle *bundle = g_ptr_array_index (bundles, i);
+               if (as_bundle_get_kind (bundle) == AS_BUNDLE_KIND_XDG_APP) {
+                       management_plugin = "XgdApp";
+                       gs_app_add_source (app, as_bundle_get_id (bundle));
+
+                       /* automatically add runtime */
+                       runtime = as_bundle_get_runtime (bundle);
+                       if (runtime != NULL) {
+                               g_autoptr(GsApp) app2 = NULL;
+                               app2 = gs_appstream_create_runtime (app, runtime);
+                               if (app2 != NULL) {
+                                       g_debug ("runtime for %s is %s",
+                                                gs_app_get_id (app), runtime);
+                                       gs_app_set_runtime (app, app2);
+                               }
+                       }
+                       break;
+               }
+               if (as_bundle_get_kind (bundle) == AS_BUNDLE_KIND_LIMBA) {
+                       management_plugin = "Limba";
+                       gs_app_add_source (app, as_bundle_get_id (bundle));
+                       break;
+               }
+       }
+
+       /* fall back to PackageKit */
+       if (management_plugin == NULL &&
+           as_app_get_pkgname_default (item) != NULL)
+               management_plugin = "PackageKit";
+       if (management_plugin != NULL)
+               gs_app_set_management_plugin (app, management_plugin);
+}
+
+/**
+ * gs_appstream_refine_app:
+ */
+gboolean
+gs_appstream_refine_app (GsPlugin *plugin, GsApp *app, AsApp *item, GError **error)
+{
+       AsRelease *rel;
+       GHashTable *urls;
+       GPtrArray *pkgnames;
+       GPtrArray *kudos;
+       const gchar *tmp;
+       guint i;
+
+       /* is an app */
+       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN ||
+           gs_app_get_kind (app) == AS_APP_KIND_GENERIC) {
+               if (as_app_get_kind (item) == AS_APP_KIND_SOURCE) {
+                       gs_app_set_kind (app, AS_APP_KIND_SOURCE);
+               } else {
+                       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+               }
+       }
+
+       /* is installed already */
+       if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
+               switch (as_app_get_source_kind (item)) {
+               case AS_APP_SOURCE_KIND_APPDATA:
+               case AS_APP_SOURCE_KIND_DESKTOP:
+                       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+                       break;
+               case AS_APP_SOURCE_KIND_METAINFO:
+                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+                       break;
+               case AS_APP_SOURCE_KIND_APPSTREAM:
+                       gs_app_set_state (app, as_app_get_state (item));
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       /* set management plugin automatically */
+       gs_refine_item_management_plugin (app, item);
+
+       /* set id */
+       if (as_app_get_id (item) != NULL && gs_app_get_id (app) == NULL)
+               gs_app_set_id (app, as_app_get_id (item));
+
+       /* set name */
+       tmp = as_app_get_name (item, NULL);
+       if (tmp != NULL) {
+               if (g_str_has_prefix (tmp, "(Nightly) ")) {
+                       tmp += 10;
+                       if (gs_app_get_metadata_item (app, "X-XdgApp-Tags") == NULL)
+                               gs_app_set_metadata (app, "X-XdgApp-Tags", "nightly");
+               }
+               gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, tmp);
+       }
+
+       /* set summary */
+       tmp = as_app_get_comment (item, NULL);
+       if (tmp != NULL) {
+               gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, tmp);
+       }
+
+       /* add urls */
+       urls = as_app_get_urls (item);
+       if (g_hash_table_size (urls) > 0 &&
+           gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL) {
+               GList *l;
+               g_autoptr(GList) keys = NULL;
+               keys = g_hash_table_get_keys (urls);
+               for (l = keys; l != NULL; l = l->next) {
+                       gs_app_set_url (app,
+                                       as_url_kind_from_string (l->data),
+                                       g_hash_table_lookup (urls, l->data));
+               }
+       }
+
+       /* set licence */
+       if (as_app_get_project_license (item) != NULL && gs_app_get_licence (app) == NULL)
+               gs_app_set_licence (app,
+                                   GS_APP_QUALITY_HIGHEST,
+                                   as_app_get_project_license (item));
+
+       /* set keywords */
+       if (as_app_get_keywords (item, NULL) != NULL &&
+           gs_app_get_keywords (app) == NULL) {
+               gs_app_set_keywords (app, as_app_get_keywords (item, NULL));
+               gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS);
+       }
+
+       /* set origin */
+       if (as_app_get_origin (item) != NULL &&
+           gs_app_get_origin (app) == NULL) {
+               gs_app_set_origin (app, as_app_get_origin (item));
+       }
+
+       /* set description */
+       tmp = as_app_get_description (item, NULL);
+       if (tmp != NULL) {
+               g_autofree gchar *from_xml = NULL;
+               from_xml = as_markup_convert_simple (tmp, error);
+               if (from_xml == NULL) {
+                       g_prefix_error (error, "trying to parse '%s': ", tmp);
+                       return FALSE;
+               }
+               gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, from_xml);
+       }
+
+       /* set icon */
+       if (as_app_get_icon_default (item) != NULL && gs_app_get_pixbuf (app) == NULL)
+               gs_refine_item_pixbuf (plugin, app, item);
+
+       /* set categories */
+       if (as_app_get_categories (item) != NULL &&
+           gs_app_get_categories (app)->len == 0)
+               gs_app_set_categories (app, as_app_get_categories (item));
+
+       /* set project group */
+       if (as_app_get_project_group (item) != NULL &&
+           gs_app_get_project_group (app) == NULL)
+               gs_app_set_project_group (app, as_app_get_project_group (item));
+
+       /* this is a core application for the desktop and cannot be removed */
+       if (_as_app_has_compulsory_for_desktop (item, "GNOME") &&
+           gs_app_get_kind (app) == AS_APP_KIND_DESKTOP)
+               gs_app_set_compulsory (app, TRUE);
+
+       /* set id kind */
+       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN)
+               gs_app_set_kind (app, as_app_get_kind (item));
+
+       /* copy all the metadata */
+       gs_appstream_copy_metadata (app, item);
+
+       /* set package names */
+       pkgnames = as_app_get_pkgnames (item);
+       if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0)
+               gs_app_set_sources (app, pkgnames);
+
+       /* set addons */
+       gs_appstream_refine_add_addons (plugin, app, item);
+
+       /* set screenshots */
+       gs_appstream_refine_add_screenshots (app, item);
+
+       /* are the screenshots perfect */
+       if (gs_appstream_are_screenshots_perfect (item))
+               gs_app_add_kudo (app, GS_APP_KUDO_PERFECT_SCREENSHOTS);
+
+       /* was this application released recently */
+       if (gs_appstream_is_recent_release (item))
+               gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);
+
+       /* add kudos */
+       if (as_app_get_language (item, plugin->locale) > 50)
+               gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);
+
+       /* add new-style kudos */
+       kudos = as_app_get_kudos (item);
+       for (i = 0; i < kudos->len; i++) {
+               tmp = g_ptr_array_index (kudos, i);
+               switch (as_kudo_kind_from_string (tmp)) {
+               case AS_KUDO_KIND_SEARCH_PROVIDER:
+                       gs_app_add_kudo (app, GS_APP_KUDO_SEARCH_PROVIDER);
+                       break;
+               case AS_KUDO_KIND_USER_DOCS:
+                       gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
+                       break;
+               case AS_KUDO_KIND_APP_MENU:
+                       gs_app_add_kudo (app, GS_APP_KUDO_USES_APP_MENU);
+                       break;
+               case AS_KUDO_KIND_MODERN_TOOLKIT:
+                       gs_app_add_kudo (app, GS_APP_KUDO_MODERN_TOOLKIT);
+                       break;
+               case AS_KUDO_KIND_NOTIFICATIONS:
+                       gs_app_add_kudo (app, GS_APP_KUDO_USES_NOTIFICATIONS);
+                       break;
+               case AS_KUDO_KIND_HIGH_CONTRAST:
+                       gs_app_add_kudo (app, GS_APP_KUDO_HIGH_CONTRAST);
+                       break;
+               case AS_KUDO_KIND_HI_DPI_ICON:
+                       gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON);
+                       break;
+               default:
+                       g_debug ("no idea how to handle kudo '%s'", tmp);
+                       break;
+               }
+       }
+
+       /* is there any update information */
+       rel = as_app_get_release_default (item);
+       if (rel != NULL) {
+               tmp = as_release_get_description (rel, NULL);
+               if (tmp != NULL) {
+                       g_autofree gchar *desc = NULL;
+                       desc = as_markup_convert (tmp,
+                                                 AS_MARKUP_CONVERT_FORMAT_MARKDOWN,
+                                                 error);
+                       if (desc == NULL)
+                               return FALSE;
+                       gs_app_set_update_details (app, desc);
+               }
+               gs_app_set_update_urgency (app, as_release_get_urgency (rel));
+               gs_app_set_update_version (app, as_release_get_version (rel));
+       }
+
+       return TRUE;
+}
diff --git a/src/plugins/gs-appstream.h b/src/plugins/gs-appstream.h
new file mode 100644
index 0000000..386f077
--- /dev/null
+++ b/src/plugins/gs-appstream.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015-2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __APPSTREAM_COMMON_H
+#define __APPSTREAM_COMMON_H
+
+#include <glib.h>
+#include <gs-plugin.h>
+#include <appstream-glib.h>
+
+G_BEGIN_DECLS
+
+gboolean        gs_appstream_refine_app                (GsPlugin       *plugin,
+                                                        GsApp          *app,
+                                                        AsApp          *item,
+                                                        GError         **error);
+GsApp          *gs_appstream_create_runtime            (GsApp          *parent,
+                                                        const gchar    *runtime);
+
+G_END_DECLS
+
+#endif /* __APPSTREAM_COMMON_H */
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index 7249be9..5ae3d21 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -27,6 +27,8 @@
 #include <gs-plugin.h>
 #include <gs-plugin-loader.h>
 
+#include "gs-appstream.h"
+
 /*
  * SECTION:
  * Uses offline AppStream data to populate and refine package results.
@@ -44,8 +46,6 @@ struct GsPluginPrivate {
        gboolean                 done_init;
 };
 
-static gboolean gs_plugin_refine_item (GsPlugin *plugin, GsApp *app, AsApp *item, GError **error);
-
 /**
  * gs_plugin_get_name:
  */
@@ -225,545 +225,6 @@ gs_plugin_appstream_startup (GsPlugin *plugin, GError **error)
        return TRUE;
 }
 
-#define        GS_PLUGIN_APPSTREAM_MAX_SCREENSHOTS     5
-
-/**
- * _as_app_has_compulsory_for_desktop:
- */
-static gboolean
-_as_app_has_compulsory_for_desktop (AsApp *app, const gchar *compulsory_for_desktop)
-{
-       GPtrArray *compulsory_for_desktops;
-       const gchar *tmp;
-       guint i;
-
-       compulsory_for_desktops = as_app_get_compulsory_for_desktops (app);
-       for (i = 0; i < compulsory_for_desktops->len; i++) {
-               tmp = g_ptr_array_index (compulsory_for_desktops, i);
-               if (g_strcmp0 (tmp, compulsory_for_desktop) == 0)
-                       return TRUE;
-       }
-       return FALSE;
-}
-
-/**
- * gs_plugin_refine_item_pixbuf:
- */
-static void
-gs_plugin_refine_item_pixbuf (GsPlugin *plugin, GsApp *app, AsApp *item)
-{
-       AsIcon *icon;
-       gboolean ret;
-       g_autoptr(GError) error = NULL;
-       g_autofree gchar *fn = NULL;
-       g_autofree gchar *cachedir = NULL;
-
-       icon = as_app_get_icon_default (item);
-       switch (as_icon_get_kind (icon)) {
-       case AS_ICON_KIND_REMOTE:
-               gs_app_set_icon (app, icon);
-               if (as_icon_get_filename (icon) == NULL) {
-                       cachedir = gs_utils_get_cachedir ("icons", NULL);
-                       fn = g_build_filename (cachedir, as_icon_get_name (icon), NULL);
-                       as_icon_set_filename (icon, fn);
-                       as_icon_set_prefix (icon, cachedir);
-               }
-               if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
-                       as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
-                       ret = gs_app_load_icon (app, plugin->scale, &error);
-                       if (!ret) {
-                               g_warning ("failed to load icon %s: %s",
-                                          as_icon_get_name (icon),
-                                          error->message);
-                               return;
-                       }
-               }
-               break;
-       case AS_ICON_KIND_STOCK:
-       case AS_ICON_KIND_LOCAL:
-               gs_app_set_icon (app, icon);
-
-               /* does not exist, so try to find using the icon theme */
-               if (as_icon_get_kind (icon) == AS_ICON_KIND_LOCAL &&
-                   as_icon_get_filename (icon) == NULL)
-                       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-
-               /* load */
-               ret = gs_app_load_icon (app, plugin->scale, &error);
-               if (!ret) {
-                       g_warning ("failed to load %s icon %s: %s",
-                                  as_icon_kind_to_string (as_icon_get_kind (icon)),
-                                  as_icon_get_name (icon),
-                                  error->message);
-                               return;
-               }
-               break;
-       case AS_ICON_KIND_CACHED:
-               if (plugin->scale == 2)
-                       icon = as_app_get_icon_for_size (item, 128, 128);
-               if (icon == NULL)
-                       icon = as_app_get_icon_for_size (item, 64, 64);
-               if (icon == NULL) {
-                       g_warning ("failed to find cached icon %s",
-                                  as_icon_get_name (icon));
-                       return;
-               }
-               if (!as_icon_load (icon, AS_ICON_LOAD_FLAG_SEARCH_SIZE, &error)) {
-                       g_warning ("failed to load cached icon %s: %s",
-                                  as_icon_get_name (icon), error->message);
-                               return;
-               }
-               gs_app_set_pixbuf (app, as_icon_get_pixbuf (icon));
-               break;
-       default:
-               g_warning ("icon kind unknown for %s", as_app_get_id (item));
-               break;
-       }
-}
-
-/**
- * gs_plugin_appstream_refine_add_addons:
- */
-static void
-gs_plugin_appstream_refine_add_addons (GsPlugin *plugin, GsApp *app, AsApp *item)
-{
-       GPtrArray *addons;
-       guint i;
-
-       addons = as_app_get_addons (item);
-       if (addons == NULL)
-               return;
-
-       for (i = 0; i < addons->len; i++) {
-               AsApp *as_addon = g_ptr_array_index (addons, i);
-               g_autoptr(GError) error = NULL;
-               g_autoptr(GsApp) addon = NULL;
-
-               addon = gs_app_new (as_app_get_id (as_addon));
-
-               /* add all the data we can */
-               if (!gs_plugin_refine_item (plugin, addon, as_addon, &error)) {
-                       g_warning ("failed to refine addon: %s", error->message);
-                       continue;
-               }
-               gs_app_add_addon (app, addon);
-       }
-}
-/**
- * gs_plugin_appstream_refine_add_screenshots:
- */
-static void
-gs_plugin_appstream_refine_add_screenshots (GsApp *app, AsApp *item)
-{
-       AsScreenshot *ss;
-       GPtrArray *images_as;
-       GPtrArray *screenshots_as;
-       guint i;
-
-       /* do we have any to add */
-       screenshots_as = as_app_get_screenshots (item);
-       if (screenshots_as->len == 0)
-               return;
-
-       /* does the app already have some */
-       gs_app_add_kudo (app, GS_APP_KUDO_HAS_SCREENSHOTS);
-       if (gs_app_get_screenshots(app)->len > 0)
-               return;
-
-       /* add any we know */
-       for (i = 0; i < screenshots_as->len &&
-                   i < GS_PLUGIN_APPSTREAM_MAX_SCREENSHOTS; i++) {
-               ss = g_ptr_array_index (screenshots_as, i);
-               images_as = as_screenshot_get_images (ss);
-               if (images_as->len == 0)
-                       continue;
-               if (as_screenshot_get_kind (ss) == AS_SCREENSHOT_KIND_UNKNOWN)
-                       continue;
-               gs_app_add_screenshot (app, ss);
-       }
-}
-
-/**
- * gs_plugin_appstream_is_recent_release:
- */
-static gboolean
-gs_plugin_appstream_is_recent_release (AsApp *app)
-{
-       AsRelease *release;
-       GPtrArray *releases;
-       guint secs;
-
-       /* get newest release */
-       releases = as_app_get_releases (app);
-       if (releases->len == 0)
-               return FALSE;
-       release = g_ptr_array_index (releases, 0);
-
-       /* is last build less than one year ago? */
-       secs = (g_get_real_time () / G_USEC_PER_SEC) -
-               as_release_get_timestamp (release);
-       return secs / (60 * 60 * 24) < 365;
-}
-
-/**
- * gs_plugin_appstream_are_screenshots_perfect:
- */
-static gboolean
-gs_plugin_appstream_are_screenshots_perfect (AsApp *app)
-{
-       AsImage *image;
-       AsScreenshot *screenshot;
-       GPtrArray *screenshots;
-       guint height;
-       guint i;
-       guint width;
-
-       screenshots = as_app_get_screenshots (app);
-       if (screenshots->len == 0)
-               return FALSE;
-       for (i = 0; i < screenshots->len; i++) {
-
-               /* get the source image as the thumbs will be resized & padded */
-               screenshot = g_ptr_array_index (screenshots, i);
-               image = as_screenshot_get_source (screenshot);
-               if (image == NULL)
-                       return FALSE;
-
-               width = as_image_get_width (image);
-               height = as_image_get_height (image);
-
-               /* too small */
-               if (width < AS_IMAGE_LARGE_WIDTH || height < AS_IMAGE_LARGE_HEIGHT)
-                       return FALSE;
-
-               /* too large */
-               if (width > AS_IMAGE_LARGE_WIDTH * 2 || height > AS_IMAGE_LARGE_HEIGHT * 2)
-                       return FALSE;
-
-               /* not 16:9 */
-               if ((width / 16) * 9 != height)
-                       return FALSE;
-       }
-       return TRUE;
-}
-
-/**
- * gs_plugin_appstream_copy_metadata:
- */
-static void
-gs_plugin_appstream_copy_metadata (GsApp *app, AsApp *item)
-{
-       GHashTable *hash;
-       GList *l;
-       g_autoptr(GList) keys = NULL;
-
-       hash = as_app_get_metadata (item);
-       keys = g_hash_table_get_keys (hash);
-       for (l = keys; l != NULL; l = l->next) {
-               const gchar *key = l->data;
-               const gchar *value = g_hash_table_lookup (hash, key);
-               gs_app_set_metadata (app, key, value);
-       }
-}
-
-/**
- * gs_plugin_appstream_create_runtime:
- */
-static GsApp *
-gs_plugin_appstream_create_runtime (GsApp *parent, const gchar *runtime)
-{
-       g_autofree gchar *id = NULL;
-       g_autofree gchar *source = NULL;
-       g_auto(GStrv) id_split = NULL;
-       g_auto(GStrv) runtime_split = NULL;
-       g_autoptr(GsApp) app = NULL;
-
-       /* get the name/arch/branch */
-       runtime_split = g_strsplit (runtime, "/", -1);
-       if (g_strv_length (runtime_split) != 3)
-               return NULL;
-
-       /* find the parent app ID prefix */
-       id_split = g_strsplit (gs_app_get_id (parent), ":", 2);
-       if (g_strv_length (id_split) == 2) {
-               id = g_strdup_printf ("%s:%s.runtime",
-                                     id_split[0],
-                                     runtime_split[0]);
-       } else {
-               id = g_strdup_printf ("%s.runtime", runtime_split[0]);
-       }
-
-       /* create the complete GsApp from the single string */
-       app = gs_app_new (id);
-       source = g_strdup_printf ("runtime/%s", runtime);
-       gs_app_add_source (app, source);
-       gs_app_set_kind (app, AS_APP_KIND_RUNTIME);
-       gs_app_set_version (app, id_split[2]);
-
-       return g_steal_pointer (&app);
-}
-
-/**
- * gs_plugin_refine_item_management_plugin:
- */
-static void
-gs_plugin_refine_item_management_plugin (GsApp *app, AsApp *item)
-{
-       GPtrArray *bundles;
-       const gchar *management_plugin = NULL;
-       const gchar *runtime = NULL;
-       guint i;
-
-       /* find the default bundle kind */
-       bundles = as_app_get_bundles (item);
-       for (i = 0; i < bundles->len; i++) {
-               AsBundle *bundle = g_ptr_array_index (bundles, i);
-               if (as_bundle_get_kind (bundle) == AS_BUNDLE_KIND_XDG_APP) {
-                       management_plugin = "XgdApp";
-                       gs_app_add_source (app, as_bundle_get_id (bundle));
-
-#if AS_CHECK_VERSION(0,5,10)
-                       /* automatically add runtime */
-                       runtime = as_bundle_get_runtime (bundle);
-                       if (runtime != NULL) {
-                               g_autoptr(GsApp) app2 = NULL;
-                               app2 = gs_plugin_appstream_create_runtime (app, runtime);
-                               if (app2 != NULL) {
-                                       g_debug ("runtime for %s is %s",
-                                                gs_app_get_id (app), runtime);
-                                       gs_app_set_runtime (app, app2);
-                               }
-                       }
-#endif
-                       break;
-               }
-               if (as_bundle_get_kind (bundle) == AS_BUNDLE_KIND_LIMBA) {
-                       management_plugin = "Limba";
-                       gs_app_add_source (app, as_bundle_get_id (bundle));
-                       break;
-               }
-       }
-
-       /* fall back to PackageKit */
-       if (management_plugin == NULL &&
-           as_app_get_pkgname_default (item) != NULL)
-               management_plugin = "PackageKit";
-       if (management_plugin != NULL)
-               gs_app_set_management_plugin (app, management_plugin);
-}
-
-/**
- * gs_plugin_refine_item:
- */
-static gboolean
-gs_plugin_refine_item (GsPlugin *plugin, GsApp *app, AsApp *item, GError **error)
-{
-       AsRelease *rel;
-       GHashTable *urls;
-       GPtrArray *pkgnames;
-       GPtrArray *kudos;
-       const gchar *tmp;
-       guint i;
-
-       /* is an app */
-       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN ||
-           gs_app_get_kind (app) == AS_APP_KIND_GENERIC) {
-               if (as_app_get_kind (item) == AS_APP_KIND_SOURCE) {
-                       gs_app_set_kind (app, AS_APP_KIND_SOURCE);
-               } else {
-                       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
-               }
-       }
-
-       /* is installed already */
-       if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
-               switch (as_app_get_source_kind (item)) {
-               case AS_APP_SOURCE_KIND_APPDATA:
-               case AS_APP_SOURCE_KIND_DESKTOP:
-                       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
-                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-                       break;
-               case AS_APP_SOURCE_KIND_METAINFO:
-                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-                       break;
-               case AS_APP_SOURCE_KIND_APPSTREAM:
-                       gs_app_set_state (app, as_app_get_state (item));
-                       break;
-               default:
-                       break;
-               }
-       }
-
-       /* set management plugin automatically */
-       gs_plugin_refine_item_management_plugin (app, item);
-
-       /* set id */
-       if (as_app_get_id (item) != NULL && gs_app_get_id (app) == NULL)
-               gs_app_set_id (app, as_app_get_id (item));
-
-       /* set name */
-       tmp = as_app_get_name (item, NULL);
-       if (tmp != NULL) {
-               if (g_str_has_prefix (tmp, "(Nightly) ")) {
-                       tmp += 10;
-                       if (gs_app_get_metadata_item (app, "X-XdgApp-Tags") == NULL)
-                               gs_app_set_metadata (app, "X-XdgApp-Tags", "nightly");
-               }
-               gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, tmp);
-       }
-
-       /* set summary */
-       tmp = as_app_get_comment (item, NULL);
-       if (tmp != NULL) {
-               gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, tmp);
-       }
-
-       /* add urls */
-       urls = as_app_get_urls (item);
-       if (g_hash_table_size (urls) > 0 &&
-           gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL) {
-               GList *l;
-               g_autoptr(GList) keys = NULL;
-               keys = g_hash_table_get_keys (urls);
-               for (l = keys; l != NULL; l = l->next) {
-                       gs_app_set_url (app,
-                                       as_url_kind_from_string (l->data),
-                                       g_hash_table_lookup (urls, l->data));
-               }
-       }
-
-       /* set licence */
-       if (as_app_get_project_license (item) != NULL && gs_app_get_licence (app) == NULL)
-               gs_app_set_licence (app,
-                                   GS_APP_QUALITY_HIGHEST,
-                                   as_app_get_project_license (item));
-
-       /* set keywords */
-       if (as_app_get_keywords (item, NULL) != NULL &&
-           gs_app_get_keywords (app) == NULL) {
-               gs_app_set_keywords (app, as_app_get_keywords (item, NULL));
-               gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS);
-       }
-
-       /* set origin */
-       if (as_app_get_origin (item) != NULL &&
-           gs_app_get_origin (app) == NULL) {
-               gs_app_set_origin (app, as_app_get_origin (item));
-       }
-
-       /* set description */
-       tmp = as_app_get_description (item, NULL);
-       if (tmp != NULL) {
-               g_autofree gchar *from_xml = NULL;
-               from_xml = as_markup_convert_simple (tmp, error);
-               if (from_xml == NULL) {
-                       g_prefix_error (error, "trying to parse '%s': ", tmp);
-                       return FALSE;
-               }
-               gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, from_xml);
-       }
-
-       /* set icon */
-       if (as_app_get_icon_default (item) != NULL && gs_app_get_pixbuf (app) == NULL)
-               gs_plugin_refine_item_pixbuf (plugin, app, item);
-
-       /* set categories */
-       if (as_app_get_categories (item) != NULL &&
-           gs_app_get_categories (app)->len == 0)
-               gs_app_set_categories (app, as_app_get_categories (item));
-
-       /* set project group */
-       if (as_app_get_project_group (item) != NULL &&
-           gs_app_get_project_group (app) == NULL)
-               gs_app_set_project_group (app, as_app_get_project_group (item));
-
-       /* this is a core application for the desktop and cannot be removed */
-       if (_as_app_has_compulsory_for_desktop (item, "GNOME") &&
-           gs_app_get_kind (app) == AS_APP_KIND_DESKTOP)
-               gs_app_set_compulsory (app, TRUE);
-
-       /* set id kind */
-       if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN)
-               gs_app_set_kind (app, as_app_get_kind (item));
-
-       /* copy all the metadata */
-       gs_plugin_appstream_copy_metadata (app, item);
-
-       /* set package names */
-       pkgnames = as_app_get_pkgnames (item);
-       if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0)
-               gs_app_set_sources (app, pkgnames);
-
-       /* set addons */
-       gs_plugin_appstream_refine_add_addons (plugin, app, item);
-
-       /* set screenshots */
-       gs_plugin_appstream_refine_add_screenshots (app, item);
-
-       /* are the screenshots perfect */
-       if (gs_plugin_appstream_are_screenshots_perfect (item))
-               gs_app_add_kudo (app, GS_APP_KUDO_PERFECT_SCREENSHOTS);
-
-       /* was this application released recently */
-       if (gs_plugin_appstream_is_recent_release (item))
-               gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);
-
-       /* add kudos */
-       if (as_app_get_language (item, plugin->locale) > 50)
-               gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);
-
-       /* add new-style kudos */
-       kudos = as_app_get_kudos (item);
-       for (i = 0; i < kudos->len; i++) {
-               tmp = g_ptr_array_index (kudos, i);
-               switch (as_kudo_kind_from_string (tmp)) {
-               case AS_KUDO_KIND_SEARCH_PROVIDER:
-                       gs_app_add_kudo (app, GS_APP_KUDO_SEARCH_PROVIDER);
-                       break;
-               case AS_KUDO_KIND_USER_DOCS:
-                       gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
-                       break;
-               case AS_KUDO_KIND_APP_MENU:
-                       gs_app_add_kudo (app, GS_APP_KUDO_USES_APP_MENU);
-                       break;
-               case AS_KUDO_KIND_MODERN_TOOLKIT:
-                       gs_app_add_kudo (app, GS_APP_KUDO_MODERN_TOOLKIT);
-                       break;
-               case AS_KUDO_KIND_NOTIFICATIONS:
-                       gs_app_add_kudo (app, GS_APP_KUDO_USES_NOTIFICATIONS);
-                       break;
-               case AS_KUDO_KIND_HIGH_CONTRAST:
-                       gs_app_add_kudo (app, GS_APP_KUDO_HIGH_CONTRAST);
-                       break;
-               case AS_KUDO_KIND_HI_DPI_ICON:
-                       gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON);
-                       break;
-               default:
-                       g_debug ("no idea how to handle kudo '%s'", tmp);
-                       break;
-               }
-       }
-
-       /* is there any update information */
-       rel = as_app_get_release_default (item);
-       if (rel != NULL) {
-               tmp = as_release_get_description (rel, NULL);
-               if (tmp != NULL) {
-                       g_autofree gchar *desc = NULL;
-                       desc = as_markup_convert (tmp,
-                                                 AS_MARKUP_CONVERT_FORMAT_MARKDOWN,
-                                                 error);
-                       if (desc == NULL)
-                               return FALSE;
-                       gs_app_set_update_details (app, desc);
-               }
-               gs_app_set_update_urgency (app, as_release_get_urgency (rel));
-               gs_app_set_update_version (app, as_release_get_version (rel));
-       }
-
-       return TRUE;
-}
-
 /**
  * gs_plugin_refine_from_id:
  */
@@ -788,7 +249,7 @@ gs_plugin_refine_from_id (GsPlugin *plugin,
                return TRUE;
 
        /* set new properties */
-       if (!gs_plugin_refine_item (plugin, app, item, error))
+       if (!gs_appstream_refine_app (plugin, app, item, error))
                return FALSE;
 
        *found = TRUE;
@@ -823,7 +284,7 @@ gs_plugin_refine_from_pkgname (GsPlugin *plugin,
                return TRUE;
 
        /* set new properties */
-       return gs_plugin_refine_item (plugin, app, item, error);
+       return gs_appstream_refine_app (plugin, app, item, error);
 }
 
 /**
@@ -860,7 +321,7 @@ gs_plugin_add_distro_upgrades (GsPlugin *plugin,
                app = gs_app_new (as_app_get_id (item));
                gs_app_set_kind (app, AS_APP_KIND_OS_UPGRADE);
                gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-               if (!gs_plugin_refine_item (plugin, app, item, error))
+               if (!gs_appstream_refine_app (plugin, app, item, error))
                        return FALSE;
                gs_plugin_add_app (list, app);
        }
@@ -953,7 +414,7 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
 
                /* got a search match, so add all the data we can */
                app = gs_app_new (as_app_get_id (item));
-               if (!gs_plugin_refine_item (plugin, app, item, error))
+               if (!gs_appstream_refine_app (plugin, app, item, error))
                        return FALSE;
                gs_plugin_add_app (list, app);
        }
@@ -991,7 +452,7 @@ gs_plugin_add_search_item (GsPlugin *plugin,
 
        /* create app */
        app = gs_app_new (as_app_get_id (item));
-       if (!gs_plugin_refine_item (plugin, app, item, error))
+       if (!gs_appstream_refine_app (plugin, app, item, error))
                return FALSE;
        gs_app_set_search_sort_key (app, match_value);
        gs_plugin_add_app (list, app);
@@ -1061,7 +522,7 @@ gs_plugin_add_installed (GsPlugin *plugin,
                if (as_app_get_state (item) == AS_APP_STATE_INSTALLED) {
                        g_autoptr(GsApp) app = NULL;
                        app = gs_app_new (as_app_get_id (item));
-                       if (!gs_plugin_refine_item (plugin, app, item, error))
+                       if (!gs_appstream_refine_app (plugin, app, item, error))
                                return FALSE;
                        gs_plugin_add_app (list, app);
                }


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