/*
* connection.c - proxy for a Telepathy connection
*
* Copyright (C) 2007-2011 Collabora Ltd.
* Copyright (C) 2007-2011 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 "telepathy-glib/connection.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEBUG_FLAG TP_DEBUG_CONNECTION
#include "telepathy-glib/capabilities-internal.h"
#include "telepathy-glib/connection-internal.h"
#include "telepathy-glib/connection-contact-list.h"
#include "telepathy-glib/dbus-internal.h"
#include "telepathy-glib/debug-internal.h"
#include "telepathy-glib/proxy-internal.h"
#include "telepathy-glib/client-factory-internal.h"
#include "telepathy-glib/contact-internal.h"
#include "telepathy-glib/util-internal.h"
#include "telepathy-glib/variant-util.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.
*
* #TpConnection objects should be obtained from a #TpAccount, unless you
* are implementing a lower-level Telepathy component (such as the account
* manager service itself).
*
* Since 0.16, #TpConnection always has a non-%NULL #TpProxy:factory, and its
* #TpProxy:factory will be propagated to its #TpChannel objects
* (if any). Similarly, the #TpProxy:factory's features
* will be used for #TpContact objects.
* If a #TpConnection is created without going via the
* #TpAccount or specifying a #TpProxy:factory, the default
* is to use a new #TpAutomaticClientFactory.
*
* Since: 0.7.1
*/
/**
* TP_CONNECTION_FEATURE_CORE:
*
* Expands to a call to a function that returns a quark for the "core" feature
* on a #TpConnection.
*
* When this feature is prepared, the basic properties of the Connection have
* been retrieved and are available for use, and change-notification has been
* set up for those that can change.
*
* Specifically, this implies that:
*
*
* #TpConnection:status has a value other than
* %TP_UNKNOWN_CONNECTION_STATUS, and #TpConnection:status-reason is
* the reason for changing to that status
* interfaces that are always available have been added to the
* #TpProxy:interfaces (although the set of interfaces is not guaranteed
* to be complete until #TpConnection:status becomes
* %TP_CONNECTION_STATUS_CONNECTED))
*
*
*
* prepared does not imply connected
* This
* feature does not imply that the connection has successfully connected.
* It only implies that an initial status (disconnected, connecting or
* connected) has been discovered.
*
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.11.3
*/
GQuark
tp_connection_get_feature_quark_core (void)
{
return g_quark_from_static_string ("tp-connection-feature-core");
}
/**
* TP_CONNECTION_FEATURE_CONNECTED:
*
* Expands to a call to a function that returns a #GQuark representing the
* "connected" feature.
*
* When this feature is prepared, it means that the connection has become
* connected to the appropriate real-time communications service, and all
* information requested via other features has been updated accordingly.
* In particular, the following aspects of %TP_CONNECTION_FEATURE_CORE
* will be up to date:
*
*
* #TpConnection:status is
* %TP_CONNECTION_STATUS_CONNECTED
* #TpConnection:self-contact is non-%NULL
* all interfaces have been added to the set of
* #TpProxy:interfaces, and that set will not change again
*
*
*
* Someone still has to call Connect()
* Requesting this feature via tp_proxy_prepare_async() means that
* you want to wait for the connection to connect, but it doesn't actually
* start the process of connecting. For connections associated with
* a #TpAccount, the account manager service is responsible for
* doing that, but if you are constructing connections directly
* (e.g. if you are implementing an account manager), you must
* tp_cli_connection_call_connect() separately.
*
*
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.11.3
*/
GQuark
tp_connection_get_feature_quark_connected (void)
{
return g_quark_from_static_string ("tp-connection-feature-connected");
}
/**
* TP_CONNECTION_FEATURE_CAPABILITIES:
*
* Expands to a call to a function that returns a #GQuark representing the
* "capabilities" feature.
*
* When this feature is prepared, the Requests.RequestableChannelClasses
* property of the Connection has been retrieved.
* In particular, the %TpConnection:capabilities property has been set.
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.11.3
*/
GQuark
tp_connection_get_feature_quark_capabilities (void)
{
return g_quark_from_static_string ("tp-connection-feature-capabilities");
}
/**
* TP_CONNECTION_FEATURE_BALANCE:
*
* Expands to a call to a function that returns a #GQuark representing the
* "balance" feature.
*
* When this feature is prepared, the Balance.AccountBalance and
* Balance.ManageCreditURI properties of the Connection have been retrieved.
* In particular, the %TpConnection:balance, %TpConnection:balance-scale,
* %TpConnection:balance-currency and %TpConnection:balance-uri properties
* have been set and the TpConnection::balance-changed: will be emitted
* when they are changed.
*
* One can ask for a feature to be prepared using the
* tp_proxy_prepare_async() function, and waiting for it to callback.
*
* Since: 0.15.1
*/
GQuark
tp_connection_get_feature_quark_balance (void)
{
return g_quark_from_static_string ("tp-connection-feature-balance");
}
/**
* 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.24, this error domain is only used if a connection manager emits
* a #TpConnectionStatusReason not known to telepathy-glib.
*
* 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:
*
* A proxy object for a Telepathy connection. There are no interesting
* public struct fields.
*
* (Changed in 0.7.12: the layout of the structure is visible, allowing
* subclassing.)
*
* Since: 0.7.1
*/
/* properties */
enum
{
PROP_STATUS = 1,
PROP_STATUS_REASON,
PROP_CM_NAME,
PROP_PROTOCOL_NAME,
PROP_SELF_CONTACT,
PROP_CAPABILITIES,
PROP_BALANCE,
PROP_BALANCE_SCALE,
PROP_BALANCE_CURRENCY,
PROP_BALANCE_URI,
PROP_CONTACT_LIST_STATE,
PROP_CONTACT_LIST_PERSISTS,
PROP_CAN_CHANGE_CONTACT_LIST,
PROP_REQUEST_USES_MESSAGE,
PROP_DISJOINT_GROUPS,
PROP_GROUP_STORAGE,
PROP_CONTACT_GROUPS,
PROP_CAN_REPORT_ABUSIVE,
PROP_BLOCKED_CONTACTS,
N_PROPS
};
enum {
SIGNAL_BALANCE_CHANGED,
SIGNAL_GROUPS_CREATED,
SIGNAL_GROUPS_REMOVED,
SIGNAL_GROUP_RENAMED,
SIGNAL_CONTACT_LIST_CHANGED,
SIGNAL_BLOCKED_CONTACTS_CHANGED,
N_SIGNALS
};
static guint signals[N_SIGNALS] = { 0 };
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_CM_NAME:
g_value_set_string (value, self->priv->cm_name);
break;
case PROP_PROTOCOL_NAME:
g_value_set_string (value, self->priv->proto_name);
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_CONTACT:
g_value_set_object (value, tp_connection_get_self_contact (self));
break;
case PROP_CAPABILITIES:
g_value_set_object (value, self->priv->capabilities);
break;
case PROP_BALANCE:
g_value_set_int (value, self->priv->balance);
break;
case PROP_BALANCE_SCALE:
g_value_set_uint (value, self->priv->balance_scale);
break;
case PROP_BALANCE_CURRENCY:
g_value_set_string (value, self->priv->balance_currency);
break;
case PROP_BALANCE_URI:
g_value_set_string (value, self->priv->balance_uri);
break;
case PROP_CONTACT_LIST_STATE:
g_value_set_uint (value, self->priv->contact_list_state);
break;
case PROP_CONTACT_LIST_PERSISTS:
g_value_set_boolean (value, self->priv->contact_list_persists);
break;
case PROP_CAN_CHANGE_CONTACT_LIST:
g_value_set_boolean (value, self->priv->can_change_contact_list);
break;
case PROP_REQUEST_USES_MESSAGE:
g_value_set_boolean (value, self->priv->request_uses_message);
break;
case PROP_DISJOINT_GROUPS:
g_value_set_boolean (value, self->priv->disjoint_groups);
break;
case PROP_GROUP_STORAGE:
g_value_set_uint (value, self->priv->group_storage);
break;
case PROP_CONTACT_GROUPS:
g_value_set_boxed (value, self->priv->contact_groups);
break;
case PROP_CAN_REPORT_ABUSIVE:
g_value_set_boolean (value, tp_connection_can_report_abusive (self));
break;
case PROP_BLOCKED_CONTACTS:
g_value_set_boxed (value, tp_connection_get_blocked_contacts (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
tp_connection_unpack_balance (TpConnection *self,
GValueArray *balance_s)
{
gint balance = 0;
guint scale = G_MAXUINT32;
const char *currency = "";
gboolean changed = FALSE;
if (balance_s == NULL)
goto finally;
tp_value_array_unpack (balance_s, 3,
&balance, &scale, ¤cy);
finally:
g_object_freeze_notify ((GObject *) self);
if (self->priv->balance != balance)
{
self->priv->balance = balance;
g_object_notify ((GObject *) self, "balance");
changed = TRUE;
}
if (self->priv->balance_scale != scale)
{
self->priv->balance_scale = scale;
g_object_notify ((GObject *) self, "balance-scale");
changed = TRUE;
}
if (tp_strdiff (self->priv->balance_currency, currency))
{
g_free (self->priv->balance_currency);
self->priv->balance_currency = g_strdup (currency);
g_object_notify ((GObject *) self, "balance-currency");
changed = TRUE;
}
g_object_thaw_notify ((GObject *) self);
if (changed)
{
g_signal_emit (self, signals[SIGNAL_BALANCE_CHANGED], 0,
balance, scale, currency);
}
}
static void
tp_connection_get_balance_cb (TpProxy *proxy,
GHashTable *props,
const GError *in_error,
gpointer user_data,
GObject *weak_obj)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result = user_data;
GValueArray *balance = NULL;
if (in_error != NULL)
{
DEBUG ("Failed to get Balance properties: %s", in_error->message);
g_simple_async_result_set_from_error (result, in_error);
goto finally;
}
balance =
tp_asv_get_boxed (props, "AccountBalance", TP_STRUCT_TYPE_CURRENCY_AMOUNT);
self->priv->balance_uri =
g_strdup (tp_asv_get_string (props, "ManageCreditURI"));
g_object_freeze_notify ((GObject *) self);
tp_connection_unpack_balance (self, balance);
_tp_proxy_set_feature_prepared (proxy, TP_CONNECTION_FEATURE_BALANCE,
TRUE);
g_object_notify ((GObject *) self, "balance-uri");
g_object_thaw_notify ((GObject *) self);
finally:
g_simple_async_result_complete (result);
}
static void
tp_connection_balance_changed_cb (TpConnection *self,
const GValueArray *balance,
gpointer user_data,
GObject *weak_obj)
{
tp_connection_unpack_balance (self, (GValueArray *) balance);
}
static void
tp_connection_prepare_balance_async (TpProxy *proxy,
const TpProxyFeature *feature,
GAsyncReadyCallback callback,
gpointer user_data)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result;
result = g_simple_async_result_new ((GObject *) proxy, callback, user_data,
tp_connection_prepare_balance_async);
g_assert (self->priv->balance_currency == NULL);
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CONNECTION_INTERFACE_BALANCE1,
tp_connection_get_balance_cb, result, g_object_unref, NULL);
tp_cli_connection_interface_balance1_connect_to_balance_changed (self,
tp_connection_balance_changed_cb,
NULL, NULL, NULL, NULL);
}
static void
tp_connection_get_rcc_cb (TpProxy *proxy,
const GValue *value,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result;
if (error != NULL)
{
DEBUG ("Failed to get RequestableChannelClasses property, using an "
"empty set: %s", error->message);
/* it's NULL-safe */
self->priv->capabilities = _tp_capabilities_new (NULL, FALSE);
goto finally;
}
g_assert (self->priv->capabilities == NULL);
if (!G_VALUE_HOLDS (value, TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST))
{
DEBUG ("RequestableChannelClasses is not of type a(a{sv}as), using an "
"empty set: %s", G_VALUE_TYPE_NAME (value));
/* it's NULL-safe */
self->priv->capabilities = _tp_capabilities_new (NULL, FALSE);
goto finally;
}
DEBUG ("CAPABILITIES ready");
self->priv->capabilities = _tp_capabilities_new (
dbus_g_value_build_g_variant (value), FALSE);
finally:
while ((result = g_queue_pop_head (&self->priv->capabilities_queue)) != NULL)
{
g_simple_async_result_complete (result);
g_object_unref (result);
}
g_object_notify ((GObject *) self, "capabilities");
}
static void
_tp_connection_do_get_capabilities_async (TpConnection *self,
GSimpleAsyncResult *result)
{
if (self->priv->capabilities != NULL)
{
/* been there, done that, bored now */
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
else
{
g_queue_push_tail (&self->priv->capabilities_queue, result);
if (g_queue_get_length (&self->priv->capabilities_queue) == 1)
{
DEBUG ("%s: Retrieving capabilities",
tp_proxy_get_object_path (self));
/* We don't check whether we actually have this interface here.
* The Requests interface is mandatory, and we assume we have it. */
tp_cli_dbus_properties_call_get (self, -1,
TP_IFACE_CONNECTION, "RequestableChannelClasses",
tp_connection_get_rcc_cb, NULL, NULL, NULL);
}
}
}
static void
tp_connection_prepare_capabilities_async (TpProxy *proxy,
const TpProxyFeature *feature,
GAsyncReadyCallback callback,
gpointer user_data)
{
TpConnection *self = (TpConnection *) proxy;
GSimpleAsyncResult *result;
DEBUG ("%s: Preparing capabilities", tp_proxy_get_object_path (self));
result = g_simple_async_result_new ((GObject *) self, callback, user_data,
tp_connection_prepare_capabilities_async);
_tp_connection_do_get_capabilities_async (self, result);
}
static void
signal_connected (TpConnection *self)
{
/* we shouldn't have gone to status CONNECTED for any reason
* that isn't REQUESTED :-) */
DEBUG ("%s (%p): CORE and CONNECTED ready",
tp_proxy_get_object_path (self), self);
self->priv->status = TP_CONNECTION_STATUS_CONNECTED;
self->priv->status_reason = TP_CONNECTION_STATUS_REASON_REQUESTED;
_tp_proxy_set_feature_prepared ((TpProxy *) self,
TP_CONNECTION_FEATURE_CONNECTED, TRUE);
_tp_proxy_set_feature_prepared ((TpProxy *) self,
TP_CONNECTION_FEATURE_CORE, TRUE);
g_object_notify ((GObject *) self, "status");
g_object_notify ((GObject *) self, "status-reason");
}
static void
will_announced_connected_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
TpConnection *self = (TpConnection *) source;
GError *error = NULL;
if (!_tp_proxy_will_announce_connected_finish ((TpProxy *) self, result,
&error))
{
DEBUG ("_tp_connection_prepare_contact_info_async failed: %s",
error->message);
g_error_free (error);
}
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("Connection has been invalidated; we're done");
return;
}
signal_connected (self);
}
static void
tp_connection_continue_introspection (TpConnection *self)
{
if (tp_proxy_get_invalidated (self) != NULL)
{
DEBUG ("Already invalidated: not becoming ready");
return;
}
if (self->priv->introspect_needed == NULL)
{
if (!self->priv->introspecting_after_connected)
{
/* Introspection will restart when we become CONNECTED */
DEBUG ("CORE ready, but not CONNECTED");
_tp_proxy_set_feature_prepared ((TpProxy *) self,
TP_CONNECTION_FEATURE_CORE, TRUE);
return;
}
/* We'll announce CONNECTED state soon, but first give a chance to
* prepared feature to be updated, if needed */
_tp_proxy_will_announce_connected_async ((TpProxy *) self,
will_announced_connected_cb, NULL);
}
else
{
TpConnectionProc next = self->priv->introspect_needed->data;
self->priv->introspect_needed = g_list_delete_link (
self->priv->introspect_needed,
self->priv->introspect_needed);
next (self);
}
}
static void
tp_connection_set_self_contact (TpConnection *self,
TpContact *contact)
{
if (contact != self->priv->self_contact)
{
TpContact *tmp = self->priv->self_contact;
self->priv->self_contact = g_object_ref (contact);
tp_clear_object (&tmp);
g_object_notify ((GObject *) self, "self-contact");
}
if (self->priv->introspecting_self_contact)
{
self->priv->introspecting_self_contact = FALSE;
tp_connection_continue_introspection (self);
}
}
static void
upgrade_self_contact_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
TpClientFactory *factory = (TpClientFactory *) object;
TpConnection *self = user_data;
GPtrArray *contacts;
TpContact *contact;
GError *error = NULL;
if (!tp_client_factory_upgrade_contacts_finish (factory, result, &contacts,
&error))
{
DEBUG ("Error upgrading self contact: %s", error->message);
g_clear_error (&error);
goto out;
}
g_assert (contacts->len == 1);
contact = g_ptr_array_index (contacts, 0);
/* Self contact could have changed while we were upgrading */
if (tp_contact_get_handle (contact) == self->priv->last_known_self_handle)
{
tp_connection_set_self_contact (self, contact);
}
g_ptr_array_unref (contacts);
out:
g_object_unref (self);
}
static void
get_self_contact (TpConnection *self)
{
TpClientFactory *factory;
TpContact *contact;
factory = tp_proxy_get_factory (self);
contact = tp_client_factory_ensure_contact (factory, self,
self->priv->last_known_self_handle,
self->priv->last_known_self_id);
tp_client_factory_upgrade_contacts_async (factory, self,
1, &contact, upgrade_self_contact_cb, g_object_ref (self));
g_object_unref (contact);
}
static void
introspect_self_contact (TpConnection *self)
{
self->priv->introspecting_self_contact = TRUE;
get_self_contact (self);
}
static void
on_self_contact_changed (TpConnection *self,
guint self_handle,
const gchar *self_id,
gpointer user_data G_GNUC_UNUSED,
GObject *user_object G_GNUC_UNUSED)
{
if (self_handle == 0)
{
DEBUG ("Ignoring alleged change of self-handle to %u", self_handle);
return;
}
if (self->priv->last_known_self_handle == 0)
{
/* We're going to call GetAll(Connection) anyway, or if the CM
* is sufficiently deficient, GetSelfHandle(). */
DEBUG ("Ignoring early self-handle change to %u, we'll pick it up later",
self_handle);
return;
}
if (self->priv->last_known_self_handle == self_handle &&
!tp_strdiff (self_id, self->priv->last_known_self_id))
{
DEBUG ("Ignoring no-op self-handle change to %u '%s'",
self_handle, self_id);
return;
}
DEBUG ("SelfHandleChanged to %u '%s'", self_handle, self_id);
self->priv->last_known_self_handle = self_handle;
g_free (self->priv->last_known_self_id);
self->priv->last_known_self_id = g_strdup (self_id);
if (self->priv->introspecting_after_connected ||
tp_connection_get_status (self, NULL) == TP_CONNECTION_STATUS_CONNECTED)
get_self_contact (self);
}
/* Appending callbacks to self->priv->introspect_needed relies on this */
G_STATIC_ASSERT (sizeof (TpConnectionProc) <= sizeof (gpointer));
static void
_tp_connection_got_properties (TpProxy *proxy,
GHashTable *asv,
const GError *error,
gpointer unused G_GNUC_UNUSED,
GObject *unused_object G_GNUC_UNUSED);
static void
tp_connection_status_changed (TpConnection *self,
guint status,
guint reason)
{
DEBUG ("%p: %d -> %d because %d", self, self->priv->status, status, reason);
if (status == TP_CONNECTION_STATUS_CONNECTED)
{
if (self->priv->introspection_call != NULL &&
!self->priv->introspecting_after_connected)
{
/* We thought we knew what was going on, but now the connection has
* gone to CONNECTED and all bets are off. Start again! */
DEBUG ("Cancelling pre-CONNECTED introspection and starting again");
tp_proxy_pending_call_cancel (self->priv->introspection_call);
self->priv->introspection_call = NULL;
g_list_free (self->priv->introspect_needed);
self->priv->introspect_needed = NULL;
}
self->priv->introspecting_after_connected = TRUE;
/* we defer the perceived change to CONNECTED until ready */
if (self->priv->introspection_call == NULL)
{
self->priv->introspection_call =
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CONNECTION, _tp_connection_got_properties, NULL, NULL, NULL);
}
}
else
{
self->priv->status = status;
self->priv->status_reason = reason;
g_object_notify ((GObject *) self, "status");
g_object_notify ((GObject *) self, "status-reason");
}
}
static void
tp_connection_connection_error_cb (TpConnection *self,
const gchar *error_name,
GHashTable *details,
gpointer user_data,
GObject *weak_object)
{
g_free (self->priv->connection_error);
self->priv->connection_error = g_strdup (error_name);
if (self->priv->connection_error_details != NULL)
g_hash_table_unref (self->priv->connection_error_details);
self->priv->connection_error_details = g_boxed_copy (
TP_HASH_TYPE_STRING_VARIANT_MAP, details);
}
void
_tp_connection_status_reason_to_gerror (TpConnectionStatusReason reason,
TpConnectionStatus prev_status,
const gchar **ret_str,
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");
if (ret_str != NULL)
*ret_str = TP_ERROR_STR_DISCONNECTED;
return;
}
g_set_error (error, TP_ERROR, code, "%s", message);
if (ret_str != NULL)
*ret_str = tp_error_get_dbus_name (code);
}
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;
/* The status is initially attempted to be discovered starting in the
* constructor. If we don't have the reply for that call yet, ignore this
* signal StatusChanged in order to run the interface introspection only one
* time. We will get the initial introspection 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)
{
GError *error = NULL;
if (self->priv->connection_error == NULL)
{
_tp_connection_status_reason_to_gerror (reason, prev_status,
NULL, &error);
}
else
{
g_assert (self->priv->connection_error_details != NULL);
tp_proxy_dbus_error_to_gerror (self, self->priv->connection_error,
tp_asv_get_string (self->priv->connection_error_details,
"debug-message"), &error);
/* ... but if we don't know anything about that D-Bus error
* name, we can still be more helpful by deriving an error code from
* TpConnectionStatusReason */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR))
{
GError *from_csr = NULL;
_tp_connection_status_reason_to_gerror (reason, prev_status,
NULL, &from_csr);
error->domain = from_csr->domain;
error->code = from_csr->code;
g_error_free (from_csr);
}
}
tp_proxy_invalidate ((TpProxy *) self, error);
g_error_free (error);
}
}
static void
tp_connection_invalidated (TpConnection *self)
{
if (self->priv->introspection_call != NULL)
{
DEBUG ("Cancelling introspection");
tp_proxy_pending_call_cancel (self->priv->introspection_call);
self->priv->introspection_call = NULL;
}
}
static void
_tp_connection_got_properties (TpProxy *proxy,
GHashTable *asv,
const GError *error,
gpointer unused G_GNUC_UNUSED,
GObject *unused_object G_GNUC_UNUSED)
{
TpConnection *self = TP_CONNECTION (proxy);
TpConnectionStatus status;
const gchar * const *interfaces;
gboolean valid;
GError *e = NULL;
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;
}
if (error != NULL)
{
/* Properties are now mandatory */
tp_proxy_invalidate (proxy, error);
return;
}
if (self->priv->introspection_call)
self->priv->introspection_call = NULL;
interfaces = tp_asv_get_strv (asv, "Interfaces");
if (interfaces == NULL)
goto error;
tp_proxy_add_interfaces (proxy, interfaces);
self->priv->ready_enough_for_contacts = TRUE;
status = tp_asv_get_uint32 (asv, "Status", &valid);
if (!valid || status > TP_CONNECTION_STATUS_DISCONNECTED)
goto error;
if (status == TP_CONNECTION_STATUS_CONNECTED)
{
TpHandle self_handle;
const gchar *self_id;
self_handle = tp_asv_get_uint32 (asv, "SelfHandle", &valid);
if (!valid || self_handle == 0)
goto error;
self_id = tp_asv_get_string (asv, "SelfID");
if (tp_str_empty (self_id))
goto error;
self->priv->introspecting_after_connected = TRUE;
self->priv->last_known_self_handle = self_handle;
g_free (self->priv->last_known_self_id);
self->priv->last_known_self_id = g_strdup (self_id);
self->priv->introspect_needed = g_list_append (
self->priv->introspect_needed, introspect_self_contact);
}
else
{
tp_connection_status_changed (self, status,
TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
}
tp_connection_continue_introspection (self);
return;
error:
e = g_error_new_literal (TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"Connection does not implement all properties.");
WARNING ("%s", e->message);
tp_proxy_invalidate (proxy, e);
g_clear_error (&e);
}
static gboolean _tp_connection_parse (const gchar *path_or_bus_name,
char delimiter,
gchar **protocol,
gchar **cm_name);
static void
tp_connection_constructed (GObject *object)
{
GObjectClass *object_class = (GObjectClass *) tp_connection_parent_class;
TpConnection *self = TP_CONNECTION (object);
const gchar *object_path;
if (object_class->constructed != NULL)
object_class->constructed (object);
DEBUG ("%s (%p) constructed", tp_proxy_get_object_path (object), object);
g_assert (tp_proxy_get_factory (self) != NULL);
/* 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 */
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);
tp_cli_connection_connect_to_self_contact_changed (self,
on_self_contact_changed, NULL, NULL, NULL, NULL);
object_path = tp_proxy_get_object_path (TP_PROXY (self));
g_assert (_tp_connection_parse (object_path, '/',
&(self->priv->proto_name), &(self->priv->cm_name)));
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CONNECTION, _tp_connection_got_properties, NULL, NULL, NULL);
g_signal_connect (self, "invalidated",
G_CALLBACK (tp_connection_invalidated), NULL);
}
static void
tp_connection_init (TpConnection *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CONNECTION,
TpConnectionPrivate);
/* This is conceptually part of Connection core now. The only reason
* it's kept separate is to reduce D-Bus traffic, since the
* ChannelDispatcher implementation is normally the only thing
* that needs to see its properties or signals. */
tp_proxy_add_interface_by_id ((TpProxy *) self,
TP_IFACE_QUARK_CONNECTION_INTERFACE_REQUESTS);
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);
self->priv->introspection_call = NULL;
self->priv->interests = tp_intset_new ();
self->priv->contact_groups = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (self->priv->contact_groups, NULL);
self->priv->roster = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_object_unref);
self->priv->contacts_changed_queue = g_queue_new ();
g_queue_init (&self->priv->capabilities_queue);
self->priv->blocked_contacts = g_ptr_array_new_with_free_func (
g_object_unref);
self->priv->blocked_changed_queue = g_queue_new ();
}
static void
tp_connection_finalize (GObject *object)
{
TpConnection *self = TP_CONNECTION (object);
DEBUG ("%p", self);
tp_clear_pointer (&self->priv->cm_name, g_free);
tp_clear_pointer (&self->priv->proto_name, g_free);
/* not true unless we were finalized before we were ready */
if (self->priv->introspect_needed != NULL)
{
g_list_free (self->priv->introspect_needed);
self->priv->introspect_needed = NULL;
}
g_free (self->priv->connection_error);
self->priv->connection_error = NULL;
if (self->priv->connection_error_details != NULL)
{
g_hash_table_unref (self->priv->connection_error_details);
self->priv->connection_error_details = NULL;
}
if (self->priv->avatar_request_queue != NULL)
{
g_array_unref (self->priv->avatar_request_queue);
self->priv->avatar_request_queue = NULL;
}
if (self->priv->avatar_request_idle_id != 0)
{
g_source_remove (self->priv->avatar_request_idle_id);
self->priv->avatar_request_idle_id = 0;
}
tp_contact_info_spec_list_free (self->priv->contact_info_supported_fields);
self->priv->contact_info_supported_fields = NULL;
tp_clear_pointer (&self->priv->balance_currency, g_free);
tp_clear_pointer (&self->priv->balance_uri, g_free);
tp_clear_pointer (&self->priv->cm_name, g_free);
tp_clear_pointer (&self->priv->proto_name, g_free);
tp_clear_pointer (&self->priv->last_known_self_id, g_free);
((GObjectClass *) tp_connection_parent_class)->finalize (object);
}
static void
contact_notify_disposed (gpointer k G_GNUC_UNUSED,
gpointer v,
gpointer d G_GNUC_UNUSED)
{
_tp_contact_connection_disposed (v);
}
static void
tp_connection_dispose (GObject *object)
{
TpConnection *self = TP_CONNECTION (object);
DEBUG ("%p", object);
if (self->priv->account != NULL)
{
g_object_remove_weak_pointer ((GObject *) self->priv->account,
(gpointer) &self->priv->account);
self->priv->account = NULL;
}
tp_clear_pointer (&self->priv->contact_groups, g_ptr_array_unref);
tp_clear_pointer (&self->priv->roster, g_hash_table_unref);
tp_clear_pointer (&self->priv->contacts_changed_queue,
_tp_connection_contacts_changed_queue_free);
tp_clear_pointer (&self->priv->blocked_changed_queue,
_tp_connection_blocked_changed_queue_free);
if (self->priv->contacts != NULL)
{
g_hash_table_foreach (self->priv->contacts, contact_notify_disposed,
NULL);
tp_clear_pointer (&self->priv->contacts, g_hash_table_unref);
}
tp_clear_object (&self->priv->capabilities);
tp_clear_pointer (&self->priv->avatar_requirements,
tp_avatar_requirements_destroy);
if (self->priv->interests != NULL)
{
guint size = tp_intset_size (self->priv->interests);
/* Before freeing the set of tokens in which we declared an
* interest, cancel those interests. We'll still get the signals
* if there's another interested TpConnection in this process,
* because the CM uses distributed refcounting. */
if (size > 0)
{
TpIntsetFastIter iter;
GPtrArray *strings;
guint element;
strings = g_ptr_array_sized_new (size + 1);
tp_intset_fast_iter_init (&iter, self->priv->interests);
while (tp_intset_fast_iter_next (&iter, &element))
g_ptr_array_add (strings,
(gchar *) g_quark_to_string (element));
g_ptr_array_add (strings, NULL);
/* no callback - if the CM replies, we'll ignore it anyway */
tp_cli_connection_call_remove_client_interest (self, -1,
(const gchar **) strings->pdata, NULL, NULL, NULL, NULL);
g_ptr_array_unref (strings);
}
tp_intset_destroy (self->priv->interests);
self->priv->interests = NULL;
}
tp_clear_pointer (&self->priv->blocked_contacts, g_ptr_array_unref);
g_clear_object (&self->priv->self_contact);
((GObjectClass *) tp_connection_parent_class)->dispose (object);
}
enum {
FEAT_CORE,
FEAT_CONNECTED,
FEAT_CAPABILITIES,
FEAT_AVATAR_REQUIREMENTS,
FEAT_CONTACT_INFO,
FEAT_BALANCE,
FEAT_CONTACT_LIST,
FEAT_CONTACT_LIST_PROPS,
FEAT_CONTACT_GROUPS,
FEAT_CONTACT_BLOCKING,
FEAT_ALIASING,
N_FEAT
};
static const TpProxyFeature *
tp_connection_list_features (TpProxyClass *cls G_GNUC_UNUSED)
{
static TpProxyFeature features[N_FEAT + 1] = { { 0 } };
static GQuark need_avatars[2] = {0, 0};
static GQuark need_contact_info[2] = {0, 0};
static GQuark need_balance[2] = {0, 0};
static GQuark need_contact_list[2] = {0, 0};
static GQuark need_contact_groups[2] = {0, 0};
static GQuark need_contact_blocking[2] = {0, 0};
static GQuark depends_contact_list[2] = {0, 0};
static GQuark need_aliasing[2] = {0, 0};
if (G_LIKELY (features[0].name != 0))
return features;
features[FEAT_CORE].name = TP_CONNECTION_FEATURE_CORE;
features[FEAT_CORE].core = TRUE;
features[FEAT_CONNECTED].name = TP_CONNECTION_FEATURE_CONNECTED;
features[FEAT_CAPABILITIES].name = TP_CONNECTION_FEATURE_CAPABILITIES;
features[FEAT_CAPABILITIES].prepare_async =
tp_connection_prepare_capabilities_async;
features[FEAT_AVATAR_REQUIREMENTS].name = TP_CONNECTION_FEATURE_AVATAR_REQUIREMENTS;
features[FEAT_AVATAR_REQUIREMENTS].prepare_async =
_tp_connection_prepare_avatar_requirements_async;
need_avatars[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS1;
features[FEAT_AVATAR_REQUIREMENTS].interfaces_needed = need_avatars;
features[FEAT_CONTACT_INFO].name = TP_CONNECTION_FEATURE_CONTACT_INFO;
features[FEAT_CONTACT_INFO].prepare_async =
_tp_connection_prepare_contact_info_async;
need_contact_info[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO1;
features[FEAT_CONTACT_INFO].interfaces_needed = need_contact_info;
features[FEAT_BALANCE].name = TP_CONNECTION_FEATURE_BALANCE;
features[FEAT_BALANCE].prepare_async = tp_connection_prepare_balance_async;
need_balance[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_BALANCE1;
features[FEAT_BALANCE].interfaces_needed = need_balance;
features[FEAT_CONTACT_LIST].name = TP_CONNECTION_FEATURE_CONTACT_LIST;
features[FEAT_CONTACT_LIST].prepare_async = _tp_connection_prepare_contact_list_async;
need_contact_list[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_LIST1;
features[FEAT_CONTACT_LIST].interfaces_needed = need_contact_list;
depends_contact_list[0] = TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES;
features[FEAT_CONTACT_LIST].depends_on = depends_contact_list;
features[FEAT_CONTACT_LIST_PROPS].name = TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES;
features[FEAT_CONTACT_LIST_PROPS].prepare_async = _tp_connection_prepare_contact_list_props_async;
features[FEAT_CONTACT_LIST_PROPS].interfaces_needed = need_contact_list;
features[FEAT_CONTACT_GROUPS].name = TP_CONNECTION_FEATURE_CONTACT_GROUPS;
features[FEAT_CONTACT_GROUPS].prepare_async = _tp_connection_prepare_contact_groups_async;
need_contact_groups[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_GROUPS1;
features[FEAT_CONTACT_GROUPS].interfaces_needed = need_contact_groups;
features[FEAT_CONTACT_BLOCKING].name = TP_CONNECTION_FEATURE_CONTACT_BLOCKING;
features[FEAT_CONTACT_BLOCKING].prepare_async = _tp_connection_prepare_contact_blocking_async;
need_contact_blocking[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING1;
features[FEAT_CONTACT_BLOCKING].interfaces_needed = need_contact_blocking;
features[FEAT_ALIASING].name = TP_CONNECTION_FEATURE_ALIASING;
features[FEAT_ALIASING].prepare_async = _tp_connection_prepare_aliasing_async;
need_aliasing[0] = TP_IFACE_QUARK_CONNECTION_INTERFACE_ALIASING1;
features[FEAT_ALIASING].interfaces_needed = need_aliasing;
/* assert that the terminator at the end is there */
g_assert (features[N_FEAT].name == 0);
return features;
}
static void
tp_connection_class_init (TpConnectionClass *klass)
{
GParamSpec *param_spec;
TpProxyClass *proxy_class = (TpProxyClass *) klass;
GObjectClass *object_class = (GObjectClass *) klass;
g_type_class_add_private (klass, sizeof (TpConnectionPrivate));
object_class->constructed = tp_connection_constructed;
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;
proxy_class->list_features = tp_connection_list_features;
/**
* TpConnection:status:
*
* This connection's status, or %TP_UNKNOWN_CONNECTION_STATUS if we don't
* know yet.
*
* To wait for a valid status (and other properties), call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_CORE.
*
* Since version 0.11.3, the change to status
* %TP_CONNECTION_STATUS_CONNECTED is delayed slightly, until introspection
* of the connection has finished.
*/
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_STRINGS);
g_object_class_install_property (object_class, PROP_STATUS,
param_spec);
/**
* TpConnection:cm-name:
*
* This connection's connection manager name.
*
* Since: 0.19.3
*/
g_object_class_install_property (object_class, PROP_CM_NAME,
g_param_spec_string ("cm-name",
"Connection manager name",
"The connection's connection manager name",
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
/**
* TpConnection:protocol-name:
*
* The connection's machine-readable protocol name, such as "jabber",
* "msn" or "local-xmpp". Recommended names for most protocols can be
* found in the Telepathy D-Bus Interface Specification.
*
* Since: 0.13.16
*
*/
g_object_class_install_property (object_class, PROP_PROTOCOL_NAME,
g_param_spec_string ("protocol-name",
"Protocol name",
"The connection's protocol name",
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
/**
* TpConnection:self-contact:
*
* A #TpContact representing the local user on this connection,
* or %NULL if not yet available.
*
* If the local user's unique identifier changes (for instance by using
* /nick on IRC), this property will change to a different #TpContact object
* representing the new identifier, and #GObject::notify will be emitted.
*
* The #TpContact object is guaranteed to have all of the features previously
* passed to tp_client_factory_add_contact_features() prepared.
*
* To wait for a non-%NULL self-contact (and other properties), call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONNECTED.
*
* Since: 0.13.9
*/
param_spec = g_param_spec_object ("self-contact", "Self contact",
"The local user's Contact object on this connection", TP_TYPE_CONTACT,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SELF_CONTACT,
param_spec);
/**
* TpConnection:status-reason:
*
* To wait for a valid status (and other properties), call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_CORE.
*
* 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_STRINGS);
g_object_class_install_property (object_class, PROP_STATUS_REASON,
param_spec);
/**
* TpConnection:capabilities:
*
* The %TpCapabilities object representing the capabilities of this
* connection, or NULL if we don't know yet.
*
* To wait for valid capability information, call tp_proxy_prepare_async()
* with the feature %TP_CONNECTION_FEATURE_CAPABILITIES.
*/
param_spec = g_param_spec_object ("capabilities", "Capabilities",
"A TpCapabilities object representing the capabilities of the connection",
TP_TYPE_CAPABILITIES,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAPABILITIES,
param_spec);
/**
* TpConnection:balance:
*
* The Amount field of the Balance.AccountBalance property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* See Also: tp_connection_get_balance()
*/
param_spec = g_param_spec_int ("balance", "Balance Amount",
"The Amount field of the Account Balance",
G_MININT32, G_MAXINT32, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE,
param_spec);
/**
* TpConnection:balance-scale:
*
* The Scale field of the Balance.AccountBalance property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* See Also: tp_connection_get_balance()
*/
param_spec = g_param_spec_uint ("balance-scale", "Balance Scale",
"The Scale field of the Account Balance",
0, G_MAXUINT32, G_MAXUINT32,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE_SCALE,
param_spec);
/**
* TpConnection:balance-currency:
*
* The Currency field of the Balance.AccountBalance property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* See Also: tp_connection_get_balance()
*/
param_spec = g_param_spec_string ("balance-currency", "Balance Currency",
"The Currency field of the Account Balance",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE_CURRENCY,
param_spec);
/**
* TpConnection:balance-uri:
*
* The Balance.ManageCreditURI property.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*/
param_spec = g_param_spec_string ("balance-uri", "Balance URI",
"The URI for managing the account balance",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BALANCE_URI,
param_spec);
/**
* TpConnection::balance-changed:
* @self: a channel
* @balance: the value of the #TpConnection:balance property
* @balance_scale: the value of the #TpConnection:balance-scale property
* @balance_currency: the value of the #TpConnection:balance-currency property
*
* Emitted when at least one of the #TpConnection:balance,
* #TpConnection:balance-scale or #TpConnection:balance-currency
* property is changed.
*
* For this signal to be emitted, you must first call
* tp_proxy_prepare_async() with the feature %TP_CONNECTION_FEATURE_BALANCE.
*
* Since: 0.15.1
*/
signals[SIGNAL_BALANCE_CHANGED] = g_signal_new ("balance-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_UINT, G_TYPE_STRING);
/**
* TpConnection:contact-list-state:
*
* The progress made in retrieving the contact list.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_uint ("contact-list-state", "ContactList state",
"The state of the contact list",
0, G_MAXUINT, TP_CONTACT_LIST_STATE_NONE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_LIST_STATE,
param_spec);
/**
* TpConnection:contact-list-persists:
*
* If true, presence subscriptions (in both directions) on this connection are
* stored by the server or other infrastructure.
*
* If false, presence subscriptions on this connection are not stored.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("contact-list-persists",
"ContactList persists", "Whether the contact list persists",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_LIST_PERSISTS,
param_spec);
/**
* TpConnection:can-change-contact-list:
*
* If true, presence subscription and publication can be changed using the
* RequestSubscription, AuthorizePublication and RemoveContacts methods.
*
* Rational: link-local XMPP, presence is implicitly published to everyone in
* the local subnet, so the user cannot control their presence publication.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("can-change-contact-list",
"ContactList can change", "Whether the contact list can change",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAN_CHANGE_CONTACT_LIST,
param_spec);
/**
* TpConnection:request-uses-message:
*
* If true, the Message parameter to RequestSubscription is likely to be
* significant, and user interfaces SHOULD prompt the user for a message to
* send with the request; a message such as "I would like to add you to my
* contact list", translated into the local user's language, might make a
* suitable default.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST_PROPERTIES or
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("request-uses-message",
"Request Uses Message", "Whether request uses message",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_REQUEST_USES_MESSAGE,
param_spec);
/**
* TpConnection:disjoint-groups:
*
* True if each contact can be in at most one group; false if each contact
* can be in many groups.
*
* This property cannot change after the connection has moved to the
* %TP_CONNECTION_STATUS_CONNECTED state. Until then, its value is undefined,
* and it may change at any time, without notification.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boolean ("disjoint-groups",
"Disjoint Groups", "Whether groups are disjoint",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_DISJOINT_GROUPS,
param_spec);
/**
* TpConnection:group-storage:
*
* Indicates the extent to which contacts' groups can be set and stored.
*
* This property cannot change after the connection has moved to the
* %TP_CONNECTION_STATUS_CONNECTED state. Until then, its value is undefined,
* and it may change at any time, without notification.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_uint ("group-storage",
"Group Storage", "Group storage capabilities",
0, G_MAXUINT, TP_CONTACT_METADATA_STORAGE_TYPE_NONE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_GROUP_STORAGE,
param_spec);
/**
* TpConnection:contact-groups:
*
* The names of all groups that currently exist. This may be a larger set than
* the union of all #TpContact:contact-groups properties, if the connection
* allows groups to be empty.
*
* This property's value is not meaningful until the
* #TpConnection:contact-list-state property has become
* %TP_CONTACT_LIST_STATE_SUCCESS.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
param_spec = g_param_spec_boxed ("contact-groups",
"Contact Groups",
"All existing contact groups",
G_TYPE_STRV,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CONTACT_GROUPS,
param_spec);
/**
* TpConnection:can-report-abusive:
*
* If this property is %TRUE, contacts may be reported as abusive to the
* server administrators by setting report_abusive to %TRUE when calling
* tp_connection_block_contacts_async().
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_BLOCKING.
*
* Since: 0.17.0
*/
param_spec = g_param_spec_boolean ("can-report-abusive",
"Can report abusive",
"Can report abusive",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CAN_REPORT_ABUSIVE,
param_spec);
/**
* TpConnection:blocked-contacts:
*
* A #GPtrArray of blocked #TpContact. Changes are notified using the
* #TpConnection::blocked-contacts-changed signal.
*
* These TpContact objects have been prepared with the desired features.
* See tp_client_factory_add_contact_features() to define which
* features needs to be prepared on them.
*
* For this property to be valid, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_BLOCKING.
*
* Since: 0.17.0
*/
param_spec = g_param_spec_boxed ("blocked-contacts",
"blocked contacts",
"Blocked contacts",
G_TYPE_PTR_ARRAY,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_BLOCKED_CONTACTS,
param_spec);
/**
* TpConnection::groups-created:
* @self: a #TpConnection
* @added: a #GStrv with the names of the new groups.
*
* Emitted when new, empty groups are created. This will often be followed by
* #TpContact::contact-groups-changed signals that add some members. When this
* signal is emitted, #TpConnection:contact-groups property is already
* updated.
*
* For this signal to be emited, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
signals[SIGNAL_GROUPS_CREATED] = g_signal_new (
"groups-created",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_STRV);
/**
* TpConnection::groups-removed:
* @self: A #TpConnection
* @added: A #GStrv with the names of the groups.
*
* Emitted when one or more groups are removed. If they had members at the
* time that they were removed, then immediately after this signal is emitted,
* #TpContact::contact-groups-changed signals that their members were removed.
* When this signal is emitted, #TpConnection:contact-groups property is
* already updated.
*
* For this signal to be emited, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
signals[SIGNAL_GROUPS_REMOVED] = g_signal_new (
"groups-removed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_STRV);
/**
* TpConnection::group-renamed:
* @self: a #TpConnection
* @old_name: the old name of the group.
* @new_name: the new name of the group.
*
* Emitted when a group is renamed, in protocols where this can be
* distinguished from group creation, removal and membership changes.
*
* Immediately after this signal is emitted, #TpConnection::groups-created
* signal the creation of a group with the new name, and
* #TpConnection::groups-removed signal the removal of a group with the old
* name.
* If the group was not empty, immediately after those signals are emitted,
* #TpContact::contact-groups-changed signal that the members of that group
* were removed from the old name and added to the new name.
*
* When this signal is emitted, #TpConnection:contact-groups property is
* already updated.
*
* For this signal to be emited, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_GROUPS.
*
* Since: 0.15.5
*/
signals[SIGNAL_GROUP_RENAMED] = g_signal_new (
"group-renamed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
/**
* TpConnection::contact-list-changed:
* @self: a #TpConnection
* @added: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact added to contacts list
* @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact removed from contacts list
*
* Notify of changes in the list of contacts as returned by
* tp_connection_dup_contact_list(). It is guaranteed that all contacts have
* desired features prepared. See
* tp_client_factory_add_contact_features() to define which features
* needs to be prepared.
*
* This signal is also emitted for the initial set of contacts once retrieved.
*
* For this signal to be emitted, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_LIST.
*
* Since: 0.15.5
*/
signals[SIGNAL_CONTACT_LIST_CHANGED] = g_signal_new (
"contact-list-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY);
/**
* TpConnection::blocked-contacts-changed:
* @self: a #TpConnection
* @added: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact which have been blocked
* @removed: (type GLib.PtrArray) (element-type TelepathyGLib.Contact):
* a #GPtrArray of #TpContact which are no longer blocked
*
* Notify of changes in #TpConnection:blocked-contacts.
* It is guaranteed that all contacts have desired features prepared. See
* tp_client_factory_add_contact_features() to define which features
* needs to be prepared.
*
* This signal is also emitted for the initial set of blocked contacts once
* retrieved.
*
* For this signal to be emitted, you must first call
* tp_proxy_prepare_async() with the feature
* %TP_CONNECTION_FEATURE_CONTACT_BLOCKING.
*
* Since: 0.17.0
*/
signals[SIGNAL_BLOCKED_CONTACTS_CHANGED] = g_signal_new (
"blocked-contacts-changed",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_PTR_ARRAY);
}
TpConnection *
_tp_connection_new (TpClientFactory *factory,
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_CLIENT_FACTORY (factory), 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_connection_parse (object_path, '/', NULL, NULL))
{
g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_INVALID_OBJECT_PATH,
"Connection object path is not in the right format");
goto finally;
}
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_connection_get_name_owner (
tp_client_factory_get_dbus_connection (factory), 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,
"bus-name", bus_name,
"object-path", object_path,
"factory", factory,
NULL));
finally:
g_free (dup_path);
g_free (dup_name);
g_free (dup_unique_name);
return ret;
}
/**
* tp_connection_get_account:
* @self: a connection
*
* Return the the #TpAccount associated with this connection. Will return %NULL
* if @self was not acquired from a #TpAccount via tp_account_get_connection(),
* or if the account object got finalized in the meantime (#TpConnection does
* not keep a strong ref on its #TpAccount).
*
* Returns: (transfer none): the account associated with this connection, or
* %NULL.
*
* Since: 0.15.5
*/
TpAccount *
tp_connection_get_account (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->account;
}
void
_tp_connection_set_account (TpConnection *self,
TpAccount *account)
{
if (self->priv->account == account)
return;
g_assert (self->priv->account == NULL);
g_assert (account != NULL);
g_assert (tp_proxy_get_factory (self) == tp_proxy_get_factory (account));
self->priv->account = account;
g_object_add_weak_pointer ((GObject *) account,
(gpointer) &self->priv->account);
}
/**
* tp_connection_get_status:
* @self: a connection
* @reason: (out): 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_get_cm_name:
* @self: a #TpConnection
*
*
*
* Returns: the same as the #TpConnection:cm-name property
*
* Since: 0.19.3
*
*/
const gchar *
tp_connection_get_cm_name (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->cm_name;
}
/**
* tp_connection_get_protocol_name:
* @self: a #TpConnection
*
*
*
* Returns: the same as the #TpConnection:protocol-name property
*
* Since: 0.13.16
*
*/
const gchar *
tp_connection_get_protocol_name (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->proto_name;
}
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:
* /im/telepathy/v1/Connection/cmname/proto/account
* im.telepathy.v1.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_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 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;
}
/* Can return a contact that's not meant to be visible to library users
* because it lacks an identifier */
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);
/* Set TP_CONTACT_FEATURE_CONTACT_BLOCKING if possible */
if (tp_proxy_is_prepared (self, TP_CONNECTION_FEATURE_CONTACT_BLOCKING))
{
_tp_connection_set_contact_blocked (self, contact);
}
}
/**
* tp_connection_get_capabilities:
* @self: a connection
*
*
*
* Returns: (transfer none): the same #TpCapabilities as the
* #TpConnection:capabilities property
* Since: 0.11.3
*/
TpCapabilities *
tp_connection_get_capabilities (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->capabilities;
}
/*
* tp_connection_get_detailed_error:
* @self: a connection
* @details: (out) (allow-none) (element-type utf8 GObject.Value) (transfer none):
* optionally used to return a map from string to #GValue, which must not be
* modified or destroyed by the caller
*
* If the connection has disconnected, return the D-Bus error name with which
* it disconnected (in particular, this is %TP_ERROR_STR_CANCELLED if it was
* disconnected by a user request).
*
* Otherwise, return %NULL, without altering @details.
*
* Returns: (transfer none) (allow-none): a D-Bus error name, or %NULL.
*
* Since: 0.11.4
*/
static const gchar *
tp_connection_get_detailed_error (TpConnection *self,
const GHashTable **details)
{
TpProxy *proxy = (TpProxy *) self;
if (tp_proxy_get_invalidated (proxy) == NULL)
return NULL;
if (self->priv->connection_error != NULL)
{
g_assert (self->priv->connection_error_details != NULL);
if (details != NULL)
*details = self->priv->connection_error_details;
return self->priv->connection_error;
}
else
{
/* no detailed error, but we *have* been invalidated - guess one based
* on the invalidation reason */
if (details != NULL)
{
if (self->priv->connection_error_details == NULL)
{
self->priv->connection_error_details = tp_asv_new (
"debug-message", G_TYPE_STRING, tp_proxy_get_invalidated (proxy)->message,
NULL);
}
*details = self->priv->connection_error_details;
}
if (tp_proxy_get_invalidated (proxy)->domain == TP_ERROR)
{
return tp_error_get_dbus_name (tp_proxy_get_invalidated (proxy)->code);
}
else if (tp_proxy_get_invalidated (proxy)->domain == TP_DBUS_ERRORS)
{
switch (tp_proxy_get_invalidated (proxy)->code)
{
case TP_DBUS_ERROR_NAME_OWNER_LOST:
/* the CM probably crashed */
return DBUS_ERROR_NO_REPLY;
break;
case TP_DBUS_ERROR_OBJECT_REMOVED:
case TP_DBUS_ERROR_INCONSISTENT:
/* ... and all other cases up to and including
* TP_DBUS_ERROR_CANCELLED don't make sense in this context, so
* just use the generic one for them too */
default:
return TP_ERROR_STR_DISCONNECTED;
}
}
else
{
/* no idea what that means */
return TP_ERROR_STR_DISCONNECTED;
}
}
}
/**
* tp_connection_dup_detailed_error:
* @self: a connection
* @details: (out) (allow-none) (transfer full):
* optionally used to return a %G_VARIANT_TYPE_VARDICT with details
* of the error
*
* If the connection has disconnected, return the D-Bus error name with which
* it disconnected (in particular, this is %TP_ERROR_STR_CANCELLED if it was
* disconnected by a user request).
*
* Otherwise, return %NULL, without altering @details.
*
* Returns: (transfer full) (allow-none): a D-Bus error name, or %NULL.
*
* Since: 0.19.0
*/
gchar *
tp_connection_dup_detailed_error (TpConnection *self,
GVariant **details)
{
const GHashTable *asv;
const gchar *error = tp_connection_get_detailed_error (self, &asv);
if (error == NULL)
return NULL;
if (details != NULL)
*details = g_variant_ref_sink (tp_asv_to_vardict (asv));
return g_strdup (error);
}
/**
* tp_connection_add_client_interest:
* @self: a connection
* @interested_in: a string identifying an interface or part of an interface
* to which this connection will subscribe
*
* Subscribe to any opt-in change notifications for @interested_in.
*
* For contact information, use #TpContact instead, which will call this
* automatically.
*
* Since: 0.11.3
*/
void
tp_connection_add_client_interest (TpConnection *self,
const gchar *interested_in)
{
tp_connection_add_client_interest_by_id (self,
g_quark_from_string (interested_in));
}
/**
* tp_connection_add_client_interest_by_id: (skip)
* @self: a connection
* @interested_in: a quark identifying an interface or part of an interface
* to which this connection will subscribe
*
* Subscribe to any opt-in change notifications for @interested_in.
*
* Equivalent to, but a little more efficient than, calling
* tp_connection_add_client_interest() for the string value of @interested_in.
*
* Since: 0.11.3
*/
void
tp_connection_add_client_interest_by_id (TpConnection *self,
GQuark interested_in)
{
TpProxy *proxy = (TpProxy *) self;
const gchar *interest = g_quark_to_string (interested_in);
const gchar *strv[2] = { interest, NULL };
g_return_if_fail (TP_IS_CONNECTION (self));
g_return_if_fail (interest != NULL);
if (tp_proxy_get_invalidated (proxy) != NULL ||
tp_intset_is_member (self->priv->interests, interested_in))
return;
tp_intset_add (self->priv->interests, interested_in);
/* no-reply flag set, and we ignore any reply */
tp_cli_connection_call_add_client_interest (self, -1,
strv, NULL, NULL, NULL, NULL);
}
/**
* tp_connection_get_self_contact:
* @self: a connection
*
* Return a #TpContact representing the local user on this connection.
*
* The returned object is not necessarily valid after the main loop is
* re-entered; ref it with g_object_ref() if you want to keep it.
*
* Returns: (transfer none): the value of the TpConnection:self-contact
* property, which may be %NULL
*
* Since: 0.13.9
*/
TpContact *
tp_connection_get_self_contact (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return self->priv->self_contact;
}
/**
* tp_connection_bind_connection_status_to_property:
* @self: a #TpConnection
* @target: the target #GObject
* @target_property: the property on @target to bind (must be %G_TYPE_BOOLEAN)
* @invert: %TRUE if you wish to invert the value of @target_property
* (i.e. %FALSE if connected)
*
* Binds the :status of @self to the boolean property of another
* object using a #GBinding such that the @target_property will be set to
* %TRUE when @self is connected (and @invert is %FALSE).
*
* @target_property will be synchronised immediately (%G_BINDING_SYNC_CREATE).
* @invert can be interpreted as analogous to %G_BINDING_INVERT_BOOLEAN.
*
* For instance, this function can be used to bind the GtkWidget:sensitive
* property to only make a widget sensitive when the account is connected.
*
* See g_object_bind_property() for more information.
*
* Returns: (transfer none): the #GBinding instance representing the binding
* between the @self and the @target. The binding is released whenever the
* #GBinding reference count reaches zero.
* Since: 0.13.16
*/
GBinding *
tp_connection_bind_connection_status_to_property (TpConnection *self,
gpointer target,
const char *target_property,
gboolean invert)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), NULL);
return g_object_bind_property_full (self, "status",
target, target_property,
G_BINDING_SYNC_CREATE,
_tp_bind_connection_status_to_boolean,
NULL, GUINT_TO_POINTER (invert), NULL);
}
/**
* tp_connection_get_balance:
* @self: a #TpConnection
* @balance: (out): a pointer to store the account balance (or %NULL)
* @scale: (out): a pointer to store the balance scale (or %NULL)
* @currency: (out) (transfer none): a pointer to store the balance
* currency (or %NULL)
*
* If @self has a valid account balance, returns %TRUE and sets the variables
* pointed to by @balance, @scale and @currency to the appropriate fields
* of the Balance.AccountBalance property.
*
* The monetary value of the balance is expressed as a fixed-point number,
* @balance, with a decimal scale defined by @scale; for instance a @balance
* of 1234 with @scale of 2 represents a value of "12.34" in the currency
* represented by @currency.
*
* Requires %TP_CONNECTION_FEATURE_BALANCE to be prepared.
*
* Returns: %TRUE if the balance is valid (and the values set), %FALSE if the
* balance is invalid.
* Since: 0.15.1
*/
gboolean
tp_connection_get_balance (TpConnection *self,
gint *balance,
guint *scale,
const gchar **currency)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
if (self->priv->balance_currency == NULL)
return FALSE;
if (self->priv->balance == 0 &&
self->priv->balance_scale == G_MAXUINT32 &&
tp_str_empty (self->priv->balance_currency))
return FALSE;
if (balance != NULL)
*balance = self->priv->balance;
if (scale != NULL)
*scale = self->priv->balance_scale;
if (currency != NULL)
*currency = self->priv->balance_currency;
return TRUE;
}
/**
* tp_connection_get_balance_uri:
* @self: a #TpConnection
*
* The value of Balance.ManageCreditURI.
*
* Requires %TP_CONNECTION_FEATURE_BALANCE to be prepared.
*
* Returns: (transfer none): the #TpConnection:balance-uri property.
* Since: 0.15.1
*/
const gchar *
tp_connection_get_balance_uri (TpConnection *self)
{
g_return_val_if_fail (TP_IS_CONNECTION (self), FALSE);
return self->priv->balance_uri;
}
static void
_tp_connection_void_cb (TpConnection *proxy,
const GError *error,
gpointer user_data,
GObject *weak_object)
{
GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data);
if (error != NULL)
g_simple_async_result_set_from_error (result, error);
g_simple_async_result_complete_in_idle (result);
g_object_unref (G_OBJECT (result));
}
/**
* tp_connection_disconnect_async:
* @self: a #TpConnection
* @callback: a callback to call when the request is satisfied
* @user_data: data to pass to @callback
*
* Disconnect the connection.
*
* This method is intended for use by AccountManager implementations,
* such as Mission Control. To disconnect a connection managed by an
* AccountManager, either use tp_account_request_presence_async()
* or tp_account_set_enabled_async(), depending whether the intention is
* to put the account offline temporarily, or disable it longer-term.
*
* Since: 0.17.5
*/
void
tp_connection_disconnect_async (TpConnection *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
g_return_if_fail (TP_IS_CONNECTION (self));
result = g_simple_async_result_new (G_OBJECT (self), callback,
user_data, tp_connection_disconnect_async);
tp_cli_connection_call_disconnect (self, -1, _tp_connection_void_cb, result,
NULL, NULL);
}
/**
* tp_connection_disconnect_finish:
* @self: a #TpConnection
* @result: a #GAsyncResult
* @error: a #GError to fill
*
* Interpret the result of tp_connection_disconnect_async().
*
* Returns: %TRUE if the call was successful, otherwise %FALSE
*
* Since: 0.17.5
*/
gboolean
tp_connection_disconnect_finish (TpConnection *self,
GAsyncResult *result,
GError **error)
{
_tp_implement_finish_void (self, tp_connection_disconnect_async);
}