diff options
Diffstat (limited to 'src/gs-extras-page.c')
-rw-r--r-- | src/gs-extras-page.c | 1220 |
1 files changed, 1220 insertions, 0 deletions
diff --git a/src/gs-extras-page.c b/src/gs-extras-page.c new file mode 100644 index 0000000..9b9aeec --- /dev/null +++ b/src/gs-extras-page.c @@ -0,0 +1,1220 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2013-2017 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2015-2018 Kalev Lember <klember@redhat.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include "gs-extras-page.h" + +#include "gs-app-row.h" +#include "gs-language.h" +#include "gs-shell.h" +#include "gs-common.h" +#include "gs-utils.h" +#include "gs-vendor.h" + +#include <glib/gi18n.h> + +typedef enum { + GS_EXTRAS_PAGE_STATE_LOADING, + GS_EXTRAS_PAGE_STATE_READY, + GS_EXTRAS_PAGE_STATE_NO_RESULTS, + GS_EXTRAS_PAGE_STATE_FAILED +} GsExtrasPageState; + +typedef struct { + gchar *title; + gchar *search; + gchar *search_filename; + gchar *package_filename; + gchar *url_not_found; + GsExtrasPage *self; +} SearchData; + +struct _GsExtrasPage +{ + GsPage parent_instance; + + GsPluginLoader *plugin_loader; + GtkBuilder *builder; + GCancellable *search_cancellable; + GsShell *shell; + GsExtrasPageState state; + GtkSizeGroup *sizegroup_image; + GtkSizeGroup *sizegroup_name; + GtkSizeGroup *sizegroup_desc; + GtkSizeGroup *sizegroup_button; + GPtrArray *array_search_data; + GsExtrasPageMode mode; + GsLanguage *language; + GsVendor *vendor; + guint pending_search_cnt; + + GtkWidget *label_failed; + GtkWidget *label_no_results; + GtkWidget *list_box_results; + GtkWidget *scrolledwindow; + GtkWidget *spinner; + GtkWidget *stack; +}; + +G_DEFINE_TYPE (GsExtrasPage, gs_extras_page, GS_TYPE_PAGE) + +static void +search_data_free (SearchData *search_data) +{ + if (search_data->self != NULL) + g_object_unref (search_data->self); + g_free (search_data->title); + g_free (search_data->search); + g_free (search_data->search_filename); + g_free (search_data->package_filename); + g_free (search_data->url_not_found); + g_slice_free (SearchData, search_data); +} + +static GsExtrasPageMode +gs_extras_page_mode_from_string (const gchar *str) +{ + if (g_strcmp0 (str, "install-package-files") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_FILES; + if (g_strcmp0 (str, "install-provide-files") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_PROVIDE_FILES; + if (g_strcmp0 (str, "install-package-names") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_NAMES; + if (g_strcmp0 (str, "install-mime-types") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_MIME_TYPES; + if (g_strcmp0 (str, "install-fontconfig-resources") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_FONTCONFIG_RESOURCES; + if (g_strcmp0 (str, "install-gstreamer-resources") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_GSTREAMER_RESOURCES; + if (g_strcmp0 (str, "install-plasma-resources") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_PLASMA_RESOURCES; + if (g_strcmp0 (str, "install-printer-drivers") == 0) + return GS_EXTRAS_PAGE_MODE_INSTALL_PRINTER_DRIVERS; + + g_assert_not_reached (); +} + +const gchar * +gs_extras_page_mode_to_string (GsExtrasPageMode mode) +{ + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_FILES) + return "install-package-files"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_PROVIDE_FILES) + return "install-provide-files"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_NAMES) + return "install-package-names"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_MIME_TYPES) + return "install-mime-types"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_FONTCONFIG_RESOURCES) + return "install-fontconfig-resources"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_GSTREAMER_RESOURCES) + return "install-gstreamer-resources"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_PLASMA_RESOURCES) + return "install-plasma-resources"; + if (mode == GS_EXTRAS_PAGE_MODE_INSTALL_PRINTER_DRIVERS) + return "install-printer-drivers"; + + g_assert_not_reached (); +} + +static gchar * +build_comma_separated_list (gchar **items) +{ + guint len; + + len = g_strv_length (items); + if (len == 2) { + /* TRANSLATORS: separator for a list of items */ + return g_strjoinv (_(" and "), items); + } else { + /* TRANSLATORS: separator for a list of items */ + return g_strjoinv (_(", "), items); + } +} + +static gchar * +build_title (GsExtrasPage *self) +{ + guint i; + g_autofree gchar *titles = NULL; + g_autoptr(GPtrArray) title_array = NULL; + + title_array = g_ptr_array_new (); + for (i = 0; i < self->array_search_data->len; i++) { + SearchData *search_data; + + search_data = g_ptr_array_index (self->array_search_data, i); + g_ptr_array_add (title_array, search_data->title); + } + g_ptr_array_add (title_array, NULL); + + titles = build_comma_separated_list ((gchar **) title_array->pdata); + + switch (self->mode) { + case GS_EXTRAS_PAGE_MODE_INSTALL_FONTCONFIG_RESOURCES: + /* TRANSLATORS: Application window title for fonts installation. + %s will be replaced by name of the script we're searching for. */ + return g_strdup_printf (ngettext ("Available fonts for the %s script", + "Available fonts for the %s scripts", + self->array_search_data->len), + titles); + break; + default: + /* TRANSLATORS: Application window title for codec installation. + %s will be replaced by actual codec name(s) */ + return g_strdup_printf (ngettext ("Available software for %s", + "Available software for %s", + self->array_search_data->len), + titles); + break; + } +} + +static void +gs_extras_page_update_ui_state (GsExtrasPage *self) +{ + GtkWidget *widget; + g_autofree gchar *title = NULL; + + if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_EXTRAS) + return; + + /* main spinner */ + switch (self->state) { + case GS_EXTRAS_PAGE_STATE_LOADING: + gs_start_spinner (GTK_SPINNER (self->spinner)); + break; + case GS_EXTRAS_PAGE_STATE_READY: + case GS_EXTRAS_PAGE_STATE_NO_RESULTS: + case GS_EXTRAS_PAGE_STATE_FAILED: + gs_stop_spinner (GTK_SPINNER (self->spinner)); + break; + default: + g_assert_not_reached (); + break; + } + + /* headerbar title */ + widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "application_details_header")); + switch (self->state) { + case GS_EXTRAS_PAGE_STATE_LOADING: + case GS_EXTRAS_PAGE_STATE_READY: + title = build_title (self); + gtk_label_set_label (GTK_LABEL (widget), title); + break; + case GS_EXTRAS_PAGE_STATE_NO_RESULTS: + case GS_EXTRAS_PAGE_STATE_FAILED: + gtk_label_set_label (GTK_LABEL (widget), _("Unable to Find Requested Software")); + break; + default: + g_assert_not_reached (); + break; + } + + /* stack */ + switch (self->state) { + case GS_EXTRAS_PAGE_STATE_LOADING: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "spinner"); + break; + case GS_EXTRAS_PAGE_STATE_READY: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "results"); + break; + case GS_EXTRAS_PAGE_STATE_NO_RESULTS: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "no-results"); + break; + case GS_EXTRAS_PAGE_STATE_FAILED: + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "failed"); + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +gs_extras_page_set_state (GsExtrasPage *self, + GsExtrasPageState state) +{ + self->state = state; + gs_extras_page_update_ui_state (self); +} + +static void +app_row_button_clicked_cb (GsAppRow *app_row, + GsExtrasPage *self) +{ + GsApp *app = gs_app_row_get_app (app_row); + + if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE && + gs_app_get_url (app, AS_URL_KIND_MISSING) != NULL) { + gs_shell_show_uri (self->shell, + gs_app_get_url (app, AS_URL_KIND_MISSING)); + } else if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE || + gs_app_get_state (app) == AS_APP_STATE_AVAILABLE_LOCAL || + gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) { + gs_page_install_app (GS_PAGE (self), app, GS_SHELL_INTERACTION_FULL, + self->search_cancellable); + } else if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED) { + gs_page_remove_app (GS_PAGE (self), app, self->search_cancellable); + } else { + g_critical ("extras: app in unexpected state %u", gs_app_get_state (app)); + } +} + +static void +gs_extras_page_add_app (GsExtrasPage *self, GsApp *app, GsAppList *list, SearchData *search_data) +{ + GtkWidget *app_row; + g_autoptr(GList) existing_apps = NULL; + + /* Don't add same app twice */ + existing_apps = gtk_container_get_children (GTK_CONTAINER (self->list_box_results)); + for (GList *l = existing_apps; l != NULL; l = l->next) { + GsApp *existing_app; + + existing_app = gs_app_row_get_app (GS_APP_ROW (l->data)); + if (app == existing_app) + gtk_container_remove (GTK_CONTAINER (self->list_box_results), + GTK_WIDGET (l->data)); + } + + app_row = gs_app_row_new (app); + gs_app_row_set_colorful (GS_APP_ROW (app_row), TRUE); + gs_app_row_set_show_buttons (GS_APP_ROW (app_row), TRUE); + + g_object_set_data_full (G_OBJECT (app_row), "missing-title", g_strdup (search_data->title), g_free); + + g_signal_connect (app_row, "button-clicked", + G_CALLBACK (app_row_button_clicked_cb), + self); + + gtk_container_add (GTK_CONTAINER (self->list_box_results), app_row); + gs_app_row_set_size_groups (GS_APP_ROW (app_row), + self->sizegroup_image, + self->sizegroup_name, + self->sizegroup_desc, + self->sizegroup_button); + gtk_widget_show (app_row); +} + +static GsApp * +create_missing_app (SearchData *search_data) +{ + GsExtrasPage *self = search_data->self; + GsApp *app; + GString *summary_missing; + g_autofree gchar *name = NULL; + g_autofree gchar *url = NULL; + + app = gs_app_new ("missing-codec"); + + /* TRANSLATORS: This string is used for codecs that weren't found */ + name = g_strdup_printf (_("%s not found"), search_data->title); + gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, name); + + /* TRANSLATORS: hyperlink title */ + url = g_strdup_printf ("<a href=\"%s\">%s</a>", search_data->url_not_found, _("on the website")); + + summary_missing = g_string_new (""); + switch (self->mode) { + case GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_FILES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No applications are available that provide the file %s."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get missing applications " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PROVIDE_FILES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No applications are available for %s support."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get missing applications " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_NAMES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("%s is not available."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get missing applications " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_MIME_TYPES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No applications are available for %s support."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get an application that can support this format " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_FONTCONFIG_RESOURCES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No fonts are available for the %s script support."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get additional fonts " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_GSTREAMER_RESOURCES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No addon codecs are available for the %s format."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get a codec that can play this format " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PLASMA_RESOURCES: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No Plasma resources are available for %s support."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get additional Plasma resources " + "might be found %s."), search_data->title, url); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PRINTER_DRIVERS: + /* TRANSLATORS: this is when we know about an application or + * addon, but it can't be listed for some reason */ + g_string_append_printf (summary_missing, _("No printer drivers are available for %s."), search_data->title); + g_string_append (summary_missing, "\n"); + /* TRANSLATORS: first %s is the codec name, and second %s is a + * hyperlink with the "on the website" text */ + g_string_append_printf (summary_missing, _("Information about %s, as well as options " + "for how to get a driver that supports this printer " + "might be found %s."), search_data->title, url); + + break; + default: + g_assert_not_reached (); + break; + } + gs_app_set_summary_missing (app, g_string_free (summary_missing, FALSE)); + + gs_app_set_kind (app, AS_APP_KIND_GENERIC); + gs_app_set_state (app, AS_APP_STATE_UNAVAILABLE); + gs_app_set_url (app, AS_URL_KIND_MISSING, search_data->url_not_found); + + return app; +} + +static gchar * +build_no_results_label (GsExtrasPage *self) +{ + GsApp *app = NULL; + guint num; + g_autofree gchar *codec_titles = NULL; + g_autofree gchar *url = NULL; + g_autoptr(GList) list = NULL; + g_autoptr(GPtrArray) array = NULL; + + list = gtk_container_get_children (GTK_CONTAINER (self->list_box_results)); + num = g_list_length (list); + + g_assert (num > 0); + + array = g_ptr_array_new (); + for (GList *l = list; l != NULL; l = l->next) { + app = gs_app_row_get_app (GS_APP_ROW (l->data)); + g_ptr_array_add (array, + g_object_get_data (G_OBJECT (l->data), "missing-title")); + } + g_ptr_array_add (array, NULL); + + url = g_strdup_printf ("<a href=\"%s\">%s</a>", + gs_app_get_url (app, AS_URL_KIND_MISSING), + /* TRANSLATORS: hyperlink title */ + _("this website")); + + codec_titles = build_comma_separated_list ((gchar **) array->pdata); + /* TRANSLATORS: no codecs were found. First %s will be replaced by actual codec name(s), second %s is a link titled "this website" */ + return g_strdup_printf (ngettext ("Unfortunately, the %s you were searching for could not be found. Please see %s for more information.", + "Unfortunately, the %s you were searching for could not be found. Please see %s for more information.", + num), + codec_titles, + url); +} + +static void +show_search_results (GsExtrasPage *self) +{ + GsApp *app; + guint n_children; + guint n_missing; + g_autoptr(GList) list = NULL; + + list = gtk_container_get_children (GTK_CONTAINER (self->list_box_results)); + n_children = g_list_length (list); + + /* count the number of rows with missing codecs */ + n_missing = 0; + for (GList *l = list; l != NULL; l = l->next) { + app = gs_app_row_get_app (GS_APP_ROW (l->data)); + if (g_strcmp0 (gs_app_get_id (app), "missing-codec") == 0) { + n_missing++; + } + } + + if (n_children == 0 || n_children == n_missing) { + g_autofree gchar *str = NULL; + + /* no results */ + g_debug ("extras: failed to find any results, %u", n_missing); + str = build_no_results_label (self); + gtk_label_set_label (GTK_LABEL (self->label_no_results), str); + gs_extras_page_set_state (self, GS_EXTRAS_PAGE_STATE_NO_RESULTS); + } else if (n_children == 1) { + /* switch directly to details view */ + g_debug ("extras: found one result, showing in details view"); + g_assert (list != NULL); + app = gs_app_row_get_app (GS_APP_ROW (list->data)); + gs_shell_change_mode (self->shell, GS_SHELL_MODE_DETAILS, app, TRUE); + } else { + /* show what we got */ + g_debug ("extras: got %u search results, showing", n_children); + gs_extras_page_set_state (self, GS_EXTRAS_PAGE_STATE_READY); + } +} + +static void +search_files_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SearchData *search_data = (SearchData *) user_data; + GsExtrasPage *self = search_data->self; + g_autoptr(GsAppList) list = NULL; + guint i; + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); + g_autoptr(GError) error = NULL; + + list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); + if (list == NULL) { + g_autofree gchar *str = NULL; + if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) { + g_debug ("extras: search files cancelled"); + return; + } + g_warning ("failed to find any search results: %s", error->message); + str = g_strdup_printf ("%s: %s", _("Failed to find any search results"), error->message); + gtk_label_set_label (GTK_LABEL (self->label_failed), str); + gs_extras_page_set_state (self, GS_EXTRAS_PAGE_STATE_FAILED); + return; + } + + /* add missing item */ + if (gs_app_list_length (list) == 0) { + g_autoptr(GsApp) app = NULL; + g_debug ("extras: no search result for %s, showing as missing", + search_data->title); + app = create_missing_app (search_data); + gs_app_list_add (list, app); + } + + for (i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); + + g_debug ("%s\n\n", gs_app_to_string (app)); + gs_extras_page_add_app (self, app, list, search_data); + } + + self->pending_search_cnt--; + + /* have all searches finished? */ + if (self->pending_search_cnt == 0) + show_search_results (self); +} + +static void +file_to_app_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SearchData *search_data = (SearchData *) user_data; + GsExtrasPage *self = search_data->self; + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); + g_autoptr(GError) error = NULL; + g_autoptr(GsApp) app = NULL; + g_autoptr(GsAppList) list = NULL; + + list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); + if (list == NULL) { + if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) { + g_debug ("extras: search what provides cancelled"); + return; + } + if (g_error_matches (error, + GS_PLUGIN_ERROR, + GS_PLUGIN_ERROR_FAILED)) { + g_debug ("extras: no search result for %s, showing as missing", search_data->title); + app = create_missing_app (search_data); + } else { + g_autofree gchar *str = NULL; + + g_warning ("failed to find any search results: %s", error->message); + str = g_strdup_printf ("%s: %s", _("Failed to find any search results"), error->message); + gtk_label_set_label (GTK_LABEL (self->label_failed), str); + gs_extras_page_set_state (self, GS_EXTRAS_PAGE_STATE_FAILED); + return; + } + } else { + app = g_object_ref (gs_app_list_index (list, 0)); + } + + g_debug ("%s\n\n", gs_app_to_string (app)); + gs_extras_page_add_app (self, app, list, search_data); + + self->pending_search_cnt--; + + /* have all searches finished? */ + if (self->pending_search_cnt == 0) + show_search_results (self); +} + +static void +get_search_what_provides_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + SearchData *search_data = (SearchData *) user_data; + GsExtrasPage *self = search_data->self; + g_autoptr(GsAppList) list = NULL; + guint i; + GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object); + g_autoptr(GError) error = NULL; + + list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error); + if (list == NULL) { + g_autofree gchar *str = NULL; + if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) { + g_debug ("extras: search what provides cancelled"); + return; + } + g_warning ("failed to find any search results: %s", error->message); + str = g_strdup_printf ("%s: %s", _("Failed to find any search results"), error->message); + gtk_label_set_label (GTK_LABEL (self->label_failed), str); + gs_extras_page_set_state (self, GS_EXTRAS_PAGE_STATE_FAILED); + return; + } + + /* add missing item */ + if (gs_app_list_length (list) == 0) { + g_autoptr(GsApp) app = NULL; + g_debug ("extras: no search result for %s, showing as missing", + search_data->title); + app = create_missing_app (search_data); + gs_app_list_add (list, app); + } + + for (i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); + + g_debug ("%s\n\n", gs_app_to_string (app)); + gs_extras_page_add_app (self, app, list, search_data); + } + + self->pending_search_cnt--; + + /* have all searches finished? */ + if (self->pending_search_cnt == 0) + show_search_results (self); +} + +static void +gs_extras_page_load (GsExtrasPage *self, GPtrArray *array_search_data) +{ + guint i; + + /* cancel any pending searches */ + g_cancellable_cancel (self->search_cancellable); + g_clear_object (&self->search_cancellable); + self->search_cancellable = g_cancellable_new (); + + if (array_search_data != NULL) { + if (self->array_search_data != NULL) + g_ptr_array_unref (self->array_search_data); + self->array_search_data = g_ptr_array_ref (array_search_data); + } + + self->pending_search_cnt = 0; + + /* remove old entries */ + gs_container_remove_all (GTK_CONTAINER (self->list_box_results)); + + /* set state as loading */ + self->state = GS_EXTRAS_PAGE_STATE_LOADING; + + /* start new searches, separate one for each codec */ + for (i = 0; i < self->array_search_data->len; i++) { + GsPluginRefineFlags refine_flags; + SearchData *search_data; + + refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE | + GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING | + GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES; + + search_data = g_ptr_array_index (self->array_search_data, i); + if (search_data->search_filename != NULL) { + g_autoptr(GsPluginJob) plugin_job = NULL; + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH_FILES, + "search", search_data->search_filename, + "refine-flags", refine_flags, + NULL); + g_debug ("searching filename: '%s'", search_data->search_filename); + gs_plugin_loader_job_process_async (self->plugin_loader, + plugin_job, + self->search_cancellable, + search_files_cb, + search_data); + } else if (search_data->package_filename != NULL) { + g_autoptr (GFile) file = NULL; + g_autoptr(GsPluginJob) plugin_job = NULL; + file = g_file_new_for_path (search_data->package_filename); + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP, + "file", file, + "refine-flags", refine_flags, + NULL); + g_debug ("resolving filename to app: '%s'", search_data->package_filename); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->search_cancellable, + file_to_app_cb, + search_data); + } else { + g_autoptr(GsPluginJob) plugin_job = NULL; + g_debug ("searching what provides: '%s'", search_data->search); + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH_PROVIDES, + "search", search_data->search, + "refine-flags", refine_flags, + NULL); + gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job, + self->search_cancellable, + get_search_what_provides_cb, + search_data); + } + self->pending_search_cnt++; + } +} + +static void +gs_extras_page_reload (GsPage *page) +{ + GsExtrasPage *self = GS_EXTRAS_PAGE (page); + if (self->array_search_data != NULL) + gs_extras_page_load (self, NULL); +} + +static void +gs_extras_page_search_package_files (GsExtrasPage *self, gchar **files) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; files[i] != NULL; i++) { + SearchData *search_data; + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup (files[i]); + search_data->package_filename = g_strdup (files[i]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_DEFAULT); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static void +gs_extras_page_search_provide_files (GsExtrasPage *self, gchar **files) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; files[i] != NULL; i++) { + SearchData *search_data; + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup (files[i]); + search_data->search_filename = g_strdup (files[i]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_DEFAULT); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static void +gs_extras_page_search_package_names (GsExtrasPage *self, gchar **package_names) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; package_names[i] != NULL; i++) { + SearchData *search_data; + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup (package_names[i]); + search_data->search = g_strdup (package_names[i]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_DEFAULT); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static void +gs_extras_page_search_mime_types (GsExtrasPage *self, gchar **mime_types) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; mime_types[i] != NULL; i++) { + SearchData *search_data; + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup_printf (_("%s file format"), mime_types[i]); + search_data->search = g_strdup (mime_types[i]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_MIME); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static gchar * +font_tag_to_lang (const gchar *tag) +{ + if (g_str_has_prefix (tag, ":lang=")) + return g_strdup (tag + 6); + + return NULL; +} + +static gchar * +gs_extras_page_font_tag_to_localised_name (GsExtrasPage *self, const gchar *tag) +{ + gchar *name; + g_autofree gchar *lang = NULL; + g_autofree gchar *language = NULL; + + /* use fontconfig to get the language code */ + lang = font_tag_to_lang (tag); + if (lang == NULL) { + g_warning ("Could not parse language tag '%s'", tag); + return NULL; + } + + /* convert to localisable name */ + language = gs_language_iso639_to_language (self->language, lang); + if (language == NULL) { + g_warning ("Could not match language code '%s' to an ISO639 language", lang); + return NULL; + } + + /* get translation, or return untranslated string */ + name = g_strdup (dgettext("iso_639", language)); + if (name == NULL) + name = g_strdup (language); + + return name; +} + +static void +gs_extras_page_search_fontconfig_resources (GsExtrasPage *self, gchar **resources) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; resources[i] != NULL; i++) { + SearchData *search_data; + + search_data = g_slice_new0 (SearchData); + search_data->title = gs_extras_page_font_tag_to_localised_name (self, resources[i]); + search_data->search = g_strdup (resources[i]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_FONT); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static void +gs_extras_page_search_gstreamer_resources (GsExtrasPage *self, gchar **resources) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; resources[i] != NULL; i++) { + SearchData *search_data; + g_auto(GStrv) parts = NULL; + + parts = g_strsplit (resources[i], "|", 2); + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup (parts[0]); + search_data->search = g_strdup (parts[1]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_CODEC); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static void +gs_extras_page_search_plasma_resources (GsExtrasPage *self, gchar **resources) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i; + + for (i = 0; resources[i] != NULL; i++) { + SearchData *search_data; + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup (resources[i]); + search_data->search = g_strdup (resources[i]); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_DEFAULT); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +static void +gs_extras_page_search_printer_drivers (GsExtrasPage *self, gchar **device_ids) +{ + g_autoptr(GPtrArray) array_search_data = g_ptr_array_new_with_free_func ((GDestroyNotify) search_data_free); + guint i, j; + guint len; + + len = g_strv_length (device_ids); + if (len > 1) + /* hardcode for now as we only support one at a time */ + len = 1; + + /* make a list of provides tags */ + for (i = 0; i < len; i++) { + SearchData *search_data; + gchar *p; + guint n_fields; + g_autofree gchar *tag = NULL; + g_autofree gchar *mfg = NULL; + g_autofree gchar *mdl = NULL; + g_auto(GStrv) fields = NULL; + + fields = g_strsplit (device_ids[i], ";", 0); + n_fields = g_strv_length (fields); + mfg = mdl = NULL; + for (j = 0; j < n_fields && (!mfg || !mdl); j++) { + if (g_str_has_prefix (fields[j], "MFG:")) + mfg = g_strdup (fields[j] + 4); + else if (g_str_has_prefix (fields[j], "MDL:")) + mdl = g_strdup (fields[j] + 4); + } + + if (!mfg || !mdl) { + g_warning("invalid line '%s', missing field", + device_ids[i]); + continue; + } + + tag = g_strdup_printf ("%s;%s;", mfg, mdl); + + /* Replace spaces with underscores */ + for (p = tag; *p != '\0'; p++) + if (*p == ' ') + *p = '_'; + + search_data = g_slice_new0 (SearchData); + search_data->title = g_strdup_printf ("%s %s", mfg, mdl); + search_data->search = g_ascii_strdown (tag, -1); + search_data->url_not_found = gs_vendor_get_not_found_url (self->vendor, GS_VENDOR_URL_TYPE_HARDWARE); + search_data->self = g_object_ref (self); + g_ptr_array_add (array_search_data, search_data); + } + + gs_extras_page_load (self, array_search_data); +} + +void +gs_extras_page_search (GsExtrasPage *self, + const gchar *mode_str, + gchar **resources) +{ + self->mode = gs_extras_page_mode_from_string (mode_str); + switch (self->mode) { + case GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_FILES: + gs_extras_page_search_package_files (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PROVIDE_FILES: + gs_extras_page_search_provide_files (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PACKAGE_NAMES: + gs_extras_page_search_package_names (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_MIME_TYPES: + gs_extras_page_search_mime_types (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_FONTCONFIG_RESOURCES: + gs_extras_page_search_fontconfig_resources (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_GSTREAMER_RESOURCES: + gs_extras_page_search_gstreamer_resources (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PLASMA_RESOURCES: + gs_extras_page_search_plasma_resources (self, resources); + break; + case GS_EXTRAS_PAGE_MODE_INSTALL_PRINTER_DRIVERS: + gs_extras_page_search_printer_drivers (self, resources); + break; + default: + g_assert_not_reached (); + break; + } +} + +static void +gs_extras_page_switch_to (GsPage *page, + gboolean scroll_up) +{ + GsExtrasPage *self = GS_EXTRAS_PAGE (page); + GtkWidget *widget; + + if (gs_shell_get_mode (self->shell) != GS_SHELL_MODE_EXTRAS) { + g_warning ("Called switch_to(codecs) when in mode %s", + gs_shell_get_mode_string (self->shell)); + return; + } + + widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "application_details_header")); + gtk_widget_show (widget); + + if (scroll_up) { + GtkAdjustment *adj; + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolledwindow)); + gtk_adjustment_set_value (adj, gtk_adjustment_get_lower (adj)); + } + + gs_extras_page_update_ui_state (self); +} + +static void +row_activated_cb (GtkListBox *list_box, + GtkListBoxRow *row, + GsExtrasPage *self) +{ + GsApp *app; + + app = gs_app_row_get_app (GS_APP_ROW (row)); + + if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE && + gs_app_get_url (app, AS_URL_KIND_MISSING) != NULL) { + gs_shell_show_uri (self->shell, + gs_app_get_url (app, AS_URL_KIND_MISSING)); + } else { + gs_shell_show_app (self->shell, app); + } +} + +static gchar * +get_app_sort_key (GsApp *app) +{ + GString *key = NULL; + g_autofree gchar *sort_name = NULL; + + key = g_string_sized_new (64); + + /* sort missing applications as last */ + switch (gs_app_get_state (app)) { + case AS_APP_STATE_UNAVAILABLE: + g_string_append (key, "9:"); + break; + default: + g_string_append (key, "1:"); + break; + } + + /* finally, sort by short name */ + if (gs_app_get_name (app) != NULL) { + sort_name = gs_utils_sort_key (gs_app_get_name (app)); + g_string_append (key, sort_name); + } + + return g_string_free (key, FALSE); +} + +static gint +list_sort_func (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer user_data) +{ + GsApp *a1 = gs_app_row_get_app (GS_APP_ROW (a)); + GsApp *a2 = gs_app_row_get_app (GS_APP_ROW (b)); + g_autofree gchar *key1 = get_app_sort_key (a1); + g_autofree gchar *key2 = get_app_sort_key (a2); + + /* compare the keys according to the algorithm above */ + return g_strcmp0 (key1, key2); +} + +static void +list_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + gpointer user_data) +{ + GtkWidget *header; + + /* first entry */ + header = gtk_list_box_row_get_header (row); + if (before == NULL) { + gtk_list_box_row_set_header (row, NULL); + return; + } + + /* already set */ + if (header != NULL) + return; + + /* set new */ + header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header (row, header); +} + +static gboolean +gs_extras_page_setup (GsPage *page, + GsShell *shell, + GsPluginLoader *plugin_loader, + GtkBuilder *builder, + GCancellable *cancellable, + GError **error) +{ + GsExtrasPage *self = GS_EXTRAS_PAGE (page); + + g_return_val_if_fail (GS_IS_EXTRAS_PAGE (self), TRUE); + + self->shell = shell; + + self->plugin_loader = g_object_ref (plugin_loader); + self->builder = g_object_ref (builder); + + g_signal_connect (self->list_box_results, "row-activated", + G_CALLBACK (row_activated_cb), self); + gtk_list_box_set_header_func (GTK_LIST_BOX (self->list_box_results), + list_header_func, + self, NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_results), + list_sort_func, + self, NULL); + return TRUE; +} + +static void +gs_extras_page_dispose (GObject *object) +{ + GsExtrasPage *self = GS_EXTRAS_PAGE (object); + + g_cancellable_cancel (self->search_cancellable); + g_clear_object (&self->search_cancellable); + + g_clear_object (&self->sizegroup_image); + g_clear_object (&self->sizegroup_name); + g_clear_object (&self->sizegroup_desc); + g_clear_object (&self->sizegroup_button); + g_clear_object (&self->language); + g_clear_object (&self->vendor); + g_clear_object (&self->builder); + g_clear_object (&self->plugin_loader); + + g_clear_pointer (&self->array_search_data, g_ptr_array_unref); + + G_OBJECT_CLASS (gs_extras_page_parent_class)->dispose (object); +} + +static void +gs_extras_page_init (GsExtrasPage *self) +{ + g_autoptr(GError) error = NULL; + + gtk_widget_init_template (GTK_WIDGET (self)); + + self->state = GS_EXTRAS_PAGE_STATE_LOADING; + self->sizegroup_image = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup_name = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup_desc = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->sizegroup_button = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + self->vendor = gs_vendor_new (); + + /* map ISO639 to language names */ + self->language = gs_language_new (); + gs_language_populate (self->language, &error); + if (error != NULL) + g_error ("Failed to map ISO639 to language names: %s", error->message); +} + +static void +gs_extras_page_class_init (GsExtrasPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsPageClass *page_class = GS_PAGE_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gs_extras_page_dispose; + page_class->switch_to = gs_extras_page_switch_to; + page_class->reload = gs_extras_page_reload; + page_class->setup = gs_extras_page_setup; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-extras-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, GsExtrasPage, label_failed); + gtk_widget_class_bind_template_child (widget_class, GsExtrasPage, label_no_results); + gtk_widget_class_bind_template_child (widget_class, GsExtrasPage, list_box_results); + gtk_widget_class_bind_template_child (widget_class, GsExtrasPage, scrolledwindow); + gtk_widget_class_bind_template_child (widget_class, GsExtrasPage, spinner); + gtk_widget_class_bind_template_child (widget_class, GsExtrasPage, stack); +} + +GsExtrasPage * +gs_extras_page_new (void) +{ + GsExtrasPage *self; + self = g_object_new (GS_TYPE_EXTRAS_PAGE, NULL); + return GS_EXTRAS_PAGE (self); +} |