diff options
Diffstat (limited to 'plugins/core/gs-plugin-icons.c')
-rw-r--r-- | plugins/core/gs-plugin-icons.c | 348 |
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; +} |