pipewire/pinos/server/node.c
Wim Taymans 8ada6736c0 Cleanups
Make events and command specific to the node
Remove some unused code
Improve state changes
Use easier fixate by just taking the element default value
Fix reuse buffer in the proxy
2016-09-06 16:43:37 +02:00

1299 lines
35 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 <string.h>
#include <stdlib.h>
#include <poll.h>
#include <errno.h>
#include <sys/eventfd.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "pinos/client/pinos.h"
#include "pinos/client/enumtypes.h"
#include "pinos/server/node.h"
#include "pinos/server/daemon.h"
#include "pinos/dbus/org-pinos.h"
#define PINOS_NODE_GET_PRIVATE(node) \
(G_TYPE_INSTANCE_GET_PRIVATE ((node), PINOS_TYPE_NODE, PinosNodePrivate))
struct _PinosNodePrivate
{
PinosDaemon *daemon;
PinosNode1 *iface;
gchar *sender;
gchar *object_path;
gchar *name;
unsigned int max_input_ports;
unsigned int max_output_ports;
unsigned int n_input_ports;
unsigned int n_output_ports;
uint32_t *input_port_ids;
uint32_t *output_port_ids;
PinosNodeState state;
GError *error;
guint idle_timeout;
PinosProperties *properties;
unsigned int n_poll;
SpaPollItem poll[16];
bool rebuild_fds;
SpaPollFd fds[16];
unsigned int n_fds;
gboolean running;
pthread_t thread;
GHashTable *links;
};
G_DEFINE_ABSTRACT_TYPE (PinosNode, pinos_node, G_TYPE_OBJECT);
enum
{
PROP_0,
PROP_DAEMON,
PROP_SENDER,
PROP_OBJECT_PATH,
PROP_NAME,
PROP_STATE,
PROP_PROPERTIES,
PROP_NODE,
PROP_NODE_STATE,
};
enum
{
SIGNAL_REMOVE,
SIGNAL_PORT_ADDED,
SIGNAL_PORT_REMOVED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static PinosDirection
get_port_direction (PinosNode *node, guint id)
{
PinosNodePrivate *priv = node->priv;
PinosDirection direction;
direction = id < priv->max_input_ports ? PINOS_DIRECTION_INPUT : PINOS_DIRECTION_OUTPUT;
return direction;
}
static void
update_port_ids (PinosNode *node, gboolean create)
{
PinosNodePrivate *priv = node->priv;
if (node->node == NULL)
return;
spa_node_get_n_ports (node->node,
&priv->n_input_ports,
&priv->max_input_ports,
&priv->n_output_ports,
&priv->max_output_ports);
g_debug ("node %p: update_port ids %u, %u", node, priv->max_input_ports, priv->max_output_ports);
priv->input_port_ids = g_realloc_n (priv->input_port_ids, priv->max_input_ports, sizeof (uint32_t));
priv->output_port_ids = g_realloc_n (priv->output_port_ids, priv->max_output_ports, sizeof (uint32_t));
spa_node_get_port_ids (node->node,
priv->max_input_ports,
priv->input_port_ids,
priv->max_output_ports,
priv->output_port_ids);
}
static void *
loop (void *user_data)
{
PinosNode *this = user_data;
PinosNodePrivate *priv = this->priv;
unsigned int i, j;
g_debug ("node %p: enter thread", this);
while (priv->running) {
SpaPollNotifyData ndata;
unsigned int n_idle = 0;
int r;
/* prepare */
for (i = 0; i < priv->n_poll; i++) {
SpaPollItem *p = &priv->poll[i];
if (p->enabled && p->idle_cb) {
ndata.fds = NULL;
ndata.n_fds = 0;
ndata.user_data = p->user_data;
p->idle_cb (&ndata);
n_idle++;
}
}
if (n_idle > 0)
continue;
/* rebuild */
if (priv->rebuild_fds) {
g_debug ("node %p: rebuild fds", this);
priv->n_fds = 1;
for (i = 0; i < priv->n_poll; i++) {
SpaPollItem *p = &priv->poll[i];
if (!p->enabled)
continue;
for (j = 0; j < p->n_fds; j++)
priv->fds[priv->n_fds + j] = p->fds[j];
p->fds = &priv->fds[priv->n_fds];
priv->n_fds += p->n_fds;
}
priv->rebuild_fds = false;
}
/* before */
for (i = 0; i < priv->n_poll; i++) {
SpaPollItem *p = &priv->poll[i];
if (p->enabled && p->before_cb) {
ndata.fds = p->fds;
ndata.n_fds = p->n_fds;
ndata.user_data = p->user_data;
p->before_cb (&ndata);
}
}
r = poll ((struct pollfd *) priv->fds, priv->n_fds, -1);
if (r < 0) {
if (errno == EINTR)
continue;
break;
}
if (r == 0) {
g_debug ("node %p: select timeout", this);
break;
}
/* check wakeup */
if (priv->fds[0].revents & POLLIN) {
uint64_t u;
if (read (priv->fds[0].fd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
g_warning ("node %p: failed to read fd", strerror (errno));
continue;
}
/* after */
for (i = 0; i < priv->n_poll; i++) {
SpaPollItem *p = &priv->poll[i];
if (p->enabled && p->after_cb) {
ndata.fds = p->fds;
ndata.n_fds = p->n_fds;
ndata.user_data = p->user_data;
p->after_cb (&ndata);
}
}
}
g_debug ("node %p: leave thread", this);
return NULL;
}
static void
wakeup_thread (PinosNode *this)
{
PinosNodePrivate *priv = this->priv;
uint64_t u = 1;
if (write (priv->fds[0].fd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
g_warning ("node %p: failed to write fd", strerror (errno));
}
static void
start_thread (PinosNode *this)
{
PinosNodePrivate *priv = this->priv;
int err;
if (!priv->running) {
priv->running = true;
if ((err = pthread_create (&priv->thread, NULL, loop, this)) != 0) {
g_warning ("node %p: can't create thread", strerror (err));
priv->running = false;
}
}
}
static void
stop_thread (PinosNode *this)
{
PinosNodePrivate *priv = this->priv;
if (priv->running) {
priv->running = false;
wakeup_thread (this);
pthread_join (priv->thread, NULL);
}
}
static void
pause_node (PinosNode *this)
{
SpaResult res;
SpaNodeCommand cmd;
g_debug ("node %p: pause node", this);
cmd.type = SPA_NODE_COMMAND_PAUSE;
if ((res = spa_node_send_command (this->node, &cmd)) < 0)
g_debug ("got error %d", res);
}
static void
suspend_node (PinosNode *this)
{
SpaResult res;
g_debug ("node %p: suspend node", this);
if ((res = spa_node_port_set_format (this->node, 0, 0, NULL)) < 0)
g_warning ("error unset format output: %d", res);
}
static gboolean
node_set_state (PinosNode *this,
PinosNodeState state)
{
g_debug ("node %p: set state %s", this, pinos_node_state_as_string (state));
switch (state) {
case PINOS_NODE_STATE_SUSPENDED:
suspend_node (this);
break;
case PINOS_NODE_STATE_INITIALIZING:
break;
case PINOS_NODE_STATE_IDLE:
pause_node (this);
break;
case PINOS_NODE_STATE_RUNNING:
break;
case PINOS_NODE_STATE_ERROR:
break;
}
pinos_node_update_state (this, state);
return TRUE;
}
typedef struct {
PinosNode *node;
PinosDirection direction;
guint port_id;
} PortData;
static gboolean
do_signal_port_added (PortData *data)
{
g_signal_emit (data->node, signals[SIGNAL_PORT_ADDED], 0, data->direction, data->port_id);
g_slice_free (PortData, data);
return FALSE;
}
static gboolean
do_signal_port_removed (PortData *data)
{
g_signal_emit (data->node, signals[SIGNAL_PORT_REMOVED], 0, data->port_id);
g_slice_free (PortData, data);
return FALSE;
}
static void
on_node_event (SpaNode *node, SpaNodeEvent *event, void *user_data)
{
PinosNode *this = user_data;
PinosNodePrivate *priv = this->priv;
switch (event->type) {
case SPA_NODE_EVENT_TYPE_PORT_ADDED:
{
SpaNodeEventPortAdded *pa = event->data;
PortData *data;
update_port_ids (this, FALSE);
data = g_slice_new (PortData);
data->node = this;
data->direction = get_port_direction (this, pa->port_id);
data->port_id = pa->port_id;
g_main_context_invoke (NULL, (GSourceFunc) do_signal_port_added, data);
break;
}
case SPA_NODE_EVENT_TYPE_PORT_REMOVED:
{
SpaNodeEventPortRemoved *pr = event->data;
PortData *data;
update_port_ids (this, FALSE);
data = g_slice_new (PortData);
data->node = this;
data->port_id = pr->port_id;
g_main_context_invoke (NULL, (GSourceFunc) do_signal_port_removed, data);
break;
}
case SPA_NODE_EVENT_TYPE_STATE_CHANGE:
{
SpaNodeEventStateChange *sc = event->data;
g_debug ("node %p: update SPA state to %d", this, sc->state);
if (sc->state == SPA_NODE_STATE_CONFIGURE) {
update_port_ids (this, FALSE);
}
g_object_notify (G_OBJECT (this), "node-state");
break;
}
case SPA_NODE_EVENT_TYPE_ADD_POLL:
{
SpaPollItem *poll = event->data;
g_debug ("node %p: add poll %d, n_fds %d", this, poll->id, poll->n_fds);
priv->poll[priv->n_poll] = *poll;
priv->n_poll++;
if (poll->n_fds)
priv->rebuild_fds = true;
wakeup_thread (this);
start_thread (this);
break;
}
case SPA_NODE_EVENT_TYPE_UPDATE_POLL:
{
unsigned int i;
SpaPollItem *poll = event->data;
for (i = 0; i < priv->n_poll; i++) {
if (priv->poll[i].id == poll->id)
priv->poll[i] = *poll;
}
if (poll->n_fds)
priv->rebuild_fds = true;
wakeup_thread (this);
break;
}
case SPA_NODE_EVENT_TYPE_REMOVE_POLL:
{
SpaPollItem *poll = event->data;
unsigned int i;
g_debug ("node %p: remove poll %d %d", this, poll->n_fds, priv->n_poll);
for (i = 0; i < priv->n_poll; i++) {
if (priv->poll[i].id == poll->id) {
priv->n_poll--;
for (; i < priv->n_poll; i++)
priv->poll[i] = priv->poll[i+1];
break;
}
}
if (priv->n_poll > 0) {
priv->rebuild_fds = true;
wakeup_thread (this);
} else {
stop_thread (this);
}
break;
}
case SPA_NODE_EVENT_TYPE_HAVE_OUTPUT:
{
PinosLink *link;
SpaPortOutputInfo oinfo[1] = { 0, };
SpaResult res;
if ((res = spa_node_port_pull_output (node, 1, oinfo)) < 0) {
g_warning ("node %p: got pull error %d, %d", this, res, oinfo[0].status);
break;
}
link = g_hash_table_lookup (priv->links, GUINT_TO_POINTER (oinfo[0].port_id));
if (link) {
SpaPortInputInfo iinfo[1];
iinfo[0].port_id = link->input_port;
iinfo[0].buffer_id = oinfo[0].buffer_id;
iinfo[0].flags = SPA_PORT_INPUT_FLAG_NONE;
if ((res = spa_node_port_push_input (link->input_node->node, 1, iinfo)) < 0)
g_warning ("node %p: error pushing buffer: %d, %d", this, res, iinfo[0].status);
}
break;
}
case SPA_NODE_EVENT_TYPE_REUSE_BUFFER:
{
PinosLink *link;
SpaResult res;
SpaNodeEventReuseBuffer *rb = event->data;
link = g_hash_table_lookup (priv->links, GUINT_TO_POINTER (rb->port_id));
if (link) {
if ((res = spa_node_port_reuse_buffer (link->output_node->node,
link->output_port,
rb->buffer_id)) < 0)
g_warning ("node %p: error reuse buffer: %d", node, res);
}
break;
}
default:
g_debug ("node %p: got event %d", this, event->type);
break;
}
}
static gboolean
handle_remove (PinosNode1 *interface,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
PinosNode *node = user_data;
g_debug ("node %p: remove", node);
pinos_node_remove (node);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("()"));
return TRUE;
}
static void
pinos_node_get_property (GObject *_object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PinosNode *node = PINOS_NODE (_object);
PinosNodePrivate *priv = node->priv;
switch (prop_id) {
case PROP_DAEMON:
g_value_set_object (value, priv->daemon);
break;
case PROP_SENDER:
g_value_set_string (value, priv->sender);
break;
case PROP_OBJECT_PATH:
g_value_set_string (value, priv->object_path);
break;
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_STATE:
g_value_set_enum (value, priv->state);
break;
case PROP_PROPERTIES:
g_value_set_boxed (value, priv->properties);
break;
case PROP_NODE:
g_value_set_pointer (value, node->node);
break;
case PROP_NODE_STATE:
g_value_set_uint (value, node->node->state);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (node, prop_id, pspec);
break;
}
}
static void
pinos_node_set_property (GObject *_object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PinosNode *node = PINOS_NODE (_object);
PinosNodePrivate *priv = node->priv;
switch (prop_id) {
case PROP_DAEMON:
priv->daemon = g_value_dup_object (value);
break;
case PROP_SENDER:
priv->sender = g_value_dup_string (value);
break;
case PROP_NAME:
priv->name = g_value_dup_string (value);
break;
case PROP_PROPERTIES:
if (priv->properties)
pinos_properties_free (priv->properties);
priv->properties = g_value_dup_boxed (value);
break;
case PROP_NODE:
node->node = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (node, prop_id, pspec);
break;
}
}
static void
node_register_object (PinosNode *node)
{
PinosNodePrivate *priv = node->priv;
PinosDaemon *daemon = priv->daemon;
PinosObjectSkeleton *skel;
skel = pinos_object_skeleton_new (PINOS_DBUS_OBJECT_NODE);
pinos_object_skeleton_set_node1 (skel, priv->iface);
g_free (priv->object_path);
priv->object_path = pinos_daemon_export_uniquely (daemon, G_DBUS_OBJECT_SKELETON (skel));
g_object_unref (skel);
g_debug ("node %p: register object %s", node, priv->object_path);
pinos_daemon_add_node (daemon, node);
return;
}
static void
node_unregister_object (PinosNode *node)
{
PinosNodePrivate *priv = node->priv;
g_debug ("node %p: unregister object %s", node, priv->object_path);
pinos_daemon_unexport (priv->daemon, priv->object_path);
g_clear_pointer (&priv->object_path, g_free);
pinos_daemon_remove_node (priv->daemon, node);
}
static void
on_property_notify (GObject *obj,
GParamSpec *pspec,
gpointer user_data)
{
PinosNode *node = user_data;
PinosNodePrivate *priv = node->priv;
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "sender") == 0) {
pinos_node1_set_owner (priv->iface, priv->sender);
}
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "name") == 0) {
pinos_node1_set_name (priv->iface, pinos_node_get_name (node));
}
if (pspec == NULL || strcmp (g_param_spec_get_name (pspec), "properties") == 0) {
PinosProperties *props = pinos_node_get_properties (node);
pinos_node1_set_properties (priv->iface, props ? pinos_properties_to_variant (props) : NULL);
}
}
static void
pinos_node_constructed (GObject * obj)
{
PinosNode *this = PINOS_NODE (obj);
PinosNodePrivate *priv = this->priv;
SpaResult res;
g_debug ("node %p: constructed", this);
g_signal_connect (this, "notify", (GCallback) on_property_notify, this);
G_OBJECT_CLASS (pinos_node_parent_class)->constructed (obj);
priv->fds[0].fd = eventfd (0, 0);
priv->fds[0].events = POLLIN | POLLPRI | POLLERR;
priv->fds[0].revents = 0;
priv->n_fds = 1;
if ((res = spa_node_set_event_callback (this->node, on_node_event, this)) < 0)
g_warning ("node %p: error setting callback", this);
update_port_ids (this, TRUE);
if (priv->sender == NULL) {
priv->sender = g_strdup (pinos_daemon_get_sender (priv->daemon));
}
on_property_notify (G_OBJECT (this), NULL, this);
node_register_object (this);
}
static void
pinos_node_dispose (GObject * obj)
{
PinosNode *node = PINOS_NODE (obj);
PinosNodePrivate *priv = node->priv;
g_debug ("node %p: dispose", node);
pinos_node_set_state (node, PINOS_NODE_STATE_SUSPENDED);
stop_thread (node);
node_unregister_object (node);
g_hash_table_unref (priv->links);
G_OBJECT_CLASS (pinos_node_parent_class)->dispose (obj);
}
static void
pinos_node_finalize (GObject * obj)
{
PinosNode *node = PINOS_NODE (obj);
PinosNodePrivate *priv = node->priv;
g_debug ("node %p: finalize", node);
g_clear_object (&priv->daemon);
g_clear_object (&priv->iface);
g_free (priv->sender);
g_free (priv->name);
g_clear_error (&priv->error);
if (priv->properties)
pinos_properties_free (priv->properties);
g_free (priv->input_port_ids);
g_free (priv->output_port_ids);
G_OBJECT_CLASS (pinos_node_parent_class)->finalize (obj);
}
static void
pinos_node_class_init (PinosNodeClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
PinosNodeClass *node_class = PINOS_NODE_CLASS (klass);
g_type_class_add_private (klass, sizeof (PinosNodePrivate));
gobject_class->constructed = pinos_node_constructed;
gobject_class->dispose = pinos_node_dispose;
gobject_class->finalize = pinos_node_finalize;
gobject_class->set_property = pinos_node_set_property;
gobject_class->get_property = pinos_node_get_property;
g_object_class_install_property (gobject_class,
PROP_DAEMON,
g_param_spec_object ("daemon",
"Daemon",
"The Daemon",
PINOS_TYPE_DAEMON,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_SENDER,
g_param_spec_string ("sender",
"Sender",
"The Sender",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_OBJECT_PATH,
g_param_spec_string ("object-path",
"Object Path",
"The object path",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_NAME,
g_param_spec_string ("name",
"Name",
"The node name",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_STATE,
g_param_spec_enum ("state",
"State",
"The state of the node",
PINOS_TYPE_NODE_STATE,
PINOS_NODE_STATE_SUSPENDED,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_PROPERTIES,
g_param_spec_boxed ("properties",
"Properties",
"The properties of the node",
PINOS_TYPE_PROPERTIES,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_NODE,
g_param_spec_pointer ("node",
"Node",
"The SPA node",
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_NODE_STATE,
g_param_spec_uint ("node-state",
"Node State",
"The state of the SPA node",
0,
G_MAXUINT,
SPA_NODE_STATE_INIT,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
signals[SIGNAL_REMOVE] = g_signal_new ("remove",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
0,
G_TYPE_NONE);
signals[SIGNAL_PORT_ADDED] = g_signal_new ("port-added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
2,
PINOS_TYPE_DIRECTION,
G_TYPE_UINT);
signals[SIGNAL_PORT_REMOVED] = g_signal_new ("port-removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
1,
G_TYPE_UINT);
node_class->set_state = node_set_state;
}
static void
pinos_node_init (PinosNode * node)
{
PinosNodePrivate *priv = node->priv = PINOS_NODE_GET_PRIVATE (node);
g_debug ("node %p: new", node);
priv->iface = pinos_node1_skeleton_new ();
g_signal_connect (priv->iface, "handle-remove",
(GCallback) handle_remove,
node);
priv->state = PINOS_NODE_STATE_SUSPENDED;
pinos_node1_set_state (priv->iface, PINOS_NODE_STATE_SUSPENDED);
priv->links = g_hash_table_new (g_direct_hash, g_direct_equal);
}
/**
* pinos_node_new:
* @daemon: a #PinosDaemon
* @sender: the path of the owner
* @name: a name
* @properties: extra properties
*
* Create a new #PinosNode.
*
* Returns: a new #PinosNode
*/
PinosNode *
pinos_node_new (PinosDaemon *daemon,
const gchar *sender,
const gchar *name,
PinosProperties *properties,
SpaNode *node)
{
g_return_val_if_fail (PINOS_IS_DAEMON (daemon), NULL);
return g_object_new (PINOS_TYPE_NODE,
"daemon", daemon,
"sender", sender,
"name", name,
"properties", properties,
"node", node,
NULL);
}
/**
* pinos_node_get_name:
* @node: a #PinosNode
*
* Get the name of @node
*
* Returns: the name of @node
*/
const gchar *
pinos_node_get_name (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
priv = node->priv;
return priv->name;
}
/**
* pinos_node_get_state:
* @node: a #PinosNode
*
* Get the state of @node
*
* Returns: the state of @node
*/
PinosNodeState
pinos_node_get_state (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), PINOS_NODE_STATE_ERROR);
priv = node->priv;
return priv->state;
}
/**
* pinos_node_get_properties:
* @node: a #PinosNode
*
* Get the properties of @node
*
* Returns: the properties of @node
*/
PinosProperties *
pinos_node_get_properties (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
priv = node->priv;
return priv->properties;
}
/**
* pinos_node_get_daemon:
* @node: a #PinosNode
*
* Get the daemon of @node.
*
* Returns: the daemon of @node.
*/
PinosDaemon *
pinos_node_get_daemon (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
priv = node->priv;
return priv->daemon;
}
/**
* pinos_node_get_sender:
* @node: a #PinosNode
*
* Get the owner path of @node.
*
* Returns: the owner path of @node.
*/
const gchar *
pinos_node_get_sender (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
priv = node->priv;
return priv->sender;
}
/**
* pinos_node_get_object_path:
* @node: a #PinosNode
*
* Get the object path of @node.
*
* Returns: the object path of @node.
*/
const gchar *
pinos_node_get_object_path (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
priv = node->priv;
return priv->object_path;
}
/**
* pinos_node_remove:
* @node: a #PinosNode
*
* Remove @node. This will stop the transfer on the node and
* free the resources allocated by @node.
*/
void
pinos_node_remove (PinosNode *node)
{
g_return_if_fail (PINOS_IS_NODE (node));
g_debug ("node %p: remove", node);
g_signal_emit (node, signals[SIGNAL_REMOVE], 0, NULL);
}
/**
* pinos_node_get_free_port_id:
* @node: a #PinosNode
* @direction: a #PinosDirection
*
* Find a new unused port id in @node with @direction
*
* Returns: the new port id of %SPA_INVALID_ID on error
*/
guint
pinos_node_get_free_port_id (PinosNode *node,
PinosDirection direction)
{
PinosNodePrivate *priv;
guint i, free_port = 0, n_ports, max_ports;
uint32_t *ports;
g_return_val_if_fail (PINOS_IS_NODE (node), -1);
priv = node->priv;
if (direction == PINOS_DIRECTION_INPUT) {
max_ports = priv->max_input_ports;
n_ports = priv->n_input_ports;
ports = priv->input_port_ids;
} else {
max_ports = priv->max_output_ports;
n_ports = priv->n_output_ports;
ports = priv->output_port_ids;
}
g_debug ("node %p: direction %d max %u, n %u", node, direction, max_ports, n_ports);
for (i = 0; i < n_ports; i++) {
if (free_port < ports[i])
break;
if (g_hash_table_lookup (priv->links, GUINT_TO_POINTER (free_port)) == NULL && free_port < max_ports)
return free_port;
free_port = ports[i] + 1;
}
if (free_port >= max_ports)
return -1;
return free_port;
}
static void
do_remove_link (PinosLink *link, PinosNode *node)
{
g_hash_table_remove (link->output_node->priv->links, GUINT_TO_POINTER (link->output_port));
if (g_hash_table_size (link->output_node->priv->links) == 0)
pinos_node_report_idle (link->output_node);
g_hash_table_remove (link->input_node->priv->links, GUINT_TO_POINTER (link->input_port));
if (g_hash_table_size (link->input_node->priv->links) == 0)
pinos_node_report_idle (link->input_node);
}
/**
* pinos_node_link:
* @output_node: a #PinosNode
* @output_port: a port
* @input_node: a #PinosNode
* @input_port: a port
* @format_filter: a format filter
* @properties: extra properties
*
* Make a link between @output_node and @input_node on the given ports.
*
* If the ports were already linked, the existing linke will be returned.
*
* If the source port was linked to a different destination node or port, it
* will be relinked.
*
* Returns: a new #PinosLink
*/
PinosLink *
pinos_node_link (PinosNode *output_node,
guint output_port,
PinosNode *input_node,
guint input_port,
GPtrArray *format_filter,
PinosProperties *properties)
{
PinosNodePrivate *priv;
PinosLink *link;
g_return_val_if_fail (PINOS_IS_NODE (output_node), NULL);
g_return_val_if_fail (PINOS_IS_NODE (input_node), NULL);
if (get_port_direction (output_node, output_port) != PINOS_DIRECTION_OUTPUT) {
PinosNode *tmp;
guint tmp_port;
tmp = output_node;
output_node = input_node;
input_node = tmp;
tmp_port = output_port;
output_port = input_port;
input_port = tmp_port;
}
priv = output_node->priv;
link = g_hash_table_lookup (priv->links, GUINT_TO_POINTER (output_port));
if (link) {
link->input_node = input_node;
link->input_port = input_port;
g_object_ref (link);
} else {
link = g_object_new (PINOS_TYPE_LINK,
"daemon", priv->daemon,
"output-node", output_node,
"output-port", output_port,
"input-node", input_node,
"input-port", input_port,
"format-filter", format_filter,
"properties", properties,
NULL);
g_signal_connect (link,
"remove",
(GCallback) do_remove_link,
output_node);
g_hash_table_insert (priv->links, GUINT_TO_POINTER (output_port), link);
g_hash_table_insert (input_node->priv->links, GUINT_TO_POINTER (input_port), link);
}
return link;
}
/**
* pinos_node_get_links:
* @node: a #PinosNode
*
* Get the links in @node.
*
* Returns: a #GList of #PinosLink g_list_free after usage.
*/
GList *
pinos_node_get_links (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_val_if_fail (PINOS_IS_NODE (node), NULL);
priv = node->priv;
return g_hash_table_get_values (priv->links);
}
static void
remove_idle_timeout (PinosNode *node)
{
PinosNodePrivate *priv = node->priv;
if (priv->idle_timeout) {
g_source_remove (priv->idle_timeout);
priv->idle_timeout = 0;
}
}
/**
* pinos_node_set_state:
* @node: a #PinosNode
* @state: a #PinosNodeState
*
* Set the state of @node to @state.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_node_set_state (PinosNode *node,
PinosNodeState state)
{
PinosNodeClass *klass;
gboolean res;
g_return_val_if_fail (PINOS_IS_NODE (node), FALSE);
klass = PINOS_NODE_GET_CLASS (node);
remove_idle_timeout (node);
g_debug ("node %p: set state to %s", node, pinos_node_state_as_string (state));
if (klass->set_state)
res = klass->set_state (node, state);
else
res = FALSE;
return res;
}
/**
* pinos_node_update_state:
* @node: a #PinosNode
* @state: a #PinosNodeState
*
* Update the state of a node. This method is used from
* inside @node itself.
*/
void
pinos_node_update_state (PinosNode *node,
PinosNodeState state)
{
PinosNodePrivate *priv;
g_return_if_fail (PINOS_IS_NODE (node));
priv = node->priv;
if (priv->state != state) {
g_debug ("node %p: update state to %s", node, pinos_node_state_as_string (state));
priv->state = state;
pinos_node1_set_state (priv->iface, state);
g_object_notify (G_OBJECT (node), "state");
}
}
/**
* pinos_node_report_error:
* @node: a #PinosNode
* @error: a #GError
*
* Report an error from within @node.
*/
void
pinos_node_report_error (PinosNode *node,
GError *error)
{
PinosNodePrivate *priv;
g_return_if_fail (PINOS_IS_NODE (node));
priv = node->priv;
g_clear_error (&priv->error);
remove_idle_timeout (node);
priv->error = error;
priv->state = PINOS_NODE_STATE_ERROR;
g_debug ("node %p: got error state %s", node, error->message);
g_object_notify (G_OBJECT (node), "state");
}
static gboolean
idle_timeout (PinosNode *node)
{
PinosNodePrivate *priv = node->priv;
priv->idle_timeout = 0;
g_debug ("node %p: idle timeout", node);
pinos_node_set_state (node, PINOS_NODE_STATE_SUSPENDED);
return G_SOURCE_REMOVE;
}
/**
* pinos_node_report_idle:
* @node: a #PinosNode
*
* Mark @node as being idle. This will start a timeout that will
* set the node to SUSPENDED.
*/
void
pinos_node_report_idle (PinosNode *node)
{
PinosNodePrivate *priv;
g_return_if_fail (PINOS_IS_NODE (node));
priv = node->priv;
g_debug ("node %p: report idle", node);
pinos_node_set_state (node, PINOS_NODE_STATE_IDLE);
priv->idle_timeout = g_timeout_add_seconds (3,
(GSourceFunc) idle_timeout,
node);
}
/**
* pinos_node_report_busy:
* @node: a #PinosNode
*
* Mark @node as being busy. This will set the state of the node
* to the RUNNING state.
*/
void
pinos_node_report_busy (PinosNode *node)
{
g_return_if_fail (PINOS_IS_NODE (node));
g_debug ("node %p: report busy", node);
pinos_node_set_state (node, PINOS_NODE_STATE_RUNNING);
}