/*
* call-manager.c - an example channel manager for Call channels.
*
* This channel manager emulates a protocol like XMPP Jingle, where you can
* make several simultaneous calls to the same or different contacts.
*
* Copyright © 2007-2009 Collabora Ltd.
* Copyright © 2007-2009 Nokia Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "config.h"
#include "call-manager.h"
#include
#include
#include "call-channel.h"
static void channel_manager_iface_init (gpointer, gpointer);
G_DEFINE_TYPE_WITH_CODE (ExampleCallManager,
example_call_manager,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
channel_manager_iface_init))
/* type definition stuff */
enum
{
PROP_CONNECTION = 1,
PROP_SIMULATION_DELAY,
N_PROPS
};
struct _ExampleCallManagerPrivate
{
TpBaseConnection *conn;
guint simulation_delay;
/* Map from reffed ExampleCallChannel to the same pointer; used as a
* set.
*/
GHashTable *channels;
/* Next channel will be ("CallChannel%u", next_channel_index) */
guint next_channel_index;
gulong status_changed_id;
gulong available_id;
};
static void
example_call_manager_init (ExampleCallManager *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
EXAMPLE_TYPE_CALL_MANAGER,
ExampleCallManagerPrivate);
self->priv->conn = NULL;
self->priv->channels = g_hash_table_new_full (NULL, NULL, g_object_unref,
NULL);
self->priv->status_changed_id = 0;
self->priv->available_id = 0;
}
static void
example_call_manager_close_all (ExampleCallManager *self)
{
if (self->priv->channels != NULL)
{
GHashTable *tmp = self->priv->channels;
GHashTableIter iter;
gpointer v;
self->priv->channels = NULL;
g_hash_table_iter_init (&iter, tmp);
while (g_hash_table_iter_next (&iter, NULL, &v))
tp_base_channel_close (v);
g_hash_table_unref (tmp);
}
if (self->priv->available_id != 0)
{
g_signal_handler_disconnect (self->priv->conn,
self->priv->available_id);
self->priv->available_id = 0;
}
if (self->priv->status_changed_id != 0)
{
g_signal_handler_disconnect (self->priv->conn,
self->priv->status_changed_id);
self->priv->status_changed_id = 0;
}
}
static void
dispose (GObject *object)
{
ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object);
example_call_manager_close_all (self);
g_assert (self->priv->channels == NULL);
((GObjectClass *) example_call_manager_parent_class)->dispose (
object);
}
static void
get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object);
switch (property_id)
{
case PROP_CONNECTION:
g_value_set_object (value, self->priv->conn);
break;
case PROP_SIMULATION_DELAY:
g_value_set_uint (value, self->priv->simulation_delay);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object);
switch (property_id)
{
case PROP_CONNECTION:
/* We don't ref the connection, because it owns a reference to the
* channel manager, and it guarantees that the manager's lifetime is
* less than its lifetime */
self->priv->conn = g_value_get_object (value);
break;
case PROP_SIMULATION_DELAY:
self->priv->simulation_delay = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
status_changed_cb (TpBaseConnection *conn,
guint status,
guint reason,
ExampleCallManager *self)
{
switch (status)
{
case TP_CONNECTION_STATUS_DISCONNECTED:
{
example_call_manager_close_all (self);
}
break;
default:
break;
}
}
static ExampleCallChannel *new_channel (ExampleCallManager *self,
TpHandle handle, TpHandle initiator, gpointer request_token,
gboolean initial_audio, gboolean initial_video);
static gboolean
simulate_incoming_call_cb (gpointer p)
{
ExampleCallManager *self = p;
TpHandleRepoIface *contact_repo;
TpHandle caller;
/* do nothing if we've been disconnected while waiting for the contact to
* call us */
if (self->priv->available_id == 0)
return FALSE;
/* We're called by someone whose ID on the IM service is "caller" */
contact_repo = tp_base_connection_get_handles (self->priv->conn,
TP_HANDLE_TYPE_CONTACT);
caller = tp_handle_ensure (contact_repo, "caller", NULL, NULL);
new_channel (self, caller, caller, NULL, TRUE, FALSE);
return FALSE;
}
/* Whenever our presence changes from away to available, and whenever our
* presence message changes while remaining available, simulate a call from
* a contact */
static void
available_cb (GObject *conn G_GNUC_UNUSED,
const gchar *message,
ExampleCallManager *self)
{
g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay,
simulate_incoming_call_cb, g_object_ref (self), g_object_unref);
}
static void
constructed (GObject *object)
{
ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object);
void (*chain_up) (GObject *) =
((GObjectClass *) example_call_manager_parent_class)->constructed;
if (chain_up != NULL)
{
chain_up (object);
}
self->priv->status_changed_id = g_signal_connect (self->priv->conn,
"status-changed", (GCallback) status_changed_cb, self);
self->priv->available_id = g_signal_connect (self->priv->conn,
"available", (GCallback) available_cb, self);
}
static void
example_call_manager_class_init (ExampleCallManagerClass *klass)
{
GParamSpec *param_spec;
GObjectClass *object_class = (GObjectClass *) klass;
object_class->constructed = constructed;
object_class->dispose = dispose;
object_class->get_property = get_property;
object_class->set_property = set_property;
param_spec = g_param_spec_object ("connection", "Connection object",
"The connection that owns this channel manager",
TP_TYPE_BASE_CONNECTION,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
"Delay between simulated network events",
0, G_MAXUINT32, 1000,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
param_spec);
g_type_class_add_private (klass,
sizeof (ExampleCallManagerPrivate));
}
static void
example_call_manager_foreach_channel (TpChannelManager *iface,
TpExportableChannelFunc callback,
gpointer user_data)
{
ExampleCallManager *self = EXAMPLE_CALL_MANAGER (iface);
GHashTableIter iter;
gpointer chan;
g_hash_table_iter_init (&iter, self->priv->channels);
while (g_hash_table_iter_next (&iter, &chan, NULL))
callback (chan, user_data);
}
static void
channel_closed_cb (ExampleCallChannel *chan,
ExampleCallManager *self)
{
tp_channel_manager_emit_channel_closed_for_object (self,
TP_EXPORTABLE_CHANNEL (chan));
if (self->priv->channels != NULL)
g_hash_table_remove (self->priv->channels, chan);
}
static ExampleCallChannel *
new_channel (ExampleCallManager *self,
TpHandle handle,
TpHandle initiator,
gpointer request_token,
gboolean initial_audio,
gboolean initial_video)
{
ExampleCallChannel *chan;
gchar *object_path;
GSList *requests = NULL;
/* FIXME: This could potentially wrap around, but only after 4 billion
* calls, which is probably plenty. */
object_path = g_strdup_printf ("%s/CallChannel%u",
tp_base_connection_get_object_path (self->priv->conn),
self->priv->next_channel_index++);
chan = g_object_new (EXAMPLE_TYPE_CALL_CHANNEL,
"connection", self->priv->conn,
"object-path", object_path,
"handle", handle,
"initiator-handle", initiator,
"requested",
(tp_base_connection_get_self_handle (self->priv->conn) == initiator),
"simulation-delay", self->priv->simulation_delay,
"initial-audio", initial_audio,
"initial-video", initial_video,
"mutable-contents", TRUE,
NULL);
g_free (object_path);
g_signal_connect (chan, "closed", G_CALLBACK (channel_closed_cb), self);
g_hash_table_insert (self->priv->channels, chan, chan);
if (request_token != NULL)
requests = g_slist_prepend (requests, request_token);
tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
requests);
g_slist_free (requests);
return chan;
}
static const gchar * const audio_fixed_properties[] = {
TP_PROP_CHANNEL_CHANNEL_TYPE,
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
NULL
};
static const gchar * const video_fixed_properties[] = {
TP_PROP_CHANNEL_CHANNEL_TYPE,
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
NULL
};
static const gchar * const audio_allowed_properties[] = {
TP_PROP_CHANNEL_TARGET_HANDLE,
TP_PROP_CHANNEL_TARGET_ID,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
NULL
};
static const gchar * const video_allowed_properties[] = {
TP_PROP_CHANNEL_TARGET_HANDLE,
TP_PROP_CHANNEL_TARGET_ID,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
NULL
};
static void
example_call_manager_type_foreach_channel_class (GType type,
TpChannelManagerTypeChannelClassFunc func,
gpointer user_data)
{
GHashTable *table = tp_asv_new (
TP_PROP_CHANNEL_CHANNEL_TYPE,
G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CALL,
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN, TRUE,
NULL);
func (type, table, audio_allowed_properties, user_data);
g_hash_table_remove (table, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO);
tp_asv_set_boolean (table, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
TRUE);
func (type, table, video_allowed_properties, user_data);
g_hash_table_unref (table);
}
static gboolean
example_call_manager_request (ExampleCallManager *self,
gpointer request_token,
GHashTable *request_properties,
gboolean require_new)
{
TpHandle handle;
GError *error = NULL;
gboolean initial_audio, initial_video;
if (tp_strdiff (tp_asv_get_string (request_properties,
TP_PROP_CHANNEL_CHANNEL_TYPE),
TP_IFACE_CHANNEL_TYPE_CALL))
{
return FALSE;
}
if (tp_asv_get_uint32 (request_properties,
TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT)
{
return FALSE;
}
handle = tp_asv_get_uint32 (request_properties,
TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
g_assert (handle != 0);
initial_audio = tp_asv_get_boolean (request_properties,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL);
initial_video = tp_asv_get_boolean (request_properties,
TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL);
if (!initial_audio && !initial_video)
{
g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
"Call channels must initially have either audio or video content");
goto error;
}
/* the set of (fixed | allowed) properties is the same for audio and video,
* so we only need to check with one set */
if (tp_channel_manager_asv_has_unknown_properties (request_properties,
audio_fixed_properties, audio_allowed_properties, &error))
{
goto error;
}
if (handle == tp_base_connection_get_self_handle (self->priv->conn))
{
/* In protocols with a concept of multiple "resources" signed in to
* one account (XMPP, and possibly MSN) it is technically possible to
* call yourself - e.g. if you're signed in on two PCs, you can call one
* from the other. For simplicity, this example simulates a protocol
* where this is not the case.
*/
g_set_error (&error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED,
"In this protocol, you can't call yourself");
goto error;
}
if (!require_new)
{
/* see if we're already calling that handle */
GHashTableIter iter;
gpointer chan;
g_hash_table_iter_init (&iter, self->priv->channels);
while (g_hash_table_iter_next (&iter, &chan, NULL))
{
guint its_handle;
g_object_get (chan,
"handle", &its_handle,
NULL);
if (its_handle == handle)
{
tp_channel_manager_emit_request_already_satisfied (self,
request_token, TP_EXPORTABLE_CHANNEL (chan));
return TRUE;
}
}
}
new_channel (self, handle,
tp_base_connection_get_self_handle (self->priv->conn),
request_token, initial_audio, initial_video);
return TRUE;
error:
tp_channel_manager_emit_request_failed (self, request_token,
error->domain, error->code, error->message);
g_error_free (error);
return TRUE;
}
static gboolean
example_call_manager_create_channel (TpChannelManager *manager,
gpointer request_token,
GHashTable *request_properties)
{
return example_call_manager_request (
EXAMPLE_CALL_MANAGER (manager),
request_token, request_properties, TRUE);
}
static gboolean
example_call_manager_ensure_channel (TpChannelManager *manager,
gpointer request_token,
GHashTable *request_properties)
{
return example_call_manager_request (
EXAMPLE_CALL_MANAGER (manager),
request_token, request_properties, FALSE);
}
static void
channel_manager_iface_init (gpointer g_iface,
gpointer iface_data G_GNUC_UNUSED)
{
TpChannelManagerIface *iface = g_iface;
iface->foreach_channel = example_call_manager_foreach_channel;
iface->type_foreach_channel_class =
example_call_manager_type_foreach_channel_class;
iface->create_channel = example_call_manager_create_channel;
iface->ensure_channel = example_call_manager_ensure_channel;
/* In this channel manager, RequestChannel is not supported; Call is not
* designed to work with the old RequestChannel API. */
iface->request_channel = NULL;
}