1
0
Fork 0
gnome-software/lib/gs-app-list.c
Daniel Baumann 68ee05b3fd
Adding upstream version 48.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 21:00:23 +02:00

1022 lines
25 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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 <richard@hughsie.com>
* Copyright (C) 2017-2018 Kalev Lember <klember@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/**
* 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 <glib.h>
#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; /* 0100 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 (0100 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_DOWNLOADING ||
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));
/* FisherYates 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 &&
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);
}
}
/* 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 (0100, 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);
}