/*
* connection.c - proxy for a Telepathy connection
*
* Copyright (C) 2007-2008 Collabora Ltd.
* 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.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_CONNECTION
#include "telepathy-glib/connection-internal.h"
#include "telepathy-glib/dbus-internal.h"
#include "telepathy-glib/debug-internal.h"
#include "_gen/tp-cli-connection-body.h"
/**
* SECTION:connection
* @title: TpConnection
* @short_description: proxy object for a Telepathy connection
* @see_also: #TpConnectionManager, #TpChannel
*
* #TpConnection objects represent Telepathy instant messaging connections
* accessed via D-Bus.
*
* Compared with a simple proxy for method calls, they add the following
* features:
*
*
* connection status tracking
* calling GetInterfaces() automatically
*
*
* Since: 0.7.1
*/
/**
* TP_ERRORS_DISCONNECTED:
*
* #GError domain representing a Telepathy connection becoming disconnected.
* The @code in a #GError with this domain must be a member of
* #TpConnectionStatusReason.
*
* This macro expands to a function call returning a #GQuark.
*
* Since: 0.7.1
*/
GQuark
tp_errors_disconnected_quark (void)
{
static GQuark q = 0;
if (q == 0)
q = g_quark_from_static_string ("tp_errors_disconnected_quark");
return q;
}
/**
* TP_UNKNOWN_CONNECTION_STATUS:
*
* An invalid connection status used in #TpConnection to indicate that the
* status has not yet been discovered.
*
* Since: 0.7.1
*/
/**
* TpConnectionClass:
* @parent_class: the parent class
*
* The class of a #TpConnection. In addition to @parent_class there are four
* pointers reserved for possible future use.
*
* (Changed in 0.7.12: the layout of the structure is visible, allowing
* subclassing.)
*
* Since: 0.7.1
*/
/**
* TpConnection:
* @parent: the parent class instance
* @priv: pointer to opaque private data
*
* A proxy object for a Telepathy connection.
*
* (Changed in 0.7.12: the layout of the structure is visible, allowing
* subclassing.)
*
* Since: 0.7.1
*/
enum
{
PROP_STATUS = 1,
PROP_STATUS_REASON,
PROP_CONNECTION_READY,
PROP_SELF_HANDLE,
N_PROPS
};
G_DEFINE_TYPE (TpConnection,
tp_connection,
TP_TYPE_PROXY);
static void
tp_connection_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
TpConnection *self = TP_CONNECTION (object);
switch (property_id)
{
case PROP_CONNECTION_READY:
g_value_set_boolean (value, self->priv->ready);
break;
case PROP_STATUS:
g_value_set_uint (value, self->priv->status);
break;
case PROP_STATUS_REASON:
g_value_set_uint (value, self->priv->status_reason);
break;
case PROP_SELF_HANDLE:
g_value_set_uint (value, self->priv->self_handle);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
tp_connection_continue_introspection (TpConnection *self)
{
g_assert (self->priv->introspect_needed != NULL);
if (self->priv->introspect_needed->len == 0)
{
g_array_free (self->priv->introspect_needed, TRUE);
self->priv->introspect_needed = NULL;
DEBUG ("%p: connection ready", self);
self->priv->ready = TRUE;
g_object_notify ((GObject *) self, "connection-ready");
}
else
{
guint i = self->priv->introspect_needed->len - 1;
TpConnectionProc next = g_array_index (self->priv->introspect_needed,
TpConnectionProc, i);
g_array_remove_index (self->priv->introspect_needed, i);
next (self);
}
}
static void
got_contact_attribute_interfaces (TpProxy *proxy,
const GValue *value,
const GError *error,
gpointer user_data G_GNUC_UNUSED,
GObject *weak_object G_GNUC_UNUSED)
{
TpConnection *self = TP_CONNECTION (proxy);
if (error == NULL)
{
if (G_VALUE_HOLDS (value, G_TYPE_STRV))
{
GArray *arr;
gchar **interfaces = g_value_get_boxed (value);
gchar **iter;
arr = g_array_sized_new (FALSE, FALSE, sizeof (GQuark),
interfaces == NULL ? 0 : g_strv_length (interfaces));
if (interfaces != NULL)
{
for (iter = interfaces; *iter != NULL; iter++)
{
if (tp_dbus_check_valid_interface_name (*iter, NULL))
{
GQuark q = g_quark_from_string (*iter);
DEBUG ("%p: ContactAttributeInterfaces has %s", self,
*iter);
g_array_append_val (arr, q);
}
else
{
DEBUG ("%p: ignoring invalid interface: %s", self,
*iter);
}
}
}
g_assert (self->priv->contact_attribute_interfaces == NULL);
self->priv->contact_attribute_interfaces = arr;
}
else
{
DEBUG ("%p: ContactAttributeInterfaces had wrong type %s, "
"ignoring", self, G_VALUE_TYPE_NAME (value));
}
}
else
{
DEBUG ("%p: Get(Contacts, ContactAttributeInterfaces) failed with "
"%s %d: %s", self, g_quark_to_string (error->domain), error->code,
error->message);
}
tp_connection_continue_introspection (self);
}
static void
introspect_contacts (TpConnection *self)
{
g_assert (self->priv->introspect_needed != NULL);
tp_cli_dbus_properties_call_get (self, -1,
TP_IFACE_CONNECTION_INTERFACE_CONTACTS, "ContactAttributeInterfaces",
got_contact_attribute_interfaces, NULL, NULL, NULL);
}
static void
_tp_connection_set_self_handle (TpConnection *self,
guint self_handle)
{
if (self_handle != self->priv->self_handle)
{
self->priv->self_handle = self_handle;
g_object_notify ((GObject *) self, "self-handle");
}
}
static void
got_self_handle (TpConnection *self,
guint self_handle,
const GError *error,
gpointer user_data G_GNUC_UNUSED,
GObject *user_object G_GNUC_UNUSED)
{
if (error != NULL)
{
DEBUG ("%p: GetSelfHandle() failed: %s", self, error->message);
self_handle = 0;
/* FIXME: abort the readying process */
}
_tp_connection_set_self_handle (self, self_handle);
tp_connection_continue_introspection (self);
}
static void
on_self_handle_changed (TpConnection *self,
guint self_handle,
gpointer user_data G_GNUC_UNUSED,
GObject *user_object G_GNUC_UNUSED)
{
_tp_connection_set_self_handle (self, self_handle);
}
static void
get_self_handle (TpConnection *self)
{
g_assert (self->priv->introspect_needed != NULL);
tp_cli_connection_connect_to_self_handle_changed (self,
on_self_handle_changed, NULL, NULL, NULL, NULL);
/* GetSelfHandle is deprecated in favour of the SelfHandle property,
* but until Connection has other interesting properties, there's no point in
* trying to implement a fast path; GetSelfHandle is the only one guaranteed
* to work, so we'll sometimes have to call it anyway */
tp_cli_connection_call_get_self_handle (self, -1,
got_self_handle, NULL, NULL, NULL);
}
static void
tp_connection_got_interfaces_cb (TpConnection *self,
const gchar **interfaces,
const GError *error,
gpointer user_data,
GObject *user_object)
{
TpConnectionProc func;
if (error != NULL)
{
DEBUG ("%p: GetInterfaces() failed, assuming no interfaces: %s",
self, error->message);
interfaces = NULL;
}
DEBUG ("%p: Introspected interfaces", self);
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("%p: already invalidated, not trying to become ready: %s",
self, tp_proxy_get_invalidated (self)->message);
return;
}
g_assert (self->priv->introspect_needed == NULL);
self->priv->introspect_needed = g_array_new (FALSE, FALSE,
sizeof (TpConnectionProc));
func = get_self_handle;
g_array_append_val (self->priv->introspect_needed, func);
if (interfaces != NULL)
{
const gchar **iter;
for (iter = interfaces; *iter != NULL; iter++)
{
if (tp_dbus_check_valid_interface_name (*iter, NULL))
{
GQuark q = g_quark_from_string (*iter);
tp_proxy_add_interface_by_id ((TpProxy *) self,
g_quark_from_string (*iter));
if (q == TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACTS)
{
func = introspect_contacts;
g_array_append_val (self->priv->introspect_needed, func);
}
}
else
{
DEBUG ("\t\tInterface %s not valid", *iter);
}
}
}
/* FIXME: give subclasses a chance to influence the definition of "ready"
* now that we have our interfaces? */
tp_connection_continue_introspection (self);
}
static void
tp_connection_status_changed (TpConnection *self,
guint status,
guint reason)
{
DEBUG ("%p: %d -> %d because %d", self, self->priv->status, status, reason);
self->priv->status = status;
self->priv->status_reason = reason;
g_object_notify ((GObject *) self, "status");
g_object_notify ((GObject *) self, "status-reason");
if (status == TP_CONNECTION_STATUS_CONNECTED &&
!self->priv->called_get_interfaces)
{
tp_cli_connection_call_get_interfaces (self, -1,
tp_connection_got_interfaces_cb, NULL, NULL, NULL);
self->priv->called_get_interfaces = TRUE;
}
}
static void
tp_connection_connection_error_cb (TpConnection *self,
const gchar *error_name,
GHashTable *details,
gpointer user_data,
GObject *weak_object)
{
if (self->priv->connection_error != NULL)
{
g_error_free (self->priv->connection_error);
self->priv->connection_error = NULL;
}
tp_proxy_dbus_error_to_gerror (self, error_name,
tp_asv_get_string (details, "debug-message"),
&(self->priv->connection_error));
}
static void
tp_connection_status_reason_to_gerror (TpConnectionStatusReason reason,
TpConnectionStatus prev_status,
GError **error)
{
TpError code;
const gchar *message;
switch (reason)
{
case TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED:
code = TP_ERROR_DISCONNECTED;
message = "Disconnected for unspecified reason";
break;
case TP_CONNECTION_STATUS_REASON_REQUESTED:
code = TP_ERROR_CANCELLED;
message = "User requested disconnection";
break;
case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR:
code = TP_ERROR_NETWORK_ERROR;
message = "Network error";
break;
case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR:
code = TP_ERROR_ENCRYPTION_ERROR;
message = "Encryption error";
break;
case TP_CONNECTION_STATUS_REASON_NAME_IN_USE:
if (prev_status == TP_CONNECTION_STATUS_CONNECTED)
{
code = TP_ERROR_CONNECTION_REPLACED;
message = "Connection replaced";
}
else
{
/* If the connection was with register=TRUE, we should ideally use
* REGISTRATION_EXISTS; but we can't actually tell that from here,
* so we'll have to rely on CMs supporting in-band registration
* (Gabble) to emit ConnectionError */
code = TP_ERROR_ALREADY_CONNECTED;
message = "Already connected (or if registering, registration "
"already exists)";
}
break;
case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED:
code = TP_ERROR_CERT_NOT_PROVIDED;
message = "Server certificate not provided";
break;
case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED:
code = TP_ERROR_CERT_UNTRUSTED;
message = "Server certificate CA not trusted";
break;
case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED:
code = TP_ERROR_CERT_EXPIRED;
message = "Server certificate expired";
break;
case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED:
code = TP_ERROR_CERT_NOT_ACTIVATED;
message = "Server certificate not valid yet";
break;
case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH:
code = TP_ERROR_CERT_HOSTNAME_MISMATCH;
message = "Server certificate has wrong hostname";
break;
case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH:
code = TP_ERROR_CERT_FINGERPRINT_MISMATCH;
message = "Server certificate fingerprint mismatch";
break;
case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED:
code = TP_ERROR_CERT_SELF_SIGNED;
message = "Server certificate is self-signed";
break;
case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR:
code = TP_ERROR_CERT_INVALID;
message = "Unspecified server certificate error";
break;
default:
g_set_error (error, TP_ERRORS_DISCONNECTED, reason,
"Unknown disconnection reason");
return;
}
g_set_error (error, TP_ERRORS, code, "%s", message);
}
static void
tp_connection_status_changed_cb (TpConnection *self,
guint status,
guint reason,
gpointer user_data,
GObject *weak_object)
{
TpConnectionStatus prev_status = self->priv->status;
/* GetStatus is called in the TpConnection constructor. If we don't have the
* reply for this GetStatus call yet, ignore this signal StatusChanged in
* order to run the interface introspection only one time. We will get the
* GetStatus reply later anyway. */
if (self->priv->status != TP_UNKNOWN_CONNECTION_STATUS)
{
tp_connection_status_changed (self, status, reason);
}
/* we only want to run this in response to a StatusChanged signal,
* not if the initial status is DISCONNECTED */
if (status == TP_CONNECTION_STATUS_DISCONNECTED)
{
if (self->priv->connection_error == NULL)
{
tp_connection_status_reason_to_gerror (reason, prev_status,
&(self->priv->connection_error));
}
tp_proxy_invalidate ((TpProxy *) self, self->priv->connection_error);
g_error_free (self->priv->connection_error);
self->priv->connection_error = NULL;
}
}
static void
tp_connection_got_status_cb (TpConnection *self,
guint status,
const GError *error,
gpointer unused,
GObject *user_object)
{
DEBUG ("%p", self);
if (error == NULL)
{
DEBUG ("%p: Initial status is %d", self, status);
tp_connection_status_changed (self, status,
TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
}
else
{
DEBUG ("%p: GetStatus() failed with %s %d \"%s\"",
self, g_quark_to_string (error->domain), error->code,
error->message);
}
}
static void
tp_connection_invalidated (TpConnection *self)
{
_tp_connection_set_self_handle (self, 0);
_tp_connection_clean_up_handle_refs (self);
}
static GObject *
tp_connection_constructor (GType type,
guint n_params,
GObjectConstructParam *params)
{
GObjectClass *object_class = (GObjectClass *) tp_connection_parent_class;
TpConnection *self = TP_CONNECTION (object_class->constructor (type,
n_params, params));
/* Connect to my own StatusChanged signal.
* The connection hasn't had a chance to become invalid yet, so we can
* assume that this signal connection will work */
DEBUG ("Connecting to StatusChanged and ConnectionError");
tp_cli_connection_connect_to_status_changed (self,
tp_connection_status_changed_cb, NULL, NULL, NULL, NULL);
tp_cli_connection_connect_to_connection_error (self,
tp_connection_connection_error_cb, NULL, NULL, NULL, NULL);
/* get my initial status */
DEBUG ("Calling GetStatus");
tp_cli_connection_call_get_status (self, -1,
tp_connection_got_status_cb, NULL, NULL, NULL);
_tp_connection_init_handle_refs (self);
g_signal_connect (self, "invalidated",
G_CALLBACK (tp_connection_invalidated), NULL);
DEBUG ("Returning %p", self);
return (GObject *) self;
}
static void
tp_connection_init (TpConnection *self)
{
DEBUG ("%p", self);
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONNECTION,
TpConnectionPrivate);
self->priv->status = TP_UNKNOWN_CONNECTION_STATUS;
self->priv->status_reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED;
self->priv->contacts = g_hash_table_new (g_direct_hash, g_direct_equal);
}
static void
tp_connection_finalize (GObject *object)
{
TpConnection *self = TP_CONNECTION (object);
DEBUG ("%p", self);
/* not true unless we were finalized before we were ready */
if (self->priv->introspect_needed != NULL)
{
g_array_free (self->priv->introspect_needed, TRUE);
self->priv->introspect_needed = NULL;
}
if (self->priv->contact_attribute_interfaces != NULL)
{
g_array_free (self->priv->contact_attribute_interfaces, TRUE);
self->priv->contact_attribute_interfaces = NULL;
}
if (self->priv->connection_error != NULL)
{
g_error_free (self->priv->connection_error);
self->priv->connection_error = NULL;
}
((GObjectClass *) tp_connection_parent_class)->finalize (object);
}
static void
contact_notify_invalidated (gpointer k G_GNUC_UNUSED,
gpointer v,
gpointer d G_GNUC_UNUSED)
{
_tp_contact_connection_invalidated (v);
}
static void
tp_connection_dispose (GObject *object)
{
TpConnection *self = TP_CONNECTION (object);
DEBUG ("%p", object);
if (self->priv->contacts != NULL)
{
g_hash_table_foreach (self->priv->contacts, contact_notify_invalidated,
NULL);
g_hash_table_destroy (self->priv->contacts);
self->priv->contacts = NULL;
}
((GObjectClass *) tp_connection_parent_class)->dispose (object);
}
static void
tp_connection_class_init (TpConnectionClass *klass)
{
GParamSpec *param_spec;
TpProxyClass *proxy_class = (TpProxyClass *) klass;
GObjectClass *object_class = (GObjectClass *) klass;
tp_connection_init_known_interfaces ();
g_type_class_add_private (klass, sizeof (TpConnectionPrivate));
object_class->constructor = tp_connection_constructor;
object_class->get_property = tp_connection_get_property;
object_class->dispose = tp_connection_dispose;
object_class->finalize = tp_connection_finalize;
proxy_class->interface = TP_IFACE_QUARK_CONNECTION;
/* If you change this, you must also change TpChannel to stop asserting
* that its connection has a unique name */
proxy_class->must_have_unique_name = TRUE;
/**
* TpConnection:status:
*
* This connection's status, or TP_UNKNOWN_CONNECTION_STATUS if we don't
* know yet.
*/
param_spec = g_param_spec_uint ("status", "Status",
"The status of this connection", 0, G_MAXUINT32,
TP_UNKNOWN_CONNECTION_STATUS,
G_PARAM_READABLE
| G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_STATUS,
param_spec);
/**
* TpConnection:self-handle:
*
* The %TP_HANDLE_TYPE_CONTACT handle of the local user on this connection,
* or 0 if we don't know yet or if the connection has become invalid.
*/
param_spec = g_param_spec_uint ("self-handle", "Self handle",
"The local user's Contact handle on this connection", 0, G_MAXUINT32,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SELF_HANDLE,
param_spec);
/**
* TpConnection:status-reason:
*
* The reason why #TpConnection:status changed to its current value,
* or TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED if unknown.
* know yet.
*/
param_spec = g_param_spec_uint ("status-reason", "Last status change reason",
"The reason why #TpConnection:status changed to its current value",
0, G_MAXUINT32, TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
G_PARAM_READABLE
| G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_STATUS_REASON,
param_spec);
/**
* TpConnection:connection-ready:
*
* Initially %FALSE; changes to %TRUE when the connection has gone to
* CONNECTED status, introspection has finished and it's ready for use.
*
* By the time this property becomes %TRUE, any extra interfaces will
* have been set up and the #TpProxy:interfaces property will have been
* populated.
*/
param_spec = g_param_spec_boolean ("connection-ready", "Connection ready?",
"Initially FALSE; changes to TRUE when introspection finishes", FALSE,
G_PARAM_READABLE
| G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
g_object_class_install_property (object_class, PROP_CONNECTION_READY,
param_spec);
}
/**
* tp_connection_new:
* @dbus: a D-Bus daemon; may not be %NULL
* @bus_name: the well-known or unique name of the connection process;
* if well-known, this function will make a blocking call to the bus daemon
* to resolve the unique name. May be %NULL if @object_path is not, in which
* case a well-known name will be derived from @object_path.
* @object_path: the object path of the connection process. May be %NULL
* if @bus_name is a well-known name, in which case the object path will
* be derived from @bus_name.
* @error: used to indicate the error if %NULL is returned
*
*
*
* Returns: a new connection proxy, or %NULL if unique-name resolution
* fails or on invalid arguments
*
* Since: 0.7.1
*/
TpConnection *
tp_connection_new (TpDBusDaemon *dbus,
const gchar *bus_name,
const gchar *object_path,
GError **error)
{
gchar *dup_path = NULL;
gchar *dup_name = NULL;
gchar *dup_unique_name = NULL;
TpConnection *ret = NULL;
g_return_val_if_fail (TP_IS_DBUS_DAEMON (dbus), NULL);
g_return_val_if_fail (object_path != NULL ||
(bus_name != NULL && bus_name[0] != ':'), NULL);
if (object_path == NULL)
{
dup_path = g_strdelimit (g_strdup_printf ("/%s", bus_name), ".", '/');
object_path = dup_path;
}
else if (bus_name == NULL)
{
dup_name = g_strdelimit (g_strdup (object_path + 1), "/", '.');
bus_name = dup_name;
}
if (!tp_dbus_check_valid_bus_name (bus_name,
TP_DBUS_NAME_TYPE_NOT_BUS_DAEMON, error))
goto finally;
/* Resolve unique name if necessary */
if (bus_name[0] != ':')
{
if (!_tp_dbus_daemon_get_name_owner (dbus, 2000, bus_name,
&dup_unique_name, error))
goto finally;
bus_name = dup_unique_name;
if (!tp_dbus_check_valid_bus_name (bus_name,
TP_DBUS_NAME_TYPE_UNIQUE, error))
goto finally;
}
if (!tp_dbus_check_valid_object_path (object_path, error))
goto finally;
ret = TP_CONNECTION (g_object_new (TP_TYPE_CONNECTION,
"dbus-daemon", dbus,
"bus-name", bus_name,
"object-path", object_path,
NULL));
finally:
g_free (dup_path);
g_free (dup_name);
g_free (dup_unique_name);
return ret;
}
/**
* tp_connection_get_self_handle:
* @self: a connection
*
* Return the %TP_HANDLE_TYPE_CONTACT handle of the local user on this
* connection, or 0 if the connection is not ready (the
* TpConnection:connection-ready property is false) or has become invalid
* (the TpProxy::invalidated signal).
*
* The returned handle is not necessarily valid forever (the
* notify::self-handle signal will be emitted if it changes, which can happen
* on protocols such as IRC). Construct a #TpContact object if you want to
* track the local user's identifier in the protocol, or other information
* like their presence status, over time.
*
* Returns: the value of the TpConnection:self-handle property
*
* Since: 0.7.26
*/
TpHandle
tp_connection_get_self_handle (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), 0);
return self->priv->self_handle;
}
/**
* tp_connection_get_status:
* @self: a connection
* @reason: a TpConnectionStatusReason, or %NULL
*
* If @reason is not %NULL it is set to the reason why "status" changed to its
* current value, or %TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED if unknown.
*
* Returns: This connection's status, or %TP_UNKNOWN_CONNECTION_STATUS if we
* don't know yet.
*
* Since: 0.7.14
*/
TpConnectionStatus
tp_connection_get_status (TpConnection *self,
TpConnectionStatusReason *reason)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), TP_UNKNOWN_CONNECTION_STATUS);
if (reason != NULL)
*reason = self->priv->status_reason;
return self->priv->status;
}
/**
* tp_connection_run_until_ready:
* @self: a connection
* @connect: if %TRUE, call Connect() if it appears to be necessary;
* if %FALSE, rely on Connect() to be called by another client
* @error: if not %NULL and %FALSE is returned, used to raise an error
* @loop: if not %NULL, a #GMainLoop is placed here while it is being run
* (so calling code can call g_main_loop_quit() to abort), and %NULL is
* placed here after the loop has been run
*
* If @self is connected and ready for use, return immediately. Otherwise,
* call Connect() (unless @connect is %FALSE) and re-enter the main loop
* until the connection becomes invalid, the connection connects successfully
* and is introspected, or the main loop stored via @loop is cancelled.
*
* Returns: %TRUE if the connection is now connected and ready for use,
* %FALSE if the connection has become invalid.
*
* Since: 0.7.1
*/
typedef struct {
GMainLoop *loop;
TpProxyPendingCall *pc;
GError *connect_error /* gets initialized */;
} RunUntilReadyData;
static void
run_until_ready_ret (TpConnection *self,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
RunUntilReadyData *data = user_data;
if (error != NULL)
{
g_main_loop_quit (data->loop);
data->connect_error = g_error_copy (error);
}
}
static void
run_until_ready_destroy (gpointer p)
{
RunUntilReadyData *data = p;
data->pc = NULL;
}
gboolean
tp_connection_run_until_ready (TpConnection *self,
gboolean connect,
GError **error,
GMainLoop **loop)
{
TpProxy *as_proxy = (TpProxy *) self;
gulong invalidated_id, ready_id;
RunUntilReadyData data = { NULL, NULL, NULL };
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
if (as_proxy->invalidated)
goto raise_invalidated;
if (self->priv->ready)
return TRUE;
data.loop = g_main_loop_new (NULL, FALSE);
invalidated_id = g_signal_connect_swapped (self, "invalidated",
G_CALLBACK (g_main_loop_quit), data.loop);
ready_id = g_signal_connect_swapped (self, "notify::connection-ready",
G_CALLBACK (g_main_loop_quit), data.loop);
if (self->priv->status != TP_CONNECTION_STATUS_CONNECTED &&
connect)
{
data.pc = tp_cli_connection_call_connect (self, -1,
run_until_ready_ret, &data,
run_until_ready_destroy, NULL);
}
if (data.connect_error == NULL)
{
if (loop != NULL)
*loop = data.loop;
g_main_loop_run (data.loop);
if (loop != NULL)
*loop = NULL;
}
if (data.pc != NULL)
tp_proxy_pending_call_cancel (data.pc);
g_signal_handler_disconnect (self, invalidated_id);
g_signal_handler_disconnect (self, ready_id);
g_main_loop_unref (data.loop);
if (data.connect_error != NULL)
{
g_propagate_error (error, data.connect_error);
return FALSE;
}
if (as_proxy->invalidated != NULL)
goto raise_invalidated;
if (self->priv->ready)
return TRUE;
g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_CANCELLED,
"tp_connection_run_until_ready() cancelled");
return FALSE;
raise_invalidated:
if (error != NULL)
{
g_return_val_if_fail (*error == NULL, FALSE);
*error = g_error_copy (as_proxy->invalidated);
}
return FALSE;
}
/**
* TpConnectionNameListCb:
* @names: %NULL-terminated array of @n connection bus names,
* or %NULL on error
* @n: number of names (not including the final %NULL), or 0 on error
* @cms: %NULL-terminated array of @n connection manager names
* (e.g. "gabble") in the same order as @names, or %NULL on error
* @protocols: %NULL-terminated array of @n protocol names as defined in the
* Telepathy spec (e.g. "jabber") in the same order as @names, or %NULL on
* error
* @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_names().
*
* Since: 0.7.1
*/
typedef struct {
TpConnectionNameListCb callback;
gpointer user_data;
GDestroyNotify destroy;
} _ListContext;
static gboolean
_tp_connection_parse (const gchar *path_or_bus_name,
char delimiter,
gchar **protocol,
gchar **cm_name)
{
const gchar *prefix;
const gchar *cm_name_start;
const gchar *protocol_start;
const gchar *account_start;
gchar *dup_cm_name = NULL;
gchar *dup_protocol = NULL;
g_return_val_if_fail (delimiter == '.' || delimiter == '/', FALSE);
/* If CM respects the spec, object path and bus name should be in the form:
* /org/freedesktop/Telepathy/Connection/cmname/proto/account
* org.freedesktop.Telepathy.Connection.cmname.proto.account
*/
if (delimiter == '.')
prefix = TP_CONN_BUS_NAME_BASE;
else
prefix = TP_CONN_OBJECT_PATH_BASE;
if (!g_str_has_prefix (path_or_bus_name, prefix))
goto OUT;
cm_name_start = path_or_bus_name + strlen (prefix);
protocol_start = strchr (cm_name_start, delimiter);
if (protocol_start == NULL)
goto OUT;
protocol_start++;
account_start = strchr (protocol_start, delimiter);
if (account_start == NULL)
goto OUT;
account_start++;
dup_cm_name = g_strndup (cm_name_start, protocol_start - cm_name_start - 1);
if (!tp_connection_manager_check_valid_name (dup_cm_name, NULL))
{
g_free (dup_cm_name);
dup_cm_name = NULL;
goto OUT;
}
dup_protocol = g_strndup (protocol_start, account_start - protocol_start - 1);
if (!tp_strdiff (dup_protocol, "local_2dxmpp"))
{
/* the CM's telepathy-glib is older than 0.7.x, work around it.
* FIXME: Remove this workaround in 0.9.x */
g_free (dup_protocol);
dup_protocol = g_strdup ("local-xmpp");
}
else
{
/* the real protocol name may have "-" in; bus names may not, but
* they may have "_", so the Telepathy spec specifies replacement.
* Here we need to undo that replacement */
g_strdelimit (dup_protocol, "_", '-');
}
if (!tp_connection_manager_check_valid_protocol_name (dup_protocol, NULL))
{
g_free (dup_protocol);
dup_protocol = NULL;
goto OUT;
}
OUT:
if (dup_protocol == NULL || dup_cm_name == NULL)
{
g_free (dup_protocol);
g_free (dup_cm_name);
return FALSE;
}
if (cm_name != NULL)
*cm_name = dup_cm_name;
else
g_free (dup_cm_name);
if (protocol != NULL)
*protocol = dup_protocol;
else
g_free (dup_protocol);
return TRUE;
}
static void
tp_list_connection_names_helper (TpDBusDaemon *bus_daemon,
const gchar * const *names,
const GError *error,
gpointer user_data,
GObject *user_object)
{
_ListContext *list_context = user_data;
const gchar * const *iter;
/* array of borrowed strings */
GPtrArray *bus_names;
/* array of dup'd strings */
GPtrArray *cms;
/* array of borrowed strings */
GPtrArray *protocols;
if (error != NULL)
{
list_context->callback (NULL, 0, NULL, NULL, error,
list_context->user_data, user_object);
return;
}
bus_names = g_ptr_array_new ();
cms = g_ptr_array_new ();
protocols = g_ptr_array_new ();
for (iter = names; iter != NULL && *iter != NULL; iter++)
{
gchar *proto, *cm_name;
if (_tp_connection_parse (*iter, '.', &proto, &cm_name))
{
/* the casts here are because g_ptr_array contains non-const pointers -
* but in this case I'll only be passing pdata to a callback with const
* arguments, so it's fine */
g_ptr_array_add (bus_names, (gpointer) *iter);
g_ptr_array_add (cms, cm_name);
g_ptr_array_add (protocols, proto);
continue;
}
DEBUG ("invalid name: %s", *iter);
}
g_ptr_array_add (bus_names, NULL);
g_ptr_array_add (cms, NULL);
g_ptr_array_add (protocols, NULL);
list_context->callback ((const gchar * const *) bus_names->pdata,
bus_names->len - 1, (const gchar * const *) cms->pdata,
(const gchar * const *) protocols->pdata,
NULL, list_context->user_data, user_object);
g_ptr_array_free (bus_names, TRUE);
g_strfreev ((char **) g_ptr_array_free (cms, FALSE));
g_strfreev ((char **) g_ptr_array_free (protocols, FALSE));
}
static void
list_context_free (gpointer p)
{
_ListContext *list_context = p;
if (list_context->destroy != NULL)
list_context->destroy (list_context->user_data);
g_slice_free (_ListContext, list_context);
}
/**
* tp_list_connection_names:
* @bus_daemon: proxy for the D-Bus daemon
* @callback: callback to be called when listing the connections succeeds or
* fails; not called if the D-Bus connection fails completely or 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 D-Bus connection fails or if the @weak_object
* goes away
* @weak_object: if not %NULL, will be weakly referenced; the callback will
* not be called if the object has vanished
*
* List the bus names of all the connections that currently exist, together
* with the connection manager name and the protocol name for each connection.
* Call the callback when done.
*
* The bus names passed to the callback can be used to construct #TpConnection
* objects for any connections that are of interest.
*
* Since: 0.7.1
*/
void
tp_list_connection_names (TpDBusDaemon *bus_daemon,
TpConnectionNameListCb callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object)
{
_ListContext *list_context = g_slice_new0 (_ListContext);
g_return_if_fail (TP_IS_DBUS_DAEMON (bus_daemon));
g_return_if_fail (callback != NULL);
list_context->callback = callback;
list_context->user_data = user_data;
tp_dbus_daemon_list_names (bus_daemon, 2000,
tp_list_connection_names_helper, list_context,
list_context_free, weak_object);
}
static gpointer
tp_connection_once (gpointer data G_GNUC_UNUSED)
{
GType type = TP_TYPE_CONNECTION;
tp_proxy_init_known_interfaces ();
tp_proxy_or_subclass_hook_on_interface_add (type,
tp_cli_connection_add_signals);
tp_proxy_subclass_add_error_mapping (type,
TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR);
return NULL;
}
/**
* tp_connection_init_known_interfaces:
*
* Ensure that the known interfaces for TpConnection have been set up.
* This is done automatically when necessary, but for correct
* overriding of library interfaces by local extensions, you should
* call this function before calling
* tp_proxy_or_subclass_hook_on_interface_add() with first argument
* %TP_TYPE_CONNECTION.
*
* Since: 0.7.6
*/
void
tp_connection_init_known_interfaces (void)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, tp_connection_once, NULL);
}
typedef struct {
TpConnectionWhenReadyCb callback;
gpointer user_data;
gulong invalidated_id;
gulong ready_id;
} CallWhenReadyContext;
static void
cwr_invalidated (TpConnection *self,
guint domain,
gint code,
gchar *message,
gpointer user_data)
{
CallWhenReadyContext *ctx = user_data;
GError e = { domain, code, message };
DEBUG ("enter");
g_assert (ctx->callback != NULL);
ctx->callback (self, &e, ctx->user_data);
g_signal_handler_disconnect (self, ctx->invalidated_id);
g_signal_handler_disconnect (self, ctx->ready_id);
ctx->callback = NULL; /* poison it to detect errors */
g_slice_free (CallWhenReadyContext, ctx);
}
static void
cwr_ready (TpConnection *self,
GParamSpec *unused G_GNUC_UNUSED,
gpointer user_data)
{
CallWhenReadyContext *ctx = user_data;
DEBUG ("enter");
g_assert (ctx->callback != NULL);
ctx->callback (self, NULL, ctx->user_data);
g_signal_handler_disconnect (self, ctx->invalidated_id);
g_signal_handler_disconnect (self, ctx->ready_id);
ctx->callback = NULL; /* poison it to detect errors */
g_slice_free (CallWhenReadyContext, ctx);
}
/**
* TpConnectionWhenReadyCb:
* @connection: the connection (which may be in the middle of being disposed,
* if error is non-%NULL, error->domain is TP_DBUS_ERRORS and error->code is
* TP_DBUS_ERROR_PROXY_UNREFERENCED)
* @error: %NULL if the connection is ready for use, or the error with which
* it was invalidated if it is now invalid
* @user_data: whatever was passed to tp_connection_call_when_ready()
*
* Signature of a callback passed to tp_connection_call_when_ready(), which
* will be called exactly once, when the connection becomes ready or
* invalid (whichever happens first)
*/
/**
* tp_connection_call_when_ready:
* @self: a connection
* @callback: called when the connection becomes ready or invalidated,
* whichever happens first
* @user_data: arbitrary user-supplied data passed to the callback
*
* If @self is ready for use or has been invalidated, call @callback
* immediately, then return. Otherwise, arrange
* for @callback to be called when @self either becomes ready for use
* or becomes invalid.
*
* Note that if the connection is not in state CONNECTED, the callback will
* not be called until the connection either goes to state CONNECTED
* or is invalidated (e.g. by going to state DISCONNECTED or by becoming
* unreferenced). In particular, this method does not call Connect().
* Call tp_cli_connection_call_connect() too, if you want to do that.
*
* Since: 0.7.7
*/
void
tp_connection_call_when_ready (TpConnection *self,
TpConnectionWhenReadyCb callback,
gpointer user_data)
{
TpProxy *as_proxy = (TpProxy *) self;
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (callback != NULL);
if (self->priv->ready || as_proxy->invalidated != NULL)
{
DEBUG ("already ready or invalidated");
callback (self, as_proxy->invalidated, user_data);
}
else
{
CallWhenReadyContext *ctx = g_slice_new (CallWhenReadyContext);
DEBUG ("arranging callback later");
ctx->callback = callback;
ctx->user_data = user_data;
ctx->invalidated_id = g_signal_connect (self, "invalidated",
G_CALLBACK (cwr_invalidated), ctx);
ctx->ready_id = g_signal_connect (self, "notify::connection-ready",
G_CALLBACK (cwr_ready), ctx);
}
}
static guint
get_presence_type_availability (TpConnectionPresenceType type)
{
switch (type)
{
case TP_CONNECTION_PRESENCE_TYPE_UNSET:
return 0;
case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
return 1;
case TP_CONNECTION_PRESENCE_TYPE_ERROR:
return 2;
case TP_CONNECTION_PRESENCE_TYPE_OFFLINE:
return 3;
case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
return 4;
case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
return 5;
case TP_CONNECTION_PRESENCE_TYPE_AWAY:
return 6;
case TP_CONNECTION_PRESENCE_TYPE_BUSY:
return 7;
case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
return 8;
}
/* This is an unexpected presence type, treat it like UNKNOWN */
return 1;
}
/**
* tp_connection_presence_type_cmp_availability:
* @p1: a #TpConnectionPresenceType
* @p2: a #TpConnectionPresenceType
*
* Compares @p1 and @p2 like strcmp(). @p1 > @p2 means @p1 is more available
* than @p2.
*
* The order used is: available > busy > away > xa > hidden > offline > error >
* unknown > unset
*
* Returns: -1, 0 or 1, if @p1 is <, == or > than @p2.
*
* Since: 0.7.16
*/
gint
tp_connection_presence_type_cmp_availability (TpConnectionPresenceType p1,
TpConnectionPresenceType p2)
{
guint availability1;
guint availability2;
availability1 = get_presence_type_availability (p1);
availability2 = get_presence_type_availability (p2);
if (availability1 < availability2)
return -1;
if (availability1 > availability2)
return +1;
return 0;
}
/**
* tp_connection_parse_object_path:
* @self: a connection
* @protocol: If not NULL, used to return the protocol of the connection
* @cm_name: If not NULL, used to return the connection manager name of the
* connection
*
* If the object path of @connection is in the correct form, set
* @protocol and @cm_name, return TRUE. Otherwise leave them unchanged and
* return FALSE.
*
* Returns: TRUE if the object path was correctly parsed, FALSE otherwise.
*
* Since: 0.7.27
*/
gboolean
tp_connection_parse_object_path (TpConnection *self,
gchar **protocol,
gchar **cm_name)
{
const gchar *object_path;
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
object_path = tp_proxy_get_object_path (TP_PROXY (self));
return _tp_connection_parse (object_path, '/', protocol, cm_name);
}
TpContact *
_tp_connection_lookup_contact (TpConnection *self,
TpHandle handle)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return g_hash_table_lookup (self->priv->contacts, GUINT_TO_POINTER (handle));
}
/* this could be done with proper weak references, but we know that every
* connection will weakly reference all its contacts, so we can just do this
* explicitly in tp_contact_dispose */
void
_tp_connection_remove_contact (TpConnection *self,
TpHandle handle,
TpContact *contact)
{
TpContact *mine;
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (TP_IS_CONTACT (contact));
mine = g_hash_table_lookup (self->priv->contacts, GUINT_TO_POINTER (handle));
g_return_if_fail (mine == contact);
g_hash_table_remove (self->priv->contacts, GUINT_TO_POINTER (handle));
}
void
_tp_connection_add_contact (TpConnection *self,
TpHandle handle,
TpContact *contact)
{
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (TP_IS_CONTACT (contact));
g_return_if_fail (g_hash_table_lookup (self->priv->contacts,
GUINT_TO_POINTER (handle)) == NULL);
g_hash_table_insert (self->priv->contacts, GUINT_TO_POINTER (handle),
contact);
}
/**
* tp_connection_is_ready:
* @self: a connection
*
* Returns the same thing as the #TpConnection:connection-ready property.
*
* Returns: %TRUE if introspection has completed
* Since: 0.7.17
*/
gboolean
tp_connection_is_ready (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->ready;
}