Commit 2e87612c authored by Iain Lane's avatar Iain Lane Committed by Richard Hughes

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
parent dbc9b1b7
......@@ -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;
......
......@@ -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);
}
}
......
......@@ -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) {
......
......@@ -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");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment