/* -*- 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); }