summaryrefslogtreecommitdiffstats
path: root/plugins/core/gs-plugin-icons.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/core/gs-plugin-icons.c')
-rw-r--r--plugins/core/gs-plugin-icons.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/plugins/core/gs-plugin-icons.c b/plugins/core/gs-plugin-icons.c
new file mode 100644
index 0000000..112f1d2
--- /dev/null
+++ b/plugins/core/gs-plugin-icons.c
@@ -0,0 +1,348 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2014 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2015 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <gnome-software.h>
+
+/*
+ * SECTION:
+ * Loads remote icons and converts them into local cached ones.
+ *
+ * It is provided so that each plugin handling icons does not
+ * have to handle the download and caching functionality.
+ */
+
+struct GsPluginData {
+ GtkIconTheme *icon_theme;
+ GMutex icon_theme_lock;
+ GHashTable *icon_theme_paths;
+};
+
+static void gs_plugin_icons_add_theme_path (GsPlugin *plugin, const gchar *path);
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+ const gchar *test_search_path;
+
+ priv->icon_theme = gtk_icon_theme_new ();
+ gtk_icon_theme_set_screen (priv->icon_theme, gdk_screen_get_default ());
+ priv->icon_theme_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ g_mutex_init (&priv->icon_theme_lock);
+
+ test_search_path = g_getenv ("GS_SELF_TEST_ICON_THEME_PATH");
+ if (test_search_path != NULL) {
+ g_auto(GStrv) dirs = g_strsplit (test_search_path, ":", -1);
+
+ /* add_theme_path() prepends, so we have to iterate in reverse to preserve order */
+ for (gsize i = g_strv_length (dirs); i > 0; i--)
+ gs_plugin_icons_add_theme_path (plugin, dirs[i - 1]);
+ }
+
+ /* needs remote icons downloaded */
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+ gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "epiphany");
+}
+
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_object_unref (priv->icon_theme);
+ g_hash_table_unref (priv->icon_theme_paths);
+ g_mutex_clear (&priv->icon_theme_lock);
+}
+
+static gboolean
+gs_plugin_icons_download (GsPlugin *plugin,
+ const gchar *uri,
+ const gchar *filename,
+ GError **error)
+{
+ guint status_code;
+ g_autoptr(GdkPixbuf) pixbuf_new = NULL;
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autoptr(GInputStream) stream = NULL;
+ g_autoptr(SoupMessage) msg = NULL;
+
+ /* create the GET data */
+ msg = soup_message_new (SOUP_METHOD_GET, uri);
+ if (msg == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "%s is not a valid URL", uri);
+ return FALSE;
+ }
+
+ /* set sync request */
+ status_code = soup_session_send_message (gs_plugin_get_soup_session (plugin), msg);
+ if (status_code != SOUP_STATUS_OK) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_DOWNLOAD_FAILED,
+ "Failed to download icon %s: %s",
+ uri, soup_status_get_phrase (status_code));
+ return FALSE;
+ }
+
+ /* we're assuming this is a 64x64 png file, resize if not */
+ stream = g_memory_input_stream_new_from_data (msg->response_body->data,
+ msg->response_body->length,
+ NULL);
+ pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
+ if (pixbuf == NULL) {
+ gs_utils_error_convert_gdk_pixbuf (error);
+ return FALSE;
+ }
+ if (gdk_pixbuf_get_height (pixbuf) == 64 &&
+ gdk_pixbuf_get_width (pixbuf) == 64) {
+ pixbuf_new = g_object_ref (pixbuf);
+ } else {
+ pixbuf_new = gdk_pixbuf_scale_simple (pixbuf, 64, 64,
+ GDK_INTERP_BILINEAR);
+ }
+
+ /* write file */
+ if (!gdk_pixbuf_save (pixbuf_new, filename, "png", error, NULL)) {
+ gs_utils_error_convert_gdk_pixbuf (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static GdkPixbuf *
+gs_plugin_icons_load_local (GsPlugin *plugin, AsIcon *icon, GError **error)
+{
+ GdkPixbuf *pixbuf;
+ gint size;
+ if (as_icon_get_filename (icon) == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "icon has no filename");
+ return NULL;
+ }
+ size = (gint) (64 * gs_plugin_get_scale (plugin));
+ pixbuf = gdk_pixbuf_new_from_file_at_size (as_icon_get_filename (icon),
+ size, size, error);
+ if (pixbuf == NULL) {
+ gs_utils_error_convert_gdk_pixbuf (error);
+ return NULL;
+ }
+ return pixbuf;
+}
+
+static gchar *
+gs_plugin_icons_get_cache_fn (AsIcon *icon)
+{
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *checksum = NULL;
+ checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1,
+ as_icon_get_url (icon),
+ -1);
+ basename = g_path_get_basename (as_icon_get_url (icon));
+ return g_strdup_printf ("%s-%s", checksum, basename);
+}
+
+static GdkPixbuf *
+gs_plugin_icons_load_remote (GsPlugin *plugin, AsIcon *icon, GError **error)
+{
+ const gchar *fn;
+ gchar *found;
+
+ /* not applicable for remote */
+ if (as_icon_get_url (icon) == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "icon has no URL");
+ return NULL;
+ }
+
+ /* set cache filename if not already set */
+ if (as_icon_get_filename (icon) == NULL) {
+ g_autofree gchar *fn_cache = NULL;
+ g_autofree gchar *fn_basename = NULL;
+
+ /* use a hash-prefixed filename to avoid cache clashes */
+ fn_basename = gs_plugin_icons_get_cache_fn (icon);
+ fn_cache = gs_utils_get_cache_filename ("icons",
+ fn_basename,
+ GS_UTILS_CACHE_FLAG_WRITEABLE,
+ error);
+ if (fn_cache == NULL)
+ return NULL;
+ as_icon_set_filename (icon, fn_cache);
+ }
+
+ /* already in cache */
+ if (g_file_test (as_icon_get_filename (icon), G_FILE_TEST_EXISTS))
+ return gs_plugin_icons_load_local (plugin, icon, error);
+
+ /* a REMOTE that's really LOCAL */
+ if (g_str_has_prefix (as_icon_get_url (icon), "file://")) {
+ as_icon_set_filename (icon, as_icon_get_url (icon) + 7);
+ as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
+ return gs_plugin_icons_load_local (plugin, icon, error);
+ }
+
+ /* convert filename from jpg to png */
+ fn = as_icon_get_filename (icon);
+ found = g_strstr_len (fn, -1, ".jpg");
+ if (found != NULL)
+ memcpy (found, ".png", 4);
+
+ /* create runtime dir and download */
+ if (!gs_mkdir_parent (fn, error))
+ return NULL;
+ if (!gs_plugin_icons_download (plugin, as_icon_get_url (icon), fn, error))
+ return NULL;
+ as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
+ return gs_plugin_icons_load_local (plugin, icon, error);
+}
+
+static void
+gs_plugin_icons_add_theme_path (GsPlugin *plugin, const gchar *path)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ if (path == NULL)
+ return;
+ if (!g_hash_table_contains (priv->icon_theme_paths, path)) {
+ gtk_icon_theme_prepend_search_path (priv->icon_theme, path);
+ g_hash_table_add (priv->icon_theme_paths, g_strdup (path));
+ }
+}
+
+static GdkPixbuf *
+gs_plugin_icons_load_stock (GsPlugin *plugin, AsIcon *icon, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ GdkPixbuf *pixbuf;
+ gint size;
+ g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->icon_theme_lock);
+
+ /* required */
+ if (as_icon_get_name (icon) == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "icon has no name");
+ return NULL;
+ }
+ gs_plugin_icons_add_theme_path (plugin, as_icon_get_prefix (icon));
+ size = (gint) (64 * gs_plugin_get_scale (plugin));
+ pixbuf = gtk_icon_theme_load_icon (priv->icon_theme,
+ as_icon_get_name (icon),
+ size,
+ GTK_ICON_LOOKUP_USE_BUILTIN |
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ error);
+ if (pixbuf == NULL) {
+ gs_utils_error_convert_gdk_pixbuf (error);
+ return NULL;
+ }
+ return pixbuf;
+}
+
+static GdkPixbuf *
+gs_plugin_icons_load_cached (GsPlugin *plugin, AsIcon *icon, GError **error)
+{
+ if (!as_icon_load (icon, AS_ICON_LOAD_FLAG_SEARCH_SIZE, error)) {
+ gs_utils_error_convert_gdk_pixbuf (error);
+ gs_utils_error_convert_appstream (error);
+ return NULL;
+ }
+ return g_object_ref (as_icon_get_pixbuf (icon));
+}
+
+static gboolean
+refine_app (GsPlugin *plugin,
+ GsApp *app,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GPtrArray *icons;
+ guint i;
+
+ /* not required */
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON) == 0)
+ return TRUE;
+
+ /* invalid */
+ if (gs_app_get_pixbuf (app) != NULL)
+ return TRUE;
+
+ /* process all icons */
+ icons = gs_app_get_icons (app);
+ for (i = 0; i < icons->len; i++) {
+ AsIcon *icon = g_ptr_array_index (icons, i);
+ g_autoptr(GdkPixbuf) pixbuf = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ /* handle different icon types */
+ switch (as_icon_get_kind (icon)) {
+ case AS_ICON_KIND_LOCAL:
+ pixbuf = gs_plugin_icons_load_local (plugin, icon, &error_local);
+ break;
+ case AS_ICON_KIND_STOCK:
+ pixbuf = gs_plugin_icons_load_stock (plugin, icon, &error_local);
+ break;
+ case AS_ICON_KIND_REMOTE:
+ pixbuf = gs_plugin_icons_load_remote (plugin, icon, &error_local);
+ break;
+ case AS_ICON_KIND_CACHED:
+ pixbuf = gs_plugin_icons_load_cached (plugin, icon, &error_local);
+ break;
+ default:
+ g_set_error (&error_local,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "icon kind '%s' unknown",
+ as_icon_kind_to_string (as_icon_get_kind (icon)));
+ break;
+ }
+ if (pixbuf != NULL) {
+ gs_app_set_pixbuf (app, pixbuf);
+ break;
+ }
+
+ /* we failed, but keep going */
+ g_debug ("failed to load icon for %s: %s",
+ gs_app_get_id (app),
+ error_local->message);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gs_plugin_refine (GsPlugin *plugin,
+ GsAppList *list,
+ GsPluginRefineFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* nothing to do here */
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON) == 0)
+ return TRUE;
+
+ for (guint i = 0; i < gs_app_list_length (list); i++) {
+ GsApp *app = gs_app_list_index (list, i);
+ if (!refine_app (plugin, app, flags, cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}