mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-03-21 17:00:35 +01:00
lib: Add new WpCollectionManager API
This API allows handling of collections easily. It mainly allows creating them, destroying them, collect globals into a them and dropping glovals from them. The manager also automatically destroys a collection of all globals where dropped from the collection.
This commit is contained in:
parent
8e1778a129
commit
19d5ef54e9
6 changed files with 1468 additions and 0 deletions
907
lib/wp/collection-manager.c
Normal file
907
lib/wp/collection-manager.c
Normal file
|
|
@ -0,0 +1,907 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2025 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "core.h"
|
||||
#include "collection-manager.h"
|
||||
#include "metadata.h"
|
||||
#include "log.h"
|
||||
#include "object-manager.h"
|
||||
|
||||
#include "private/collection.h"
|
||||
|
||||
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-collection-manager")
|
||||
|
||||
/*! \defgroup wpcollectionmanager WpCollectionManager */
|
||||
/*!
|
||||
* \struct WpCollectionManager
|
||||
*
|
||||
* The WpCollectionManager class is the object charged to create and destroy
|
||||
* the collections in WirePlumber.
|
||||
*
|
||||
* WpCollection API.
|
||||
*/
|
||||
|
||||
struct _WpCollectionManager
|
||||
{
|
||||
WpObject parent;
|
||||
|
||||
/* Props */
|
||||
gchar *metadata_name;
|
||||
|
||||
/* Metadata */
|
||||
WpObjectManager *metadata_om;
|
||||
GWeakRef metadata;
|
||||
|
||||
/* Collections info */
|
||||
guint pending_collections;
|
||||
guint failed_collections;
|
||||
GHashTable *collections;
|
||||
GHashTable *create_collection_tasks;
|
||||
|
||||
/* Globals info */
|
||||
WpObjectManager *global_proxy_om;
|
||||
GHashTable *collected_globals;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_METADATA_NAME,
|
||||
};
|
||||
|
||||
enum {
|
||||
SIGNAL_GLOBAL_COLLECTED,
|
||||
SIGNAL_GLOBAL_DROPPED,
|
||||
LAST_SIGNAL,
|
||||
};
|
||||
|
||||
static guint signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
G_DEFINE_TYPE (WpCollectionManager, wp_collection_manager, WP_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
wp_collection_manager_init (WpCollectionManager * self)
|
||||
{
|
||||
self->collections = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, g_object_unref);
|
||||
self->create_collection_tasks = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal, g_free, g_object_unref);
|
||||
self->collected_globals = g_hash_table_new_full (g_direct_hash,
|
||||
g_direct_equal, NULL, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_collection_manager_set_property (GObject * object, guint property_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
WpCollectionManager *self = WP_COLLECTION_MANAGER (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_METADATA_NAME:
|
||||
g_clear_pointer (&self->metadata_name, g_free);
|
||||
self->metadata_name = g_value_dup_string (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wp_collection_manager_get_property (GObject * object, guint property_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
WpCollectionManager *self = WP_COLLECTION_MANAGER (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_METADATA_NAME:
|
||||
g_value_set_string (value, self->metadata_name);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
enum {
|
||||
STEP_LOAD = WP_TRANSITION_STEP_CUSTOM_START,
|
||||
};
|
||||
|
||||
static WpObjectFeatures
|
||||
wp_collection_manager_get_supported_features (WpObject * self)
|
||||
{
|
||||
return WP_COLLECTION_MANAGER_LOADED;
|
||||
}
|
||||
|
||||
static guint
|
||||
wp_collection_manager_activate_get_next_step (WpObject * self,
|
||||
WpFeatureActivationTransition * transition, guint step,
|
||||
WpObjectFeatures missing)
|
||||
{
|
||||
g_return_val_if_fail (missing == WP_COLLECTION_MANAGER_LOADED,
|
||||
WP_TRANSITION_STEP_ERROR);
|
||||
|
||||
return STEP_LOAD;
|
||||
}
|
||||
|
||||
static void
|
||||
on_global_proxy_removed (WpObjectManager *om, WpGlobalProxy *global, gpointer d)
|
||||
{
|
||||
WpCollectionManager * self = WP_COLLECTION_MANAGER (d);
|
||||
|
||||
/* Drop global from the collection if global is removed */
|
||||
wp_collection_manager_drop_global (self, global);
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_activated (GObject * o, GAsyncResult *res,
|
||||
WpTransition * transition)
|
||||
{
|
||||
WpCollectionManager * self = wp_transition_get_source_object (transition);
|
||||
g_autoptr (WpCollection) collection = WP_COLLECTION (o);
|
||||
g_autoptr (GError) error = NULL;
|
||||
const gchar *name;
|
||||
|
||||
/* Get the task name */
|
||||
name = wp_collection_get_name (collection);
|
||||
g_return_if_fail (name);
|
||||
|
||||
/* Add collection to hash table if it was activated successfully */
|
||||
if (!wp_object_activate_finish (WP_OBJECT (collection), res, &error)) {
|
||||
self->failed_collections++;
|
||||
wp_warning_object (self, "Failed to activate collection '%s': %s", name,
|
||||
error->message);
|
||||
} else {
|
||||
g_autoptr (WpMetadata) m = wp_collection_get_metadata (collection);
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
/* Collect the globals */
|
||||
it = wp_metadata_new_iterator (m, PW_ID_ANY);
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpMetadataItem *mi = g_value_get_boxed (&item);
|
||||
const gchar *key = wp_metadata_item_get_key (mi);
|
||||
guint32 global_id = SPA_ID_INVALID;
|
||||
|
||||
/* Parse global Id */
|
||||
if (!spa_atou32 (key, &global_id, 10)) {
|
||||
wp_warning_object (self,
|
||||
"Failed to parse global Id from metadata key '%s'. Ignoring...",
|
||||
key);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_autoptr (WpGlobalProxy) global = wp_object_manager_lookup (
|
||||
self->global_proxy_om, WP_TYPE_GLOBAL_PROXY,
|
||||
WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", global_id, NULL);
|
||||
if (global)
|
||||
g_hash_table_insert (self->collected_globals, global, g_strdup (name));
|
||||
else
|
||||
wp_warning_object (self,
|
||||
"Could not find global with Id %u for collection %s. Not adding...",
|
||||
global_id, name);
|
||||
}
|
||||
|
||||
/* Add the collection */
|
||||
g_hash_table_insert (self->collections, g_strdup (name),
|
||||
g_object_ref (collection));
|
||||
wp_info_object (self, "Added collection '%s' successfully", name);
|
||||
}
|
||||
|
||||
g_return_if_fail (self->pending_collections > 0);
|
||||
self->pending_collections--;
|
||||
|
||||
/* Set loaded feature if no pending collections */
|
||||
if (self->pending_collections == 0) {
|
||||
/* Check for errors */
|
||||
if (self->failed_collections > 0) {
|
||||
wp_transition_return_error (transition, g_error_new (
|
||||
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
||||
"%d collections failed to activate",
|
||||
self->failed_collections));
|
||||
} else {
|
||||
wp_object_update_features (WP_OBJECT (self), WP_COLLECTION_MANAGER_LOADED, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_ready (GObject * o, GAsyncResult *res, gpointer d)
|
||||
{
|
||||
WpCollectionManager * self = WP_COLLECTION_MANAGER (d);
|
||||
g_autoptr (WpCollection) collection = WP_COLLECTION (o);
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autoptr (GTask) t = NULL;
|
||||
g_autofree gchar *k = NULL;
|
||||
const gchar *name;
|
||||
|
||||
/* Get the task name */
|
||||
name = wp_collection_get_name (collection);
|
||||
g_return_if_fail (name);
|
||||
|
||||
/* Get the pending task if any */
|
||||
g_hash_table_steal_extended (self->create_collection_tasks, name,
|
||||
(gpointer *)&k, (gpointer *)&t);
|
||||
|
||||
/* Add collection to hash table if it was activated successfully */
|
||||
name = wp_collection_get_name (collection);
|
||||
if (!wp_object_activate_finish (WP_OBJECT (collection), res, &error)) {
|
||||
wp_warning_object (self, "Failed to activate collection '%s': %s", name,
|
||||
error->message);
|
||||
if (t)
|
||||
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"Could not add collection '%s' as its activation failed: %s", name,
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add collection to the hash table */
|
||||
g_hash_table_insert (self->collections, g_strdup (name),
|
||||
g_object_ref (collection));
|
||||
wp_info_object (self, "Added collection '%s' successfully", name);
|
||||
|
||||
/* Notify pending task */
|
||||
if (t)
|
||||
g_task_return_pointer (t, collection, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_metadata_changed (WpMetadata *m, guint32 subject,
|
||||
const gchar *key, const gchar *type, const gchar *value, gpointer d)
|
||||
{
|
||||
WpCollectionManager *self = WP_COLLECTION_MANAGER (d);
|
||||
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
||||
|
||||
if (key) {
|
||||
if (value) {
|
||||
WpCollection *collection;
|
||||
wp_info_object (self, "Adding collection '%s'", key);
|
||||
collection = wp_collection_new (core, key);
|
||||
wp_object_activate (WP_OBJECT (collection), WP_OBJECT_FEATURES_ALL, NULL,
|
||||
on_collection_ready, self);
|
||||
} else {
|
||||
g_hash_table_remove (self->collections, key);
|
||||
wp_info_object (self, "Removed collection '%s'", key);
|
||||
}
|
||||
} else {
|
||||
g_hash_table_remove_all (self->collections);
|
||||
wp_info_object (self, "Removed all collections");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_metadata_added (WpObjectManager *om, WpMetadata *m, gpointer d)
|
||||
{
|
||||
WpTransition * transition = WP_TRANSITION (d);
|
||||
WpCollectionManager * self = wp_transition_get_source_object (transition);
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
const gchar *metadata_name = NULL;
|
||||
g_autoptr (WpCore) core = NULL;
|
||||
g_autoptr (WpIterator) it = NULL;
|
||||
g_auto (GValue) item = G_VALUE_INIT;
|
||||
|
||||
/* Make sure the metadata has a name */
|
||||
props = wp_global_proxy_get_global_properties (WP_GLOBAL_PROXY (m));
|
||||
g_return_if_fail (props);
|
||||
metadata_name = wp_properties_get (props, "metadata.name");
|
||||
g_return_if_fail (metadata_name);
|
||||
g_return_if_fail (g_str_equal (metadata_name, self->metadata_name));
|
||||
|
||||
/* Set the metadata weak ref */
|
||||
g_weak_ref_set (&self->metadata, m);
|
||||
|
||||
/* Listen for metadata changes */
|
||||
g_signal_connect_object (m, "changed", G_CALLBACK (on_metadata_changed),
|
||||
self, 0);
|
||||
|
||||
/* Check if there are collections, and activate them if so */
|
||||
self->pending_collections = 0;
|
||||
self->failed_collections = 0;
|
||||
core = wp_object_get_core (WP_OBJECT (self));
|
||||
it = wp_metadata_new_iterator (m, 0);
|
||||
for (; wp_iterator_next (it, &item); g_value_unset (&item)) {
|
||||
WpMetadataItem *mi = g_value_get_boxed (&item);
|
||||
const gchar *key = wp_metadata_item_get_key (mi);
|
||||
WpCollection *collection = wp_collection_new (core, key);
|
||||
self->pending_collections++;
|
||||
wp_object_activate_closure (WP_OBJECT (collection),
|
||||
WP_OBJECT_FEATURES_ALL, NULL,
|
||||
g_cclosure_new_object (
|
||||
(GCallback) on_collection_activated, G_OBJECT (transition)));
|
||||
}
|
||||
|
||||
/* Otherwise just set loaded feature */
|
||||
if (self->pending_collections == 0)
|
||||
wp_object_update_features (WP_OBJECT (self), WP_COLLECTION_MANAGER_LOADED, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
on_global_proxy_om_installed (WpObjectManager *om, gpointer d)
|
||||
{
|
||||
WpTransition * transition = WP_TRANSITION (d);
|
||||
WpCollectionManager * self = wp_transition_get_source_object (transition);
|
||||
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
||||
|
||||
/* Install metadata object manager */
|
||||
g_clear_object (&self->metadata_om);
|
||||
self->metadata_om = wp_object_manager_new ();
|
||||
wp_object_manager_add_interest (self->metadata_om, WP_TYPE_METADATA,
|
||||
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY,
|
||||
"metadata.name", "=s", self->metadata_name,
|
||||
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY,
|
||||
"wireplumber.collection-manager", "=b", TRUE,
|
||||
NULL);
|
||||
wp_object_manager_request_object_features (self->metadata_om,
|
||||
WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL);
|
||||
g_signal_connect_object (self->metadata_om, "object-added",
|
||||
G_CALLBACK (on_metadata_added), transition, 0);
|
||||
wp_core_install_object_manager (core, self->metadata_om);
|
||||
|
||||
wp_info_object (self, "looking for metadata object named %s",
|
||||
self->metadata_name);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_collection_manager_activate_execute_step (WpObject * object,
|
||||
WpFeatureActivationTransition * transition, guint step,
|
||||
WpObjectFeatures missing)
|
||||
{
|
||||
WpCollectionManager *self = WP_COLLECTION_MANAGER (object);
|
||||
g_autoptr (WpCore) core = wp_object_get_core (object);
|
||||
|
||||
switch (step) {
|
||||
case STEP_LOAD: {
|
||||
/* Install global proxy object manager */
|
||||
g_clear_object (&self->global_proxy_om);
|
||||
self->global_proxy_om = wp_object_manager_new ();
|
||||
wp_object_manager_add_interest (self->global_proxy_om,
|
||||
WP_TYPE_GLOBAL_PROXY, NULL);
|
||||
wp_object_manager_request_object_features (self->global_proxy_om,
|
||||
WP_TYPE_GLOBAL_PROXY, WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
|
||||
g_signal_connect_object (self->global_proxy_om, "object-removed",
|
||||
G_CALLBACK (on_global_proxy_removed), self, 0);
|
||||
g_signal_connect_object (self->global_proxy_om, "installed",
|
||||
G_CALLBACK (on_global_proxy_om_installed), transition, 0);
|
||||
wp_core_install_object_manager (core, self->global_proxy_om);
|
||||
break;
|
||||
}
|
||||
case WP_TRANSITION_STEP_ERROR:
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
finish_task (gpointer key, gpointer value, gpointer user_data)
|
||||
{
|
||||
const char *collection_name = key;
|
||||
GTask *t = G_TASK (value);
|
||||
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"Deactivated collection manager before collection '%s' was created",
|
||||
collection_name);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_collection_manager_deactivate (WpObject * object, WpObjectFeatures features)
|
||||
{
|
||||
WpCollectionManager *self = WP_COLLECTION_MANAGER (object);
|
||||
|
||||
/* Globals info */
|
||||
g_hash_table_remove_all (self->collected_globals);
|
||||
g_clear_object (&self->global_proxy_om);
|
||||
|
||||
/* Collections info */
|
||||
g_hash_table_foreach (self->create_collection_tasks, finish_task, NULL);
|
||||
g_hash_table_remove_all (self->create_collection_tasks);
|
||||
g_hash_table_remove_all (self->collections);
|
||||
|
||||
/* Metadata */
|
||||
g_weak_ref_set (&self->metadata, NULL);
|
||||
g_clear_object (&self->metadata_om);
|
||||
|
||||
wp_object_update_features (WP_OBJECT (self), 0, WP_OBJECT_FEATURES_ALL);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_collection_manager_finalize (GObject * object)
|
||||
{
|
||||
WpCollectionManager *self = WP_COLLECTION_MANAGER (object);
|
||||
|
||||
/* Globals info */
|
||||
g_clear_pointer (&self->collected_globals, g_hash_table_unref);
|
||||
g_clear_object (&self->global_proxy_om);
|
||||
|
||||
/* Collections info */
|
||||
g_hash_table_foreach (self->create_collection_tasks, finish_task, NULL);
|
||||
g_clear_pointer (&self->create_collection_tasks, g_hash_table_unref);
|
||||
g_clear_pointer (&self->collections, g_hash_table_unref);
|
||||
|
||||
/* Metadata */
|
||||
g_weak_ref_clear (&self->metadata);
|
||||
g_clear_object (&self->metadata_om);
|
||||
|
||||
/* Props */
|
||||
g_clear_pointer (&self->metadata_name, g_free);
|
||||
|
||||
G_OBJECT_CLASS (wp_collection_manager_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_collection_manager_class_init (WpCollectionManagerClass * klass)
|
||||
{
|
||||
GObjectClass * object_class = (GObjectClass *) klass;
|
||||
WpObjectClass *wpobject_class = (WpObjectClass *) klass;
|
||||
|
||||
object_class->finalize = wp_collection_manager_finalize;
|
||||
object_class->set_property = wp_collection_manager_set_property;
|
||||
object_class->get_property = wp_collection_manager_get_property;
|
||||
|
||||
wpobject_class->get_supported_features =
|
||||
wp_collection_manager_get_supported_features;
|
||||
wpobject_class->activate_get_next_step =
|
||||
wp_collection_manager_activate_get_next_step;
|
||||
wpobject_class->activate_execute_step =
|
||||
wp_collection_manager_activate_execute_step;
|
||||
wpobject_class->deactivate = wp_collection_manager_deactivate;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_METADATA_NAME,
|
||||
g_param_spec_string ("metadata-name", "metadata-name",
|
||||
"The metadata object to look after", NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
signals[SIGNAL_GLOBAL_COLLECTED] = g_signal_new (
|
||||
"global-collected", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 2, WP_TYPE_GLOBAL_PROXY, G_TYPE_STRING);
|
||||
|
||||
signals[SIGNAL_GLOBAL_DROPPED] = g_signal_new (
|
||||
"global-dropped", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 2, WP_TYPE_GLOBAL_PROXY, G_TYPE_STRING);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates a new WpCollectionManager object
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param core the WpCore
|
||||
* \param metadata_name (nullable): the name of the metadata object to
|
||||
* associate with the collection manager object; NULL means the default
|
||||
* "sm-collection-manager"
|
||||
* \returns (transfer full): a new WpCollectionManager object
|
||||
*/
|
||||
WpCollectionManager *
|
||||
wp_collection_manager_new (WpCore * core, const gchar * metadata_name)
|
||||
{
|
||||
return g_object_new (WP_TYPE_COLLECTION_MANAGER,
|
||||
"core", core,
|
||||
"metadata-name", metadata_name ? metadata_name : "sm-collection-manager",
|
||||
NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
find_collection_manager_func (gpointer g_object, gpointer metadata_name)
|
||||
{
|
||||
if (!WP_IS_COLLECTION_MANAGER (g_object))
|
||||
return FALSE;
|
||||
|
||||
if (metadata_name)
|
||||
return g_str_equal (((WpCollectionManager *) g_object)->metadata_name,
|
||||
(gchar *) metadata_name);
|
||||
else
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Finds a registered WpCollectionManager object by its metadata name
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param core the WpCore
|
||||
* \param metadata_name (nullable): the name of the metadata object that the
|
||||
* collection manager object is associated with; NULL returns the first
|
||||
* collection manager object that is found
|
||||
* \returns (transfer full) (nullable): the WpCollectionManager object, or NULL
|
||||
* if not found
|
||||
*/
|
||||
WpCollectionManager *
|
||||
wp_collection_manager_find (WpCore * core, const gchar *metadata_name)
|
||||
{
|
||||
g_return_val_if_fail (WP_IS_CORE (core), NULL);
|
||||
|
||||
GObject *cm = wp_core_find_object (core,
|
||||
(GEqualFunc) find_collection_manager_func, metadata_name);
|
||||
return cm ? WP_COLLECTION_MANAGER (cm) : NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Callback version of wp_collection_manager_create_collection_closure()
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param collection_name the new collection name to create
|
||||
* \param cancellable (nullable): a cancellable for the async operation
|
||||
* \param callback (scope async)(closure user_data): a function to call when
|
||||
* the collection was created
|
||||
* \param user_data data for \a callback
|
||||
*/
|
||||
void
|
||||
wp_collection_manager_create_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (WP_IS_COLLECTION_MANAGER (self));
|
||||
|
||||
GClosure *closure = g_cclosure_new (G_CALLBACK (callback), user_data, NULL);
|
||||
|
||||
wp_collection_manager_create_collection_closure (self, collection_name,
|
||||
cancellable, closure);
|
||||
}
|
||||
|
||||
static void
|
||||
invoke_closure (GObject * obj, GAsyncResult * res, gpointer data)
|
||||
{
|
||||
GClosure *closure = data;
|
||||
GValue values[2] = { G_VALUE_INIT, G_VALUE_INIT };
|
||||
g_value_init (&values[0], G_TYPE_OBJECT);
|
||||
g_value_init (&values[1], G_TYPE_OBJECT);
|
||||
g_value_set_object (&values[0], obj);
|
||||
g_value_set_object (&values[1], res);
|
||||
g_closure_invoke (closure, NULL, 2, values, NULL);
|
||||
g_value_unset (&values[0]);
|
||||
g_value_unset (&values[1]);
|
||||
g_closure_unref (closure);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates a new collection into the manager
|
||||
*
|
||||
* \note \a closure may be invoked in sync while this method is being called,
|
||||
* if the collection already exists.
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param collection_name the new collection name to create
|
||||
* \param cancellable (nullable): a cancellable for the async operation
|
||||
* \param closure (transfer full): the closure to use when the collection was
|
||||
* created
|
||||
*/
|
||||
void
|
||||
wp_collection_manager_create_collection_closure (WpCollectionManager *self,
|
||||
const gchar *collection_name, GCancellable * cancellable, GClosure *closure)
|
||||
{
|
||||
g_autoptr (GTask) t = NULL;
|
||||
g_autoptr (WpMetadata) m = NULL;
|
||||
WpCollection *c = NULL;
|
||||
g_autoptr (WpSpaJson) json_value = NULL;
|
||||
|
||||
g_return_if_fail (WP_IS_COLLECTION_MANAGER (self));
|
||||
|
||||
/* Create the task from the closure */
|
||||
if (G_CLOSURE_NEEDS_MARSHAL (closure))
|
||||
g_closure_set_marshal (closure, g_cclosure_marshal_VOID__OBJECT);
|
||||
t = g_task_new (self, cancellable, invoke_closure, closure);
|
||||
|
||||
/* Just return if the collection already exists */
|
||||
c = g_hash_table_lookup (self->collections, collection_name);
|
||||
if (c) {
|
||||
g_task_return_pointer (t, c, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure the new collection is not being created already */
|
||||
if (g_hash_table_contains (self->create_collection_tasks, collection_name)) {
|
||||
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"Cannot create collection '%s' as another task is already creating it",
|
||||
collection_name);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the metadata */
|
||||
m = g_weak_ref_get (&self->metadata);
|
||||
if (!m) {
|
||||
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"Collection manager metadata not found, cannot create collection '%s'",
|
||||
collection_name);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Request new collection */
|
||||
g_hash_table_insert (self->create_collection_tasks,
|
||||
g_strdup (collection_name), g_object_ref (t));
|
||||
json_value = wp_spa_json_new_boolean (TRUE);
|
||||
wp_metadata_set (m, 0, collection_name, "Spa:String:JSON",
|
||||
wp_spa_json_get_data (json_value));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Finishes the async operation that was started with
|
||||
* wp_collection_manager_create_collection()
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param res the async operation result
|
||||
* \param error (out) (optional): the error of the operation, if any
|
||||
* \returns (nullable) (transfer none): The newly created WpCollection, or NULL
|
||||
* if an error happened.
|
||||
*/
|
||||
WpCollection *
|
||||
wp_collection_manager_create_collection_finish (WpCollectionManager * self,
|
||||
GAsyncResult * res, GError ** error)
|
||||
{
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), FALSE);
|
||||
g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (res), error);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Destroys a collection from the manager
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param collection_name the collection name to destroy
|
||||
* \returns TRUE if the collection was destroyed, FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
wp_collection_manager_destroy_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name)
|
||||
{
|
||||
g_autoptr (WpMetadata) m = NULL;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), FALSE);
|
||||
|
||||
m = g_weak_ref_get (&self->metadata);
|
||||
if (!m)
|
||||
return FALSE;
|
||||
|
||||
g_hash_table_remove (self->collections, collection_name);
|
||||
wp_metadata_set (m, 0, collection_name, "Spa:String:JSON", NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Checks whether the collection manager has a collection or not
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param collection_name the collection name to check
|
||||
* \returns TRUE if the collection exists, FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
wp_collection_manager_has_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name)
|
||||
{
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), FALSE);
|
||||
|
||||
return g_hash_table_contains (self->collections, collection_name);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get the collection for the given collection name
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param collection_name the collection name to get
|
||||
* \returns (nullable) (transfer full): the collection for the given name
|
||||
*/
|
||||
WpCollection *
|
||||
wp_collection_manager_get_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name)
|
||||
{
|
||||
WpCollection *c = NULL;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), NULL);
|
||||
|
||||
c = g_hash_table_lookup (self->collections, collection_name);
|
||||
return c ? g_object_ref (c) : NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Gets the total number of collections the manager has
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \returns the total number of collections the manager has
|
||||
*/
|
||||
guint
|
||||
wp_collection_manager_get_collection_count (WpCollectionManager *self)
|
||||
{
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), 0);
|
||||
|
||||
return g_hash_table_size (self->collections);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Iterates over all the collections that the manager has
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \returns (transfer full): an iterator that iterates over the collections
|
||||
*/
|
||||
WpIterator *
|
||||
wp_collection_manager_new_collection_iterator (WpCollectionManager *self)
|
||||
{
|
||||
GPtrArray *collections;
|
||||
GHashTableIter iter;
|
||||
WpCollection *c;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), NULL);
|
||||
|
||||
collections = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
g_hash_table_iter_init (&iter, self->collections);
|
||||
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&c))
|
||||
g_ptr_array_add (collections, g_object_ref (c));
|
||||
|
||||
return wp_iterator_new_ptr_array (collections, WP_TYPE_COLLECTION);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Collects the bound global into a collection.
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param global (transfer none): the global to collect
|
||||
* \param collection_name: the name of the collection to collect the global into
|
||||
* \returns TRUE if the global could be collected, FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
wp_collection_manager_collect_global (WpCollectionManager *self,
|
||||
WpGlobalProxy *global, const gchar *collection_name)
|
||||
{
|
||||
const gchar *cn;
|
||||
WpCollection *c;
|
||||
g_autoptr (WpMetadata) c_meta = NULL;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), FALSE);
|
||||
g_return_val_if_fail (wp_object_test_active_features (
|
||||
WP_OBJECT (global), WP_PROXY_FEATURE_BOUND), FALSE);
|
||||
g_return_val_if_fail (collection_name, FALSE);
|
||||
|
||||
/* Just return if this global is already collected */
|
||||
cn = g_hash_table_lookup (self->collected_globals, global);
|
||||
if (cn)
|
||||
return g_str_equal (cn, collection_name);
|
||||
|
||||
/* Get the collection */
|
||||
c = g_hash_table_lookup (self->collections, collection_name);
|
||||
if (!c)
|
||||
return FALSE;
|
||||
c_meta = wp_collection_get_metadata (c);
|
||||
g_return_val_if_fail (c_meta, FALSE);
|
||||
|
||||
/* Make sure we are not collecting a collection into itself */
|
||||
if (WP_GLOBAL_PROXY (c_meta) == global) {
|
||||
wp_warning_object (self, "Cannot collect a collection into itself");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!wp_collection_collect_global (c, global))
|
||||
return FALSE;
|
||||
|
||||
g_hash_table_insert (self->collected_globals, global,
|
||||
g_strdup (collection_name));
|
||||
|
||||
g_signal_emit (self, signals[SIGNAL_GLOBAL_COLLECTED], 0, global,
|
||||
collection_name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Drops the bound global from its collection
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param global (transfer none): the global to drop
|
||||
* \returns TRUE if the global could be dropped, FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
wp_collection_manager_drop_global (WpCollectionManager *self,
|
||||
WpGlobalProxy *global)
|
||||
{
|
||||
g_autofree gchar *cn = NULL;
|
||||
WpCollection *c;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), FALSE);
|
||||
g_return_val_if_fail (wp_object_test_active_features (
|
||||
WP_OBJECT (global), WP_PROXY_FEATURE_BOUND), FALSE);
|
||||
|
||||
/* Just return if this global is not collected */
|
||||
if (!g_hash_table_steal_extended (self->collected_globals, global, NULL,
|
||||
(gpointer *)&cn))
|
||||
return TRUE;
|
||||
|
||||
/* Get the collection */
|
||||
c = g_hash_table_lookup (self->collections, cn);
|
||||
g_return_val_if_fail (c, FALSE);
|
||||
|
||||
if (!wp_collection_drop_global (c, global))
|
||||
return FALSE;
|
||||
|
||||
/* Destroy the collection if it is empty */
|
||||
if (wp_collection_get_global_count (c) == 0)
|
||||
wp_collection_manager_destroy_collection (self, cn);
|
||||
|
||||
g_signal_emit (self, signals[SIGNAL_GLOBAL_DROPPED], 0, global, cn);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Gets the collection that a given global belongs to
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param global (transfer none): the global to get the collection from
|
||||
* \returns (nullable) (transfer full): the collection that the given global
|
||||
* belongs too
|
||||
*/
|
||||
WpCollection *
|
||||
wp_collection_manager_get_global_collection (WpCollectionManager *self,
|
||||
WpGlobalProxy *global)
|
||||
{
|
||||
const gchar *cn;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), NULL);
|
||||
|
||||
cn = g_hash_table_lookup (self->collected_globals, global);
|
||||
return cn ? wp_collection_manager_get_collection (self, cn) : NULL;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Convenience method to checks whether a global is collected into a
|
||||
* collection or not
|
||||
*
|
||||
* \ingroup wpcollectionmanager
|
||||
* \param self the WpCollectionManager
|
||||
* \param global (transfer none): the global to check if it is collected or not
|
||||
* \param collection_name (nullable): the collection name
|
||||
* \returns TRUE if the global is collected, FALSE otherwise
|
||||
* belongs too
|
||||
*/
|
||||
gboolean
|
||||
wp_collection_manager_is_global_collected (WpCollectionManager *self,
|
||||
WpGlobalProxy *global, const gchar *collection_name)
|
||||
{
|
||||
const gchar *cn;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), FALSE);
|
||||
|
||||
cn = g_hash_table_lookup (self->collected_globals, global);
|
||||
if (!cn)
|
||||
return FALSE;
|
||||
|
||||
return collection_name ? g_str_equal (cn, collection_name) : TRUE;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Iterates over all globals that a collection has
|
||||
*
|
||||
* \ingroup wpcollection
|
||||
* \param self the WpCollection
|
||||
* \param collection_name (nullable): the collection name
|
||||
* \returns (transfer full): an iterator that iterates over the globals.
|
||||
*/
|
||||
WpIterator *
|
||||
wp_collection_manager_new_global_iterator (WpCollectionManager *self,
|
||||
const gchar *collection_name)
|
||||
{
|
||||
GPtrArray *globals;
|
||||
GHashTableIter iter;
|
||||
WpGlobalProxy *g = NULL;
|
||||
const gchar *cn = NULL;
|
||||
|
||||
g_return_val_if_fail (WP_IS_COLLECTION_MANAGER (self), NULL);
|
||||
|
||||
globals = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
g_hash_table_iter_init (&iter, self->collected_globals);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&g, (gpointer *)&cn))
|
||||
if (!collection_name || g_str_equal (cn, collection_name))
|
||||
g_ptr_array_add (globals, g_object_ref (g));
|
||||
|
||||
return wp_iterator_new_ptr_array (globals, WP_TYPE_GLOBAL_PROXY);
|
||||
}
|
||||
105
lib/wp/collection-manager.h
Normal file
105
lib/wp/collection-manager.h
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2025 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@ollabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef __WIREPLUMBER_COLLECTION_MANAGER_H__
|
||||
#define __WIREPLUMBER_COLLECTION_MANAGER_H__
|
||||
|
||||
#include "object.h"
|
||||
#include "collection.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/*!
|
||||
* \brief Flags to be used as WpObjectFeatures for WpCollectionManager.
|
||||
* \ingroup wpcollectionmanager
|
||||
*/
|
||||
typedef enum { /*< flags >*/
|
||||
/*! Loads the collection manager */
|
||||
WP_COLLECTION_MANAGER_LOADED = (1 << 0),
|
||||
} WpCollectionManagerFeatures;
|
||||
|
||||
/*!
|
||||
* \brief The WpCollectionManager GType
|
||||
* \ingroup wpcollectionmanager
|
||||
*/
|
||||
#define WP_TYPE_COLLECTION_MANAGER (wp_collection_manager_get_type ())
|
||||
|
||||
WP_API
|
||||
G_DECLARE_FINAL_TYPE (WpCollectionManager, wp_collection_manager, WP,
|
||||
COLLECTION_MANAGER, WpObject)
|
||||
|
||||
WP_API
|
||||
WpCollectionManager * wp_collection_manager_new (WpCore * core,
|
||||
const gchar * metadata_name);
|
||||
|
||||
WP_API
|
||||
WpCollectionManager * wp_collection_manager_find (WpCore * core,
|
||||
const gchar *metadata_name);
|
||||
|
||||
|
||||
/* Collection */
|
||||
|
||||
WP_API
|
||||
void wp_collection_manager_create_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data);
|
||||
|
||||
WP_API
|
||||
void wp_collection_manager_create_collection_closure (WpCollectionManager *self,
|
||||
const gchar *collection_name, GCancellable * cancellable,
|
||||
GClosure *closure);
|
||||
|
||||
WP_API
|
||||
WpCollection *wp_collection_manager_create_collection_finish (
|
||||
WpCollectionManager * self, GAsyncResult * res, GError ** error);
|
||||
|
||||
WP_API
|
||||
gboolean wp_collection_manager_destroy_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name);
|
||||
|
||||
WP_API
|
||||
gboolean wp_collection_manager_has_collection (WpCollectionManager *self,
|
||||
const gchar *collection_name);
|
||||
|
||||
WP_API
|
||||
WpCollection * wp_collection_manager_get_collection (
|
||||
WpCollectionManager *self, const gchar *collection_name);
|
||||
|
||||
WP_API
|
||||
guint wp_collection_manager_get_collection_count (WpCollectionManager *self);
|
||||
|
||||
WP_API
|
||||
WpIterator * wp_collection_manager_new_collection_iterator (
|
||||
WpCollectionManager *self);
|
||||
|
||||
|
||||
/* Global */
|
||||
|
||||
WP_API
|
||||
gboolean wp_collection_manager_collect_global (WpCollectionManager *self,
|
||||
WpGlobalProxy *global, const gchar *collection_name);
|
||||
|
||||
WP_API
|
||||
gboolean wp_collection_manager_drop_global (WpCollectionManager *self,
|
||||
WpGlobalProxy *global);
|
||||
|
||||
WP_API
|
||||
WpCollection * wp_collection_manager_get_global_collection (
|
||||
WpCollectionManager *self, WpGlobalProxy *global);
|
||||
|
||||
WP_API
|
||||
gboolean wp_collection_manager_is_global_collected (WpCollectionManager *self,
|
||||
WpGlobalProxy *global, const gchar *collection_name);
|
||||
|
||||
WP_API
|
||||
WpIterator * wp_collection_manager_new_global_iterator (
|
||||
WpCollectionManager *self, const gchar *collection_name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -2,6 +2,7 @@ wp_lib_sources = files(
|
|||
'base-dirs.c',
|
||||
'client.c',
|
||||
'collection.c',
|
||||
'collection-manager.c',
|
||||
'component-loader.c',
|
||||
'conf.c',
|
||||
'core.c',
|
||||
|
|
@ -51,6 +52,7 @@ wp_lib_headers = files(
|
|||
'base-dirs.h',
|
||||
'client.h',
|
||||
'collection.h',
|
||||
'collection-manager.h',
|
||||
'component-loader.h',
|
||||
'conf.h',
|
||||
'core.h',
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include "settings.h"
|
||||
#include "permission-manager.h"
|
||||
#include "collection.h"
|
||||
#include "collection-manager.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
|
|
|||
446
tests/wp/collection-manager.c
Normal file
446
tests/wp/collection-manager.c
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2025 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "../common/base-test-fixture.h"
|
||||
|
||||
#define COLLECTION_A_NAME "sm-collection-a"
|
||||
#define COLLECTION_B_NAME "sm-collection-b"
|
||||
#define COLLECTION_C_NAME "sm-collection-c"
|
||||
#define COLLECTION_MANAGER_METADATA_NAME "sm-collection-manager"
|
||||
|
||||
typedef struct {
|
||||
WpBaseTestFixture base;
|
||||
WpImplMetadata *collection_a_metadata;
|
||||
WpImplMetadata *collection_b_metadata;
|
||||
WpImplMetadata *collection_manager_metadata;
|
||||
WpCollectionManager *collection_manager;
|
||||
WpCollection *collection_a;
|
||||
WpCollection *collection_b;
|
||||
} TestCollectionManagerFixture;
|
||||
|
||||
static void
|
||||
on_metadata_activated (WpMetadata * m, GAsyncResult * res,
|
||||
gpointer d)
|
||||
{
|
||||
TestCollectionManagerFixture *self = d;
|
||||
g_assert_true (wp_object_activate_finish (WP_OBJECT (m), res, NULL));
|
||||
g_main_loop_quit (self->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_manager_metadata_activated (WpMetadata * m, GAsyncResult * res,
|
||||
gpointer d)
|
||||
{
|
||||
TestCollectionManagerFixture *self = d;
|
||||
g_assert_true (wp_object_activate_finish (WP_OBJECT (m), res, NULL));
|
||||
|
||||
/* Add collections A and B */
|
||||
wp_metadata_set (m, 0, COLLECTION_A_NAME, "Spa:String:JSON", "1");
|
||||
wp_metadata_set (m, 0, COLLECTION_B_NAME, "Spa:String:JSON", "1");
|
||||
|
||||
g_main_loop_quit (self->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_manager_ready (WpCollectionManager *s, GAsyncResult *res,
|
||||
gpointer d)
|
||||
{
|
||||
TestCollectionManagerFixture *self = d;
|
||||
|
||||
g_assert_true (wp_object_activate_finish (WP_OBJECT (s), res, NULL));
|
||||
|
||||
wp_core_register_object (self->base.core, g_object_ref (s));
|
||||
|
||||
g_main_loop_quit (self->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
collection_manager_setup (TestCollectionManagerFixture *self, gconstpointer d)
|
||||
{
|
||||
wp_base_test_fixture_setup (&self->base, WP_BASE_TEST_FLAG_CLIENT_CORE);
|
||||
|
||||
/* Create collection A metadata and activate it */
|
||||
self->collection_a_metadata = wp_impl_metadata_new_full (
|
||||
self->base.core, COLLECTION_A_NAME,
|
||||
wp_properties_new ("wireplumber.collection", "true", NULL));
|
||||
wp_object_activate (WP_OBJECT (self->collection_a_metadata),
|
||||
WP_OBJECT_FEATURES_ALL, NULL,
|
||||
(GAsyncReadyCallback)on_metadata_activated, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Create collection B metadata and activate it */
|
||||
self->collection_b_metadata = wp_impl_metadata_new_full (
|
||||
self->base.core, COLLECTION_B_NAME,
|
||||
wp_properties_new ("wireplumber.collection", "true", NULL));
|
||||
wp_object_activate (WP_OBJECT (self->collection_b_metadata),
|
||||
WP_OBJECT_FEATURES_ALL, NULL,
|
||||
(GAsyncReadyCallback)on_metadata_activated, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Create collection manager metadata and activate it */
|
||||
self->collection_manager_metadata = wp_impl_metadata_new_full (
|
||||
self->base.core, COLLECTION_MANAGER_METADATA_NAME,
|
||||
wp_properties_new ("wireplumber.collection-manager", "true", NULL));
|
||||
wp_object_activate (WP_OBJECT (self->collection_manager_metadata),
|
||||
WP_OBJECT_FEATURES_ALL, NULL,
|
||||
(GAsyncReadyCallback)on_collection_manager_metadata_activated, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Create the collection manager and activate it */
|
||||
self->collection_manager = wp_collection_manager_new (self->base.core,
|
||||
COLLECTION_MANAGER_METADATA_NAME);
|
||||
wp_object_activate (WP_OBJECT (self->collection_manager),
|
||||
WP_OBJECT_FEATURES_ALL,
|
||||
NULL,
|
||||
(GAsyncReadyCallback)on_collection_manager_ready,
|
||||
self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Get collections A and B */
|
||||
self->collection_a = wp_collection_manager_get_collection (
|
||||
self->collection_manager, COLLECTION_A_NAME);
|
||||
g_assert_nonnull (self->collection_a);
|
||||
self->collection_b = wp_collection_manager_get_collection (
|
||||
self->collection_manager, COLLECTION_B_NAME);
|
||||
g_assert_nonnull (self->collection_b);
|
||||
}
|
||||
|
||||
static void
|
||||
collection_manager_teardown (TestCollectionManagerFixture *self,
|
||||
gconstpointer d)
|
||||
{
|
||||
g_clear_object (&self->collection_b);
|
||||
g_clear_object (&self->collection_a);
|
||||
g_clear_object (&self->collection_manager);
|
||||
g_clear_object (&self->collection_manager_metadata);
|
||||
g_clear_object (&self->collection_b_metadata);
|
||||
g_clear_object (&self->collection_a_metadata);
|
||||
wp_base_test_fixture_teardown (&self->base);
|
||||
}
|
||||
|
||||
static void
|
||||
on_collection_created (WpCollectionManager * cm, GAsyncResult * res, gpointer d)
|
||||
{
|
||||
TestCollectionManagerFixture *self = d;
|
||||
WpCollection *c = wp_collection_manager_create_collection_finish (cm, res,
|
||||
NULL);
|
||||
g_assert_nonnull (c);
|
||||
g_assert_true (WP_IS_COLLECTION (c));
|
||||
g_main_loop_quit (self->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_collection_manager_collection (TestCollectionManagerFixture *self,
|
||||
gconstpointer d)
|
||||
{
|
||||
g_autoptr (WpImplMetadata) collection_c_metadata;
|
||||
|
||||
/* Make sure the manager has 2 collections */
|
||||
g_assert_cmpuint (wp_collection_manager_get_collection_count (
|
||||
self->collection_manager), ==, 2);
|
||||
|
||||
/* Make sure collections A and B were added */
|
||||
g_assert_true (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_A_NAME));
|
||||
g_assert_true (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_B_NAME));
|
||||
|
||||
/* Make sure collection C was not added */
|
||||
g_assert_false (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_C_NAME));
|
||||
|
||||
/* Create collection C metadata and activate it */
|
||||
collection_c_metadata = wp_impl_metadata_new_full (
|
||||
self->base.core, COLLECTION_C_NAME,
|
||||
wp_properties_new ("wireplumber.collection", "true", NULL));
|
||||
wp_object_activate (WP_OBJECT (collection_c_metadata),
|
||||
WP_OBJECT_FEATURES_ALL, NULL,
|
||||
(GAsyncReadyCallback)on_metadata_activated, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Make sure collection C is still not added */
|
||||
g_assert_false (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_C_NAME));
|
||||
|
||||
/* Create collection C */
|
||||
wp_collection_manager_create_collection (self->collection_manager,
|
||||
COLLECTION_C_NAME, NULL, (GAsyncReadyCallback)on_collection_created, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Make sure the manager has 3 collections */
|
||||
g_assert_cmpuint (wp_collection_manager_get_collection_count (
|
||||
self->collection_manager), ==, 3);
|
||||
|
||||
/* Iterate collections */
|
||||
{
|
||||
g_autoptr (WpIterator) iter = NULL;
|
||||
g_auto (GValue) val = G_VALUE_INIT;
|
||||
gboolean has_collection_a = FALSE;
|
||||
gboolean has_collection_b = FALSE;
|
||||
gboolean has_collection_c = FALSE;
|
||||
guint count = 0;
|
||||
|
||||
iter = wp_collection_manager_new_collection_iterator (
|
||||
self->collection_manager);
|
||||
g_assert_nonnull (iter);
|
||||
|
||||
while (wp_iterator_next (iter, &val)) {
|
||||
WpCollection *c = g_value_get_object (&val);
|
||||
g_assert_nonnull (c);
|
||||
if (g_str_equal (wp_collection_get_name (c), COLLECTION_A_NAME))
|
||||
has_collection_a = TRUE;
|
||||
if (g_str_equal (wp_collection_get_name (c), COLLECTION_B_NAME))
|
||||
has_collection_b = TRUE;
|
||||
if (g_str_equal (wp_collection_get_name (c), COLLECTION_C_NAME))
|
||||
has_collection_c = TRUE;
|
||||
count++;
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert_true (has_collection_a);
|
||||
g_assert_true (has_collection_b);
|
||||
g_assert_true (has_collection_c);
|
||||
g_assert_cmpuint (count, ==, 3);
|
||||
}
|
||||
|
||||
/* Make sure collection C was added */
|
||||
g_assert_true (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_C_NAME));
|
||||
|
||||
/* Destroy collection C */
|
||||
g_assert_true (wp_collection_manager_destroy_collection (
|
||||
self->collection_manager, COLLECTION_C_NAME));
|
||||
|
||||
/* Make sure the manager has 2 collections */
|
||||
g_assert_cmpuint (wp_collection_manager_get_collection_count (
|
||||
self->collection_manager), ==, 2);
|
||||
|
||||
/* Make sure collection C was removed */
|
||||
g_assert_false (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_C_NAME));
|
||||
|
||||
/* Remove collections A and B */
|
||||
g_assert_true (wp_collection_manager_destroy_collection (
|
||||
self->collection_manager, COLLECTION_A_NAME));
|
||||
g_assert_true (wp_collection_manager_destroy_collection (
|
||||
self->collection_manager, COLLECTION_B_NAME));
|
||||
|
||||
/* Make sure the manager does not have any collections */
|
||||
g_assert_cmpuint (wp_collection_manager_get_collection_count (
|
||||
self->collection_manager), ==, 0);
|
||||
|
||||
/* Make sure collections A and B were removed */
|
||||
g_assert_false (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_A_NAME));
|
||||
g_assert_false (wp_collection_manager_has_collection (self->collection_manager,
|
||||
COLLECTION_B_NAME));
|
||||
}
|
||||
|
||||
static void
|
||||
test_collection_manager_global (TestCollectionManagerFixture *self,
|
||||
gconstpointer d)
|
||||
{
|
||||
g_autoptr (WpGlobalProxy) global_a = NULL;
|
||||
g_autoptr (WpGlobalProxy) global_b = NULL;
|
||||
|
||||
/* Activate global A */
|
||||
global_a = WP_GLOBAL_PROXY (wp_impl_metadata_new_full (self->base.core,
|
||||
"global-a", NULL));
|
||||
wp_object_activate (WP_OBJECT (global_a), WP_OBJECT_FEATURES_ALL, NULL,
|
||||
(GAsyncReadyCallback)on_metadata_activated, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Activate global B */
|
||||
global_b = WP_GLOBAL_PROXY (wp_impl_metadata_new_full (self->base.core,
|
||||
"global-a", NULL));
|
||||
wp_object_activate (WP_OBJECT (global_b), WP_OBJECT_FEATURES_ALL, NULL,
|
||||
(GAsyncReadyCallback)on_metadata_activated, self);
|
||||
g_main_loop_run (self->base.loop);
|
||||
|
||||
/* Make sure collection A and B don't have any globals */
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_a), ==, 0);
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_b), ==, 0);
|
||||
|
||||
/* Make sure global A and B don't belong to any collection */
|
||||
g_assert_false (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_a, NULL));
|
||||
g_assert_false (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_b, NULL));
|
||||
|
||||
/* Collect global A into collection A and global B into collection B */
|
||||
g_assert_true (wp_collection_manager_collect_global (self->collection_manager,
|
||||
global_a, COLLECTION_A_NAME));
|
||||
g_assert_true (wp_collection_manager_collect_global (self->collection_manager,
|
||||
global_b, COLLECTION_B_NAME));
|
||||
|
||||
/* Make sure collection A and B have only one global */
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_a), ==, 1);
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_b), ==, 1);
|
||||
|
||||
/* Iterate collection A */
|
||||
{
|
||||
g_autoptr (WpIterator) iter = NULL;
|
||||
g_auto (GValue) val = G_VALUE_INIT;
|
||||
guint count = 0;
|
||||
|
||||
iter = wp_collection_manager_new_global_iterator (self->collection_manager,
|
||||
COLLECTION_A_NAME);
|
||||
g_assert_nonnull (iter);
|
||||
|
||||
while (wp_iterator_next (iter, &val)) {
|
||||
WpGlobalProxy *g = g_value_get_object (&val);
|
||||
g_assert_nonnull (g);
|
||||
g_assert_true (g == global_a);
|
||||
count++;
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert_cmpuint (count, ==, 1);
|
||||
}
|
||||
|
||||
/* Iterate collection B */
|
||||
{
|
||||
g_autoptr (WpIterator) iter = NULL;
|
||||
g_auto (GValue) val = G_VALUE_INIT;
|
||||
guint count = 0;
|
||||
|
||||
iter = wp_collection_manager_new_global_iterator (self->collection_manager,
|
||||
COLLECTION_B_NAME);
|
||||
g_assert_nonnull (iter);
|
||||
|
||||
while (wp_iterator_next (iter, &val)) {
|
||||
WpGlobalProxy *g = g_value_get_object (&val);
|
||||
g_assert_nonnull (g);
|
||||
g_assert_true (g == global_b);
|
||||
count++;
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert_cmpuint (count, ==, 1);
|
||||
}
|
||||
|
||||
/* Make sure global A and global B are in their respective collection */
|
||||
g_assert_true (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_a, COLLECTION_A_NAME));
|
||||
g_assert_true (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_b, COLLECTION_B_NAME));
|
||||
{
|
||||
g_autoptr (WpCollection) ca = NULL;
|
||||
g_autoptr (WpCollection) cb = NULL;
|
||||
ca = wp_collection_manager_get_global_collection (self->collection_manager,
|
||||
global_a);
|
||||
g_assert_true (ca == self->collection_a);
|
||||
cb = wp_collection_manager_get_global_collection (self->collection_manager,
|
||||
global_b);
|
||||
g_assert_true (cb == self->collection_b);
|
||||
}
|
||||
|
||||
g_assert_true (WP_IS_COLLECTION (self->collection_a));
|
||||
|
||||
/* Make sure we cannot add global A into collection B and vice versa */
|
||||
g_assert_false (wp_collection_manager_collect_global (
|
||||
self->collection_manager, global_a, COLLECTION_B_NAME));
|
||||
g_assert_false (wp_collection_manager_collect_global (
|
||||
self->collection_manager, global_b, COLLECTION_A_NAME));
|
||||
|
||||
/* Drop global A from collection A */
|
||||
g_assert_true (wp_collection_manager_drop_global (self->collection_manager,
|
||||
global_a));
|
||||
|
||||
/* Make sure collection A does not have any globals */
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_a), ==, 0);
|
||||
|
||||
/* Make sure global A does not belong to any collection */
|
||||
g_assert_false (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_a, NULL));
|
||||
|
||||
/* Make sure collection A was automatically removed */
|
||||
g_assert_false (wp_collection_manager_has_collection (
|
||||
self->collection_manager, COLLECTION_A_NAME));
|
||||
|
||||
/* Collect global A into collection B */
|
||||
g_assert_true (wp_collection_manager_collect_global (self->collection_manager,
|
||||
global_a, COLLECTION_B_NAME));
|
||||
|
||||
/* Make sure global A is in collection B */
|
||||
g_assert_true (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_a, COLLECTION_B_NAME));
|
||||
|
||||
/* Make sure collection B has 2 globals */
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_b), ==, 2);
|
||||
|
||||
/* Iterate collection B */
|
||||
{
|
||||
g_autoptr (WpIterator) iter = NULL;
|
||||
g_auto (GValue) val = G_VALUE_INIT;
|
||||
gboolean has_global_a = FALSE;
|
||||
gboolean has_global_b = FALSE;
|
||||
guint count = 0;
|
||||
|
||||
iter = wp_collection_manager_new_global_iterator (self->collection_manager,
|
||||
COLLECTION_B_NAME);
|
||||
g_assert_nonnull (iter);
|
||||
|
||||
while (wp_iterator_next (iter, &val)) {
|
||||
WpGlobalProxy *g = g_value_get_object (&val);
|
||||
g_assert_nonnull (g);
|
||||
if (g == global_a)
|
||||
has_global_a = TRUE;
|
||||
if (g == global_b)
|
||||
has_global_b = TRUE;
|
||||
count++;
|
||||
g_value_unset (&val);
|
||||
}
|
||||
|
||||
g_assert_true (has_global_a);
|
||||
g_assert_true (has_global_b);
|
||||
g_assert_cmpuint (count, ==, 2);
|
||||
}
|
||||
|
||||
/* Drop global B from collection B */
|
||||
g_assert_true (wp_collection_manager_drop_global (self->collection_manager,
|
||||
global_b));
|
||||
|
||||
/* Make sure collection B has only one global */
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_b), ==, 1);
|
||||
|
||||
/* Make sure global B does not belong to any collection */
|
||||
g_assert_false (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_b, NULL));
|
||||
|
||||
/* Drop global A from collection B */
|
||||
g_assert_true (wp_collection_manager_drop_global (self->collection_manager,
|
||||
global_a));
|
||||
|
||||
/* Make sure collection B does not have any globals */
|
||||
g_assert_cmpuint (wp_collection_get_global_count (self->collection_b), ==, 0);
|
||||
|
||||
/* Make sure global A does not belong to any collection */
|
||||
g_assert_false (wp_collection_manager_is_global_collected (
|
||||
self->collection_manager, global_a, NULL));
|
||||
|
||||
/* Make sure collection B was automatically removed */
|
||||
g_assert_false (wp_collection_manager_has_collection (
|
||||
self->collection_manager, COLLECTION_B_NAME));
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
wp_init (WP_INIT_ALL);
|
||||
|
||||
g_test_add ("/wp/collection-manager/collection",
|
||||
TestCollectionManagerFixture, NULL, collection_manager_setup,
|
||||
test_collection_manager_collection, collection_manager_teardown);
|
||||
g_test_add ("/wp/collection-manager/global",
|
||||
TestCollectionManagerFixture, NULL, collection_manager_setup,
|
||||
test_collection_manager_global, collection_manager_teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -3,6 +3,13 @@ common_env = common_test_env
|
|||
common_env.set('G_TEST_SRCDIR', meson.current_source_dir())
|
||||
common_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
|
||||
|
||||
test(
|
||||
'test-collection-manager',
|
||||
executable('test-collection-manager', 'collection-manager.c',
|
||||
dependencies: common_deps),
|
||||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-component-loader',
|
||||
executable('test-component-loader', 'component-loader.c',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue