From 6f0f7d1b40a8fa8d46a2d6f4317600001cdbbb18 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:57:27 +0200 Subject: Adding upstream version 43.5. Signed-off-by: Daniel Baumann --- lib/gs-app-list.c | 1025 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1025 insertions(+) create mode 100644 lib/gs-app-list.c (limited to 'lib/gs-app-list.c') diff --git a/lib/gs-app-list.c b/lib/gs-app-list.c new file mode 100644 index 0000000..a5f6785 --- /dev/null +++ b/lib/gs-app-list.c @@ -0,0 +1,1025 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2013-2016 Richard Hughes + * Copyright (C) 2017-2018 Kalev Lember + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/** + * SECTION:gs-app-list + * @title: GsAppList + * @include: gnome-software.h + * @stability: Unstable + * @short_description: An application list + * + * These functions provide a refcounted list of #GsApp objects. + */ + +#include "config.h" + +#include + +#include "gs-app-private.h" +#include "gs-app-list-private.h" +#include "gs-app-collation.h" +#include "gs-enums.h" + +struct _GsAppList +{ + GObject parent_instance; + GPtrArray *array; + GMutex mutex; + guint size_peak; + GsAppListFlags flags; + GsAppState state; + guint progress; /* 0–100 inclusive, or %GS_APP_PROGRESS_UNKNOWN */ + guint custom_progress; /* overrides the 'progress', if not %GS_APP_PROGRESS_UNKNOWN */ +}; + +G_DEFINE_TYPE (GsAppList, gs_app_list, G_TYPE_OBJECT) + +enum { + PROP_STATE = 1, + PROP_PROGRESS, + PROP_LAST +}; + +enum { + SIGNAL_APP_STATE_CHANGED, + SIGNAL_LAST +}; + +static guint signals [SIGNAL_LAST] = { 0 }; + +/** + * gs_app_list_get_state: + * @list: A #GsAppList + * + * Gets the state of the list. + * + * This method will only return a valid result if gs_app_list_add_flag() has + * been called with %GS_APP_LIST_FLAG_WATCH_APPS. + * + * Returns: the #GsAppState, e.g. %GS_APP_STATE_INSTALLED + * + * Since: 3.30 + **/ +GsAppState +gs_app_list_get_state (GsAppList *list) +{ + g_return_val_if_fail (GS_IS_APP_LIST (list), GS_APP_STATE_UNKNOWN); + return list->state; +} + +/** + * gs_app_list_get_progress: + * @list: A #GsAppList + * + * Gets the average percentage completion of all apps in the list. If any of the + * apps in the list has progress %GS_APP_PROGRESS_UNKNOWN, or if the app list + * is empty, %GS_APP_PROGRESS_UNKNOWN will be returned. + * + * This method will only return a valid result if gs_app_list_add_flag() has + * been called with %GS_APP_LIST_FLAG_WATCH_APPS. + * + * Returns: the percentage completion (0–100 inclusive), or %GS_APP_PROGRESS_UNKNOWN for unknown + * + * Since: 3.30 + **/ +guint +gs_app_list_get_progress (GsAppList *list) +{ + g_return_val_if_fail (GS_IS_APP_LIST (list), GS_APP_PROGRESS_UNKNOWN); + if (list->custom_progress != GS_APP_PROGRESS_UNKNOWN) + return list->custom_progress; + return list->progress; +} + +static gboolean +app_list_notify_progress_idle_cb (gpointer user_data) +{ + GsAppList *list = user_data; + + g_object_notify (G_OBJECT (list), "progress"); + g_object_unref (list); + + return G_SOURCE_REMOVE; +} + +/** + * gs_app_list_override_progress: + * @list: a #GsAppList + * @progress: a progress to set, between 0 and 100 inclusive, or %GS_APP_PROGRESS_UNKNOWN + * + * Override the progress property to be this value, or use %GS_APP_PROGRESS_UNKNOWN, + * to unset the override. This can be used when only the overall progress is known, + * instead of a per-application progress. + * + * Since: 42 + **/ +void +gs_app_list_override_progress (GsAppList *list, + guint progress) +{ + g_return_if_fail (GS_IS_APP_LIST (list)); + + if (list->custom_progress != progress) { + list->custom_progress = progress; + g_idle_add (app_list_notify_progress_idle_cb, g_object_ref (list)); + } +} + +static void +gs_app_list_add_watched_for_app (GsAppList *list, GPtrArray *apps, GsApp *app) +{ + if (list->flags & GS_APP_LIST_FLAG_WATCH_APPS) + g_ptr_array_add (apps, app); + if (list->flags & GS_APP_LIST_FLAG_WATCH_APPS_ADDONS) { + g_autoptr(GsAppList) list2 = gs_app_dup_addons (app); + + for (guint i = 0; list2 != NULL && i < gs_app_list_length (list2); i++) { + GsApp *app2 = gs_app_list_index (list2, i); + g_ptr_array_add (apps, app2); + } + } + if (list->flags & GS_APP_LIST_FLAG_WATCH_APPS_RELATED) { + GsAppList *list2 = gs_app_get_related (app); + for (guint i = 0; i < gs_app_list_length (list2); i++) { + GsApp *app2 = gs_app_list_index (list2, i); + g_ptr_array_add (apps, app2); + } + } +} + +static GPtrArray * +gs_app_list_get_watched_for_app (GsAppList *list, GsApp *app) +{ + GPtrArray *apps = g_ptr_array_new (); + gs_app_list_add_watched_for_app (list, apps, app); + return apps; +} + +static GPtrArray * +gs_app_list_get_watched (GsAppList *list) +{ + GPtrArray *apps = g_ptr_array_new (); + for (guint i = 0; i < list->array->len; i++) { + GsApp *app_tmp = g_ptr_array_index (list->array, i); + gs_app_list_add_watched_for_app (list, apps, app_tmp); + } + return apps; +} + +static void +gs_app_list_invalidate_progress (GsAppList *self) +{ + guint progress = 0; + g_autoptr(GPtrArray) apps = gs_app_list_get_watched (self); + + /* find the average percentage complete of the list */ + if (apps->len > 0) { + guint64 pc_cnt = 0; + gboolean unknown_seen = FALSE; + + for (guint i = 0; i < apps->len; i++) { + GsApp *app_tmp = g_ptr_array_index (apps, i); + guint app_progress = gs_app_get_progress (app_tmp); + + if (app_progress == GS_APP_PROGRESS_UNKNOWN) { + unknown_seen = TRUE; + break; + } + pc_cnt += gs_app_get_progress (app_tmp); + } + + progress = (!unknown_seen) ? pc_cnt / apps->len : GS_APP_PROGRESS_UNKNOWN; + } else { + progress = GS_APP_PROGRESS_UNKNOWN; + } + + if (self->progress != progress) { + self->progress = progress; + g_object_notify (G_OBJECT (self), "progress"); + } +} + +static void +gs_app_list_invalidate_state (GsAppList *self) +{ + GsAppState state = GS_APP_STATE_UNKNOWN; + g_autoptr(GPtrArray) apps = gs_app_list_get_watched (self); + + /* find any action state of the list */ + for (guint i = 0; i < apps->len; i++) { + GsApp *app_tmp = g_ptr_array_index (apps, i); + GsAppState state_tmp = gs_app_get_state (app_tmp); + if (state_tmp == GS_APP_STATE_INSTALLING || + state_tmp == GS_APP_STATE_REMOVING) { + state = state_tmp; + break; + } + } + if (self->state != state) { + self->state = state; + g_object_notify (G_OBJECT (self), "state"); + } +} + +static void +gs_app_list_progress_notify_cb (GsApp *app, GParamSpec *pspec, GsAppList *self) +{ + gs_app_list_invalidate_progress (self); +} + +static void +gs_app_list_state_notify_cb (GsApp *app, GParamSpec *pspec, GsAppList *self) +{ + gs_app_list_invalidate_state (self); + + g_signal_emit (self, signals[SIGNAL_APP_STATE_CHANGED], 0, app); +} + +static void +gs_app_list_maybe_watch_app (GsAppList *list, GsApp *app) +{ + g_autoptr(GPtrArray) apps = gs_app_list_get_watched_for_app (list, app); + for (guint i = 0; i < apps->len; i++) { + GsApp *app_tmp = g_ptr_array_index (apps, i); + g_signal_connect_object (app_tmp, "notify::progress", + G_CALLBACK (gs_app_list_progress_notify_cb), + list, 0); + g_signal_connect_object (app_tmp, "notify::state", + G_CALLBACK (gs_app_list_state_notify_cb), + list, 0); + } +} + +static void +gs_app_list_maybe_unwatch_app (GsAppList *list, GsApp *app) +{ + g_autoptr(GPtrArray) apps = gs_app_list_get_watched_for_app (list, app); + for (guint i = 0; i < apps->len; i++) { + GsApp *app_tmp = g_ptr_array_index (apps, i); + g_signal_handlers_disconnect_by_data (app_tmp, list); + } +} + +/** + * gs_app_list_get_size_peak: + * @list: A #GsAppList + * + * Returns the largest size the list has ever been. + * + * Returns: integer + * + * Since: 3.24 + **/ +guint +gs_app_list_get_size_peak (GsAppList *list) +{ + return list->size_peak; +} + +/** + * gs_app_list_set_size_peak: + * @list: A #GsAppList + * @size_peak: A value to set + * + * Sets the largest size the list has ever been. + * + * Since: 43 + **/ +void +gs_app_list_set_size_peak (GsAppList *list, + guint size_peak) +{ + g_return_if_fail (GS_IS_APP_LIST (list)); + list->size_peak = size_peak; +} + +static GsApp * +gs_app_list_lookup_safe (GsAppList *list, const gchar *unique_id) +{ + for (guint i = 0; i < list->array->len; i++) { + GsApp *app = g_ptr_array_index (list->array, i); + if (as_utils_data_id_equal (gs_app_get_unique_id (app), unique_id)) + return app; + } + return NULL; +} + +/** + * gs_app_list_lookup: + * @list: A #GsAppList + * @unique_id: A unique_id + * + * Finds the first matching application in the list using the usual wildcard + * rules allowed in unique_ids. + * + * Returns: (transfer none): a #GsApp, or %NULL if not found + * + * Since: 3.22 + **/ +GsApp * +gs_app_list_lookup (GsAppList *list, const gchar *unique_id) +{ + g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&list->mutex); + return gs_app_list_lookup_safe (list, unique_id); +} + +/** + * gs_app_list_has_flag: + * @list: A #GsAppList + * @flag: A flag to test, e.g. %GS_APP_LIST_FLAG_IS_TRUNCATED + * + * Gets if a specific flag is set. + * + * Returns: %TRUE if the flag is set + * + * Since: 3.24 + **/ +gboolean +gs_app_list_has_flag (GsAppList *list, GsAppListFlags flag) +{ + return (list->flags & flag) > 0; +} + +/** + * gs_app_list_add_flag: + * @list: A #GsAppList + * @flag: A flag to test, e.g. %GS_APP_LIST_FLAG_IS_TRUNCATED + * + * Gets if a specific flag is set. + * + * Returns: %TRUE if the flag is set + * + * Since: 3.30 + **/ +void +gs_app_list_add_flag (GsAppList *list, GsAppListFlags flag) +{ + if (list->flags & flag) + return; + list->flags |= flag; + + /* turn this on for existing apps */ + for (guint i = 0; i < list->array->len; i++) { + GsApp *app = g_ptr_array_index (list->array, i); + gs_app_list_maybe_watch_app (list, app); + } +} + +static gboolean +gs_app_list_check_for_duplicate (GsAppList *list, GsApp *app) +{ + GsApp *app_old; + const gchar *id; + + /* adding a wildcard */ + if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD)) { + for (guint i = 0; i < list->array->len; i++) { + GsApp *app_tmp = g_ptr_array_index (list->array, i); + if (!gs_app_has_quirk (app_tmp, GS_APP_QUIRK_IS_WILDCARD)) + continue; + /* not adding exactly the same wildcard */ + if (g_strcmp0 (gs_app_get_unique_id (app_tmp), + gs_app_get_unique_id (app)) == 0) + return FALSE; + } + return TRUE; + } + + for (guint i = 0; i < list->array->len; i++) { + GsApp *app_tmp = g_ptr_array_index (list->array, i); + if (app_tmp == app) + return FALSE; + } + + /* does not exist */ + id = gs_app_get_unique_id (app); + if (id == NULL) { + /* not much else we can do... */ + return TRUE; + } + + /* existing app is a wildcard */ + app_old = gs_app_list_lookup_safe (list, id); + if (app_old == NULL) + return TRUE; + if (gs_app_has_quirk (app_old, GS_APP_QUIRK_IS_WILDCARD)) + return TRUE; + + /* already exists */ + return FALSE; +} + +typedef enum { + GS_APP_LIST_ADD_FLAG_NONE = 0, + GS_APP_LIST_ADD_FLAG_CHECK_FOR_DUPE = 1 << 0, + GS_APP_LIST_ADD_FLAG_LAST +} GsAppListAddFlag; + +static void +gs_app_list_add_safe (GsAppList *list, GsApp *app, GsAppListAddFlag flag) +{ + /* check for duplicate */ + if ((flag & GS_APP_LIST_ADD_FLAG_CHECK_FOR_DUPE) > 0 && + !gs_app_list_check_for_duplicate (list, app)) + return; + + /* just use the ref */ + gs_app_list_maybe_watch_app (list, app); + g_ptr_array_add (list->array, g_object_ref (app)); + + /* update the historical max */ + if (list->array->len > list->size_peak) + list->size_peak = list->array->len; +} + +/** + * gs_app_list_add: + * @list: A #GsAppList + * @app: A #GsApp + * + * If the application does not already exist in the list then it is added, + * incrementing the reference count. + * If the application already exists then a warning is printed to the console. + * + * Applications that have the application ID lazy-loaded will always be added + * to the list, and to clean these up the plugin loader will also call the + * gs_app_list_filter_duplicates() method when all plugins have run. + * + * Since: 3.22 + **/ +void +gs_app_list_add (GsAppList *list, GsApp *app) +{ + g_autoptr(GMutexLocker) locker = NULL; + g_return_if_fail (GS_IS_APP_LIST (list)); + g_return_if_fail (GS_IS_APP (app)); + locker = g_mutex_locker_new (&list->mutex); + gs_app_list_add_safe (list, app, GS_APP_LIST_ADD_FLAG_CHECK_FOR_DUPE); + + /* recalculate global state */ + gs_app_list_invalidate_state (list); + gs_app_list_invalidate_progress (list); +} + +/** + * gs_app_list_remove: + * @list: A #GsAppList + * @app: A #GsApp + * + * Removes an application from the list. If the application does not exist the + * request is ignored. + * + * Returns: %TRUE if the app was removed, %FALSE if it did not exist in the @list + * Since: 43 + **/ +gboolean +gs_app_list_remove (GsAppList *list, GsApp *app) +{ + g_autoptr(GMutexLocker) locker = NULL; + gboolean removed; + + g_return_val_if_fail (GS_IS_APP_LIST (list), FALSE); + g_return_val_if_fail (GS_IS_APP (app), FALSE); + + locker = g_mutex_locker_new (&list->mutex); + removed = g_ptr_array_remove (list->array, app); + if (removed) { + gs_app_list_maybe_unwatch_app (list, app); + + /* recalculate global state */ + gs_app_list_invalidate_state (list); + gs_app_list_invalidate_progress (list); + } + + return removed; +} + +/** + * gs_app_list_add_list: + * @list: A #GsAppList + * @donor: Another #GsAppList + * + * Adds all the applications in @donor to @list. + * + * Since: 3.22 + **/ +void +gs_app_list_add_list (GsAppList *list, GsAppList *donor) +{ + guint i; + g_autoptr(GMutexLocker) locker = NULL; + + g_return_if_fail (GS_IS_APP_LIST (list)); + g_return_if_fail (GS_IS_APP_LIST (donor)); + g_return_if_fail (list != donor); + + locker = g_mutex_locker_new (&list->mutex); + + /* add each app */ + for (i = 0; i < donor->array->len; i++) { + GsApp *app = gs_app_list_index (donor, i); + gs_app_list_add_safe (list, app, GS_APP_LIST_ADD_FLAG_CHECK_FOR_DUPE); + } + + /* recalculate global state */ + gs_app_list_invalidate_state (list); + gs_app_list_invalidate_progress (list); +} + +/** + * gs_app_list_index: + * @list: A #GsAppList + * @idx: An index into the list + * + * Gets an application at a specific position in the list. + * + * Returns: (transfer none): a #GsApp, or %NULL if invalid + * + * Since: 3.22 + **/ +GsApp * +gs_app_list_index (GsAppList *list, guint idx) +{ + return GS_APP (g_ptr_array_index (list->array, idx)); +} + +/** + * gs_app_list_length: + * @list: A #GsAppList + * + * Gets the length of the application list. + * + * Returns: Integer + * + * Since: 3.22 + **/ +guint +gs_app_list_length (GsAppList *list) +{ + g_return_val_if_fail (GS_IS_APP_LIST (list), 0); + return list->array->len; +} + +static void +gs_app_list_remove_all_safe (GsAppList *list) +{ + for (guint i = 0; i < list->array->len; i++) { + GsApp *app = g_ptr_array_index (list->array, i); + gs_app_list_maybe_unwatch_app (list, app); + } + g_ptr_array_set_size (list->array, 0); + gs_app_list_invalidate_state (list); + gs_app_list_invalidate_progress (list); +} + +/** + * gs_app_list_remove_all: + * @list: A #GsAppList + * + * Removes all applications from the list. + * + * Since: 3.22 + **/ +void +gs_app_list_remove_all (GsAppList *list) +{ + g_autoptr(GMutexLocker) locker = NULL; + g_return_if_fail (GS_IS_APP_LIST (list)); + locker = g_mutex_locker_new (&list->mutex); + gs_app_list_remove_all_safe (list); +} + +/** + * gs_app_list_filter: + * @list: A #GsAppList + * @func: A #GsAppListFilterFunc + * @user_data: the user pointer to pass to @func + * + * If func() returns TRUE for the GsApp, then the app is kept. + * + * Since: 3.22 + **/ +void +gs_app_list_filter (GsAppList *list, GsAppListFilterFunc func, gpointer user_data) +{ + guint i; + GsApp *app; + g_autoptr(GsAppList) old = NULL; + g_autoptr(GMutexLocker) locker = NULL; + + g_return_if_fail (GS_IS_APP_LIST (list)); + g_return_if_fail (func != NULL); + + locker = g_mutex_locker_new (&list->mutex); + + /* deep copy to a temp list and clear the current one */ + old = gs_app_list_copy (list); + gs_app_list_remove_all_safe (list); + + /* see if any of the apps need filtering */ + for (i = 0; i < old->array->len; i++) { + app = gs_app_list_index (old, i); + if (func (app, user_data)) + gs_app_list_add_safe (list, app, GS_APP_LIST_ADD_FLAG_NONE); + } +} + +typedef struct { + GsAppListSortFunc func; + gpointer user_data; +} GsAppListSortHelper; + +static gint +gs_app_list_sort_cb (gconstpointer a, gconstpointer b, gpointer user_data) +{ + GsApp *app1 = GS_APP (*(GsApp **) a); + GsApp *app2 = GS_APP (*(GsApp **) b); + const GsAppListSortHelper *helper = (GsAppListSortHelper *) user_data; + return helper->func (app1, app2, helper->user_data); +} + +/** + * gs_app_list_sort: + * @list: A #GsAppList + * @func: A #GsAppListSortFunc + * @user_data: user data to pass to @func + * + * Sorts the application list. + * + * Since: 3.22 + **/ +void +gs_app_list_sort (GsAppList *list, GsAppListSortFunc func, gpointer user_data) +{ + g_autoptr(GMutexLocker) locker = NULL; + GsAppListSortHelper helper; + g_return_if_fail (GS_IS_APP_LIST (list)); + locker = g_mutex_locker_new (&list->mutex); + helper.func = func; + helper.user_data = user_data; + g_ptr_array_sort_with_data (list->array, gs_app_list_sort_cb, &helper); +} + +/** + * gs_app_list_truncate: + * @list: A #GsAppList + * @length: the new length + * + * Truncates the application list. It is an error if @length is larger than the + * size of the list. + * + * Since: 3.24 + **/ +void +gs_app_list_truncate (GsAppList *list, guint length) +{ + g_autoptr(GMutexLocker) locker = NULL; + + g_return_if_fail (GS_IS_APP_LIST (list)); + g_return_if_fail (length <= list->array->len); + + /* mark this list as unworthy */ + list->flags |= GS_APP_LIST_FLAG_IS_TRUNCATED; + + /* everything */ + if (length == 0) { + gs_app_list_remove_all (list); + return; + } + + /* remove the apps in the positions larger than the length */ + locker = g_mutex_locker_new (&list->mutex); + g_ptr_array_set_size (list->array, length); +} + +/** + * gs_app_list_randomize: + * @list: A #GsAppList + * + * Randomize the order of the list, but don't change the order until + * the next day. + * + * Since: 3.22 + **/ +void +gs_app_list_randomize (GsAppList *list) +{ + GRand *rand; + g_autoptr(GDateTime) date = NULL; + g_autoptr(GMutexLocker) locker = NULL; + + g_return_if_fail (GS_IS_APP_LIST (list)); + + locker = g_mutex_locker_new (&list->mutex); + + if (!gs_app_list_length (list)) + return; + + rand = g_rand_new (); + date = g_date_time_new_now_utc (); + g_rand_set_seed (rand, (guint32) g_date_time_get_day_of_year (date)); + + /* Fisher–Yates shuffle of the array. + * See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle */ + for (guint i = gs_app_list_length (list) - 1; i >= 1; i--) { + gpointer tmp; + guint j = g_rand_int_range (rand, 0, i + 1); + + tmp = list->array->pdata[i]; + list->array->pdata[i] = list->array->pdata[j]; + list->array->pdata[j] = tmp; + } + + g_rand_free (rand); +} + +static gboolean +gs_app_list_filter_app_is_better (GsApp *app, GsApp *found, GsAppListFilterFlags flags) +{ + /* optional 1st layer sort */ + if ((flags & GS_APP_LIST_FILTER_FLAG_PREFER_INSTALLED) > 0) { + if (gs_app_is_installed (app) && !gs_app_is_installed (found)) + return TRUE; + if (!gs_app_is_installed (app) && gs_app_is_installed (found)) + return FALSE; + } + + /* 2nd layer, priority and bundle kind */ + if (gs_app_compare_priority (app, found) < 0) + return TRUE; + + /* assume is worse */ + return FALSE; +} + +static GPtrArray * +gs_app_list_filter_app_get_keys (GsApp *app, GsAppListFilterFlags flags) +{ + GPtrArray *keys = g_ptr_array_new_with_free_func (g_free); + g_autoptr(GString) key = NULL; + + /* just use the unique ID */ + if (flags == GS_APP_LIST_FILTER_FLAG_NONE) { + if (gs_app_get_unique_id (app) != NULL) + g_ptr_array_add (keys, g_strdup (gs_app_get_unique_id (app))); + return keys; + } + + /* use the ID and any provided items */ + if (flags & GS_APP_LIST_FILTER_FLAG_KEY_ID_PROVIDES) { + GPtrArray *provided = gs_app_get_provided (app); + g_ptr_array_add (keys, g_strdup (gs_app_get_id (app))); + for (guint i = 0; i < provided->len; i++) { + AsProvided *prov = g_ptr_array_index (provided, i); + GPtrArray *items; + if (as_provided_get_kind (prov) != AS_PROVIDED_KIND_ID) + continue; + items = as_provided_get_items (prov); + for (guint j = 0; j < items->len; j++) + g_ptr_array_add (keys, g_strdup (g_ptr_array_index (items, j))); + } + return keys; + } + + /* specific compound type */ + 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) + return keys; + g_ptr_array_add (keys, g_string_free (g_steal_pointer (&key), FALSE)); + return keys; +} + +/** + * gs_app_list_filter_duplicates: + * @list: A #GsAppList + * @flags: a #GsAppListFilterFlags, e.g. GS_APP_LIST_FILTER_KEY_ID + * + * Filter any duplicate applications from the list. + * + * Since: 3.22 + **/ +void +gs_app_list_filter_duplicates (GsAppList *list, GsAppListFilterFlags flags) +{ + g_autoptr(GHashTable) hash = NULL; + g_autoptr(GHashTable) kept_apps = NULL; + g_autoptr(GsAppList) old = NULL; + g_autoptr(GMutexLocker) locker = NULL; + + g_return_if_fail (GS_IS_APP_LIST (list)); + + locker = g_mutex_locker_new (&list->mutex); + + /* a hash table to hold apps with unique app ids */ + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + /* a hash table containing apps we want to keep */ + kept_apps = g_hash_table_new (g_direct_hash, g_direct_equal); + + for (guint i = 0; i < list->array->len; i++) { + GsApp *app = gs_app_list_index (list, i); + GsApp *found = NULL; + g_autoptr(GPtrArray) keys = NULL; + + /* get all the keys used to identify this app */ + keys = gs_app_list_filter_app_get_keys (app, flags); + for (guint j = 0; j < keys->len; j++) { + const gchar *key = g_ptr_array_index (keys, j); + found = g_hash_table_lookup (hash, key); + if (found != NULL) + break; + } + + /* new app */ + if (found == NULL) { + for (guint j = 0; j < keys->len; j++) { + const gchar *key = g_ptr_array_index (keys, j); + g_hash_table_insert (hash, g_strdup (key), app); + } + g_hash_table_add (kept_apps, app); + continue; + } + + /* better? */ + if (flags != GS_APP_LIST_FILTER_FLAG_NONE) { + if (gs_app_list_filter_app_is_better (app, found, flags)) { + for (guint j = 0; j < keys->len; j++) { + const gchar *key = g_ptr_array_index (keys, j); + g_hash_table_insert (hash, g_strdup (key), app); + } + g_hash_table_remove (kept_apps, found); + g_hash_table_add (kept_apps, app); + continue; + } + continue; + } + continue; + } + + /* deep copy to a temp list and clear the current one */ + old = gs_app_list_copy (list); + gs_app_list_remove_all_safe (list); + + /* add back the apps we want to keep */ + for (guint i = 0; i < old->array->len; i++) { + GsApp *app = gs_app_list_index (old, i); + if (g_hash_table_contains (kept_apps, app)) { + gs_app_list_add_safe (list, app, GS_APP_LIST_ADD_FLAG_NONE); + /* In case the same instance is in the 'list' multiple times */ + g_hash_table_remove (kept_apps, app); + } + } +} + +/** + * gs_app_list_copy: + * @list: A #GsAppList + * + * Returns a deep copy of the application list. + * + * Returns: A newly allocated #GsAppList + * + * Since: 3.22 + **/ +GsAppList * +gs_app_list_copy (GsAppList *list) +{ + GsAppList *new; + guint i; + + g_return_val_if_fail (GS_IS_APP_LIST (list), NULL); + + new = gs_app_list_new (); + for (i = 0; i < gs_app_list_length (list); i++) { + GsApp *app = gs_app_list_index (list, i); + gs_app_list_add_safe (new, app, GS_APP_LIST_ADD_FLAG_NONE); + } + return new; +} + +static void +gs_app_list_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GsAppList *self = GS_APP_LIST (object); + switch (prop_id) { + case PROP_STATE: + g_value_set_enum (value, self->state); + break; + case PROP_PROGRESS: + g_value_set_uint (value, self->progress); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_app_list_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_app_list_finalize (GObject *object) +{ + GsAppList *list = GS_APP_LIST (object); + g_ptr_array_unref (list->array); + g_mutex_clear (&list->mutex); + G_OBJECT_CLASS (gs_app_list_parent_class)->finalize (object); +} + +static void +gs_app_list_class_init (GsAppListClass *klass) +{ + GParamSpec *pspec; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->get_property = gs_app_list_get_property; + object_class->set_property = gs_app_list_set_property; + object_class->finalize = gs_app_list_finalize; + + /** + * GsAppList:state: + */ + pspec = g_param_spec_enum ("state", NULL, NULL, + GS_TYPE_APP_STATE, + GS_APP_STATE_UNKNOWN, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_STATE, pspec); + + /** + * GsAppList:progress: + * + * A percentage (0–100, inclusive) indicating the progress through the + * current task on this app list. The value may otherwise be + * %GS_APP_PROGRESS_UNKNOWN if the progress is unknown or has a wide + * confidence interval on any app, or if the app list is empty. + */ + pspec = g_param_spec_uint ("progress", NULL, NULL, + 0, GS_APP_PROGRESS_UNKNOWN, GS_APP_PROGRESS_UNKNOWN, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_PROGRESS, pspec); + + /** + * GsAppList:app-state-changed: + * @app: a #GsApp + * + * Emitted when any of the internal #GsApp instances changes its state. + * + * Since: 3.40 + */ + signals [SIGNAL_APP_STATE_CHANGED] = + g_signal_new ("app-state-changed", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + 0, NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 1, GS_TYPE_APP); +} + +static void +gs_app_list_init (GsAppList *list) +{ + g_mutex_init (&list->mutex); + list->array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + list->custom_progress = GS_APP_PROGRESS_UNKNOWN; +} + +/** + * gs_app_list_new: + * + * Creates a new list. + * + * Returns: A newly allocated #GsAppList + * + * Since: 3.22 + **/ +GsAppList * +gs_app_list_new (void) +{ + GsAppList *list; + list = g_object_new (GS_TYPE_APP_LIST, NULL); + return GS_APP_LIST (list); +} -- cgit v1.2.3