[gnome-software] Allow passing an optional sorting function when searching



commit 969b601e2f709182c62de809b77f0acbb42cf5c1
Author: Richard Hughes <richard hughsie com>
Date:   Fri Apr 21 14:13:09 2017 +0100

    Allow passing an optional sorting function when searching
    
    Depending on what we are doing with the search results sometimes we want to
    prioritize certain types of applications.
    
    When we run the search provider we return up to 20 results to gnome-shell and
    then gnome-shell typically chooses only three to show. This means it's a bit
    more critical how we sort the returned list as we only have three tries to get
    it right.
    
    For the case of the search provider, make sure we show available desktop
    applications first and order them by how well they match the search term.
    
    If the caller doesn't specify a custom sorting function just use the old
    match_value sorting method if a truncation is required.

 lib/gs-cmd.c                    |    1 +
 lib/gs-plugin-loader-sync.c     |    4 +
 lib/gs-plugin-loader-sync.h     |    2 +
 lib/gs-plugin-loader.c          |   15 ++++-
 lib/gs-plugin-loader.h          |    2 +
 plugins/core/gs-self-test.c     |    1 +
 plugins/dummy/gs-self-test.c    |    1 +
 plugins/flatpak/gs-self-test.c  |    6 ++
 plugins/modalias/gs-self-test.c |    1 +
 src/gs-search-page.c            |  137 +++++++++++++++------------------------
 src/gs-shell-search-provider.c  |   45 +++++++++++++
 11 files changed, 129 insertions(+), 86 deletions(-)
---
diff --git a/lib/gs-cmd.c b/lib/gs-cmd.c
index 8e8e19f..2859f82 100644
--- a/lib/gs-cmd.c
+++ b/lib/gs-cmd.c
@@ -308,6 +308,7 @@ main (int argc, char **argv)
                                g_object_unref (list);
                        list = gs_plugin_loader_search (plugin_loader,
                                                        argv[2], 0,
+                                                       NULL, NULL,
                                                        refine_flags,
                                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                                        NULL,
diff --git a/lib/gs-plugin-loader-sync.c b/lib/gs-plugin-loader-sync.c
index d442aab..5b178d0 100644
--- a/lib/gs-plugin-loader-sync.c
+++ b/lib/gs-plugin-loader-sync.c
@@ -93,6 +93,8 @@ GsAppList *
 gs_plugin_loader_search (GsPluginLoader *plugin_loader,
                         const gchar *value,
                         guint max_results,
+                        GsAppListSortFunc sort_func,
+                        gpointer sort_func_data,
                         GsPluginRefineFlags refine_flags,
                         GsPluginFailureFlags failure_flags,
                         GCancellable *cancellable,
@@ -111,6 +113,8 @@ gs_plugin_loader_search (GsPluginLoader *plugin_loader,
        gs_plugin_loader_search_async (plugin_loader,
                                       value,
                                       max_results,
+                                      sort_func,
+                                      sort_func_data,
                                       refine_flags,
                                       failure_flags,
                                       cancellable,
diff --git a/lib/gs-plugin-loader-sync.h b/lib/gs-plugin-loader-sync.h
index e6216ea..61ad653 100644
--- a/lib/gs-plugin-loader-sync.h
+++ b/lib/gs-plugin-loader-sync.h
@@ -36,6 +36,8 @@ GsAppList     *gs_plugin_loader_get_installed         (GsPluginLoader *plugin_loader,
 GsAppList      *gs_plugin_loader_search                (GsPluginLoader *plugin_loader,
                                                         const gchar    *value,
                                                         guint           max_results,
+                                                        GsAppListSortFunc sort_func,
+                                                        gpointer        sort_func_data,
                                                         GsPluginRefineFlags refine_flags,
                                                         GsPluginFailureFlags failure_flags,
                                                         GCancellable   *cancellable,
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 1f759a1..7a93444 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -193,6 +193,8 @@ typedef struct {
        GsPluginAction                   action;
        gboolean                         anything_ran;
        guint                            max_results;
+       GsAppListSortFunc                sort_func;
+       gpointer                         sort_func_data;
 } GsPluginLoaderJob;
 
 static GsPluginLoaderJob *
@@ -2085,10 +2087,14 @@ gs_plugin_loader_search_thread_cb (GTask *task,
                }
        }
 
+       /* fallback to the match value */
+       if (job->sort_func == NULL)
+               job->sort_func = gs_plugin_loader_app_sort_match_value_cb;
+
        /* too many results */
        if (job->max_results > 0 &&
            gs_app_list_length (job->list) > job->max_results) {
-               gs_app_list_sort (job->list, gs_plugin_loader_app_sort_match_value_cb, NULL);
+               gs_app_list_sort (job->list, job->sort_func, job->sort_func_data);
                g_debug ("truncating results to %u from %u",
                         job->max_results, gs_app_list_length (job->list));
                gs_app_list_truncate (job->list, job->max_results);
@@ -2112,6 +2118,9 @@ gs_plugin_loader_search_thread_cb (GTask *task,
        gs_app_list_filter (job->list, gs_plugin_loader_app_set_prio, plugin_loader);
        gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_NONE);
 
+       /* sort these again as the refine may have added useful metadata */
+       gs_app_list_sort (job->list, job->sort_func, job->sort_func_data);
+
        /* too many */
        if (gs_app_list_length (job->list) > 500) {
                g_task_return_new_error (task,
@@ -2150,6 +2159,8 @@ void
 gs_plugin_loader_search_async (GsPluginLoader *plugin_loader,
                               const gchar *value,
                               guint max_results,
+                              GsAppListSortFunc sort_func,
+                              gpointer sort_func_data,
                               GsPluginRefineFlags refine_flags,
                               GsPluginFailureFlags failure_flags,
                               GCancellable *cancellable,
@@ -2168,6 +2179,8 @@ gs_plugin_loader_search_async (GsPluginLoader *plugin_loader,
        job->failure_flags = failure_flags;
        job->list = gs_app_list_new ();
        job->max_results = max_results;
+       job->sort_func = sort_func;
+       job->sort_func_data = sort_func_data;
        job->value = g_strdup (value);
        job->values = as_utils_search_tokenize (job->value);
        job->action = GS_PLUGIN_ACTION_SEARCH;
diff --git a/lib/gs-plugin-loader.h b/lib/gs-plugin-loader.h
index 5db5db1..e51b02e 100644
--- a/lib/gs-plugin-loader.h
+++ b/lib/gs-plugin-loader.h
@@ -137,6 +137,8 @@ GsAppList   *gs_plugin_loader_get_category_apps_finish (GsPluginLoader      *plugin_loa
 void            gs_plugin_loader_search_async          (GsPluginLoader *plugin_loader,
                                                         const gchar    *value,
                                                         guint           max_results,
+                                                        GsAppListSortFunc sort_func,
+                                                        gpointer        sort_func_data,
                                                         GsPluginRefineFlags refine_flags,
                                                         GsPluginFailureFlags failure_flags,
                                                         GCancellable   *cancellable,
diff --git a/plugins/core/gs-self-test.c b/plugins/core/gs-self-test.c
index 8d7d2e5..04e18b2 100644
--- a/plugins/core/gs-self-test.c
+++ b/plugins/core/gs-self-test.c
@@ -105,6 +105,7 @@ gs_plugins_core_search_repo_name_func (GsPluginLoader *plugin_loader)
        /* get search result based on addon keyword */
        list = gs_plugin_loader_search (plugin_loader,
                                        "yellow", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                        NULL,
diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c
index a26fe12..cf2bb0b 100644
--- a/plugins/dummy/gs-self-test.c
+++ b/plugins/dummy/gs-self-test.c
@@ -356,6 +356,7 @@ gs_plugins_dummy_search_func (GsPluginLoader *plugin_loader)
        /* get search result based on addon keyword */
        list = gs_plugin_loader_search (plugin_loader,
                                        "spell", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                        NULL,
diff --git a/plugins/flatpak/gs-self-test.c b/plugins/flatpak/gs-self-test.c
index b1b8804..b2b03f7 100644
--- a/plugins/flatpak/gs-self-test.c
+++ b/plugins/flatpak/gs-self-test.c
@@ -252,6 +252,7 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        /* find available application */
        list = gs_plugin_loader_search (plugin_loader,
                                        "Bingo", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
@@ -469,6 +470,7 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        /* find available application */
        list = gs_plugin_loader_search (plugin_loader,
                                        "Bingo", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                        NULL,
@@ -764,6 +766,7 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        /* find available application */
        list = gs_plugin_loader_search (plugin_loader,
                                        "runtime", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                        NULL,
@@ -853,6 +856,7 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        /* search for the application */
        search1 = gs_plugin_loader_search (plugin_loader,
                                           "chiron", 0,
+                                          NULL, NULL,
                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                           NULL,
@@ -904,6 +908,7 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        /* there should be no matches now */
        search2 = gs_plugin_loader_search (plugin_loader,
                                           "chiron", 0,
+                                          NULL, NULL,
                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                           NULL,
@@ -998,6 +1003,7 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        /* find available application */
        list = gs_plugin_loader_search (plugin_loader,
                                        "Bingo", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                        NULL,
diff --git a/plugins/modalias/gs-self-test.c b/plugins/modalias/gs-self-test.c
index 9304931..1b70317 100644
--- a/plugins/modalias/gs-self-test.c
+++ b/plugins/modalias/gs-self-test.c
@@ -36,6 +36,7 @@ gs_plugins_modalias_func (GsPluginLoader *plugin_loader)
        /* get search result based on addon keyword */
        list = gs_plugin_loader_search (plugin_loader,
                                        "colorhug2", 0,
+                                       NULL, NULL,
                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                        GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
                                        NULL,
diff --git a/src/gs-search-page.c b/src/gs-search-page.c
index b3af0e3..2eb7b81 100644
--- a/src/gs-search-page.c
+++ b/src/gs-search-page.c
@@ -202,6 +202,57 @@ gs_search_page_waiting_show_cb (gpointer user_data)
        return FALSE;
 }
 
+static gchar *
+gs_search_page_get_app_sort_key (GsApp *app)
+{
+       GString *key = g_string_sized_new (64);
+
+       /* sort apps before runtimes and extensions */
+       switch (gs_app_get_kind (app)) {
+       case AS_APP_KIND_DESKTOP:
+       case AS_APP_KIND_SHELL_EXTENSION:
+               g_string_append (key, "9:");
+               break;
+       default:
+               g_string_append (key, "1:");
+               break;
+       }
+
+       /* sort missing codecs before applications */
+       switch (gs_app_get_state (app)) {
+       case AS_APP_STATE_UNAVAILABLE:
+               g_string_append (key, "9:");
+               break;
+       default:
+               g_string_append (key, "1:");
+               break;
+       }
+
+       /* sort by the search key */
+       g_string_append_printf (key, "%05x:", gs_app_get_match_value (app));
+
+       /* sort by rating */
+       g_string_append_printf (key, "%03i:", gs_app_get_rating (app));
+
+       /* sort by kudos */
+       g_string_append_printf (key, "%03u:", gs_app_get_kudos_percentage (app));
+
+       /* tie-break with id */
+       g_string_append (key, gs_app_get_unique_id (app));
+
+       return g_string_free (key, FALSE);
+}
+
+static gboolean
+gs_search_page_sort_cb (GsApp *app1, GsApp *app2, gpointer user_data)
+{
+       g_autofree gchar *key1 = NULL;
+       g_autofree gchar *key2 = NULL;
+       key1 = gs_search_page_get_app_sort_key (app1);
+       key2 = gs_search_page_get_app_sort_key (app2);
+       return g_strcmp0 (key2, key1);
+}
+
 static void
 gs_search_page_load (GsSearchPage *self)
 {
@@ -219,6 +270,7 @@ gs_search_page_load (GsSearchPage *self)
        gs_plugin_loader_search_async (self->plugin_loader,
                                       self->value,
                                       GS_SEARCH_PAGE_MAX_RESULTS,
+                                      gs_search_page_sort_cb, self,
                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
@@ -302,88 +354,6 @@ gs_search_page_switch_to (GsPage *page, gboolean scroll_up)
        gs_search_page_load (self);
 }
 
-/**
- * gs_installed_page_sort_func:
- *
- * Get a sort key to achive this:
- *
- * 1. Application rating
- * 2. Length of the long description
- * 3. Number of screenshots
- * 4. Install date
- * 5. Name
- **/
-static gchar *
-gs_search_page_get_app_sort_key (GsApp *app)
-{
-       GPtrArray *ss;
-       GString *key;
-       const gchar *desc;
-
-       /* sort installed, removing, other */
-       key = g_string_sized_new (64);
-
-       /* sort missing codecs before applications */
-       switch (gs_app_get_state (app)) {
-       case AS_APP_STATE_UNAVAILABLE:
-               g_string_append (key, "9:");
-               break;
-       default:
-               g_string_append (key, "1:");
-               break;
-       }
-
-       /* artificially cut the rating of applications with no description */
-       desc = gs_app_get_description (app);
-       g_string_append_printf (key, "%c:", desc != NULL ? '2' : '1');
-
-       /* sort by the search key */
-       g_string_append_printf (key, "%05x:", gs_app_get_match_value (app));
-
-       /* sort by kudos */
-       g_string_append_printf (key, "%03u:", gs_app_get_kudos_percentage (app));
-
-       /* sort by length of description */
-       g_string_append_printf (key, "%03" G_GSIZE_FORMAT ":",
-                               desc != NULL ? strlen (desc) : 0);
-
-       /* sort by number of screenshots */
-       ss = gs_app_get_screenshots (app);
-       g_string_append_printf (key, "%02u:", ss->len);
-
-       /* sort by install date */
-       g_string_append_printf (key, "%09" G_GUINT64_FORMAT ":",
-                               G_MAXUINT64 - gs_app_get_install_date (app));
-
-       /* finally, sort by short name */
-       g_string_append (key, gs_app_get_name (app));
-
-       return g_string_free (key, FALSE);
-}
-
-static gint
-gs_search_page_sort_func (GtkListBoxRow *a,
-                          GtkListBoxRow *b,
-                          gpointer user_data)
-{
-       GsAppRow *ar;
-       GsAppRow *br;
-       g_autofree gchar *key1 = NULL;
-       g_autofree gchar *key2 = NULL;
-
-       if (!GS_IS_APP_ROW (a))
-               return 1;
-       if (!GS_IS_APP_ROW (b))
-               return -1;
-
-       /* compare the keys according to the algorithm above */
-       ar = GS_APP_ROW (a);
-       br = GS_APP_ROW (b);
-       key1 = gs_search_page_get_app_sort_key (gs_app_row_get_app (GS_APP_ROW (ar)));
-       key2 = gs_search_page_get_app_sort_key (gs_app_row_get_app (GS_APP_ROW (br)));
-       return g_strcmp0 (key2, key1);
-}
-
 static void
 gs_search_page_list_header_func (GtkListBoxRow *row,
                                  GtkListBoxRow *before,
@@ -455,9 +425,6 @@ 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);
-       gtk_list_box_set_sort_func (GTK_LIST_BOX (self->list_box_search),
-                                   gs_search_page_sort_func,
-                                   self, NULL);
        return TRUE;
 }
 
diff --git a/src/gs-shell-search-provider.c b/src/gs-shell-search-provider.c
index ebff5c9..0d4ac26 100644
--- a/src/gs-shell-search-provider.c
+++ b/src/gs-shell-search-provider.c
@@ -103,6 +103,50 @@ search_done_cb (GObject *source,
        g_application_release (g_application_get_default ());
 }
 
+static gchar *
+gs_shell_search_provider_get_app_sort_key (GsApp *app)
+{
+       GString *key = g_string_sized_new (64);
+
+       /* sort available apps before installed ones */
+       switch (gs_app_get_state (app)) {
+       case AS_APP_STATE_AVAILABLE:
+               g_string_append (key, "9:");
+               break;
+       default:
+               g_string_append (key, "1:");
+               break;
+       }
+
+       /* sort apps before runtimes and extensions */
+       switch (gs_app_get_kind (app)) {
+       case AS_APP_KIND_DESKTOP:
+               g_string_append (key, "9:");
+               break;
+       default:
+               g_string_append (key, "1:");
+               break;
+       }
+
+       /* sort by the search key */
+       g_string_append_printf (key, "%05x:", gs_app_get_match_value (app));
+
+       /* tie-break with id */
+       g_string_append (key, gs_app_get_unique_id (app));
+
+       return g_string_free (key, FALSE);
+}
+
+static gboolean
+gs_shell_search_provider_sort_cb (GsApp *app1, GsApp *app2, gpointer user_data)
+{
+       g_autofree gchar *key1 = NULL;
+       g_autofree gchar *key2 = NULL;
+       key1 = gs_shell_search_provider_get_app_sort_key (app1);
+       key2 = gs_shell_search_provider_get_app_sort_key (app2);
+       return g_strcmp0 (key2, key1);
+}
+
 static void
 execute_search (GsShellSearchProvider  *self,
                GDBusMethodInvocation  *invocation,
@@ -134,6 +178,7 @@ execute_search (GsShellSearchProvider  *self,
        gs_plugin_loader_search_async (self->plugin_loader,
                                       string,
                                       GS_SHELL_SEARCH_PROVIDER_MAX_RESULTS,
+                                      gs_shell_search_provider_sort_cb, self,
                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
                                       GS_PLUGIN_FAILURE_FLAGS_NONE,
                                       self->cancellable,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]