From 9183787325de48542cb13e10b5a8ec12bf5db5f8 Mon Sep 17 00:00:00 2001 From: Julian Bouzas Date: Tue, 29 Mar 2022 09:47:41 -0400 Subject: [PATCH] lib: add new WpDbus API --- lib/wp/dbus.c | 361 +++++++++++++++++++++++++++++++++++++++++++ lib/wp/dbus.h | 59 +++++++ lib/wp/meson.build | 2 + lib/wp/wp.h | 1 + tests/wp/dbus.c | 103 ++++++++++++ tests/wp/meson.build | 8 + 6 files changed, 534 insertions(+) create mode 100644 lib/wp/dbus.c create mode 100644 lib/wp/dbus.h create mode 100644 tests/wp/dbus.c diff --git a/lib/wp/dbus.c b/lib/wp/dbus.c new file mode 100644 index 00000000..1ed32823 --- /dev/null +++ b/lib/wp/dbus.c @@ -0,0 +1,361 @@ +/* WirePlumber + * + * Copyright © 2022 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#define G_LOG_DOMAIN "wp-dbus" + +#include "private/registry.h" +#include "log.h" +#include "wpenums.h" +#include "dbus.h" + +enum { + STEP_DBUS_ENABLE = WP_TRANSITION_STEP_CUSTOM_START, +}; + +enum +{ + PROP_DBUS_0, + PROP_DBUS_BUS_TYPE, + PROP_DBUS_STATE, +}; + +struct _WpDbus +{ + WpObject parent; + + /* Props */ + GBusType bus_type; + WpDBusState state; + + GCancellable *cancellable; + GDBusConnection *connection; +}; + +static void on_connection_closed (GDBusConnection *connection, gboolean + remote_peer_vanished, GError *error, gpointer data); + +G_DEFINE_TYPE (WpDbus, wp_dbus, WP_TYPE_OBJECT) + +static void +wp_dbus_init (WpDbus * self) +{ +} + +static void +wp_dbus_set_state (WpDbus *self, WpDBusState new_state) +{ + if (self->state != new_state) { + self->state = new_state; + g_object_notify (G_OBJECT (self), "state"); + } +} + +static void +on_got_bus (GObject * obj, GAsyncResult * res, gpointer data) +{ + WpTransition *transition = WP_TRANSITION (data); + WpDbus *self = wp_transition_get_source_object (transition); + g_autoptr (GError) error = NULL; + + self->connection = g_dbus_connection_new_for_address_finish (res, &error); + if (!self->connection) { + g_prefix_error (&error, "Failed to connect to bus: "); + wp_transition_return_error (transition, g_steal_pointer (&error)); + return; + } + + wp_debug_object (self, "Connected to bus"); + + /* set up connection */ + g_signal_connect_object (self->connection, "closed", + G_CALLBACK (on_connection_closed), self, 0); + g_dbus_connection_set_exit_on_close (self->connection, FALSE); + + wp_dbus_set_state (self, WP_DBUS_STATE_CONNECTED); + + wp_object_update_features (WP_OBJECT (self), WP_DBUS_FEATURE_ENABLED, 0); +} + +static gboolean +do_connect (WpDbus *self, GAsyncReadyCallback callback, gpointer data, + GError **error) +{ + g_autofree gchar *address = NULL; + + address = g_dbus_address_get_for_bus_sync (self->bus_type, NULL, error); + if (!address) { + g_prefix_error (error, "Error acquiring bus address: "); + return FALSE; + } + + wp_dbus_set_state (self, WP_DBUS_STATE_CONNECTING); + + wp_debug_object (self, "Connecting to bus: %s", address); + g_dbus_connection_new_for_address (address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, self->cancellable, callback, data); + return TRUE; +} + +static void +on_sync_reconnect (WpCore * core, GAsyncResult * res, WpDbus * self) +{ + g_autoptr (GError) error = NULL; + + if (!wp_core_sync_finish (core, res, &error)) { + wp_warning_object (self, "core sync error: %s", error->message); + return; + } + + if (!do_connect (self, on_got_bus, self, &error)) + wp_info_object (self, "Cannot reconnect on sync: %s", error->message); +} + +static void +on_connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, GError *error, gpointer data) +{ + WpDbus *self = WP_DBUS (data); + g_autoptr (WpCore) core = NULL; + + wp_info_object (self, "DBus connection closed: %s", error->message); + + g_clear_object (&self->connection); + wp_dbus_set_state (self, WP_DBUS_STATE_CLOSED); + + /* try to reconnect on idle if connection was closed */ + core = wp_object_get_core (WP_OBJECT (self)); + if (core && wp_core_is_connected (core)) { + wp_info_object (self, "Trying to reconnect on sync"); + wp_core_sync_closure (core, NULL, g_cclosure_new_object ( + G_CALLBACK (on_sync_reconnect), G_OBJECT (self))); + } +} + +static void +wp_dbus_enable (WpDbus *self, WpTransition *transition) +{ + g_autoptr (GError) error = NULL; + if (!do_connect (self, on_got_bus, transition, &error)) { + wp_transition_return_error (transition, g_steal_pointer (&error)); + return; + } +} + +static void +wp_dbus_disable (WpDbus *self) +{ + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->connection); + wp_dbus_set_state (self, WP_DBUS_STATE_CLOSED); + + g_clear_object (&self->cancellable); + self->cancellable = g_cancellable_new (); + + wp_object_update_features (WP_OBJECT (self), 0, WP_DBUS_FEATURE_ENABLED); +} + +static WpObjectFeatures +wp_dbus_get_supported_features (WpObject * self) +{ + return WP_DBUS_FEATURE_ENABLED; +} + +static guint +wp_dbus_activate_get_next_step (WpObject * object, + WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing) +{ + switch (step) { + case WP_TRANSITION_STEP_NONE: + return STEP_DBUS_ENABLE; + case STEP_DBUS_ENABLE: + return WP_TRANSITION_STEP_NONE; + default: + return WP_TRANSITION_STEP_ERROR; + } +} + +static void +wp_dbus_activate_execute_step (WpObject * object, + WpFeatureActivationTransition * t, guint step, WpObjectFeatures missing) +{ + WpDbus *self = WP_DBUS (object); + WpTransition *transition = WP_TRANSITION (t); + + switch (step) { + case STEP_DBUS_ENABLE: + wp_dbus_enable (self, transition); + break; + + case WP_TRANSITION_STEP_ERROR: + break; + + default: + g_return_if_reached (); + } +} + +static void +wp_dbus_deactivate (WpObject * object, WpObjectFeatures features) +{ + WpDbus *self = WP_DBUS (object); + guint current = wp_object_get_active_features (object); + + if (features & current & WP_DBUS_FEATURE_ENABLED) + wp_dbus_disable (self); +} + +static void +wp_dbus_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + WpDbus *self = WP_DBUS (object); + + switch (property_id) { + case PROP_DBUS_BUS_TYPE: + g_value_set_enum (value, self->bus_type); + break; + case PROP_DBUS_STATE: + g_value_set_enum (value, self->state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +wp_dbus_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + WpDbus *self = WP_DBUS (object); + + switch (property_id) { + case PROP_DBUS_BUS_TYPE: + self->bus_type = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +wp_dbus_class_init (WpDbusClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + WpObjectClass *wpobject_class = (WpObjectClass *) klass; + + object_class->set_property = wp_dbus_set_property; + object_class->get_property = wp_dbus_get_property; + + wpobject_class->get_supported_features = wp_dbus_get_supported_features; + wpobject_class->activate_get_next_step = wp_dbus_activate_get_next_step; + wpobject_class->activate_execute_step = wp_dbus_activate_execute_step; + wpobject_class->deactivate = wp_dbus_deactivate; + + g_object_class_install_property (object_class, PROP_DBUS_BUS_TYPE, + g_param_spec_enum ("bus-type", "bus-type", "The bus type", + G_TYPE_BUS_TYPE, G_BUS_TYPE_NONE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_DBUS_STATE, + g_param_spec_enum ("state", "state", "The dbus connection state", + WP_TYPE_DBUS_STATE, WP_DBUS_STATE_CLOSED, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static gboolean +find_dbus_func (gpointer object, gpointer p) +{ + GBusType *bus_type = p; + + if (!WP_IS_DBUS (object) || !bus_type) + return FALSE; + + return wp_dbus_get_bus_type (WP_DBUS (object)) == *bus_type; +} + +/*! + * \brief Returns the dbus instance that is associated with the given core and + * bus type. + * + * This method will also create the instance and register it with the core + * if it had not been created before. + * + * \param core the core + * \param bus_type the bus type + * \return (transfer full): the dbus instance + */ +WpDbus * +wp_dbus_get_instance (WpCore *core, GBusType bus_type) +{ + WpRegistry *registry; + WpDbus *dbus; + + g_return_val_if_fail (core, NULL); + g_return_val_if_fail ( + bus_type != G_BUS_TYPE_NONE || bus_type != G_BUS_TYPE_STARTER, NULL); + + registry = wp_core_get_registry (core); + dbus = wp_registry_find_object (registry, (GEqualFunc) find_dbus_func, + &bus_type); + if (G_UNLIKELY (!dbus)) { + dbus = g_object_new (WP_TYPE_DBUS, + "core", core, + "bus-type", bus_type, + NULL); + wp_registry_register_object (registry, g_object_ref (dbus)); + } + + return dbus; +} + +/*! + * \brief Returns the bus type of the dbus object. + * + * \param self the DBus object + * \returns the bus type + */ +GBusType +wp_dbus_get_bus_type (WpDbus *self) +{ + g_return_val_if_fail (WP_IS_DBUS (self), G_BUS_TYPE_NONE); + + return self->bus_type; +} + +/*! + * \brief Returns the dbus connection state of the dbus object. + * + * \param self the DBus object + * \returns the dbus connection state + */ +WpDBusState +wp_dbus_get_state (WpDbus *self) +{ + g_return_val_if_fail (WP_IS_DBUS (self), WP_DBUS_STATE_CLOSED); + + return self->state; +} + +/*! + * \brief Returns the DBus connection object of the dbus object. + * + * \param self the DBus object + * \return (transfer full): the DBus connection object + */ +GDBusConnection * +wp_dbus_get_connection (WpDbus *self) +{ + g_return_val_if_fail (WP_IS_DBUS (self), NULL); + + return self->connection ? g_object_ref (self->connection) : NULL; +} diff --git a/lib/wp/dbus.h b/lib/wp/dbus.h new file mode 100644 index 00000000..b67aac32 --- /dev/null +++ b/lib/wp/dbus.h @@ -0,0 +1,59 @@ +/* WirePlumber + * + * Copyright © 2022 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_DBUS_H__ +#define __WIREPLUMBER_DBUS_H__ + +#include "object.h" + +G_BEGIN_DECLS + +/* WpDbus */ + +/*! + * \brief Flags to be used as WpObjectFeatures for WpDbus. + * \ingroup wpdbus + */ +typedef enum { /*< flags >*/ + /* main features */ + WP_DBUS_FEATURE_ENABLED = (1 << 0), +} WpDbusFeatures; + +/*! + * \brief The state of the dbus connection + * \ingroup wpdbus + */ +typedef enum { + WP_DBUS_STATE_CLOSED = 0, + WP_DBUS_STATE_CONNECTING, + WP_DBUS_STATE_CONNECTED, +} WpDBusState; + +/*! + * \brief The WpDbus GType + * \ingroup wpdbus + */ +#define WP_TYPE_DBUS (wp_dbus_get_type ()) +WP_API +G_DECLARE_FINAL_TYPE (WpDbus, wp_dbus, WP, DBUS, WpObject) + +WP_API +WpDbus *wp_dbus_get_instance (WpCore *core, GBusType bus_type); + +WP_API +GBusType wp_dbus_get_bus_type (WpDbus *self); + +WP_API +WpDBusState wp_dbus_get_state (WpDbus *self); + +WP_API +GDBusConnection *wp_dbus_get_connection (WpDbus *self); + +G_END_DECLS + +#endif diff --git a/lib/wp/meson.build b/lib/wp/meson.build index cf674953..9c7a90c3 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -2,6 +2,7 @@ wp_lib_sources = files( 'client.c', 'component-loader.c', 'core.c', + 'dbus.c', 'device.c', 'endpoint.c', 'error.c', @@ -40,6 +41,7 @@ wp_lib_headers = files( 'client.h', 'component-loader.h', 'core.h', + 'dbus.h', 'defs.h', 'device.h', 'endpoint.h', diff --git a/lib/wp/wp.h b/lib/wp/wp.h index 53acb04f..fa09efa8 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -12,6 +12,7 @@ #include "client.h" #include "component-loader.h" #include "core.h" +#include "dbus.h" #include "device.h" #include "endpoint.h" #include "error.h" diff --git a/tests/wp/dbus.c b/tests/wp/dbus.c new file mode 100644 index 00000000..d90f504a --- /dev/null +++ b/tests/wp/dbus.c @@ -0,0 +1,103 @@ +/* WirePlumber + * + * Copyright © 2022 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include "../common/base-test-fixture.h" + +typedef struct { + WpBaseTestFixture base; + GTestDBus *test_dbus; +} TestFixture; + +static void +test_dbus_setup (TestFixture *self, gconstpointer user_data) +{ + wp_base_test_fixture_setup (&self->base, 0); + self->test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (self->test_dbus); +} + +static void +test_dbus_teardown (TestFixture *self, gconstpointer user_data) +{ + g_test_dbus_down (self->test_dbus); + g_clear_object (&self->test_dbus); + wp_base_test_fixture_teardown (&self->base); +} + +static void +test_dbus_basic (TestFixture *f, gconstpointer user_data) +{ + g_autoptr (WpDbus) dbus = wp_dbus_get_instance (f->base.core, + G_BUS_TYPE_SESSION); + g_assert_nonnull (dbus); + g_autoptr (WpDbus) dbus2 = wp_dbus_get_instance (f->base.core, + G_BUS_TYPE_SESSION); + g_assert_nonnull (dbus2); + + GBusType bus_type = wp_dbus_get_bus_type (dbus); + g_assert_true (bus_type == G_BUS_TYPE_SESSION); + GBusType bus_type2 = wp_dbus_get_bus_type (dbus); + g_assert_true (bus_type2 == G_BUS_TYPE_SESSION); + g_assert_true (dbus == dbus2); +} + +static void +on_dbus_activated (WpObject * dbus, GAsyncResult * res, TestFixture * f) +{ + g_autoptr (GError) error = NULL; + if (!wp_object_activate_finish (dbus, res, &error)) { + wp_critical_object (dbus, "%s", error->message); + g_main_loop_quit (f->base.loop); + } +} + +static void +on_dbus_state_changed (GObject * obj, GParamSpec * spec, TestFixture *f) +{ + WpDBusState state = wp_dbus_get_state (WP_DBUS (obj)); + if (state == WP_DBUS_STATE_CONNECTED) + g_main_loop_quit (f->base.loop); +} + +static void +test_dbus_activation (TestFixture *f, gconstpointer user_data) +{ + g_autoptr (WpDbus) dbus = wp_dbus_get_instance ( + f->base.core, G_BUS_TYPE_SESSION); + g_assert_nonnull (dbus); + + wp_object_activate (WP_OBJECT (dbus), WP_DBUS_FEATURE_ENABLED, + NULL, (GAsyncReadyCallback) on_dbus_activated, f); + + g_signal_connect (dbus, "notify::state", G_CALLBACK (on_dbus_state_changed), + f); + + g_main_loop_run (f->base.loop); + g_assert_cmpint (wp_dbus_get_state (WP_DBUS (dbus)), ==, + WP_DBUS_STATE_CONNECTED); + + wp_object_deactivate (WP_OBJECT (dbus), WP_DBUS_FEATURE_ENABLED); + g_assert_cmpint (wp_dbus_get_state (WP_DBUS (dbus)), ==, + WP_DBUS_STATE_CLOSED); +} + +gint +main (gint argc, gchar *argv[]) +{ + g_test_init (&argc, &argv, NULL); + wp_init (WP_INIT_ALL); + + g_test_add ("/wp/dbus/basic", TestFixture, NULL, + test_dbus_setup, test_dbus_basic, test_dbus_teardown); + g_test_add ("/wp/dbus/activation", TestFixture, NULL, + test_dbus_setup, test_dbus_activation, test_dbus_teardown); + + return g_test_run (); +} diff --git a/tests/wp/meson.build b/tests/wp/meson.build index 5849cef3..01bca578 100644 --- a/tests/wp/meson.build +++ b/tests/wp/meson.build @@ -91,6 +91,14 @@ test( env: common_env, ) +test( + 'test-dbus', + executable('test-dbus', 'dbus.c', + dependencies: common_deps, c_args: common_args), + env: common_env, +) + + test( 'test-transition', executable('test-transition', 'transition.c',