/* -*- 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 * Copyright (C) 2015 Kalev Lember * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include /* * 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; }