[gnome-software] search: Try to deduplicate the same logical app



commit 2e87612c163b36309beb8103196c616c1b46efd6
Author: Iain Lane <iain orangesquash org uk>
Date:   Mon May 8 21:04:08 2017 +0100

    search: Try to deduplicate the same logical app
    
    If there are several results for the same app from the 'same place' -
    for example a distro package has an update the user hasn't yet installed
    - only show one result in the search results list.
    
    Do this by filtering on id/source/version, which should be stable across
    updates ('version' is the installed version if the app is installed
    [regardless of available updates] or the candidate version if not).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=782340

 lib/gs-app-list-private.h |   13 ++++++---
 lib/gs-app-list.c         |   62 ++++++++++++++++++++++++++++----------------
 lib/gs-plugin-loader.c    |   17 +++++++-----
 lib/gs-self-test.c        |   33 +++++++++++++++++++++++-
 4 files changed, 90 insertions(+), 35 deletions(-)
---
diff --git a/lib/gs-app-list-private.h b/lib/gs-app-list-private.h
index 61d5ff4..e88ef33 100644
--- a/lib/gs-app-list-private.h
+++ b/lib/gs-app-list-private.h
@@ -44,14 +44,19 @@ typedef enum {
 
 /**
  * GsAppListFilterFlags:
- * @GS_APP_LIST_FILTER_FLAG_NONE:      No flags set
- * @GS_APP_LIST_FILTER_FLAG_PRIORITY:  Filter by application priority
+ * @GS_APP_LIST_FILTER_FLAG_NONE:              No flags set
+ * @GS_APP_LIST_FILTER_FLAG_KEY_ID:            Filter by ID
+ * @GS_APP_LIST_FILTER_FLAG_KEY_SOURCE:                Filter by default source
+ * @GS_APP_LIST_FILTER_FLAG_KEY_VERSION:       Filter by version
  *
- * Flags to use when filtering.
+ * Flags to use when filtering. The priority of eash #GsApp is used to choose
+ * which application object to keep.
  **/
 typedef enum {
        GS_APP_LIST_FILTER_FLAG_NONE            = 0,
-       GS_APP_LIST_FILTER_FLAG_PRIORITY        = 1 << 0,
+       GS_APP_LIST_FILTER_FLAG_KEY_ID          = 1 << 0,
+       GS_APP_LIST_FILTER_FLAG_KEY_SOURCE      = 1 << 1,
+       GS_APP_LIST_FILTER_FLAG_KEY_VERSION     = 1 << 2,
        /*< private >*/
        GS_APP_LIST_FILTER_FLAG_LAST
 } GsAppListFilterFlags;
diff --git a/lib/gs-app-list.c b/lib/gs-app-list.c
index c7de5be..f402bc6 100644
--- a/lib/gs-app-list.c
+++ b/lib/gs-app-list.c
@@ -479,14 +479,9 @@ gs_app_list_randomize (GsAppList *list)
 void
 gs_app_list_filter_duplicates (GsAppList *list, GsAppListFilterFlags flags)
 {
-       guint i;
-       GsApp *app;
-       GsApp *found;
-       const gchar *id;
        g_autoptr(GHashTable) hash = NULL;
        g_autoptr(GList) values = NULL;
        g_autoptr(GPtrArray) apps_no_id = NULL;
-       GList *l;
        g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&list->mutex);
 
        g_return_if_fail (GS_IS_APP_LIST (list));
@@ -497,60 +492,81 @@ gs_app_list_filter_duplicates (GsAppList *list, GsAppListFilterFlags flags)
        /* an array to hold apps that have NULL app ids */
        apps_no_id = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
 
-       for (i = 0; i < list->array->len; i++) {
+       for (guint i = 0; i < list->array->len; i++) {
+               GsApp *app;
+               GsApp *found;
+               g_autoptr(GString) key = NULL;
+
                app = gs_app_list_index (list, i);
-               id = gs_app_get_unique_id (app);
-               if (flags & GS_APP_LIST_FILTER_FLAG_PRIORITY)
-                       id = gs_app_get_id (app);
-               if (id == NULL) {
+               if (flags == GS_APP_LIST_FILTER_FLAG_NONE) {
+                       key = g_string_new (gs_app_get_unique_id (app));
+               } else {
+                       key = g_string_new (NULL);
+                       if (flags & GS_APP_LIST_FILTER_FLAG_KEY_ID) {
+                               const gchar *tmp = gs_app_get_id (app);
+                               if (tmp != NULL)
+                                       g_string_append (key, gs_app_get_id (app));
+                       }
+                       if (flags & GS_APP_LIST_FILTER_FLAG_KEY_SOURCE) {
+                               const gchar *tmp = gs_app_get_source_default (app);
+                               if (tmp != NULL)
+                                       g_string_append_printf (key, ":%s", tmp);
+                       }
+                       if (flags & GS_APP_LIST_FILTER_FLAG_KEY_VERSION) {
+                               const gchar *tmp = gs_app_get_version (app);
+                               if (tmp != NULL)
+                                       g_string_append_printf (key, ":%s", tmp);
+                       }
+               }
+               if (key->len == 0) {
                        g_autofree gchar *str = gs_app_to_string (app);
-                       g_debug ("adding without deduplication as no app id: %s", str);
+                       g_debug ("adding without deduplication as no app key: %s", str);
                        g_ptr_array_add (apps_no_id, g_object_ref (app));
                        continue;
                }
-               found = g_hash_table_lookup (hash, id);
+               found = g_hash_table_lookup (hash, key->str);
                if (found == NULL) {
-                       g_debug ("found new %s", id);
+                       g_debug ("found new %s", key->str);
                        g_hash_table_insert (hash,
-                                            g_strdup (id),
+                                            g_strdup (key->str),
                                             g_object_ref (app));
                        continue;
                }
 
                /* better? */
-               if (flags & GS_APP_LIST_FILTER_FLAG_PRIORITY) {
+               if (flags != GS_APP_LIST_FILTER_FLAG_NONE) {
                        if (gs_app_get_priority (app) >
                            gs_app_get_priority (found)) {
                                g_debug ("using better %s (priority %u > %u)",
-                                        id,
+                                        key->str,
                                         gs_app_get_priority (app),
                                         gs_app_get_priority (found));
                                g_hash_table_insert (hash,
-                                                    g_strdup (id),
+                                                    g_strdup (key->str),
                                                     g_object_ref (app));
                                continue;
                        }
                        g_debug ("ignoring worse duplicate %s (priority %u > %u)",
-                                id,
+                                key->str,
                                 gs_app_get_priority (app),
                                 gs_app_get_priority (found));
                        continue;
                }
-               g_debug ("ignoring duplicate %s", id);
+               g_debug ("ignoring duplicate %s", key->str);
                continue;
        }
 
        /* add back the best results to the existing list */
        gs_app_list_remove_all_safe (list);
        values = g_hash_table_get_values (hash);
-       for (l = values; l != NULL; l = l->next) {
-               app = GS_APP (l->data);
+       for (GList *l = values; l != NULL; l = l->next) {
+               GsApp *app = GS_APP (l->data);
                gs_app_list_add_safe (list, app);
        }
 
        /* add back apps with NULL app ids to the existing list */
-       for (i = 0; i < apps_no_id->len; i++) {
-               app = g_ptr_array_index (apps_no_id, i);
+       for (guint i = 0; i < apps_no_id->len; i++) {
+               GsApp *app = g_ptr_array_index (apps_no_id, i);
                gs_app_list_add_safe (list, app);
        }
 }
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 7a93444..f0ac05d 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -1823,7 +1823,7 @@ gs_plugin_loader_get_popular_thread_cb (GTask *task,
 
        /* filter duplicates with priority */
        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_PRIORITY);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
 
        /* success */
        g_task_return_pointer (task, g_object_ref (job->list), (GDestroyNotify) g_object_unref);
@@ -1914,7 +1914,7 @@ gs_plugin_loader_get_featured_thread_cb (GTask *task,
 
        /* filter duplicates with priority */
        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_PRIORITY);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
 
        /* success */
        g_task_return_pointer (task, g_object_ref (job->list), (GDestroyNotify) g_object_unref);
@@ -2114,9 +2114,12 @@ gs_plugin_loader_search_thread_cb (GTask *task,
        gs_app_list_filter (job->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
        gs_app_list_filter (job->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
 
-       /* filter duplicates with priority */
+       /* filter duplicates with priority, taking into account the source name
+        * & version, so we combine available updates with the installed app */
        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);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_KEY_ID |
+                                                 GS_APP_LIST_FILTER_FLAG_KEY_SOURCE |
+                                                 GS_APP_LIST_FILTER_FLAG_KEY_VERSION);
 
        /* sort these again as the refine may have added useful metadata */
        gs_app_list_sort (job->list, job->sort_func, job->sort_func_data);
@@ -2553,7 +2556,7 @@ gs_plugin_loader_get_category_apps_thread_cb (GTask *task,
 
        /* filter duplicates with priority */
        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_PRIORITY);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
 
        /* sort, just in case the UI doesn't do this */
        gs_app_list_sort (job->list, gs_plugin_loader_app_sort_name_cb, NULL);
@@ -4523,7 +4526,7 @@ gs_plugin_loader_file_to_app_thread_cb (GTask *task,
 
        /* filter package list */
        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_PRIORITY);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
 
        /* check the apps have an icon set */
        for (guint j = 0; j < gs_app_list_length (job->list); j++) {
@@ -4674,7 +4677,7 @@ gs_plugin_loader_url_to_app_thread_cb (GTask *task,
        /* filter package list */
        gs_app_list_filter (job->list, gs_plugin_loader_app_is_valid, job);
        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_PRIORITY);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
 
        /* success */
        if (gs_app_list_length (job->list) != 1) {
diff --git a/lib/gs-self-test.c b/lib/gs-self-test.c
index 85ba013..af8a13b 100644
--- a/lib/gs-self-test.c
+++ b/lib/gs-self-test.c
@@ -286,11 +286,42 @@ gs_plugin_func (void)
        gs_app_set_priority (app, 50);
        g_object_unref (app);
        g_assert_cmpint (gs_app_list_length (list), ==, 3);
-       gs_app_list_filter_duplicates (list, GS_APP_LIST_FILTER_FLAG_PRIORITY);
+       gs_app_list_filter_duplicates (list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
        g_assert_cmpint (gs_app_list_length (list), ==, 1);
        g_assert_cmpstr (gs_app_get_unique_id (gs_app_list_index (list, 0)), ==, "user/bar/*/*/e/*");
        g_object_unref (list);
 
+       /* respect priority (using name and version) when deduplicating */
+       list = gs_app_list_new ();
+       app = gs_app_new ("e");
+       gs_app_add_source (app, "foo");
+       gs_app_set_version (app, "1.2.3");
+       gs_app_set_unique_id (app, "user/foo/repo/*/*/*");
+       gs_app_list_add (list, app);
+       gs_app_set_priority (app, 0);
+       g_object_unref (app);
+       app = gs_app_new ("e");
+       gs_app_add_source (app, "foo");
+       gs_app_set_version (app, "1.2.3");
+       gs_app_set_unique_id (app, "user/foo/repo-security/*/*/*");
+       gs_app_list_add (list, app);
+       gs_app_set_priority (app, 99);
+       g_object_unref (app);
+       app = gs_app_new ("e");
+       gs_app_add_source (app, "foo");
+       gs_app_set_version (app, "1.2.3");
+       gs_app_set_unique_id (app, "user/foo/repo-universe/*/*/*");
+       gs_app_list_add (list, app);
+       gs_app_set_priority (app, 50);
+       g_object_unref (app);
+       g_assert_cmpint (gs_app_list_length (list), ==, 3);
+       gs_app_list_filter_duplicates (list, GS_APP_LIST_FILTER_FLAG_KEY_ID |
+                                            GS_APP_LIST_FILTER_FLAG_KEY_SOURCE |
+                                            GS_APP_LIST_FILTER_FLAG_KEY_VERSION);
+       g_assert_cmpint (gs_app_list_length (list), ==, 1);
+       g_assert_cmpstr (gs_app_get_unique_id (gs_app_list_index (list, 0)), ==, 
"user/foo/repo-security/*/*/*");
+       g_object_unref (list);
+
        /* use globs when adding */
        list = gs_app_list_new ();
        app = gs_app_new ("b");


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