/*
* connection-manager.c - proxy for a Telepathy connection manager
*
* Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
* Copyright (C) 2007-2008 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 "telepathy-glib/connection-manager.h"
#include <string.h>
#include "telepathy-glib/defs.h"
#include "telepathy-glib/enums.h"
#include "telepathy-glib/errors.h"
#include "telepathy-glib/gtypes.h"
#include <telepathy-glib/interfaces.h>
#include <telepathy-glib/proxy-subclass.h>
#include "telepathy-glib/util.h"
#define DEBUG_FLAG TP_DEBUG_MANAGER
#include "telepathy-glib/debug-internal.h"
#include "telepathy-glib/_gen/tp-cli-connection-manager-body.h"
/**
* SECTION:connection-manager
* @title: TpConnectionManager
* @short_description: proxy object for a Telepathy connection manager
* @see_also: #TpConnection
*
* #TpConnectionManager objects represent Telepathy connection managers. They
* can be used to open connections.
*
* Since: 0.7.1
*/
/**
* TpConnectionManagerListCb:
* @cms: %NULL-terminated array of #TpConnectionManager (the objects will
* be unreferenced and the array will be freed after the callback returns,
* so the callback must reference any CMs it stores a pointer to),
* or %NULL on error
* @n_cms: number of connection managers in @cms (not including the final
* %NULL)
* @error: %NULL on success, or an error that occurred
* @user_data: user-supplied data
* @weak_object: user-supplied weakly referenced object
*
* Signature of the callback supplied to tp_list_connection_managers().
*
* Since: 0.7.1
*/
/**
* TpCMInfoSource:
* @TP_CM_INFO_SOURCE_NONE: no information available
* @TP_CM_INFO_SOURCE_FILE: information came from a .manager file
* @TP_CM_INFO_SOURCE_LIVE: information came from the connection manager
*
* Describes possible sources of information on connection managers'
* supported protocols.
*
* Since: 0.7.1
*/
/**
* TpConnectionManagerClass:
*
* The class of a #TpConnectionManager.
*
* Since: 0.7.1
*/
enum
{
SIGNAL_ACTIVATED,
SIGNAL_GOT_INFO,
SIGNAL_EXITED,
N_SIGNALS
};
static guint signals[N_SIGNALS] = {0};
enum
{
PROP_INFO_SOURCE = 1,
PROP_MANAGER_FILE,
PROP_ALWAYS_INTROSPECT,
PROP_CONNECTION_MANAGER,
N_PROPS
};
/**
* TpConnectionManager:
* @parent: The parent class instance
* @name: The identifier of the connection manager (e.g. "gabble").
* Should be considered read-only
* @protocols: If info_source > %TP_CM_INFO_SOURCE_NONE, a %NULL-terminated
* array of pointers to #TpConnectionManagerProtocol structures; otherwise
* %NULL. Should be considered read-only
* @running: %TRUE if the CM is currently known to be running. Should be
* considered read-only
* @always_introspect: %TRUE if the CM will be introspected automatically.
* Should be considered read-only: use the
* #TpConnectionManager:always-introspect property if you want to change it
* @info_source: The source of @protocols, or %TP_CM_INFO_SOURCE_NONE
* if no info has been discovered yet
* @reserved_flags: Reserved for future use
* @priv: Pointer to opaque private data
*
* A proxy object for a Telepathy connection manager.
*
* This might represent a connection manager which is currently running
* (in which case it can be introspected) or not (in which case its
* capabilities can be read from .manager files in the filesystem).
* Accordingly, this object never emits #TpProxy::invalidated unless all
* references to it are discarded.
*
* On initialization, we find and read the .manager file, and emit
* got-info(FILE) on success, got-info(NONE) if no file
* or if reading the file failed.
*
* When the CM runs, we automatically introspect it if @always_introspect
* is %TRUE. On success we emit got-info(LIVE). On failure, re-emit
* got-info(NONE) or got-info(FILE) as appropriate.
*
* If we're asked to activate the CM, it'll implicitly be introspected.
*
* If the CM exits, we still consider it to have been "introspected". If it's
* re-run, we introspect it again.
*
* Since: 0.7.1
*/
/**
* TpConnectionManagerParam:
* @name: The name of this parameter
* @dbus_signature: This parameter's D-Bus signature
* @default_value: This parameter's default value, or an arbitrary value
* of an appropriate type if %TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT is not
* set on this parameter, or an unset GValue if the signature is not
* recognised by telepathy-glib
* @flags: This parameter's flags (a combination of #TpConnMgrParamFlags)
* @priv: Pointer to opaque private data
*
* Structure representing a connection manager parameter.
*
* Since: 0.7.1
*/
/**
* TpConnectionManagerProtocol:
* @name: The name of this connection manager
* @params: Array of #TpConnectionManagerParam structures, terminated by
* a structure whose @name is %NULL
*
* Structure representing a protocol supported by a connection manager.
* Note that the size of this structure may change, so its size must not be
* relied on.
*
* Since: 0.7.1
*/
struct _TpConnectionManagerPrivate {
/* absolute path to .manager file */
gchar *manager_file;
/* TRUE if we're waiting for ListProtocols */
gboolean listing_protocols:1;
/* GPtrArray of TpConnectionManagerProtocol *. This is the implementation
* for self->protocols.
*
* NULL if file_info and live_info are both FALSE
* Protocols from file, if file_info is TRUE but live_info is FALSE
* Protocols from last time introspecting the CM succeeded, if live_info
* is TRUE */
GPtrArray *protocols;
/* If we're waiting for a GetParameters, then GPtrArray of g_strdup'd
* gchar * representing protocols we haven't yet introspected.
* Otherwise NULL */
GPtrArray *pending_protocols;
/* If we're waiting for a GetParameters, then GPtrArray of
* TpConnectionManagerProtocol * for the introspection that is in
* progress (will replace ->protocols when finished).
* Otherwise NULL */
GPtrArray *found_protocols;
};
G_DEFINE_TYPE (TpConnectionManager,
tp_connection_manager,
TP_TYPE_PROXY);
static void tp_connection_manager_continue_introspection
(TpConnectionManager *self);
static void
tp_connection_manager_got_parameters (TpConnectionManager *self,
const GPtrArray *parameters,
const GError *error,
gpointer user_data,
GObject *user_object)
{
gchar *protocol = user_data;
GArray *output;
guint i;
TpConnectionManagerProtocol *proto_struct;
DEBUG ("Protocol name: %s", protocol);
if (error != NULL)
{
DEBUG ("Error getting params for %s, skipping it", protocol);
tp_connection_manager_continue_introspection (self);
}
output = g_array_sized_new (TRUE, TRUE,
sizeof (TpConnectionManagerParam), parameters->len);
for (i = 0; i < parameters->len; i++)
{
GValue structure = { 0 };
GValue *tmp;
/* Points to the zeroed entry just after the end of the array
* - but we're about to extend the array to make it valid */
TpConnectionManagerParam *param = &g_array_index (output,
TpConnectionManagerParam, output->len);
g_value_init (&structure, TP_STRUCT_TYPE_PARAM_SPEC);
g_value_set_static_boxed (&structure, g_ptr_array_index (parameters, i));
g_array_set_size (output, output->len + 1);
if (!dbus_g_type_struct_get (&structure,
0, ¶m->name,
1, ¶m->flags,
2, ¶m->dbus_signature,
3, &tmp,
G_MAXUINT))
{
DEBUG ("Unparseable parameter #%d for %s, ignoring", i, protocol);
/* *shrug* that one didn't work, let's skip it */
g_array_set_size (output, output->len - 1);
continue;
}
g_value_init (¶m->default_value,
G_VALUE_TYPE (tmp));
g_value_copy (tmp, ¶m->default_value);
g_value_unset (tmp);
g_free (tmp);
param->priv = NULL;
DEBUG ("\tParam name: %s", param->name);
DEBUG ("\tParam flags: 0x%x", param->flags);
DEBUG ("\tParam sig: %s", param->dbus_signature);
#ifdef ENABLE_DEBUG
{
gchar *repr = g_strdup_value_contents (&(param->default_value));
DEBUG ("\tParam default value: %s of type %s", repr,
G_VALUE_TYPE_NAME (&(param->default_value)));
g_free (repr);
}
#endif
}
proto_struct = g_slice_new (TpConnectionManagerProtocol);
proto_struct->name = g_strdup (protocol);
proto_struct->params =
(TpConnectionManagerParam *) g_array_free (output, FALSE);
g_ptr_array_add (self->priv->found_protocols, proto_struct);
tp_connection_manager_continue_introspection (self);
}
static void
tp_connection_manager_free_protocols (GPtrArray *protocols)
{
guint i;
for (i = 0; i < protocols->len; i++)
{
TpConnectionManagerProtocol *proto = g_ptr_array_index (protocols, i);
TpConnectionManagerParam *param;
if (proto == NULL)
continue;
g_free (proto->name);
for (param = proto->params; param->name != NULL; param++)
{
g_free (param->name);
g_free (param->dbus_signature);
g_value_unset (&(param->default_value));
}
g_free (proto->params);
g_slice_free (TpConnectionManagerProtocol, proto);
}
g_ptr_array_free (protocols, TRUE);
}
static void
tp_connection_manager_end_introspection (TpConnectionManager *self)
{
gboolean emit = self->priv->listing_protocols;
guint i;
self->priv->listing_protocols = FALSE;
if (self->priv->found_protocols != NULL)
{
tp_connection_manager_free_protocols (self->priv->found_protocols);
self->priv->found_protocols = NULL;
}
if (self->priv->pending_protocols != NULL)
{
emit = TRUE;
for (i = 0; i < self->priv->pending_protocols->len; i++)
g_free (self->priv->pending_protocols->pdata[i]);
g_ptr_array_free (self->priv->pending_protocols, TRUE);
self->priv->pending_protocols = NULL;
}
if (emit)
g_signal_emit (self, signals[SIGNAL_GOT_INFO], 0, self->info_source);
}
static void
tp_connection_manager_continue_introspection (TpConnectionManager *self)
{
gchar *next_protocol;
g_assert (self->priv->pending_protocols != NULL);
if (self->priv->pending_protocols->len == 0)
{
GPtrArray *tmp;
g_ptr_array_add (self->priv->found_protocols, NULL);
/* swap found_protocols and protocols, so we'll free the old protocols
* as part of end_introspection */
tmp = self->priv->protocols;
self->priv->protocols = self->priv->found_protocols;
self->priv->found_protocols = tmp;
self->protocols = (const TpConnectionManagerProtocol * const *)
self->priv->protocols->pdata;
self->info_source = TP_CM_INFO_SOURCE_LIVE;
tp_connection_manager_end_introspection (self);
return;
}
next_protocol = g_ptr_array_remove_index_fast (self->priv->pending_protocols,
0);
tp_cli_connection_manager_call_get_parameters (self, -1, next_protocol,
tp_connection_manager_got_parameters, next_protocol, g_free,
NULL);
}
static void
tp_connection_manager_got_protocols (TpConnectionManager *self,
const gchar **protocols,
const GError *error,
gpointer user_data,
GObject *user_object)
{
guint i = 0;
const gchar **iter;
self->priv->listing_protocols = FALSE;
if (error != NULL)
{
if (!self->running)
{
/* ListProtocols failed to start it - we assume this is because
* activation failed */
g_signal_emit (self, signals[SIGNAL_EXITED], 0);
}
tp_connection_manager_end_introspection (self);
return;
}
for (iter = protocols; *iter != NULL; iter++)
i++;
g_assert (self->priv->found_protocols == NULL);
/* Allocate one more pointer - we're going to append NULL afterwards */
self->priv->found_protocols = g_ptr_array_sized_new (i + 1);
g_assert (self->priv->pending_protocols == NULL);
self->priv->pending_protocols = g_ptr_array_sized_new (i);
for (iter = protocols; *iter != NULL; iter++)
{
g_ptr_array_add (self->priv->pending_protocols, g_strdup (*iter));
}
tp_connection_manager_continue_introspection (self);
}
static gboolean
tp_connection_manager_idle_introspect (gpointer data)
{
TpConnectionManager *self = data;
/* Start introspecting if we want to and we're not already */
if (!self->priv->listing_protocols &&
self->priv->found_protocols == NULL &&
(self->always_introspect ||
self->info_source == TP_CM_INFO_SOURCE_NONE))
{
self->priv->listing_protocols = TRUE;
tp_cli_connection_manager_call_list_protocols (self, -1,
tp_connection_manager_got_protocols, NULL, NULL,
NULL);
}
return FALSE;
}
static void
tp_connection_manager_name_owner_changed_cb (TpDBusDaemon *bus,
const gchar *name,
const gchar *new_owner,
gpointer user_data)
{
TpConnectionManager *self = user_data;
if (new_owner[0] == '\0')
{
self->running = FALSE;
/* cancel pending introspection, if any */
tp_connection_manager_end_introspection (self);
g_signal_emit (self, signals[SIGNAL_EXITED], 0);
}
else
{
/* represent an atomic change of ownership as if it was an exit and
* restart */
if (self->running)
tp_connection_manager_name_owner_changed_cb (bus, name, "", self);
self->running = TRUE;
g_signal_emit (self, signals[SIGNAL_ACTIVATED], 0);
g_idle_add (tp_connection_manager_idle_introspect, self);
}
}
static gboolean
init_gvalue_from_dbus_sig (const gchar *sig,
GValue *value)
{
switch (sig[0])
{
case 'b':
g_value_init (value, G_TYPE_BOOLEAN);
return TRUE;
case 's':
g_value_init (value, G_TYPE_STRING);
return TRUE;
case 'q':
case 'u':
g_value_init (value, G_TYPE_UINT);
return TRUE;
case 'y':
g_value_init (value, G_TYPE_UCHAR);
return TRUE;
case 'n':
case 'i':
g_value_init (value, G_TYPE_INT);
return TRUE;
case 'x':
g_value_init (value, G_TYPE_INT64);
return TRUE;
case 't':
g_value_init (value, G_TYPE_UINT64);
return TRUE;
case 'o':
g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
g_value_set_static_string (value, "/");
return TRUE;
case 'd':
g_value_init (value, G_TYPE_DOUBLE);
return TRUE;
case 'v':
g_value_init (value, G_TYPE_VALUE);
return TRUE;
case 'a':
switch (sig[1])
{
case 's':
g_value_init (value, G_TYPE_STRV);
return TRUE;
case 'y':
g_value_init (value, DBUS_TYPE_G_UCHAR_ARRAY);
return TRUE;
}
}
g_value_unset (value);
return FALSE;
}
static gboolean
parse_default_value (GValue *value,
const gchar *sig,
gchar *string)
{
gchar *p;
switch (sig[0])
{
case 'b':
for (p = string; *p != '\0'; p++)
*p = g_ascii_tolower (*p);
if (!tp_strdiff (string, "1") || !tp_strdiff (string, "true"))
g_value_set_boolean (value, TRUE);
else if (!tp_strdiff (string, "0") || !tp_strdiff (string, "false"))
g_value_set_boolean (value, TRUE);
else
return FALSE;
return TRUE;
case 's':
g_value_set_string (value, string);
return TRUE;
case 'q':
case 'u':
case 't':
if (string[0] == '\0')
{
return FALSE;
}
else
{
gchar *end;
guint64 v = g_ascii_strtoull (string, &end, 10);
if (*end != '\0')
return FALSE;
if (sig[0] == 't')
{
g_value_set_uint64 (value, v);
return TRUE;
}
if (v > G_MAXUINT32 || (sig[0] == 'q' && v > G_MAXUINT16))
return FALSE;
g_value_set_uint (value, v);
return TRUE;
}
case 'n':
case 'i':
case 'x':
if (string[0] == '\0')
{
return FALSE;
}
else
{
gchar *end;
gint64 v = g_ascii_strtoll (string, &end, 10);
if (*end != '\0')
return FALSE;
if (sig[0] == 'x')
{
g_value_set_int64 (value, v);
return TRUE;
}
if (v > G_MAXINT32 || (sig[0] == 'q' && v > G_MAXINT16))
return FALSE;
if (v < G_MININT32 || (sig[0] == 'n' && v < G_MININT16))
return FALSE;
g_value_set_int (value, v);
return TRUE;
}
case 'o':
if (string[0] != '/')
return FALSE;
g_value_set_boxed (value, string);
return TRUE;
case 'd':
{
gchar *end;
gdouble v = g_ascii_strtod (string, &end);
if (*end != '\0')
return FALSE;
g_value_set_double (value, v);
return TRUE;
}
}
g_value_unset (value);
return FALSE;
}
static void
tp_connection_manager_read_file (TpConnectionManager *self,
const gchar *filename)
{
GKeyFile *file;
GError *error = NULL;
gchar **groups, **group;
guint i;
TpConnectionManagerProtocol *proto_struct;
GPtrArray *protocols;
file = g_key_file_new ();
if (!g_key_file_load_from_file (file, filename, G_KEY_FILE_NONE, &error))
{
DEBUG ("Failed to read %s: %s", filename, error->message);
g_signal_emit (self, signals[SIGNAL_GOT_INFO], 0, self->info_source);
}
groups = g_key_file_get_groups (file, NULL);
i = 0;
for (group = groups; *group != NULL; group++)
{
if (g_str_has_prefix (*group, "Protocol "))
i++;
}
/* We're going to add a NULL at the end, so +1 */
protocols = g_ptr_array_sized_new (i + 1);
for (group = groups; *group != NULL; group++)
{
gchar **keys, **key;
GArray *output;
if (!g_str_has_prefix (*group, "Protocol "))
continue;
proto_struct = g_slice_new (TpConnectionManagerProtocol);
keys = g_strsplit (*group, " ", 2);
proto_struct->name = g_strdup (keys[1]);
g_strfreev (keys);
keys = g_key_file_get_keys (file, *group, NULL, &error);
i = 0;
for (key = keys; *key != NULL; key++)
{
if (g_str_has_prefix (*key, "param-"))
i++;
}
output = g_array_sized_new (TRUE, TRUE,
sizeof (TpConnectionManagerParam), i);
for (key = keys; *key != NULL; key++)
{
if (g_str_has_prefix (*key, "param-"))
{
gchar **strv, **iter;
gchar *value, *def;
/* Points to the zeroed entry just after the end of the array
* - but we're about to extend the array to make it valid */
TpConnectionManagerParam *param = &g_array_index (output,
TpConnectionManagerParam, output->len);
value = g_key_file_get_string (file, *group, *key, NULL);
if (value == NULL)
continue;
/* zero_terminated=TRUE and clear_=TRUE */
g_assert (param->name == NULL);
g_array_set_size (output, output->len + 1);
/* strlen ("param-") == 6 */
param->name = g_strdup (*key + 6);
strv = g_strsplit (value, " ", 0);
g_free (value);
param->dbus_signature = g_strdup (strv[0]);
for (iter = strv + 1; *iter != NULL; iter++)
{
if (!tp_strdiff (*iter, "required"))
param->flags |= TP_CONN_MGR_PARAM_FLAG_REQUIRED;
if (!tp_strdiff (*iter, "register"))
param->flags |= TP_CONN_MGR_PARAM_FLAG_REGISTER;
if (!tp_strdiff (*iter, "secret"))
param->flags |= TP_CONN_MGR_PARAM_FLAG_SECRET;
}
g_strfreev (strv);
def = g_strdup_printf ("default-%s", param->name);
value = g_key_file_get_string (file, *group, def, NULL);
g_free (def);
init_gvalue_from_dbus_sig (param->dbus_signature,
¶m->default_value);
if (value != NULL && parse_default_value (¶m->default_value,
param->dbus_signature, value))
param->flags |= TP_CONN_MGR_PARAM_FLAG_HAS_DEFAULT;
g_free (value);
DEBUG ("\tParam name: %s", param->name);
DEBUG ("\tParam flags: 0x%x", param->flags);
DEBUG ("\tParam sig: %s", param->dbus_signature);
#ifdef ENABLE_DEBUG
{
gchar *repr = g_strdup_value_contents
(&(param->default_value));
DEBUG ("\tParam default value: %s of type %s", repr,
G_VALUE_TYPE_NAME (&(param->default_value)));
g_free (repr);
}
#endif
}
}
g_strfreev (keys);
proto_struct->params =
(TpConnectionManagerParam *) g_array_free (output, FALSE);
g_ptr_array_add (protocols, proto_struct);
}
g_ptr_array_add (protocols, NULL);
g_assert (self->priv->protocols == NULL);
self->priv->protocols = protocols;
self->info_source = TP_CM_INFO_SOURCE_FILE;
self->protocols = (const TpConnectionManagerProtocol * const *)
self->priv->protocols->pdata;
g_signal_emit (self, signals[SIGNAL_GOT_INFO], 0, self->info_source);
g_strfreev (groups);
g_key_file_free (file);
}
static gboolean
tp_connection_manager_idle_read_manager_file (gpointer data)
{
TpConnectionManager *self = TP_CONNECTION_MANAGER (data);
if (self->priv->protocols == NULL && self->priv->manager_file != NULL
&& self->priv->manager_file[0] != '\0')
tp_connection_manager_read_file (self, self->priv->manager_file);
return FALSE;
}
static gchar *
tp_connection_manager_find_manager_file (const gchar *name)
{
gchar *filename;
const gchar * const * data_dirs;
/* if no name yet, do nothing */
if (name == NULL)
return NULL;
filename = g_strdup_printf ("%s/telepathy/managers/%s.manager",
g_get_user_data_dir (), name);
DEBUG ("in XDG_DATA_HOME: trying %s", filename);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
return filename;
g_free (filename);
for (data_dirs = g_get_system_data_dirs ();
*data_dirs != NULL;
data_dirs++)
{
filename = g_strdup_printf ("%s/telepathy/managers/%s.manager",
*data_dirs, name);
DEBUG ("in XDG_DATA_DIRS: trying %s", filename);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
return filename;
g_free (filename);
}
return NULL;
}
static GObject *
tp_connection_manager_constructor (GType type,
guint n_params,
GObjectConstructParam *params)
{
GObjectClass *object_class =
(GObjectClass *) tp_connection_manager_parent_class;
TpConnectionManager *self =
TP_CONNECTION_MANAGER (object_class->constructor (type, n_params,
params));
TpProxy *as_proxy = (TpProxy *) self;
const gchar *object_path = as_proxy->object_path;
/* Watch my D-Bus name */
tp_dbus_daemon_watch_name_owner (as_proxy->dbus_daemon,
as_proxy->bus_name, tp_connection_manager_name_owner_changed_cb, self,
NULL);
if (object_path == NULL || object_path[0] != '/')
self->name = NULL;
else
self->name = strrchr (object_path, '/') + 1;
if (self->priv->manager_file == NULL && self->name != NULL)
{
self->priv->manager_file =
tp_connection_manager_find_manager_file (self->name);
g_idle_add (tp_connection_manager_idle_read_manager_file, self);
}
return (GObject *) self;
}
static void
tp_connection_manager_init (TpConnectionManager *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONNECTION_MANAGER,
TpConnectionManagerPrivate);
}
static void
tp_connection_manager_dispose (GObject *object)
{
TpProxy *as_proxy = TP_PROXY (object);
tp_dbus_daemon_cancel_name_owner_watch (as_proxy->dbus_daemon,
as_proxy->bus_name, tp_connection_manager_name_owner_changed_cb, object);
G_OBJECT_CLASS (tp_connection_manager_parent_class)->dispose (object);
}
static void
tp_connection_manager_finalize (GObject *object)
{
TpConnectionManager *self = TP_CONNECTION_MANAGER (object);
guint i;
g_free (self->priv->manager_file);
if (self->priv->protocols != NULL)
{
tp_connection_manager_free_protocols (self->priv->protocols);
}
if (self->priv->pending_protocols != NULL)
{
for (i = 0; i < self->priv->pending_protocols->len; i++)
g_free (self->priv->pending_protocols->pdata[i]);
g_ptr_array_free (self->priv->pending_protocols, TRUE);
}
if (self->priv->found_protocols != NULL)
{
tp_connection_manager_free_protocols (self->priv->found_protocols);
}
G_OBJECT_CLASS (tp_connection_manager_parent_class)->finalize (object);
}
static void
tp_connection_manager_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
TpConnectionManager *self = TP_CONNECTION_MANAGER (object);
switch (property_id)
{
case PROP_CONNECTION_MANAGER:
g_value_set_string (value, self->name);
break;
case PROP_INFO_SOURCE:
g_value_set_uint (value, self->info_source);
break;
case PROP_MANAGER_FILE:
g_value_set_string (value, self->priv->manager_file);
break;
case PROP_ALWAYS_INTROSPECT:
g_value_set_boolean (value, self->always_introspect);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
tp_connection_manager_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
TpConnectionManager *self = TP_CONNECTION_MANAGER (object);
const gchar *tmp;
switch (property_id)
{
case PROP_MANAGER_FILE:
g_free (self->priv->manager_file);
tmp = g_value_get_string (value);
if (tmp == NULL)
{
self->priv->manager_file =
tp_connection_manager_find_manager_file (self->name);
}
else
{
self->priv->manager_file = g_strdup (tmp);
}
g_idle_add (tp_connection_manager_idle_read_manager_file, self);
break;
case PROP_ALWAYS_INTROSPECT:
{
gboolean old = self->always_introspect;
self->always_introspect = g_value_get_boolean (value);
if (self->running && !old && self->always_introspect)
{
/* It's running, we weren't previously auto-introspecting,
* but we are now. Try it when idle
*/
g_idle_add (tp_connection_manager_idle_introspect, self);
}
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
tp_connection_manager_class_init (TpConnectionManagerClass *klass)
{
GType tp_type = TP_TYPE_CONNECTION_MANAGER;
TpProxyClass *proxy_class = (TpProxyClass *) klass;
GObjectClass *object_class = (GObjectClass *) klass;
GParamSpec *param_spec;
g_type_class_add_private (klass, sizeof (TpConnectionManagerPrivate));
object_class->constructor = tp_connection_manager_constructor;
object_class->get_property = tp_connection_manager_get_property;
object_class->set_property = tp_connection_manager_set_property;
object_class->dispose = tp_connection_manager_dispose;
object_class->finalize = tp_connection_manager_finalize;
proxy_class->interface = TP_IFACE_QUARK_CONNECTION_MANAGER;
tp_proxy_or_subclass_hook_on_interface_add (tp_type,
tp_cli_connection_manager_add_signals);
tp_proxy_subclass_add_error_mapping (tp_type,
TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
/**
* TpConnectionManager::info-source:
*
* Where we got the current information on supported protocols
* (a #TpCMInfoSource).
*/
param_spec = g_param_spec_uint ("info-source", "CM info source",
"Where we got the current information on supported protocols",
TP_CM_INFO_SOURCE_NONE, TP_CM_INFO_SOURCE_LIVE, TP_CM_INFO_SOURCE_NONE,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_INFO_SOURCE,
param_spec);
/**
* TpConnectionManager::connection-manager:
*
* The name of the connection manager, e.g. "gabble" (read-only).
*/
param_spec = g_param_spec_string ("connection-manager", "CM name",
"The name of the connection manager, e.g. \"gabble\" (read-only)",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_CONNECTION_MANAGER,
param_spec);
/**
* TpConnectionManager::manager-file:
*
* The absolute path of the .manager file. If set to %NULL (the default),
* the XDG data directories will be searched for a .manager file of the
* correct name.
*
* If set to the empty string, no .manager file will be read.
*/
param_spec = g_param_spec_string ("manager-file", ".manager filename",
"The .manager filename",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE |
G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_MANAGER_FILE,
param_spec);
/**
* TpConnectionManager::always-introspect:
*
* If %TRUE, always introspect the connection manager as it comes online,
* even if we already have its info from a .manager file. Default %FALSE.
*/
param_spec = g_param_spec_boolean ("always-introspect", "Always introspect?",
"Opportunistically introspect the CM when it's run", FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_ALWAYS_INTROSPECT,
param_spec);
/**
* TpConnectionManager::activated:
* @self: the connection manager proxy
*
* Emitted when the connection manager's well-known name appears on the bus.
*/
signals[SIGNAL_ACTIVATED] = g_signal_new ("activated",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* TpConnectionManager::exited:
* @self: the connection manager proxy
*
* Emitted when the connection manager's well-known name disappears from
* the bus or when activation fails.
*/
signals[SIGNAL_EXITED] = g_signal_new ("exited",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* TpConnectionManager::got-info:
* @self: the connection manager proxy
* @source: a #TpCMInfoSource
*
* Emitted when the connection manager's capabilities have been discovered.
*/
signals[SIGNAL_GOT_INFO] = g_signal_new ("got-info",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL,
g_cclosure_marshal_VOID__UINT,
G_TYPE_NONE, 1, G_TYPE_UINT);
}
/**
* tp_connection_manager_new:
* @dbus: Proxy for the D-Bus daemon
* @name: The connection manager name
* @manager_filename: The #TpConnectionManager::manager-file property
* @error: used to return an error if %NULL is returned
*
* Convenience function to create a new connection manager proxy.
*
* Returns: a new reference to a connection manager proxy
*/
TpConnectionManager *
tp_connection_manager_new (TpDBusDaemon *dbus,
const gchar *name,
const gchar *manager_filename,
GError **error)
{
TpConnectionManager *cm;
gchar *object_path, *bus_name;
g_return_val_if_fail (dbus != NULL, NULL);
g_return_val_if_fail (name != NULL, NULL);
if (!tp_connection_manager_check_valid_name (name, error))
return NULL;
object_path = g_strdup_printf ("%s%s", TP_CM_OBJECT_PATH_BASE, name);
bus_name = g_strdup_printf ("%s%s", TP_CM_BUS_NAME_BASE, name);
cm = TP_CONNECTION_MANAGER (g_object_new (TP_TYPE_CONNECTION_MANAGER,
"dbus-daemon", dbus,
"dbus-connection", ((TpProxy *) dbus)->dbus_connection,
"bus-name", bus_name,
"object-path", object_path,
"manager-file", manager_filename,
NULL));
g_free (object_path);
g_free (bus_name);
return cm;
}
/**
* tp_connection_manager_activate:
* @self: a connection manager proxy
*
* Attempt to run and introspect the connection manager, asynchronously.
*
* If the CM was already running, do nothing and return %FALSE.
*
* On success, emit #TpConnectionManager::activated when the CM appears
* on the bus, and #TpConnectionManager::got-info when its capabilities
* have been (re-)discovered.
*
* On failure, emit #TpConnectionManager::exited without first emitting
* activated.
*
* Returns: %TRUE if activation was needed and is now in progress, %FALSE
* if the connection manager was already running and no additional signals
* will be emitted.
*
* Since: 0.7.1
*/
gboolean
tp_connection_manager_activate (TpConnectionManager *self)
{
if (self->running)
return FALSE;
self->priv->listing_protocols = TRUE;
tp_cli_connection_manager_call_list_protocols (self, -1,
tp_connection_manager_got_protocols, NULL, NULL, NULL);
return TRUE;
}
static gboolean
steal_into_ptr_array (gpointer key,
gpointer value,
gpointer user_data)
{
if (value != NULL)
g_ptr_array_add (user_data, value);
g_free (key);
return TRUE;
}
typedef struct
{
GHashTable *table;
TpConnectionManagerListCb callback;
gpointer user_data;
GDestroyNotify destroy;
TpProxyPendingCall *pending_call;
size_t base_len;
gboolean getting_names:1;
guint refcount:2;
} _ListContext;
static void
list_context_unref (_ListContext *list_context)
{
if (--list_context->refcount > 0)
return;
if (list_context->destroy != NULL)
list_context->destroy (list_context->user_data);
g_hash_table_destroy (list_context->table);
g_slice_free (_ListContext, list_context);
}
static void
tp_list_connection_managers_got_names (TpDBusDaemon *bus_daemon,
const gchar **names,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
_ListContext *list_context = user_data;
const gchar **name_iter;
if (error != NULL)
{
list_context->callback (NULL, 0, error, list_context->user_data,
weak_object);
return;
}
for (name_iter = names; name_iter != NULL && *name_iter != NULL; name_iter++)
{
const gchar *name;
TpConnectionManager *cm;
if (strncmp (TP_CM_BUS_NAME_BASE, *name_iter, list_context->base_len)
!= 0)
continue;
name = *name_iter + list_context->base_len;
if (g_hash_table_lookup (list_context->table, name) == NULL)
{
/* just ignore connection managers with bad names */
cm = tp_connection_manager_new (bus_daemon, name, NULL, NULL);
if (cm != NULL)
g_hash_table_insert (list_context->table, g_strdup (name), cm);
}
}
if (list_context->getting_names)
{
/* actually call the callback */
GPtrArray *arr = g_ptr_array_sized_new (g_hash_table_size
(list_context->table));
TpConnectionManager **cms;
TpConnectionManager * const *cm_iter;
gsize n_cms;
g_hash_table_foreach_steal (list_context->table, steal_into_ptr_array,
arr);
n_cms = arr->len;
g_ptr_array_add (arr, NULL);
cms = (TpConnectionManager **) g_ptr_array_free (arr, FALSE);
list_context->callback (cms, n_cms, NULL, list_context->user_data,
weak_object);
list_context->callback = NULL;
for (cm_iter = cms; *cm_iter != NULL; cm_iter++)
{
g_object_unref (*cm_iter);
}
g_free (cms);
}
else
{
list_context->getting_names = TRUE;
list_context->refcount++;
tp_cli_dbus_daemon_call_list_names (bus_daemon, 2000,
tp_list_connection_managers_got_names, list_context,
(GDestroyNotify) list_context_unref, weak_object);
}
}
/**
* tp_list_connection_managers:
* @bus_daemon: proxy for the D-Bus daemon
* @callback: callback to be called when listing the CMs succeeds or fails;
* not called if the @weak_object goes away
* @user_data: user-supplied data for the callback
* @destroy: callback to destroy the user-supplied data, called after
* @callback, but also if the @weak_object goes away
* @weak_object: if not %NULL, will be weakly referenced; the callback will
* not be called, and the call will be cancelled, if the object has vanished
*
* List the available (running or installed) connection managers. Call the
* callback when done.
*
* Since: 0.7.1
*/
void
tp_list_connection_managers (TpDBusDaemon *bus_daemon,
TpConnectionManagerListCb callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
_ListContext *list_context = g_slice_new0 (_ListContext);
list_context->base_len = strlen (TP_CM_BUS_NAME_BASE);
list_context->callback = callback;
list_context->user_data = user_data;
list_context->destroy = destroy;
list_context->getting_names = FALSE;
list_context->refcount = 1;
list_context->table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
g_object_unref);
tp_cli_dbus_daemon_call_list_activatable_names (bus_daemon, 2000,
tp_list_connection_managers_got_names, list_context,
(GDestroyNotify) list_context_unref, weak_object);
}
/**
* tp_connection_manager_check_valid_name:
* @name: a possible connection manager name
* @error: used to raise %TP_ERROR_INVALID_ARGUMENT if %FALSE is returned
*
* Check that the given string is a valid connection manager name, i.e. that
* it consists entirely of ASCII letters, digits and underscores, and starts
* with a letter.
*
* Returns: %TRUE if @name is valid
*
* Since: 0.7.1
*/
gboolean
tp_connection_manager_check_valid_name (const gchar *name,
GError **error)
{
const gchar *name_char;
if (name == NULL || name[0] == '\0')
{
g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
"The empty string is not a valid connection manager name");
return FALSE;
}
if (!g_ascii_isalpha (name[0]))
{
g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
"Not a valid connection manager name because first character "
"is not an ASCII letter: %s", name);
return FALSE;
}
for (name_char = name; *name_char != '\0'; name_char++)
{
if (!g_ascii_isalnum (*name_char) && *name_char != '_')
{
g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
"Not a valid connection manager name because character '%c' "
"is not an ASCII letter, digit or underscore: %s",
*name_char, name);
return FALSE;
}
}
return TRUE;
}
/**
* tp_connection_manager_check_valid_protocol_name:
* @name: a possible protocol name
* @error: used to raise %TP_ERROR_INVALID_ARGUMENT if %FALSE is returned
*
* Check that the given string is a valid protocol name, i.e. that
* it consists entirely of ASCII letters, digits and hyphen/minus, and starts
* with a letter.
*
* Returns: %TRUE if @name is valid
*
* Since: 0.7.1
*/
gboolean
tp_connection_manager_check_valid_protocol_name (const gchar *name,
GError **error)
{
const gchar *name_char;
if (name == NULL || name[0] == '\0')
{
g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
"The empty string is not a valid protocol name");
return FALSE;
}
if (!g_ascii_isalpha (name[0]))
{
g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
"Not a valid protocol name because first character "
"is not an ASCII letter: %s", name);
return FALSE;
}
for (name_char = name; *name_char != '\0'; name_char++)
{
if (!g_ascii_isalnum (*name_char) && *name_char != '-')
{
g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
"Not a valid protocol name because character '%c' "
"is not an ASCII letter, digit or hyphen/minus: %s",
*name_char, name);
return FALSE;
}
}
return TRUE;
}