pipewire/pinos/client/subscribe.c
Wim Taymans 5f10a933a1 remove client object
Remove the client object, it is not very useful now that we have the
nodes.
Fix some properties on the proxy objects.
Use sendmsg and recvmsg directly because the GIO ones do allocations.
make pinos_properties_merge and use it to combine properties from nodes
and ports.
2016-05-18 17:22:34 +02:00

816 lines
24 KiB
C

/* Pinos
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gio/gio.h>
#include "pinos/client/pinos.h"
#include "pinos/client/enumtypes.h"
#include "pinos/client/private.h"
struct _PinosSubscribePrivate
{
gchar *service;
PinosSubscriptionFlags subscription_mask;
GDBusConnection *connection;
GCancellable *cancellable;
GDBusProxy *manager_proxy;
guint owner_id;
guint signal_id;
guint pending_proxies;
GList *objects;
PinosSubscriptionState state;
GError *error;
};
typedef struct
{
PinosSubscribe *subscribe;
gchar *sender_name;
gchar *object_path;
gchar *interface_name;
gboolean pending;
GDBusProxy *proxy;
guint prop_id;
GList *tasks;
gboolean removed;
} PinosObjectData;
#define PINOS_SUBSCRIBE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), PINOS_TYPE_SUBSCRIBE, PinosSubscribePrivate))
G_DEFINE_TYPE (PinosSubscribe, pinos_subscribe, G_TYPE_OBJECT);
enum
{
PROP_0,
PROP_CONNECTION,
PROP_SERVICE,
PROP_SUBSCRIPTION_MASK,
PROP_STATE,
};
enum
{
SIGNAL_SUBSCRIPTION_EVENT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
subscription_set_state (PinosSubscribe *subscribe,
PinosSubscriptionState state)
{
PinosSubscribePrivate *priv = subscribe->priv;
if (state != priv->state) {
priv->state = state;
g_object_notify (G_OBJECT (subscribe), "state");
}
}
static void
notify_event (PinosSubscribe *subscribe,
PinosObjectData *data,
PinosSubscriptionEvent event)
{
const gchar *interface_name;
PinosSubscriptionFlags flags = 0;
interface_name = g_dbus_proxy_get_interface_name (data->proxy);
if (g_strcmp0 (interface_name, "org.pinos.Daemon1") == 0) {
flags = PINOS_SUBSCRIPTION_FLAG_DAEMON;
}
else if (g_strcmp0 (interface_name, "org.pinos.Node1") == 0) {
flags = PINOS_SUBSCRIPTION_FLAG_NODE;
}
else if (g_strcmp0 (interface_name, "org.pinos.Port1") == 0) {
flags = PINOS_SUBSCRIPTION_FLAG_PORT;
}
g_signal_emit (subscribe, signals[SIGNAL_SUBSCRIPTION_EVENT], 0,
event, flags, data->proxy);
}
static void
on_proxy_properties_changed (GDBusProxy *proxy,
GVariant *changed_properties,
GStrv invalidated_properties,
gpointer user_data)
{
PinosObjectData *data = user_data;
GVariantIter iter;
GVariant *value;
gchar *key;
GHashTable *props;
gboolean need_notify = FALSE;
props = g_object_get_data (G_OBJECT (proxy), "pinos-changed-properties");
g_variant_iter_init (&iter, changed_properties);
while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) {
if (!g_hash_table_contains (props, key))
g_hash_table_add (props, g_strdup (key));
need_notify = TRUE;
}
if (need_notify)
notify_event (data->subscribe, data, PINOS_SUBSCRIPTION_EVENT_CHANGE);
}
static void
object_data_free (PinosObjectData *data)
{
g_signal_handler_disconnect (data->proxy, data->prop_id);
g_object_unref (data->proxy);
g_free (data->sender_name);
g_free (data->object_path);
g_free (data->interface_name);
g_free (data);
}
static void
remove_data (PinosSubscribe *subscribe,
PinosObjectData *data)
{
if (data->pending) {
data->removed = TRUE;
} else {
GHashTable *props = g_object_get_data (G_OBJECT (data->proxy), "pinos-changed-properties");
g_hash_table_remove_all (props);
notify_event (subscribe, data, PINOS_SUBSCRIPTION_EVENT_REMOVE);
object_data_free (data);
}
}
static void
remove_all_data (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv = subscribe->priv;
GList *walk;
for (walk = priv->objects; walk; walk = g_list_next (walk)) {
PinosObjectData *data = walk->data;
remove_data (subscribe, data);
}
g_list_free (priv->objects);
priv->objects = NULL;
}
static void
on_proxy_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PinosObjectData *data = user_data;
PinosSubscribe *subscribe = data->subscribe;
PinosSubscribePrivate *priv = subscribe->priv;
GError *error = NULL;
GList *walk;
data->pending = FALSE;
data->proxy = g_dbus_proxy_new_finish (res, &error);
if (data->proxy == NULL) {
priv->objects = g_list_remove (priv->objects, data);
g_warning ("could not create proxy: %s", error->message);
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_ERROR);
priv->error = error;
return;
}
data->prop_id = g_signal_connect (data->proxy,
"g-properties-changed",
(GCallback) on_proxy_properties_changed,
data);
notify_event (subscribe, data, PINOS_SUBSCRIPTION_EVENT_NEW);
g_object_set_data_full (G_OBJECT (data->proxy),
"pinos-changed-properties",
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL),
(GDestroyNotify) g_hash_table_unref);
for (walk = data->tasks; walk; walk = g_list_next (walk)) {
GTask *task = walk->data;
g_task_return_pointer (task, g_object_ref (data->proxy), g_object_unref);
g_object_unref (task);
}
g_list_free (data->tasks);
data->tasks = NULL;
if (--priv->pending_proxies == 0)
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_READY);
if (data->removed) {
priv->objects = g_list_remove (priv->objects, data);
remove_data (subscribe, data);
}
}
static void
add_interface (PinosSubscribe *subscribe,
const gchar *object_path,
const gchar *interface_name,
GVariant *properties)
{
PinosSubscribePrivate *priv = subscribe->priv;
PinosObjectData *data;
data = g_new0 (PinosObjectData, 1);
data->subscribe = subscribe;
data->sender_name = g_strdup (priv->service);
data->object_path = g_strdup (object_path);
data->interface_name = g_strdup (interface_name);
data->pending = TRUE;
priv->objects = g_list_prepend (priv->objects, data);
priv->pending_proxies++;
g_dbus_proxy_new (priv->connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL, /* GDBusInterfaceInfo* */
priv->service,
object_path,
interface_name,
priv->cancellable,
on_proxy_created,
data);
}
static void
remove_interface (PinosSubscribe *subscribe,
const gchar *object_path,
const gchar *interface_name)
{
PinosSubscribePrivate *priv = subscribe->priv;
GList *walk;
for (walk = priv->objects; walk; walk = g_list_next (walk)) {
PinosObjectData *data = walk->data;
if (g_strcmp0 (data->object_path, object_path) == 0 &&
g_strcmp0 (data->interface_name, interface_name) == 0) {
priv->objects = g_list_remove (priv->objects, data);
remove_data (subscribe, data);
break;
}
}
}
static void
add_ifaces_and_properties (PinosSubscribe *subscribe,
const gchar *object_path,
GVariant *ifaces_and_properties)
{
GVariantIter iter;
const gchar *interface_name;
GVariant *properties;
g_variant_iter_init (&iter, ifaces_and_properties);
while (g_variant_iter_next (&iter,
"{&s@a{sv}}",
&interface_name,
&properties)) {
add_interface (subscribe, object_path, interface_name, properties);
g_variant_unref (properties);
}
}
static void
remove_ifaces (PinosSubscribe *subscribe,
const gchar *object_path,
const gchar **ifaces)
{
while (*ifaces) {
remove_interface (subscribe, object_path, *ifaces);
ifaces++;
}
}
static void
on_manager_proxy_signal (GDBusProxy *proxy,
const gchar *sender_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
PinosSubscribe *subscribe = user_data;
const gchar *object_path;
if (g_strcmp0 (signal_name, "InterfacesAdded") == 0) {
GVariant *ifaces_and_properties;
g_variant_get (parameters,
"(&o@a{sa{sv}})",
&object_path,
&ifaces_and_properties);
add_ifaces_and_properties (subscribe, object_path, ifaces_and_properties);
g_variant_unref (ifaces_and_properties);
} else if (g_strcmp0 (signal_name, "InterfacesRemoved") == 0) {
const gchar **ifaces;
g_variant_get (parameters,
"(&o^a&s)",
&object_path,
&ifaces);
remove_ifaces (subscribe, object_path, ifaces);
g_free (ifaces);
}
}
static void
on_managed_objects_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PinosSubscribe *subscribe = user_data;
PinosSubscribePrivate *priv = subscribe->priv;
GError *error = NULL;
GVariant *objects;
GVariant *arg0;
const gchar *object_path;
GVariant *ifaces_and_properties;
GVariantIter object_iter;
objects = g_dbus_proxy_call_finish (priv->manager_proxy, res, &error);
if (objects == NULL) {
g_warning ("could not get objects: %s", error->message);
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_ERROR);
priv->error = error;
return;
}
arg0 = g_variant_get_child_value (objects, 0);
g_variant_iter_init (&object_iter, arg0);
while (g_variant_iter_next (&object_iter,
"{&o@a{sa{sv}}}",
&object_path,
&ifaces_and_properties)) {
add_ifaces_and_properties (subscribe, object_path, ifaces_and_properties);
g_variant_unref (ifaces_and_properties);
}
g_variant_unref (arg0);
g_variant_unref (objects);
if (priv->pending_proxies == 0)
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_READY);
}
static void
manager_proxy_appeared (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv = subscribe->priv;
g_dbus_proxy_call (priv->manager_proxy,
"GetManagedObjects",
NULL, /* parameters */
G_DBUS_CALL_FLAGS_NONE,
-1,
priv->cancellable,
on_managed_objects_ready,
subscribe);
}
static void
manager_proxy_disappeared (PinosSubscribe *subscribe)
{
remove_all_data (subscribe);
}
static void
on_manager_proxy_name_owner (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
PinosSubscribe *subscribe = user_data;
PinosSubscribePrivate *priv = subscribe->priv;
gchar *name_owner;
g_object_get (priv->manager_proxy, "g-name-owner", &name_owner, NULL);
if (name_owner) {
manager_proxy_appeared (subscribe);
g_free (name_owner);
} else {
manager_proxy_disappeared (subscribe);
}
}
static void
connect_client_signals (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv = subscribe->priv;
priv->owner_id = g_signal_connect (priv->manager_proxy, "notify::g-name-owner",
(GCallback) on_manager_proxy_name_owner, subscribe);
priv->signal_id = g_signal_connect (priv->manager_proxy, "g-signal",
(GCallback) on_manager_proxy_signal, subscribe);
}
static void
on_manager_proxy_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PinosSubscribe *subscribe = user_data;
PinosSubscribePrivate *priv = subscribe->priv;
GError *error = NULL;
priv->manager_proxy = g_dbus_proxy_new_finish (res, &error);
if (priv->manager_proxy == NULL)
goto manager_error;
connect_client_signals (subscribe);
on_manager_proxy_name_owner (G_OBJECT (priv->manager_proxy), NULL, subscribe);
g_object_unref (subscribe);
return;
/* ERRORS */
manager_error:
{
g_warning ("could not create client manager: %s", error->message);
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_ERROR);
priv->error = error;
g_object_unref (subscribe);
return;
}
}
static void
install_subscription (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv = subscribe->priv;
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_CONNECTING);
g_dbus_proxy_new (priv->connection,
G_DBUS_PROXY_FLAGS_NONE,
NULL, /* GDBusInterfaceInfo* */
priv->service,
PINOS_DBUS_OBJECT_PREFIX,
"org.freedesktop.DBus.ObjectManager",
priv->cancellable,
on_manager_proxy_ready,
g_object_ref (subscribe));
}
static void
uninstall_subscription (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv = subscribe->priv;
if (priv->manager_proxy) {
g_signal_handler_disconnect (priv->manager_proxy, priv->owner_id);
g_signal_handler_disconnect (priv->manager_proxy, priv->signal_id);
g_clear_object (&priv->manager_proxy);
}
g_clear_error (&priv->error);
subscription_set_state (subscribe, PINOS_SUBSCRIPTION_STATE_UNCONNECTED);
}
static void
pinos_subscribe_get_property (GObject *_object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PinosSubscribe *subscribe = PINOS_SUBSCRIBE (_object);
PinosSubscribePrivate *priv = subscribe->priv;
switch (prop_id) {
case PROP_CONNECTION:
g_value_set_object (value, priv->connection);
break;
case PROP_SERVICE:
g_value_set_string (value, priv->service);
break;
case PROP_SUBSCRIPTION_MASK:
g_value_set_flags (value, priv->subscription_mask);
break;
case PROP_STATE:
g_value_set_enum (value, priv->state);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (subscribe, prop_id, pspec);
break;
}
}
static void
pinos_subscribe_set_property (GObject *_object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PinosSubscribe *subscribe = PINOS_SUBSCRIBE (_object);
PinosSubscribePrivate *priv = subscribe->priv;
switch (prop_id) {
case PROP_CONNECTION:
{
uninstall_subscription (subscribe);
if (priv->connection)
g_object_unref (priv->connection);
priv->connection = g_value_dup_object (value);
if (priv->connection)
install_subscription (subscribe);
break;
}
case PROP_SERVICE:
g_free (priv->service);
priv->service = g_value_dup_string (value);
break;
case PROP_SUBSCRIPTION_MASK:
priv->subscription_mask = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (subscribe, prop_id, pspec);
break;
}
}
static void
pinos_subscribe_finalize (GObject * object)
{
PinosSubscribe *subscribe = PINOS_SUBSCRIBE (object);
PinosSubscribePrivate *priv = subscribe->priv;
remove_all_data (subscribe);
g_cancellable_cancel (priv->cancellable);
if (priv->manager_proxy)
g_object_unref (priv->manager_proxy);
g_object_unref (priv->cancellable);
g_free (priv->service);
G_OBJECT_CLASS (pinos_subscribe_parent_class)->finalize (object);
}
static void
pinos_subscribe_class_init (PinosSubscribeClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (PinosSubscribePrivate));
gobject_class->finalize = pinos_subscribe_finalize;
gobject_class->set_property = pinos_subscribe_set_property;
gobject_class->get_property = pinos_subscribe_get_property;
/**
* PinosSubscribe:connection
*
* The connection of the subscribe.
*/
g_object_class_install_property (gobject_class,
PROP_CONNECTION,
g_param_spec_object ("connection",
"Connection",
"The DBus connection",
G_TYPE_DBUS_CONNECTION,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PinosSubscribe:service
*
* The service of the subscribe.
*/
g_object_class_install_property (gobject_class,
PROP_SERVICE,
g_param_spec_string ("service",
"Service",
"The service",
PINOS_DBUS_SERVICE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PinosSubscribe:subscription-mask
*
* A mask for what object notifications will be signaled with
* PinosSubscribe:subscription-event
*/
g_object_class_install_property (gobject_class,
PROP_SUBSCRIPTION_MASK,
g_param_spec_flags ("subscription-mask",
"Subscription Mask",
"The object to receive subscription events of",
PINOS_TYPE_SUBSCRIPTION_FLAGS,
0,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PinosSubscribe:state
*
* The state of the subscription
*/
g_object_class_install_property (gobject_class,
PROP_STATE,
g_param_spec_enum ("state",
"State",
"The state",
PINOS_TYPE_SUBSCRIPTION_STATE,
PINOS_SUBSCRIPTION_STATE_UNCONNECTED,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PinosSubscribe:subscription-event
* @subscribe: The #PinosSubscribe emitting the signal.
* @event: A #PinosSubscriptionEvent
* @flags: #PinosSubscriptionFlags indicating the object
* @id: the unique and opaque object id
*
* Notify about a new object that was added/removed/modified.
*/
signals[SIGNAL_SUBSCRIPTION_EVENT] = g_signal_new ("subscription-event",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
3,
PINOS_TYPE_SUBSCRIPTION_EVENT,
PINOS_TYPE_SUBSCRIPTION_FLAGS,
G_TYPE_POINTER);
}
static void
pinos_subscribe_init (PinosSubscribe * subscribe)
{
PinosSubscribePrivate *priv = subscribe->priv = PINOS_SUBSCRIBE_GET_PRIVATE (subscribe);
priv->service = g_strdup (PINOS_DBUS_SERVICE);
priv->state = PINOS_SUBSCRIPTION_STATE_UNCONNECTED;
priv->cancellable = g_cancellable_new ();
}
/**
* pinos_subscribe_new:
* @name: an application name
* @properties: optional properties
*
* Make a new unconnected #PinosSubscribe
*
* Returns: a new unconnected #PinosSubscribe
*/
PinosSubscribe *
pinos_subscribe_new (void)
{
return g_object_new (PINOS_TYPE_SUBSCRIBE, NULL);
}
PinosSubscriptionState
pinos_subscribe_get_state (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv;
g_return_val_if_fail (PINOS_IS_SUBSCRIBE (subscribe), PINOS_SUBSCRIPTION_STATE_ERROR);
priv = subscribe->priv;
return priv->state;
}
GError *
pinos_subscribe_get_error (PinosSubscribe *subscribe)
{
PinosSubscribePrivate *priv;
g_return_val_if_fail (PINOS_IS_SUBSCRIBE (subscribe), NULL);
priv = subscribe->priv;
return priv->error;
}
static gint
compare_data (PinosObjectData *data,
const gchar *name,
const gchar *object_path,
const gchar *interface_name)
{
gint res;
if ((res = g_strcmp0 (data->sender_name, name)) != 0)
return res;
if ((res = g_strcmp0 (data->object_path, object_path)) != 0)
return res;
return g_strcmp0 (data->interface_name, interface_name);
}
/**
* pinos_subscribe_get_proxy:
* @subscribe: a #PinosSubscribe
* @name: the owner name
* @object_path: the object path
* @interface_name: the interface name
* @cancellable: a #GCancellable
* @callback: a #GAsyncReadyCallback
* @user_data: extra user data
*
* Asyncronously get a #GDBusProxy for the object with the given
* @name/@object_path/@interface.
*
* Use pinos_subscribe_get_proxy_finish() to get the result or error in
* @callback.
*/
void
pinos_subscribe_get_proxy (PinosSubscribe *subscribe,
const gchar *name,
const gchar *object_path,
const gchar *interface_name,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
PinosSubscribePrivate *priv;
GList *walk;
g_return_if_fail (PINOS_IS_SUBSCRIBE (subscribe));
priv = subscribe->priv;
for (walk = priv->objects; walk; walk = g_list_next (walk)) {
PinosObjectData *data = walk->data;
if (compare_data (data, name, object_path, interface_name) == 0) {
GTask *task;
task = g_task_new (subscribe,
cancellable,
callback,
user_data);
if (data->pending) {
data->tasks = g_list_prepend (data->tasks, task);
} else {
if (data->proxy)
g_task_return_pointer (task, g_object_ref (data->proxy), g_object_unref);
else
g_task_return_error (task, NULL);
g_object_unref (task);
}
break;
}
}
}
/**
* pinos_subscribe_get_proxy_finish:
* @subscribe: a #PinosSubscribe
* @res: a #GAsyncResult
* @error: a #GError or %NULL
*
* Get the requested #GDBusProxy. This function should be called in the callback
* of pinos_subscribe_get_proxy() to get the result or error.
*
* Returns: the requested #GDBusProxy. If %NULL is returned, @error will
* be set.
*/
GDBusProxy *
pinos_subscribe_get_proxy_finish (PinosSubscribe *subscribe,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}