[gnome-software/1243-searching-for-an-app-results-in-a-never-ending-spinner: 65/65] gs-search-page: Provide partial search results as soon as known
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/1243-searching-for-an-app-results-in-a-never-ending-spinner: 65/65] gs-search-page: Provide partial search results as soon as known
- Date: Tue, 8 Jun 2021 17:56:07 +0000 (UTC)
commit e66120cdf8492b49558b6108558ae0e17a087af9
Author: Milan Crha <mcrha redhat com>
Date: Wed May 19 16:01:09 2021 +0200
gs-search-page: Provide partial search results as soon as known
As soon as any plugin returns new search results show them in the search
page, instead of waiting until all the plugins finish the search.
Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1243
lib/gs-plugin-loader.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++
src/gs-search-page.c | 101 ++++++++++++++++++++++++++++++----------
2 files changed, 199 insertions(+), 24 deletions(-)
---
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 034bac69a..659f5d907 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -90,6 +90,7 @@ enum {
SIGNAL_UPDATES_CHANGED,
SIGNAL_RELOAD,
SIGNAL_BASIC_AUTH_START,
+ SIGNAL_SEARCH_MATCH,
SIGNAL_LAST
};
@@ -1108,6 +1109,11 @@ gs_plugin_loader_job_sorted_truncation (GsPluginLoaderHelper *helper)
gs_app_list_truncate (list, max_results);
}
+static gboolean
+gs_plugin_loader_report_partial_search_results (GsPluginLoaderHelper *helper,
+ GCancellable *cancellable,
+ GError **error);
+
static gboolean
gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
GCancellable *cancellable,
@@ -1117,6 +1123,7 @@ gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
#ifdef HAVE_SYSPROF
gint64 begin_time_nsec G_GNUC_UNUSED = SYSPROF_CAPTURE_CURRENT_TIME;
#endif
+ guint last_list_length = 0;
/* run each plugin */
for (guint i = 0; i < plugin_loader->plugins->len; i++) {
@@ -1130,6 +1137,17 @@ gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
cancellable, error)) {
return FALSE;
}
+ if (gs_plugin_job_get_action (helper->plugin_job) == GS_PLUGIN_ACTION_SEARCH) {
+ GsAppList *app_list = gs_plugin_job_get_list (helper->plugin_job);
+ guint list_length = gs_app_list_length (app_list);
+
+ /* Notify with the partial result only if the list changed */
+ if (last_list_length < list_length) {
+ last_list_length = list_length;
+ if (!gs_plugin_loader_report_partial_search_results (helper, cancellable,
error))
+ return FALSE;
+ }
+ }
gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
}
@@ -2810,6 +2828,11 @@ gs_plugin_loader_class_init (GsPluginLoaderClass *klass)
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
0, NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
+ signals [SIGNAL_SEARCH_MATCH] =
+ g_signal_new ("search-match",
+ G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, G_TYPE_STRING, GS_TYPE_APP_LIST);
}
static void
@@ -3478,6 +3501,105 @@ gs_plugin_loader_process_thread_cb (GTask *task,
g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref);
}
+typedef struct _SearchResultsData {
+ GWeakRef plugin_loader_weakref;
+ GsAppList *list;
+ gchar *search;
+} SearchResultsData;
+
+static void
+search_results_data_free (gpointer ptr)
+{
+ SearchResultsData *srd = ptr;
+
+ if (srd) {
+ g_weak_ref_clear (&srd->plugin_loader_weakref);
+ g_clear_object (&srd->list);
+ g_free (srd->search);
+ g_slice_free (SearchResultsData, srd);
+ }
+}
+
+static gboolean
+gs_plugin_loader_search_results_idle_cb (gpointer user_data)
+{
+ SearchResultsData *srd = user_data;
+ g_autoptr(GsPluginLoader) plugin_loader = NULL;
+
+ plugin_loader = g_weak_ref_get (&srd->plugin_loader_weakref);
+ if (plugin_loader)
+ g_signal_emit (plugin_loader, signals[SIGNAL_SEARCH_MATCH], 0, srd->search, srd->list);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gs_plugin_loader_report_partial_search_results (GsPluginLoaderHelper *helper,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginLoader *plugin_loader = helper->plugin_loader;
+ GsAppListFilterFlags dedupe_flags;
+ GsPluginRefineFlags filter_flags;
+ guint max_results;
+ GsAppListSortFunc sort_func;
+ SearchResultsData *srd;
+ g_autoptr(GsAppList) list = NULL;
+
+ list = gs_app_list_copy (gs_plugin_job_get_list (helper->plugin_job));
+
+ /* refine with enough data so that the sort_func in
+ * gs_plugin_loader_job_sorted_truncation() can do what it needs */
+ filter_flags = gs_plugin_job_get_filter_flags (helper->plugin_job);
+ max_results = gs_plugin_job_get_max_results (helper->plugin_job);
+ sort_func = gs_plugin_job_get_sort_func (helper->plugin_job, NULL);
+ if (filter_flags > 0 && max_results > 0 && sort_func != NULL) {
+ g_autoptr(GsPluginLoaderHelper) helper2 = NULL;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+ "list", list,
+ "refine-flags", filter_flags,
+ NULL);
+ helper2 = gs_plugin_loader_helper_new (helper->plugin_loader, plugin_job);
+ helper2->function_name_parent = helper->function_name;
+ g_debug ("running filter flags with early refine");
+ if (!gs_plugin_loader_run_refine_filter (helper2, list,
+ filter_flags,
+ cancellable, error)) {
+ gs_utils_error_convert_gio (error);
+ return FALSE;
+ }
+ }
+
+ /* filter to reduce to a sane set */
+ gs_plugin_loader_job_sorted_truncation (helper);
+
+ gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+ gs_app_list_filter (list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+ gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+ if (gs_plugin_job_get_refine_flags (helper->plugin_job) != 0) {
+ if (!gs_plugin_loader_run_refine (helper, list, cancellable, error)) {
+ return FALSE;
+ }
+ }
+ gs_app_list_filter (list, gs_plugin_loader_app_set_prio, plugin_loader);
+ dedupe_flags = gs_plugin_job_get_dedupe_flags (helper->plugin_job);
+ if (dedupe_flags != GS_APP_LIST_FILTER_FLAG_NONE)
+ gs_app_list_filter_duplicates (list, dedupe_flags);
+
+ /* sort these again as the refine may have added useful metadata */
+ gs_plugin_loader_job_sorted_truncation_again (helper);
+
+ srd = g_slice_new0 (SearchResultsData);
+ g_weak_ref_init (&srd->plugin_loader_weakref, plugin_loader);
+ srd->search = g_strdup (gs_plugin_job_get_search (helper->plugin_job));
+ srd->list = g_steal_pointer (&list);
+
+ g_idle_add_full (G_PRIORITY_DEFAULT, gs_plugin_loader_search_results_idle_cb, srd,
search_results_data_free);
+
+ return TRUE;
+}
+
static void
gs_plugin_loader_process_in_thread_pool_cb (gpointer data,
gpointer user_data)
diff --git a/src/gs-search-page.c b/src/gs-search-page.c
index ec028b30c..2829f4a24 100644
--- a/src/gs-search-page.c
+++ b/src/gs-search-page.c
@@ -36,6 +36,7 @@ struct _GsSearchPage
guint waiting_id;
guint max_results;
gboolean changed;
+ GHashTable *known_results; /* GsApp * as pointer ~> nothing */
GtkWidget *list_box_search;
GtkWidget *scrolledwindow_search;
@@ -86,16 +87,58 @@ gs_search_page_waiting_cancel (GsSearchPage *self)
self->waiting_id = 0;
}
+static void
+gs_search_page_claim_results (GsSearchPage *self,
+ GsAppList *list)
+{
+ guint i;
+
+ /* don't do the delayed spinner */
+ gs_search_page_waiting_cancel (self);
+
+ if (gs_app_list_length (list) == 0)
+ return;
+
+ if (!g_hash_table_size (self->known_results)) {
+ /* remove old entries */
+ gs_container_remove_all (GTK_CONTAINER (self->list_box_search));
+ }
+
+ gs_stop_spinner (GTK_SPINNER (self->spinner_search));
+ gtk_stack_set_visible_child_name (GTK_STACK (self->stack_search), "results");
+ for (i = 0; i < gs_app_list_length (list); i++) {
+ GtkWidget *app_row;
+ GsApp *app;
+
+ app = gs_app_list_index (list, i);
+ if (g_hash_table_contains (self->known_results, app))
+ continue;
+ g_hash_table_add (self->known_results, app);
+ app_row = gs_app_row_new (app);
+ gs_app_row_set_show_rating (GS_APP_ROW (app_row), TRUE);
+ g_signal_connect (app_row, "button-clicked",
+ G_CALLBACK (gs_search_page_app_row_clicked_cb),
+ self);
+ gtk_container_add (GTK_CONTAINER (self->list_box_search), 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);
+
+ if (g_hash_table_size (self->known_results) >= self->max_results)
+ break;
+ }
+}
+
static void
gs_search_page_get_search_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
- guint i;
- GsApp *app;
GsSearchPage *self = GS_SEARCH_PAGE (user_data);
GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
- GtkWidget *app_row;
g_autoptr(GError) error = NULL;
g_autoptr(GsAppList) list = NULL;
@@ -121,26 +164,9 @@ gs_search_page_get_search_cb (GObject *source_object,
return;
}
- /* remove old entries */
- gs_container_remove_all (GTK_CONTAINER (self->list_box_search));
-
- gs_stop_spinner (GTK_SPINNER (self->spinner_search));
- gtk_stack_set_visible_child_name (GTK_STACK (self->stack_search), "results");
- for (i = 0; i < gs_app_list_length (list); i++) {
- app = gs_app_list_index (list, i);
- app_row = gs_app_row_new (app);
- gs_app_row_set_show_rating (GS_APP_ROW (app_row), TRUE);
- g_signal_connect (app_row, "button-clicked",
- G_CALLBACK (gs_search_page_app_row_clicked_cb),
- self);
- gtk_container_add (GTK_CONTAINER (self->list_box_search), 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);
- }
+ /* This will show the precise data */
+ g_hash_table_remove_all (self->known_results);
+ gs_search_page_claim_results (self, list);
/* too many results */
if (gs_app_list_has_flag (list, GS_APP_LIST_FLAG_IS_TRUNCATED)) {
@@ -149,7 +175,7 @@ gs_search_page_get_search_cb (GObject *source_object,
g_autofree gchar *str = NULL;
/* TRANSLATORS: this is when there are too many search results
- * to show in in the search page */
+ * to show in the search page */
str = g_strdup_printf (ngettext("%u more match",
"%u more matches",
gs_app_list_get_size_peak (list) - gs_app_list_length (list)),
@@ -258,6 +284,7 @@ gs_search_page_load (GsSearchPage *self)
/* search for apps */
gs_search_page_waiting_cancel (self);
self->waiting_id = g_timeout_add (250, gs_search_page_waiting_show_cb, self);
+ g_hash_table_remove_all (self->known_results);
plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
"search", self->value,
"max-results", self->max_results,
@@ -414,6 +441,26 @@ gs_search_page_app_removed (GsPage *page, GsApp *app)
gs_search_page_reload (page);
}
+static void
+gs_search_page_search_match_cb (GsPluginLoader *plugin_loader,
+ const gchar *search_term,
+ GsAppList *list,
+ GsSearchPage *self)
+{
+ g_return_if_fail (GS_IS_SEARCH_PAGE (self));
+ g_return_if_fail (GS_IS_APP_LIST (list));
+
+ if (g_strcmp0 (search_term, self->value) != 0)
+ return;
+
+ gs_app_list_sort (list, gs_search_page_sort_cb, self);
+
+ if (gs_app_list_length (list) > self->max_results)
+ gs_app_list_truncate (list, self->max_results);
+
+ gs_search_page_claim_results (self, list);
+}
+
static gboolean
gs_search_page_setup (GsPage *page,
GsShell *shell,
@@ -440,6 +487,10 @@ gs_search_page_setup (GsPage *page,
gtk_list_box_set_header_func (GTK_LIST_BOX (self->list_box_search),
gs_search_page_list_header_func,
self, NULL);
+
+ g_signal_connect_object (plugin_loader, "search-match",
+ G_CALLBACK (gs_search_page_search_match_cb), self, 0);
+
return TRUE;
}
@@ -520,6 +571,7 @@ gs_search_page_finalize (GObject *object)
{
GsSearchPage *self = GS_SEARCH_PAGE (object);
+ g_hash_table_destroy (self->known_results);
g_free (self->appid_to_show);
g_free (self->value);
@@ -573,6 +625,7 @@ gs_search_page_init (GsSearchPage *self)
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->known_results = g_hash_table_new (g_direct_hash, g_direct_equal);
self->max_results = GS_SEARCH_PAGE_MAX_RESULTS;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]