diff -r 000000000000 -r d0f3a028347a telepathygabble/src/gabble-connection.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telepathygabble/src/gabble-connection.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,5227 @@ +/* + * gabble-connection.c - Source for GabbleConnection + * Copyright (C) 2005 Collabora Ltd. + * and/or its subsidiary/subsidiaries. All rights reserved. + * + * 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 +#include +#include +#include "loudmouth/loudmouth.h" +#include +#include +#include + + +#include "handles.h" +#include "handle-set.h" +#include "telepathy-constants.h" +#include "telepathy-errors.h" +#include "telepathy-helpers.h" +#include "telepathy-interfaces.h" +#include "loudmouth/lm-connection.h" + +#include "tp-channel-iface.h" +#include "tp-channel-factory-iface.h" + +#include "gabble-connection.h" +#include "gabble-connection-glue.h" +#include "gabble-connection-signals-marshal.h" + + +#include "capabilities.h" +#include "debug.h" +#include "disco.h" +#include "gabble-presence-cache.h" +#include "gabble-presence.h" +#include "gabble-register.h" +#include "im-factory.h" +#include "search-factory.h" +#include "jingle-info.h" +#include "media-factory.h" +#include "muc-factory.h" +#include "namespaces.h" +#include "roster.h" +#include "util.h" +#include "vcard-manager.h" +#include "search-keys-info.h" + +#include "gabble-media-channel.h" +#include "gabble-roomlist-channel.h" + +#include "gabble_enums.h" +#include "sha1.h" +#include "base64.h" + +#define DEBUG_FLAG GABBLE_DEBUG_CONNECTION +#define DBUS_API_SUBJECT_TO_CHANGE +#define BUS_NAME "org.freedesktop.Telepathy.Connection.gabble" +#define OBJECT_PATH "/org/freedesktop/Telepathy/Connection/gabble" + +#define TP_ALIAS_PAIR_TYPE (dbus_g_type_get_struct ("GValueArray", \ + G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID)) +#define TP_CAPABILITY_PAIR_TYPE (dbus_g_type_get_struct ("GValueArray", \ + G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INVALID)) +#define TP_CAPABILITIES_CHANGED_MONSTER_TYPE (dbus_g_type_get_struct \ + ("GValueArray", G_TYPE_UINT, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, \ + G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID)) +#define TP_GET_CAPABILITIES_MONSTER_TYPE (dbus_g_type_get_struct \ + ("GValueArray", G_TYPE_UINT, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, \ + G_TYPE_INVALID)) +#define TP_CHANNEL_LIST_ENTRY_TYPE (dbus_g_type_get_struct ("GValueArray", \ + DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, \ + G_TYPE_INVALID)) + +#define ERROR_IF_NOT_CONNECTED(CONN, ERROR) \ + if ((CONN)->status != TP_CONN_STATUS_CONNECTED) \ + { \ + gabble_debug (DEBUG_FLAG, "rejected request as disconnected"); \ + g_set_error (ERROR, TELEPATHY_ERRORS, NotAvailable, \ + "Connection is disconnected"); \ + return FALSE; \ + } + +#define ERROR_IF_NOT_CONNECTED_ASYNC(CONN, ERROR, CONTEXT) \ + if ((CONN)->status != TP_CONN_STATUS_CONNECTED) \ + { \ + gabble_debug (DEBUG_FLAG, "rejected request as disconnected"); \ + (ERROR) = g_error_new (TELEPATHY_ERRORS, NotAvailable, \ + "Connection is disconnected"); \ + dbus_g_method_return_error ((CONTEXT), (ERROR)); \ + g_error_free ((ERROR)); \ + return; \ + } + +#ifdef DEBUG_FLAG +//#define DEBUG(format, ...) +#define DEBUGGING 0 +#define NODE_DEBUG(n, s) +#endif /* DEBUG_FLAG */ + +#ifndef EMULATOR +G_DEFINE_TYPE(GabbleConnection, gabble_connection, G_TYPE_OBJECT) +#endif + +typedef struct _StatusInfo StatusInfo; + +struct _StatusInfo +{ + const gchar *name; + TpConnectionPresenceType presence_type; + const gboolean self; + const gboolean exclusive; +}; + +/* order must match PresenceId enum in gabble-connection.h */ +/* in increasing order of presence */ +static const StatusInfo gabble_statuses[LAST_GABBLE_PRESENCE] = { + { "offline", TP_CONN_PRESENCE_TYPE_OFFLINE, TRUE, TRUE }, + { "hidden", TP_CONN_PRESENCE_TYPE_HIDDEN, TRUE, TRUE }, + { "xa", TP_CONN_PRESENCE_TYPE_EXTENDED_AWAY, TRUE, TRUE }, + { "away", TP_CONN_PRESENCE_TYPE_AWAY, TRUE, TRUE }, + { "dnd", TP_CONN_PRESENCE_TYPE_AWAY, TRUE, TRUE }, + { "available", TP_CONN_PRESENCE_TYPE_AVAILABLE, TRUE, TRUE }, + { "chat", TP_CONN_PRESENCE_TYPE_AVAILABLE, TRUE, TRUE } +}; + +/* signal enum */ +enum +{ + ALIASES_CHANGED, + CAPABILITIES_CHANGED, + NEW_CHANNEL, + PRESENCE_UPDATE, + STATUS_CHANGED, + DISCONNECTED, + LAST_SIGNAL +#ifdef EMULATOR + = LAST_SIGNAL_CON +#endif + +}; + + +#ifdef EMULATOR +#include "libgabble_wsd_solution.h" + + GET_STATIC_ARRAY_FROM_TLS(signals,gabble_con,guint) + #define signals (GET_WSD_VAR_NAME(signals,gabble_con, s)()) + + + GET_STATIC_VAR_FROM_TLS(arguments,gabble_con,GHashTable*) + #define arguments (*GET_WSD_VAR_NAME(arguments,gabble_con, s)()) + + GET_STATIC_VAR_FROM_TLS(gabble_connection_parent_class,gabble_con,gpointer) + #define gabble_connection_parent_class (*GET_WSD_VAR_NAME(gabble_connection_parent_class,gabble_con,s)()) + + GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_con,GType) + #define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_con,s)()) + + //GET_STATIC_ARRAY_FROM_TLS(assumed_caps,gabble_con,gchar*) + /*gchar** _s_gabble_con_assumed_caps() { return (gchar**)((libgabble_ImpurePtr()->_s_gabble_con_assumed_caps)); } + + #define assumed_caps (GET_WSD_VAR_NAME(assumed_caps,gabble_con, s)()) */ + + + +static void gabble_connection_init (GabbleConnection *self); +static void gabble_connection_class_init (GabbleConnectionClass *klass); +static void gabble_connection_class_intern_init (gpointer klass) +{ +gabble_connection_parent_class = g_type_class_peek_parent (klass); +gabble_connection_class_init ((GabbleConnectionClass*) klass); +} +EXPORT_C GType gabble_connection_get_type (void) +{ + +if ((g_define_type_id == 0)) +{ +static const GTypeInfo g_define_type_info = + { sizeof (GabbleConnectionClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_connection_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleConnection), 0, (GInstanceInitFunc) gabble_connection_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleConnection"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; + } ; + +#else + + static guint signals[LAST_SIGNAL] = {0}; + +#endif + +/* properties */ +enum +{ + PROP_PROTOCOL = 1, + PROP_CONNECT_SERVER, + PROP_PORT, + PROP_OLD_SSL, + PROP_REGISTER, + PROP_LOW_BANDWIDTH, + PROP_STREAM_SERVER, + PROP_USERNAME, + PROP_PASSWORD, + PROP_RESOURCE, + PROP_PRIORITY, + PROP_HTTPS_PROXY_SERVER, + PROP_HTTPS_PROXY_PORT, + PROP_FALLBACK_CONFERENCE_SERVER, + PROP_STUN_SERVER, + PROP_STUN_PORT, + PROP_STUN_RELAY_MAGIC_COOKIE, + PROP_STUN_RELAY_SERVER, + PROP_STUN_RELAY_UDP_PORT, + PROP_STUN_RELAY_TCP_PORT, + PROP_STUN_RELAY_SSLTCP_PORT, + PROP_STUN_RELAY_USERNAME, + PROP_STUN_RELAY_PASSWORD, + PROP_IGNORE_SSL_ERRORS, + PROP_ALIAS, + + LAST_PROPERTY +}; + +/* TP properties */ +enum +{ + CONN_PROP_STUN_SERVER = 0, + CONN_PROP_STUN_PORT, + CONN_PROP_STUN_RELAY_MAGIC_COOKIE, + CONN_PROP_STUN_RELAY_SERVER, + CONN_PROP_STUN_RELAY_UDP_PORT, + CONN_PROP_STUN_RELAY_TCP_PORT, + CONN_PROP_STUN_RELAY_SSLTCP_PORT, + CONN_PROP_STUN_RELAY_USERNAME, + CONN_PROP_STUN_RELAY_PASSWORD, + + NUM_CONN_PROPS, + + INVALID_CONN_PROP, +}; + +const GabblePropertySignature connection_property_signatures[NUM_CONN_PROPS] = { + { "stun-server", G_TYPE_STRING }, + { "stun-port", G_TYPE_UINT }, + { "stun-relay-magic-cookie", G_TYPE_STRING }, + { "stun-relay-server", G_TYPE_STRING }, + { "stun-relay-udp-port", G_TYPE_UINT }, + { "stun-relay-tcp-port", G_TYPE_UINT }, + { "stun-relay-ssltcp-port", G_TYPE_UINT }, + { "stun-relay-username", G_TYPE_STRING }, + { "stun-relay-password", G_TYPE_STRING }, +}; + +/* private structure */ +typedef struct _GabbleConnectionPrivate GabbleConnectionPrivate; + +struct _GabbleConnectionPrivate +{ + LmMessageHandler *iq_jingle_info_cb; + LmMessageHandler *iq_search_keys_cb; + LmMessageHandler *iq_disco_cb; + LmMessageHandler *iq_unknown_cb; + LmMessageHandler *stream_error_cb; + + /* telepathy properties */ + gchar *protocol; + + /* connection properties */ + gchar *connect_server; + guint port; + gboolean old_ssl; + + gboolean ignore_ssl_errors; + TpConnectionStatusReason ssl_error; + + gboolean do_register; + + gboolean low_bandwidth; + + gchar *https_proxy_server; + guint https_proxy_port; + + gchar *fallback_conference_server; + + /* authentication properties */ + gchar *stream_server; + gchar *username; + gchar *password; + gchar *resource; + gint8 priority; + gchar *alias; + + /* reference to conference server name */ + const gchar *conference_server; + + /* channel factories */ + GPtrArray *channel_factories; + GPtrArray *channel_requests; + gboolean suppress_next_handler; + + /* serial number of current advertised caps */ + guint caps_serial; + + /* gobject housekeeping */ + gboolean dispose_has_run; +}; + +#define GABBLE_CONNECTION_GET_PRIVATE(obj) \ + ((GabbleConnectionPrivate *)obj->priv) + +typedef struct _ChannelRequest ChannelRequest; + +struct _ChannelRequest +{ + DBusGMethodInvocation *context; + gchar *channel_type; + guint handle_type; + guint handle; + gboolean suppress_handler; +}; + +static void connection_new_channel_cb (TpChannelFactoryIface *, GObject *, gpointer); +static void connection_channel_error_cb (TpChannelFactoryIface *, GObject *, GError *, gpointer); +static void connection_nickname_update_cb (GObject *, GabbleHandle, gpointer); +static void connection_presence_update_cb (GabblePresenceCache *, GabbleHandle, gpointer); +static void connection_capabilities_update_cb (GabblePresenceCache *, GabbleHandle, GabblePresenceCapabilities, GabblePresenceCapabilities, gpointer); + +static void +gabble_connection_init (GabbleConnection *self) +{ + GabbleConnectionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + GABBLE_TYPE_CONNECTION, GabbleConnectionPrivate); + guint i; + GValue val = { 0, }; + + self->priv = priv; + self->lmconn = lm_connection_new (NULL); + self->status = TP_CONN_STATUS_NEW; + self->handles = gabble_handle_repo_new (); + self->disco = gabble_disco_new (self); + self->vcard_manager = gabble_vcard_manager_new (self); + self->search_instr = NULL; + self->search_key_names = NULL; + self->search_reported_fields = NULL; + self->search_form = FALSE; + self->search_service_jid = NULL; + self->self_avatar_sha1 = NULL; + self->self_handle = 0; + g_signal_connect (self->vcard_manager, "nickname-update", G_CALLBACK + (connection_nickname_update_cb), self); + + self->presence_cache = gabble_presence_cache_new (self); + g_signal_connect (self->presence_cache, "nickname-update", G_CALLBACK + (connection_nickname_update_cb), self); + g_signal_connect (self->presence_cache, "presence-update", G_CALLBACK + (connection_presence_update_cb), self); + g_signal_connect (self->presence_cache, "capabilities-update", G_CALLBACK + (connection_capabilities_update_cb), self); + + capabilities_fill_cache (self->presence_cache); + + self->roster = gabble_roster_new (self); + g_signal_connect (self->roster, "nickname-update", G_CALLBACK + (connection_nickname_update_cb), self); + + priv->channel_factories = g_ptr_array_sized_new (1); + + g_ptr_array_add (priv->channel_factories, self->roster); + + g_ptr_array_add (priv->channel_factories, + g_object_new (GABBLE_TYPE_MUC_FACTORY, + "connection", self, + NULL)); + + g_ptr_array_add (priv->channel_factories, + g_object_new (GABBLE_TYPE_MEDIA_FACTORY, + "connection", self, + NULL)); + + g_ptr_array_add (priv->channel_factories, + g_object_new (GABBLE_TYPE_IM_FACTORY, + "connection", self, + NULL)); + + //add for search here + g_ptr_array_add (priv->channel_factories, + g_object_new (GABBLE_TYPE_SEARCH_FACTORY, + "connection", self, + NULL)); + + for (i = 0; i < priv->channel_factories->len; i++) + { + GObject *factory = g_ptr_array_index (priv->channel_factories, i); + g_signal_connect (factory, "new-channel", G_CALLBACK + (connection_new_channel_cb), self); + g_signal_connect (factory, "channel-error", G_CALLBACK + (connection_channel_error_cb), self); + } + + priv->channel_requests = g_ptr_array_new (); + + /* Set default parameters for optional parameters */ + priv->resource = g_strdup (GABBLE_PARAMS_DEFAULT_RESOURCE); + priv->port = GABBLE_PARAMS_DEFAULT_PORT; + priv->https_proxy_port = GABBLE_PARAMS_DEFAULT_HTTPS_PROXY_PORT; + + /* initialize properties mixin */ + gabble_properties_mixin_init (G_OBJECT (self), G_STRUCT_OFFSET ( + GabbleConnection, properties)); + + g_value_init (&val, G_TYPE_UINT); + g_value_set_uint (&val, GABBLE_PARAMS_DEFAULT_STUN_PORT); + + gabble_properties_mixin_change_value (G_OBJECT (self), CONN_PROP_STUN_PORT, + &val, NULL); + gabble_properties_mixin_change_flags (G_OBJECT (self), CONN_PROP_STUN_PORT, + TP_PROPERTY_FLAG_READ, 0, NULL); + + g_value_unset (&val); + + priv->caps_serial = 1; +} + +static void +gabble_connection_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GabbleConnection *self = (GabbleConnection *) object; + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (self); + const gchar *param_name; + guint tp_property_id; + + switch (property_id) { + case PROP_PROTOCOL: + g_value_set_string (value, priv->protocol); + break; + case PROP_CONNECT_SERVER: + g_value_set_string (value, priv->connect_server); + break; + case PROP_STREAM_SERVER: + g_value_set_string (value, priv->stream_server); + break; + case PROP_PORT: + g_value_set_uint (value, priv->port); + break; + case PROP_OLD_SSL: + g_value_set_boolean (value, priv->old_ssl); + break; + case PROP_REGISTER: + g_value_set_boolean (value, priv->do_register); + break; + case PROP_LOW_BANDWIDTH: + g_value_set_boolean (value, priv->low_bandwidth); + break; + case PROP_USERNAME: + g_value_set_string (value, priv->username); + break; + case PROP_PASSWORD: + g_value_set_string (value, priv->password); + break; + case PROP_RESOURCE: + g_value_set_string (value, priv->resource); + break; + case PROP_PRIORITY: + g_value_set_int (value, priv->priority); + break; + case PROP_HTTPS_PROXY_SERVER: + g_value_set_string (value, priv->https_proxy_server); + break; + case PROP_HTTPS_PROXY_PORT: + g_value_set_uint (value, priv->https_proxy_port); + break; + case PROP_FALLBACK_CONFERENCE_SERVER: + g_value_set_string (value, priv->fallback_conference_server); + break; + case PROP_IGNORE_SSL_ERRORS: + g_value_set_boolean (value, priv->ignore_ssl_errors); + break; + case PROP_ALIAS: + g_value_set_string (value, priv->alias); + break; + default: + param_name = g_param_spec_get_name (pspec); + + if (gabble_properties_mixin_has_property (object, param_name, + &tp_property_id)) + { + GValue *tp_property_value = + self->properties.properties[tp_property_id].value; + + if (tp_property_value) + { + g_value_copy (tp_property_value, value); + return; + } + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gabble_connection_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GabbleConnection *self = (GabbleConnection *) object; + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (self); + const gchar *param_name; + guint tp_property_id; + + switch (property_id) { + case PROP_PROTOCOL: + g_free (priv->protocol); + priv->protocol = g_value_dup_string (value); + break; + case PROP_CONNECT_SERVER: + g_free (priv->connect_server); + priv->connect_server = g_value_dup_string (value); + break; + case PROP_PORT: + priv->port = g_value_get_uint (value); + break; + case PROP_OLD_SSL: + priv->old_ssl = g_value_get_boolean (value); + break; + case PROP_REGISTER: + priv->do_register = g_value_get_boolean (value); + break; + case PROP_LOW_BANDWIDTH: + priv->low_bandwidth = g_value_get_boolean (value); + break; + case PROP_STREAM_SERVER: + g_free (priv->stream_server); + priv->stream_server = g_value_dup_string (value); + break; + case PROP_USERNAME: + g_free (priv->username); + priv->username = g_value_dup_string (value); + break; + case PROP_PASSWORD: + g_free (priv->password); + priv->password = g_value_dup_string (value); + break; + case PROP_RESOURCE: + g_free (priv->resource); + priv->resource = g_value_dup_string (value); + break; + case PROP_PRIORITY: + priv->priority = CLAMP (g_value_get_int (value), G_MININT8, G_MAXINT8); + break; + case PROP_HTTPS_PROXY_SERVER: + g_free (priv->https_proxy_server); + priv->https_proxy_server = g_value_dup_string (value); + break; + case PROP_HTTPS_PROXY_PORT: + priv->https_proxy_port = g_value_get_uint (value); + break; + case PROP_FALLBACK_CONFERENCE_SERVER: + g_free (priv->fallback_conference_server); + priv->fallback_conference_server = g_value_dup_string (value); + break; + case PROP_IGNORE_SSL_ERRORS: + priv->ignore_ssl_errors = g_value_get_boolean (value); + break; + case PROP_ALIAS: + g_free (priv->alias); + priv->alias = g_value_dup_string (value); + break; + default: + param_name = g_param_spec_get_name (pspec); + + if (gabble_properties_mixin_has_property (object, param_name, + &tp_property_id)) + { + gabble_properties_mixin_change_value (object, tp_property_id, value, + NULL); + gabble_properties_mixin_change_flags (object, tp_property_id, + TP_PROPERTY_FLAG_READ, + 0, NULL); + + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void gabble_connection_dispose (GObject *object); +static void gabble_connection_finalize (GObject *object); + +static void +gabble_connection_class_init (GabbleConnectionClass *gabble_connection_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (gabble_connection_class); + GParamSpec *param_spec; + + object_class->get_property = gabble_connection_get_property; + object_class->set_property = gabble_connection_set_property; + + g_type_class_add_private (gabble_connection_class, sizeof (GabbleConnectionPrivate)); + + object_class->dispose = gabble_connection_dispose; + object_class->finalize = gabble_connection_finalize; + + param_spec = g_param_spec_string ("protocol", "Telepathy identifier for protocol", + "Identifier string used when the protocol " + "name is required. Unused internally.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PROTOCOL, param_spec); + + param_spec = g_param_spec_string ("connect-server", "Hostname or IP of Jabber server", + "The server used when establishing a connection.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECT_SERVER, param_spec); + + param_spec = g_param_spec_uint ("port", "Jabber server port", + "The port used when establishing a connection.", + 0, G_MAXUINT16, GABBLE_PARAMS_DEFAULT_PORT, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PORT, param_spec); + + param_spec = g_param_spec_boolean ("old-ssl", "Old-style SSL tunneled connection", + "Establish the entire connection to the server " + "within an SSL-encrypted tunnel. Note that this " + "is not the same as connecting with TLS, which " + "is not yet supported.", FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_OLD_SSL, param_spec); + + param_spec = g_param_spec_boolean ("register", "Register account on server", + "Register a new account on server.", FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_REGISTER, param_spec); + + param_spec = g_param_spec_boolean ("low-bandwidth", "Low bandwidth mode", + "Determines whether we are in low " + "bandwidth mode. This influences " + "polling behaviour.", FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_LOW_BANDWIDTH, param_spec); + + param_spec = g_param_spec_string ("stream-server", "The server name used to initialise the stream.", + "The server name used when initialising the stream, " + "which is usually the part after the @ in the user's JID.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STREAM_SERVER, param_spec); + + param_spec = g_param_spec_string ("username", "Jabber username", + "The username used when authenticating.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_USERNAME, param_spec); + + param_spec = g_param_spec_string ("password", "Jabber password", + "The password used when authenticating.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PASSWORD, param_spec); + + param_spec = g_param_spec_string ("resource", "Jabber resource", + "The Jabber resource used when authenticating.", + "Telepathy", + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_RESOURCE, param_spec); + + param_spec = g_param_spec_int ("priority", "Jabber presence priority", + "The default priority used when reporting our presence.", + G_MININT8, G_MAXINT8, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_PRIORITY, param_spec); + + param_spec = g_param_spec_string ("https-proxy-server", "The server name " + "used as an HTTPS proxy server", + "The server name used as an HTTPS proxy " + "server.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_HTTPS_PROXY_SERVER, param_spec); + + param_spec = g_param_spec_uint ("https-proxy-port", "The HTTP proxy server " + "port", "The HTTP proxy server port.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_HTTPS_PROXY_PORT, param_spec); + + param_spec = g_param_spec_string ("fallback-conference-server", + "The conference server used as fallback", + "The conference server used as fallback when " + "everything else fails.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_FALLBACK_CONFERENCE_SERVER, + param_spec); + + param_spec = g_param_spec_string ("stun-server", + "STUN server", + "STUN server.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_SERVER, param_spec); + + param_spec = g_param_spec_uint ("stun-port", + "STUN port", + "STUN port.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_PORT, param_spec); + + param_spec = g_param_spec_string ("stun-relay-magic-cookie", + "STUN relay magic cookie", + "STUN relay magic cookie.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_MAGIC_COOKIE, + param_spec); + + param_spec = g_param_spec_string ("stun-relay-server", + "STUN relay server", + "STUN relay server.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_SERVER, + param_spec); + + param_spec = g_param_spec_uint ("stun-relay-udp-port", + "STUN relay UDP port", + "STUN relay UDP port.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_UDP_PORT, + param_spec); + + param_spec = g_param_spec_uint ("stun-relay-tcp-port", + "STUN relay TCP port", + "STUN relay TCP port.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_TCP_PORT, + param_spec); + + param_spec = g_param_spec_uint ("stun-relay-ssltcp-port", + "STUN relay SSL-TCP port", + "STUN relay SSL-TCP port.", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_SSLTCP_PORT, + param_spec); + + param_spec = g_param_spec_string ("stun-relay-username", + "STUN relay username", + "STUN relay username.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_USERNAME, + param_spec); + + param_spec = g_param_spec_string ("stun-relay-password", + "STUN relay password", + "STUN relay password.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_STUN_RELAY_PASSWORD, + param_spec); + + param_spec = g_param_spec_boolean ("ignore-ssl-errors", "Ignore SSL errors", + "Continue connecting even if the server's " + "SSL certificate is invalid or missing.", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_IGNORE_SSL_ERRORS, param_spec); + + param_spec = g_param_spec_string ("alias", + "Alias/nick for local user", + "Alias/nick for local user", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_ALIAS, param_spec); + + /* signal definitions */ + + signals[ALIASES_CHANGED] = + g_signal_new ("aliases-changed", + G_OBJECT_CLASS_TYPE (gabble_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID))))); + + signals[CAPABILITIES_CHANGED] = + g_signal_new ("capabilities-changed", + G_OBJECT_CLASS_TYPE (gabble_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_collection ("GPtrArray", (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INVALID))))); + + signals[NEW_CHANNEL] = + g_signal_new ("new-channel", + G_OBJECT_CLASS_TYPE (gabble_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_connection_marshal_VOID__STRING_STRING_UINT_UINT_BOOLEAN, + G_TYPE_NONE, 5, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_BOOLEAN); + + signals[PRESENCE_UPDATE] = + g_signal_new ("presence-update", + G_OBJECT_CLASS_TYPE (gabble_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, (dbus_g_type_get_map ("GHashTable", G_TYPE_UINT, (dbus_g_type_get_struct ("GValueArray", G_TYPE_UINT, (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)))), G_TYPE_INVALID))))); + + signals[STATUS_CHANGED] = + g_signal_new ("status-changed", + G_OBJECT_CLASS_TYPE (gabble_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + gabble_connection_marshal_VOID__UINT_UINT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + signals[DISCONNECTED] = + g_signal_new ("disconnected", + G_OBJECT_CLASS_TYPE (gabble_connection_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (gabble_connection_class), &dbus_glib_gabble_connection_object_info); + + gabble_properties_mixin_class_init (G_OBJECT_CLASS (gabble_connection_class), + G_STRUCT_OFFSET (GabbleConnectionClass, properties_class), + connection_property_signatures, NUM_CONN_PROPS, + NULL); +} + +static gboolean +_unref_lm_connection (gpointer data) +{ + LmConnection *conn = (LmConnection *) data; + + lm_connection_unref (conn); + return FALSE; +} + +void +gabble_connection_dispose (GObject *object) +{ + GabbleConnection *self = GABBLE_CONNECTION (object); + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (self); + DBusGProxy *bus_proxy; + guint i; + bus_proxy = tp_get_bus_proxy (); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + gabble_debug (DEBUG_FLAG, "called"); + + g_message("1 Inside gabble_connection_dispose() method"); + + // are these asserts a valid one ? + //As in case of lm_connection_open these will not be valid assertions ? + //to remove these? + g_assert ((self->status == TP_CONN_STATUS_DISCONNECTED) || + (self->status == TP_CONN_STATUS_NEW)); + //This is not a valid statement as the self_handle is set to a value in _gabble_connection_connect which wil call + //lm_connection_open(within do_connect). If the call fails then control comes here.. in which case self_handle can be other + //than 0) + //g_assert (self->self_handle == 0); + + g_message("after assert"); + if (priv->channel_requests) + { + g_assert (priv->channel_requests->len == 0); + g_ptr_array_free (priv->channel_requests, TRUE); + priv->channel_requests = NULL; + } + + g_ptr_array_foreach (priv->channel_factories, (GFunc) g_object_unref, NULL); + g_ptr_array_free (priv->channel_factories, TRUE); + priv->channel_factories = NULL; + g_message("unrefed channel factories"); + if(self->search_instr) + { + g_free( self->search_instr ); + self->search_instr = NULL; + } + + if(self->search_key_names) + { + for( i=0; self->search_key_names[i]; i++ ) + { + g_free( self->search_key_names[i] ); + self->search_key_names[i] = NULL; + } + g_free( self->search_key_names ); + self->search_key_names = NULL; + } + g_message("freed search keys"); + if ( self->self_avatar_sha1 ) + { + g_free ( self->self_avatar_sha1 ); + } + if(self->search_key_ht) + { + g_hash_table_destroy (self->search_key_ht); + self->search_key_ht = NULL; + } + if(self->search_service_jid) + { + g_free(self->search_service_jid); + self->search_service_jid = NULL; + } + /* unreffing channel factories frees the roster */ + self->roster = NULL; + + g_object_unref (self->disco); + self->disco = NULL; + + g_object_unref (self->vcard_manager); + self->vcard_manager = NULL; + + g_object_unref (self->presence_cache); + self->presence_cache = NULL; + + /* if this is not already the case, we'll crash anyway */ + g_assert (!lm_connection_is_open (self->lmconn)); + g_assert (priv->iq_search_keys_cb == NULL); + g_assert (priv->iq_jingle_info_cb == NULL); + g_assert (priv->iq_disco_cb == NULL); + g_assert (priv->iq_unknown_cb == NULL); + g_assert (priv->stream_error_cb == NULL); + + /* + * The Loudmouth connection can't be unref'd immediately because this + * function might (indirectly) return into Loudmouth code which expects the + * connection to always be there. + */ + g_idle_add (_unref_lm_connection, self->lmconn); + + if (NULL != self->bus_name) + { + dbus_g_proxy_call_no_reply (bus_proxy, "ReleaseName", + G_TYPE_STRING, self->bus_name, + G_TYPE_INVALID); + } + + if (G_OBJECT_CLASS (gabble_connection_parent_class)->dispose) + G_OBJECT_CLASS (gabble_connection_parent_class)->dispose (object); + + g_message("out of the method"); +} + +void +gabble_connection_finalize (GObject *object) +{ + GabbleConnection *self = GABBLE_CONNECTION (object); + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + gabble_debug (DEBUG_FLAG, "called with %p", object); + + g_free (self->bus_name); + g_free (self->object_path); + + g_free (priv->protocol); + g_free (priv->connect_server); + g_free (priv->stream_server); + g_free (priv->username); + g_free (priv->password); + g_free (priv->resource); + + g_free (priv->https_proxy_server); + g_free (priv->fallback_conference_server); + + g_free (priv->alias); + + gabble_properties_mixin_finalize (object); + + gabble_handle_repo_destroy (self->handles); + + G_OBJECT_CLASS (gabble_connection_parent_class)->finalize (object); +} + +/** + * _gabble_connection_set_properties_from_account + * + * Parses an account string which may be one of the following forms: + * username + * username/resource + * username@server + * username@server/resource + * and sets the properties for username, stream server and resource + * appropriately. Also sets the connect server to the stream server if one has + * not yet been specified. + */ + +gboolean +_gabble_connection_set_properties_from_account (GabbleConnection *conn, + const gchar *account, + GError **error) +{ + GabbleConnectionPrivate *priv; + char *username, *server, *resource; + gboolean result; + + g_assert (GABBLE_IS_CONNECTION (conn)); + g_assert (account != NULL); + + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + username = server = resource = NULL; + result = TRUE; + + gabble_decode_jid (account, &username, &server, &resource); + + if (username == NULL || server == NULL || + *username == '\0' || *server == '\0') + { + g_set_error (error, TELEPATHY_ERRORS, InvalidArgument, + "unable to get username and server from account"); + result = FALSE; + goto OUT; + } + + g_object_set (G_OBJECT (conn), + "username", username, + "stream-server", server, + NULL); + + /* only override the default resource if we actually got one */ + if (resource) + g_object_set (G_OBJECT (conn), "resource", resource, NULL); + +OUT: + g_free (username); + g_free (server); + g_free (resource); + + return result; +} + +/** + * _gabble_connection_register + * + * Make the connection object appear on the bus, returning the bus + * name and object path used. + */ +gboolean +_gabble_connection_register (GabbleConnection *conn, + gchar **bus_name, + gchar **object_path, + GError **error) +{ + DBusGConnection *bus; + DBusGProxy *bus_proxy; + GabbleConnectionPrivate *priv; + const char *allowed_chars = "_1234567890" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + char *safe_proto; + char *unique_name; + guint request_name_result; + GError *request_error; + + g_message("1 Inside _gabble_connection_register() method"); + g_assert (GABBLE_IS_CONNECTION (conn)); + + bus = tp_get_bus (); + bus_proxy = tp_get_bus_proxy (); + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + safe_proto = g_strdup (priv->protocol); + g_strcanon (safe_proto, allowed_chars, '_'); + + unique_name = g_strdup_printf ("_%s_%s_%s", + priv->username, + priv->stream_server, + priv->resource); + g_strcanon (unique_name, allowed_chars, '_'); + + conn->bus_name = g_strdup_printf (BUS_NAME ".%s.%s", + safe_proto, + unique_name); + conn->object_path = g_strdup_printf (OBJECT_PATH "/%s/%s", + safe_proto, + unique_name); + + g_free (safe_proto); + g_free (unique_name); + + if (!dbus_g_proxy_call (bus_proxy, "RequestName", &request_error, + G_TYPE_STRING, conn->bus_name, + G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE, + G_TYPE_INVALID, + G_TYPE_UINT, &request_name_result, + G_TYPE_INVALID)) + { + g_set_error (error, TELEPATHY_ERRORS, NotAvailable, + "Error acquiring bus name %s: %s", conn->bus_name, + request_error->message); + + g_error_free (request_error); + + g_free (conn->bus_name); + conn->bus_name = NULL; + + return FALSE; + } + + if (request_name_result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + { + gchar *msg; + + switch (request_name_result) + { + case DBUS_REQUEST_NAME_REPLY_IN_QUEUE: + msg = "Request has been queued, though we request non-queueing."; + break; + case DBUS_REQUEST_NAME_REPLY_EXISTS: + msg = "A connection manger already has this busname."; + break; + case DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: + msg = "Connection manager already has a connection to this account."; + break; + default: + msg = "Unknown error return from ReleaseName"; + } + + g_set_error (error, TELEPATHY_ERRORS, NotAvailable, + "Error acquiring bus name %s: %s", conn->bus_name, msg); + + g_free (conn->bus_name); + conn->bus_name = NULL; + + return FALSE; + } + + gabble_debug (DEBUG_FLAG, "bus name %s", conn->bus_name); + + dbus_g_connection_register_g_object (bus, conn->object_path, G_OBJECT (conn)); + + gabble_debug (DEBUG_FLAG, "object path %s", conn->object_path); + + *bus_name = g_strdup (conn->bus_name); + *object_path = g_strdup (conn->object_path); + + return TRUE; +} + + +/** + * _gabble_connection_send + * + * Send an LmMessage and trap network errors appropriately. + */ +gboolean +_gabble_connection_send (GabbleConnection *conn, LmMessage *msg, GError **error) +{ + GabbleConnectionPrivate *priv; + GError *lmerror = NULL; + + g_assert (GABBLE_IS_CONNECTION (conn)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + if (!lm_connection_send (conn->lmconn, msg, &lmerror)) + { + gabble_debug (DEBUG_FLAG, "failed: %s", lmerror->message); + + g_set_error (error, TELEPATHY_ERRORS, NetworkError, + "message send failed: %s", lmerror->message); + + g_error_free (lmerror); + + return FALSE; + } + + return TRUE; +} + +typedef struct { + GabbleConnectionMsgReplyFunc reply_func; + + GabbleConnection *conn; + LmMessage *sent_msg; + gpointer user_data; + + GObject *object; + gboolean object_alive; +} GabbleMsgHandlerData; + +static LmHandlerResult +message_send_reply_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *reply_msg, + gpointer user_data) +{ + GabbleMsgHandlerData *handler_data = user_data; + LmMessageSubType sub_type; + + sub_type = lm_message_get_sub_type (reply_msg); + + /* Is it a reply to this message? If we're talking to another loudmouth, + * they can send us messages which have the same ID as ones we send. :-O */ + if (sub_type != LM_MESSAGE_SUB_TYPE_RESULT && + sub_type != LM_MESSAGE_SUB_TYPE_ERROR) + { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + if (handler_data->object_alive) + { + return handler_data->reply_func (handler_data->conn, + handler_data->sent_msg, + reply_msg, + handler_data->object, + handler_data->user_data); + } + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static void +message_send_object_destroy_notify_cb (gpointer data, + GObject *where_the_object_was) +{ + GabbleMsgHandlerData *handler_data = data; + + handler_data->object = NULL; + handler_data->object_alive = FALSE; +} + +static void +message_send_handler_destroy_cb (gpointer data) +{ + GabbleMsgHandlerData *handler_data = data; + + lm_message_unref (handler_data->sent_msg); + + if (handler_data->object != NULL) + { + g_object_weak_unref (handler_data->object, + message_send_object_destroy_notify_cb, + handler_data); + } + + g_free (handler_data); +} + +/** + * _gabble_connection_send_with_reply + * + * Send a tracked LmMessage and trap network errors appropriately. + * + * If object is non-NULL the handler will follow the lifetime of that object, + * which means that if the object is destroyed the callback will not be invoked. + */ +gboolean +_gabble_connection_send_with_reply (GabbleConnection *conn, + LmMessage *msg, + GabbleConnectionMsgReplyFunc reply_func, + GObject *object, + gpointer user_data, + GError **error) +{ + GabbleConnectionPrivate *priv; + LmMessageHandler *handler; + GabbleMsgHandlerData *handler_data; + gboolean ret; + GError *lmerror = NULL; + + g_assert (GABBLE_IS_CONNECTION (conn)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + lm_message_ref (msg); + + handler_data = g_new (GabbleMsgHandlerData, 1); + handler_data->reply_func = reply_func; + handler_data->conn = conn; + handler_data->sent_msg = msg; + handler_data->user_data = user_data; + + handler_data->object = object; + handler_data->object_alive = TRUE; + + if (object != NULL) + { + g_object_weak_ref (object, message_send_object_destroy_notify_cb, + handler_data); + } + + handler = lm_message_handler_new (message_send_reply_cb, handler_data, + message_send_handler_destroy_cb); + + ret = lm_connection_send_with_reply (conn->lmconn, msg, handler, &lmerror); + if (!ret) + { + gabble_debug (DEBUG_FLAG, "failed: %s", lmerror->message); + + if (error) + { + g_set_error (error, TELEPATHY_ERRORS, NetworkError, + "message send failed: %s", lmerror->message); + } + + g_error_free (lmerror); + } + + lm_message_handler_unref (handler); + + return ret; +} + +static LmHandlerResult connection_iq_disco_cb (LmMessageHandler*, LmConnection*, LmMessage*, gpointer); +static LmHandlerResult connection_iq_unknown_cb (LmMessageHandler*, LmConnection*, LmMessage*, gpointer); +static LmHandlerResult connection_stream_error_cb (LmMessageHandler*, LmConnection*, LmMessage*, gpointer); +static LmSSLResponse connection_ssl_cb (LmSSL*, LmSSLStatus, gpointer); +static void connection_open_cb (LmConnection*, gboolean, gpointer); +static void connection_auth_cb (LmConnection*, gboolean, gpointer); +static void connection_disco_cb (GabbleDisco *, GabbleDiscoRequest *, const gchar *, const gchar *, LmMessageNode *, GError *, gpointer); +static void connection_disconnected_cb (LmConnection *, LmDisconnectReason, gpointer); +static void connection_status_change (GabbleConnection *, TpConnectionStatus, TpConnectionStatusReason); + +static void channel_request_cancel (gpointer data, gpointer user_data); + +static void emit_one_presence_update (GabbleConnection *self, GabbleHandle handle); + + +static gboolean +do_connect (GabbleConnection *conn, GError **error) +{ + GError *lmerror = NULL; + + gabble_debug (DEBUG_FLAG, "calling lm_connection_open"); + g_message("**gabble do_connect: before calling lm_connection_open\n"); + + if (!lm_connection_open (conn->lmconn, connection_open_cb, + conn, NULL, &lmerror)) + { + gabble_debug (DEBUG_FLAG, "lm_connection_open failed %s", lmerror->message); + + g_set_error (error, TELEPATHY_ERRORS, NetworkError, + "lm_connection_open failed: %s", lmerror->message); + + g_signal_emit (conn, signals[DISCONNECTED], 0); + g_error_free (lmerror); + + return FALSE; + } + + g_message("**gabble do_connect: after calling lm_connection_open and it passed\n"); + + return TRUE; +} + +static void +connect_callbacks (GabbleConnection *conn) +{ + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + g_message("inside connect_callbacks\n"); + g_assert (priv->iq_search_keys_cb == NULL); + g_assert (priv->iq_jingle_info_cb == NULL); + g_assert (priv->iq_disco_cb == NULL); + g_assert (priv->iq_unknown_cb == NULL); + g_assert (priv->stream_error_cb == NULL); + + + + priv->iq_search_keys_cb = lm_message_handler_new (search_keys_iq_cb, + conn, NULL); + lm_connection_register_message_handler (conn->lmconn, + priv->iq_search_keys_cb, + LM_MESSAGE_TYPE_IQ, + LM_HANDLER_PRIORITY_NORMAL); + + priv->iq_jingle_info_cb = lm_message_handler_new (jingle_info_iq_callback, + conn, NULL); + lm_connection_register_message_handler (conn->lmconn, + priv->iq_jingle_info_cb, + LM_MESSAGE_TYPE_IQ, + LM_HANDLER_PRIORITY_NORMAL); + + priv->iq_disco_cb = lm_message_handler_new (connection_iq_disco_cb, + conn, NULL); + lm_connection_register_message_handler (conn->lmconn, priv->iq_disco_cb, + LM_MESSAGE_TYPE_IQ, + LM_HANDLER_PRIORITY_NORMAL); + + priv->iq_unknown_cb = lm_message_handler_new (connection_iq_unknown_cb, + conn, NULL); + lm_connection_register_message_handler (conn->lmconn, priv->iq_unknown_cb, + LM_MESSAGE_TYPE_IQ, + LM_HANDLER_PRIORITY_LAST); + + priv->stream_error_cb = lm_message_handler_new (connection_stream_error_cb, + conn, NULL); + lm_connection_register_message_handler (conn->lmconn, priv->stream_error_cb, + LM_MESSAGE_TYPE_STREAM_ERROR, + LM_HANDLER_PRIORITY_LAST); + g_message("leaving connect_callbacks\n"); +} + +static void +disconnect_callbacks (GabbleConnection *conn) +{ + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + g_message("[disconnect_callbacks]"); + g_assert (priv->iq_search_keys_cb != NULL); + g_assert (priv->iq_jingle_info_cb != NULL); + g_assert (priv->iq_disco_cb != NULL); + g_assert (priv->iq_unknown_cb != NULL); + g_assert (priv->stream_error_cb != NULL); + + lm_connection_unregister_message_handler (conn->lmconn, priv->iq_search_keys_cb, + LM_MESSAGE_TYPE_IQ); + lm_message_handler_unref (priv->iq_search_keys_cb); + priv->iq_search_keys_cb = NULL; + + lm_connection_unregister_message_handler (conn->lmconn, priv->iq_jingle_info_cb, + LM_MESSAGE_TYPE_IQ); + lm_message_handler_unref (priv->iq_jingle_info_cb); + priv->iq_jingle_info_cb = NULL; + + lm_connection_unregister_message_handler (conn->lmconn, priv->iq_disco_cb, + LM_MESSAGE_TYPE_IQ); + lm_message_handler_unref (priv->iq_disco_cb); + priv->iq_disco_cb = NULL; + + lm_connection_unregister_message_handler (conn->lmconn, priv->iq_unknown_cb, + LM_MESSAGE_TYPE_IQ); + lm_message_handler_unref (priv->iq_unknown_cb); + priv->iq_unknown_cb = NULL; + + lm_connection_unregister_message_handler (conn->lmconn, priv->stream_error_cb, + LM_MESSAGE_TYPE_STREAM_ERROR); + lm_message_handler_unref (priv->stream_error_cb); + priv->stream_error_cb = NULL; +} + +/** + * _gabble_connection_connect + * + * Use the stored server & authentication details to commence + * the stages for connecting to the server and authenticating. Will + * re-use an existing LmConnection if it is present, or create it + * if necessary. + * + * Stage 1 is _gabble_connection_connect calling lm_connection_open + * Stage 2 is connection_open_cb calling lm_connection_authenticate + * Stage 3 is connection_auth_cb initiating service discovery + * Stage 4 is connection_disco_cb advertising initial presence, requesting + * the roster and setting the CONNECTED state + */ +static gboolean +_gabble_connection_connect (GabbleConnection *conn, + GError **error) +{ + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + char *jid; + GabblePresence *presence; + g_message("[_gabble_connection_connect]"); + g_assert (priv->port > 0 && priv->port <= G_MAXUINT16); + g_assert (priv->stream_server != NULL); + g_assert (priv->username != NULL); + g_assert (priv->password != NULL); + g_assert (priv->resource != NULL); + g_assert (lm_connection_is_open (conn->lmconn) == FALSE); + + g_message("In _gabble_connection_connect" ); + + jid = g_strdup_printf ("%s@%s", priv->username, priv->stream_server); + lm_connection_set_jid (conn->lmconn, jid); + + conn->self_handle = gabble_handle_for_contact (conn->handles, + jid, FALSE); + g_free (jid); + + if (conn->self_handle == 0) + { + g_set_error (error, TELEPATHY_ERRORS, InvalidArgument, + "Invalid JID: %s@%s", priv->username, priv->stream_server); + return FALSE; + } + gabble_handle_ref (conn->handles, TP_HANDLE_TYPE_CONTACT, conn->self_handle); + + /* set initial presence */ + /* TODO: some way for the user to set this */ + gabble_presence_cache_update (conn->presence_cache, conn->self_handle, + priv->resource, GABBLE_PRESENCE_AVAILABLE, NULL, priv->priority); + emit_one_presence_update (conn, conn->self_handle); + + /* set initial capabilities */ + presence = gabble_presence_cache_get (conn->presence_cache, conn->self_handle); + + gabble_presence_set_capabilities (presence, priv->resource, + capabilities_get_initial_caps (), priv->caps_serial++); + + /* always override server and port if one was forced upon us */ + if (priv->connect_server != NULL) + { + lm_connection_set_server (conn->lmconn, priv->connect_server); + lm_connection_set_port (conn->lmconn, priv->port); + g_message("LM Server is %s \n",priv->connect_server ); + g_message("LM port is %ld \n",priv->port); + } + /* otherwise set the server & port to the stream server, + * if one didn't appear from a SRV lookup */ + else if (lm_connection_get_server (conn->lmconn) == NULL) + { + lm_connection_set_server (conn->lmconn, priv->stream_server); + lm_connection_set_port (conn->lmconn, priv->port); + } + + + if (priv->https_proxy_server) + { + LmProxy *proxy; + + proxy = lm_proxy_new_with_server (LM_PROXY_TYPE_HTTP, + priv->https_proxy_server, priv->https_proxy_port); + + lm_connection_set_proxy (conn->lmconn, proxy); + + lm_proxy_unref (proxy); + } + + if (priv->old_ssl) + { + LmSSL *ssl = lm_ssl_new (NULL, connection_ssl_cb, conn, NULL); + lm_connection_set_ssl (conn->lmconn, ssl); + lm_ssl_unref (ssl); + } + else //if we want to use tls (not old ssl?) then need to set tls flags. + { + LmSSL *ssl = lm_ssl_new (NULL, connection_ssl_cb, conn, NULL); + lm_connection_set_ssl (conn->lmconn, ssl); + + lm_ssl_use_starttls (ssl, TRUE, TRUE); + + lm_ssl_unref (ssl); + } + + /* send whitespace to the server every 30 seconds */ + //lm_connection_set_keep_alive_rate (conn->lmconn, 45); + + lm_connection_set_disconnect_function (conn->lmconn, + connection_disconnected_cb, + conn, + NULL); + + if (do_connect (conn, error)) + { + gboolean valid; + + connection_status_change (conn, + TP_CONN_STATUS_CONNECTING, + TP_CONN_STATUS_REASON_REQUESTED); + + valid = gabble_handle_ref (conn->handles, + TP_HANDLE_TYPE_CONTACT, + conn->self_handle); + g_message("before after valid assert" ); + g_assert (valid); + g_message("In after valid assert" ); + } + else + { + return FALSE; + } + g_message("Out _gabble_connection_connect" ); + return TRUE; +} + + + +static void +connection_disconnected_cb (LmConnection *lmconn, + LmDisconnectReason lm_reason, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + g_message("[connection_disconnected_cb]\n"); + g_assert (conn->lmconn == lmconn); + + gabble_debug (DEBUG_FLAG, "called with reason %u", lm_reason); + + /* if we were expecting this disconnection, we're done so can tell + * the connection manager to unref us. otherwise it's a network error + * or some other screw up we didn't expect, so we emit the status + * change */ + if (conn->status == TP_CONN_STATUS_DISCONNECTED) + { + g_message("[connection_disconnected_cb]expected; emitting DISCONNECTED"); + g_signal_emit (conn, signals[DISCONNECTED], 0); + } + else + { + g_message("[connection_disconnected_cb]unexpected; calling connection_status_change"); + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_NETWORK_ERROR); + } +} + + +/** + * connection_status_change: + * @conn: a #GabbleConnection + * @status: new status to advertise + * @reason: reason for new status + * + * Compares status with current status. If different, emits a signal + * for the new status, and updates it in the #GabbleConnection. + */ +static void +connection_status_change (GabbleConnection *conn, + TpConnectionStatus status, + TpConnectionStatusReason reason) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (conn)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + g_message ("[connection_status_change]status %u reason %u", status, reason); + + g_assert (status != TP_CONN_STATUS_NEW); + + if (conn->status != status) + { + if ((status == TP_CONN_STATUS_DISCONNECTED) && + (conn->status == TP_CONN_STATUS_NEW)) + { + g_message("[connection_status_change] line 1721"); + conn->status = status; + + /* unref our self handle if it's set */ + if (conn->self_handle != 0) + { + gabble_handle_unref (conn->handles, TP_HANDLE_TYPE_CONTACT, + conn->self_handle); + conn->self_handle = 0; + } + + g_message("[connection_status_change]new connection closed; emitting DISCONNECTED"); + g_signal_emit (conn, signals[DISCONNECTED], 0); + return; + } + + conn->status = status; + + if (status == TP_CONN_STATUS_DISCONNECTED) + { + /* remove the channels so we don't get any race conditions where + * method calls are delivered to a channel after we've started + * disconnecting */ + g_message("[connection_status_change] TP_CONN_STATUS_DISCONNECTED"); + + /* trigger close_all on all channel factories */ + g_ptr_array_foreach (priv->channel_factories, (GFunc) + tp_channel_factory_iface_close_all, NULL); + + /* cancel all queued channel requests */ + if (priv->channel_requests->len > 0) + { + g_ptr_array_foreach (priv->channel_requests, (GFunc) + channel_request_cancel, NULL); + g_ptr_array_remove_range (priv->channel_requests, 0, + priv->channel_requests->len); + } + + /* unref our self handle */ + gabble_handle_unref (conn->handles, TP_HANDLE_TYPE_CONTACT, + conn->self_handle); + conn->self_handle = 0; + } + + g_message("[connection_status_change]emitting status-changed with status %u reason %u", + status, reason); + + g_signal_emit (conn, signals[STATUS_CHANGED], 0, status, reason); + + if (status == TP_CONN_STATUS_CONNECTING) + { + /* add our callbacks */ + connect_callbacks (conn); + + /* trigger connecting on all channel factories */ + g_ptr_array_foreach (priv->channel_factories, (GFunc) + tp_channel_factory_iface_connecting, NULL); + } + else if (status == TP_CONN_STATUS_CONNECTED) + { + /* send whitespace to the server every 30 seconds resetting to 30*/ + lm_connection_set_keep_alive_rate (conn->lmconn, 10); + + /* trigger connected on all channel factories */ + g_ptr_array_foreach (priv->channel_factories, (GFunc) + tp_channel_factory_iface_connected, NULL); + } + else if (status == TP_CONN_STATUS_DISCONNECTED) + { + /* remove our callbacks */ + disconnect_callbacks (conn); + + /* trigger disconnected on all channel factories */ + g_ptr_array_foreach (priv->channel_factories, (GFunc) + tp_channel_factory_iface_disconnected, NULL); + + /* if the connection is open, this function will close it for you. + * if it's already closed (eg network error) then we're done, so + * can emit DISCONNECTED and have the connection manager unref us */ + if (lm_connection_is_open (conn->lmconn)) + { + g_message ("still open; calling lm_connection_close"); + lm_connection_close (conn->lmconn, NULL); + } + else + { + /* lm_connection_is_open() returns FALSE if LmConnection is in the + * middle of connecting, so call this just in case */ + lm_connection_cancel_open (conn->lmconn); + g_message ("closed; emitting DISCONNECTED"); + g_signal_emit (conn, signals[DISCONNECTED], 0); + } + } + } + else + { + g_warning ("%s: attempted to re-emit the current status %u reason %u", + G_STRFUNC, status, reason); + } + g_message("[connection_status_change]"); + g_message ("out connection status changed "); +} + +static ChannelRequest * +channel_request_new (DBusGMethodInvocation *context, + const char *channel_type, + guint handle_type, + guint handle, + gboolean suppress_handler) +{ + ChannelRequest *ret; + + g_assert (NULL != context); + g_assert (NULL != channel_type); + + ret = g_new0 (ChannelRequest, 1); + ret->context = context; + ret->channel_type = g_strdup (channel_type); + ret->handle_type = handle_type; + ret->handle = handle; + ret->suppress_handler = suppress_handler; + + return ret; +} + +static void +channel_request_free (ChannelRequest *request) +{ + g_assert (NULL == request->context); + g_free (request->channel_type); + g_free (request); +} + +static void +channel_request_cancel (gpointer data, gpointer user_data) +{ + ChannelRequest *request = (ChannelRequest *) data; + GError *error = NULL; + + gabble_debug (DEBUG_FLAG, "cancelling request for %s/%d/%d", request->channel_type, request->handle_type, request->handle); + + error = g_error_new (TELEPATHY_ERRORS, Disconnected, "unable to " + "service this channel request, we're disconnecting!"); + + dbus_g_method_return_error (request->context, error); + request->context = NULL; + + g_error_free (error); + channel_request_free (request); +} + +static GPtrArray * +find_matching_channel_requests (GabbleConnection *conn, + const gchar *channel_type, + guint handle_type, + guint handle, + gboolean *suppress_handler) +{ + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + GPtrArray *requests; + guint i; + + requests = g_ptr_array_sized_new (1); + + for (i = 0; i < priv->channel_requests->len; i++) + { + ChannelRequest *request = g_ptr_array_index (priv->channel_requests, i); + + if (0 != strcmp (request->channel_type, channel_type)) + continue; + + if (handle_type != request->handle_type) + continue; + + if (handle != request->handle) + continue; + + if (request->suppress_handler && suppress_handler) + *suppress_handler = TRUE; + + g_ptr_array_add (requests, request); + } + + return requests; +} + +static void +connection_new_channel_cb (TpChannelFactoryIface *factory, + GObject *chan, + gpointer data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (data); + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + gchar *object_path = NULL, *channel_type = NULL; + guint handle_type = 0, handle = 0; + gboolean suppress_handler = priv->suppress_next_handler; + GPtrArray *tmp; + guint i; + + g_object_get (chan, + "object-path", &object_path, + "channel-type", &channel_type, + "handle-type", &handle_type, + "handle", &handle, + NULL); + + gabble_debug (DEBUG_FLAG, "called for %s", object_path); + + tmp = find_matching_channel_requests (conn, channel_type, handle_type, + handle, &suppress_handler); + + g_signal_emit (conn, signals[NEW_CHANNEL], 0, + object_path, channel_type, + handle_type, handle, + suppress_handler); + + for (i = 0; i < tmp->len; i++) + { + ChannelRequest *request = g_ptr_array_index (tmp, i); + + gabble_debug (DEBUG_FLAG, "completing queued request, channel_type=%s, handle_type=%u, " + "handle=%u, suppress_handler=%u", request->channel_type, + request->handle_type, request->handle, request->suppress_handler); + + dbus_g_method_return (request->context, object_path); + request->context = NULL; + + g_ptr_array_remove (priv->channel_requests, request); + + channel_request_free (request); + } + + g_ptr_array_free (tmp, TRUE); + + g_free (object_path); + g_free (channel_type); +} + +static void +connection_channel_error_cb (TpChannelFactoryIface *factory, + GObject *chan, + GError *error, + gpointer data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (data); + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + gchar *channel_type = NULL; + guint handle_type = 0, handle = 0; + GPtrArray *tmp; + guint i; + + gabble_debug (DEBUG_FLAG, "channel_type=%s, handle_type=%u, handle=%u, error_code=%u, " + "error_message=\"%s\"", channel_type, handle_type, handle, + error->code, error->message); + + g_object_get (chan, + "channel-type", &channel_type, + "handle-type", &handle_type, + "handle", &handle, + NULL); + + tmp = find_matching_channel_requests (conn, channel_type, handle_type, + handle, NULL); + + for (i = 0; i < tmp->len; i++) + { + ChannelRequest *request = g_ptr_array_index (tmp, i); + + gabble_debug (DEBUG_FLAG, "completing queued request %p, channel_type=%s, " + "handle_type=%u, handle=%u, suppress_handler=%u", + request, request->channel_type, + request->handle_type, request->handle, request->suppress_handler); + + dbus_g_method_return_error (request->context, error); + request->context = NULL; + + g_ptr_array_remove (priv->channel_requests, request); + + channel_request_free (request); + } + + g_ptr_array_free (tmp, TRUE); + g_free (channel_type); +} + +static void +connection_presence_update_cb (GabblePresenceCache *cache, GabbleHandle handle, gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + + emit_one_presence_update (conn, handle); +} + +GabbleConnectionAliasSource +_gabble_connection_get_cached_alias (GabbleConnection *conn, + GabbleHandle handle, + gchar **alias) +{ + GabbleConnectionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (conn, + GABBLE_TYPE_CONNECTION, GabbleConnectionPrivate); + GabbleConnectionAliasSource ret = GABBLE_CONNECTION_ALIAS_NONE; + GabblePresence *pres; + const gchar *tmp; + gchar *user = NULL, *resource = NULL; + + g_return_val_if_fail (NULL != conn, GABBLE_CONNECTION_ALIAS_NONE); + g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), GABBLE_CONNECTION_ALIAS_NONE); + g_return_val_if_fail (gabble_handle_is_valid (conn->handles, + TP_HANDLE_TYPE_CONTACT, handle, NULL), GABBLE_CONNECTION_ALIAS_NONE); + + tmp = gabble_roster_handle_get_name (conn->roster, handle); + if (NULL != tmp) + { + ret = GABBLE_CONNECTION_ALIAS_FROM_ROSTER; + + if (NULL != alias) + *alias = g_strdup (tmp); + + goto OUT; + } + + pres = gabble_presence_cache_get (conn->presence_cache, handle); + if (NULL != pres && NULL != pres->nickname) + { + ret = GABBLE_CONNECTION_ALIAS_FROM_PRESENCE; + + if (NULL != alias) + *alias = g_strdup (pres->nickname); + + goto OUT; + } + + /* if it's our own handle, use alias passed to the connmgr, if any */ + if (handle == conn->self_handle && priv->alias != NULL) + { + ret = GABBLE_CONNECTION_ALIAS_FROM_CONNMGR; + + if (NULL != alias) + *alias = g_strdup (priv->alias); + + goto OUT; + } + + /* if we've seen a nickname in their vCard, use that */ + tmp = gabble_vcard_manager_get_cached_alias (conn->vcard_manager, handle); + if (NULL != tmp) + { + ret = GABBLE_CONNECTION_ALIAS_FROM_VCARD; + + if (NULL != alias) + *alias = g_strdup (tmp); + + goto OUT; + } + + /* fallback to JID */ + tmp = gabble_handle_inspect (conn->handles, TP_HANDLE_TYPE_CONTACT, handle); + g_assert (NULL != tmp); + + gabble_decode_jid (tmp, &user, NULL, &resource); + + /* MUC handles have the nickname in the resource */ + if (NULL != resource) + { + ret = GABBLE_CONNECTION_ALIAS_FROM_JID; + + if (NULL != alias) + { + *alias = resource; + resource = NULL; + } + + goto OUT; + } + + /* otherwise just take their local part */ + if (NULL != user) + { + ret = GABBLE_CONNECTION_ALIAS_FROM_JID; + + if (NULL != alias) + { + *alias = user; + user = NULL; + } + + goto OUT; + } + +OUT: + g_free (user); + g_free (resource); + return ret; +} + +static void +connection_nickname_update_cb (GObject *object, + GabbleHandle handle, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + GabbleConnectionAliasSource signal_source, real_source; + gchar *alias = NULL; + GPtrArray *aliases; + GValue entry = { 0, }; + + if (object == G_OBJECT (conn->roster)) + { + signal_source = GABBLE_CONNECTION_ALIAS_FROM_ROSTER; + } + else if (object == G_OBJECT (conn->presence_cache)) + { + signal_source = GABBLE_CONNECTION_ALIAS_FROM_PRESENCE; + } + else if (object == G_OBJECT (conn->vcard_manager)) + { + signal_source = GABBLE_CONNECTION_ALIAS_FROM_VCARD; + } + else + { + g_assert_not_reached (); + return; + } + + real_source = _gabble_connection_get_cached_alias (conn, handle, &alias); + + g_assert (real_source != GABBLE_CONNECTION_ALIAS_NONE); + + /* if the active alias for this handle is already known and from + * a higher priority, this signal is not interesting so we do + * nothing */ + if (real_source > signal_source) + { + gabble_debug (DEBUG_FLAG, "ignoring boring alias change for handle %u, signal from %u " + "but source %u has alias \"%s\"", handle, signal_source, + real_source, alias); + goto OUT; + } + + g_value_init (&entry, TP_ALIAS_PAIR_TYPE); + g_value_take_boxed (&entry, dbus_g_type_specialized_construct + (TP_ALIAS_PAIR_TYPE)); + + dbus_g_type_struct_set (&entry, + 0, handle, + 1, alias, + G_MAXUINT); + + aliases = g_ptr_array_sized_new (1); + g_ptr_array_add (aliases, g_value_get_boxed (&entry)); + + g_signal_emit (conn, signals[ALIASES_CHANGED], 0, aliases); + + g_value_unset (&entry); + g_ptr_array_free (aliases, TRUE); + +OUT: + g_free (alias); +} + +/** + * status_is_available + * + * Returns a boolean to indicate whether the given gabble status is + * available on this connection. + */ +static gboolean +status_is_available (GabbleConnection *conn, int status) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (conn)); + g_assert (status < LAST_GABBLE_PRESENCE); + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + if (gabble_statuses[status].presence_type == TP_CONN_PRESENCE_TYPE_HIDDEN && + (conn->features & GABBLE_CONNECTION_FEATURES_PRESENCE_INVISIBLE) == 0) + return FALSE; + else + return TRUE; +} + +/** + * destroy_the_bastard: + * @data: a GValue to destroy + * + * destroys a GValue allocated on the heap + */ +static void +destroy_the_bastard (GValue *value) +{ + g_value_unset (value); + g_free (value); +} + +static GHashTable * +construct_presence_hash (GabbleConnection *self, + const GArray *contact_handles) +{ + guint i; + GabbleHandle handle; + GHashTable *presence_hash, *contact_status, *parameters; + GValueArray *vals; + GValue *message; + GabblePresence *presence; + GabblePresenceId status; + const gchar *status_message; + /* this is never set at the moment*/ + guint timestamp = 0; + + g_assert (gabble_handles_are_valid (self->handles, TP_HANDLE_TYPE_CONTACT, + contact_handles, FALSE, NULL)); + + presence_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) g_value_array_free); + + for (i = 0; i < contact_handles->len; i++) + { + handle = g_array_index (contact_handles, GabbleHandle, i); + presence = gabble_presence_cache_get (self->presence_cache, handle); + + if (presence) + { + status = presence->status; + status_message = presence->status_message; + } + else + { + status = GABBLE_PRESENCE_OFFLINE; + status_message = NULL; + } + + message = g_new0 (GValue, 1); + g_value_init (message, G_TYPE_STRING); + g_value_set_static_string (message, status_message); + + parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) destroy_the_bastard); + + g_hash_table_insert (parameters, "message", message); + + contact_status = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) g_hash_table_destroy); + g_hash_table_insert (contact_status, + (gchar *) gabble_statuses[status].name, parameters); + + vals = g_value_array_new (2); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 0), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (vals, 0), timestamp); + + g_value_array_append (vals, NULL); + g_value_init (g_value_array_get_nth (vals, 1), + dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, + dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))); + g_value_take_boxed (g_value_array_get_nth (vals, 1), contact_status); + + g_hash_table_insert (presence_hash, GINT_TO_POINTER (handle), vals); + } + + return presence_hash; +} + +/** + * emit_presence_update: + * @self: A #GabbleConnection + * @contact_handles: A zero-terminated array of #GabbleHandle for + * the contacts to emit presence for + * + * Emits the Telepathy PresenceUpdate signal with the current + * stored presence information for the given contact. + */ +static void +emit_presence_update (GabbleConnection *self, + const GArray *contact_handles) +{ + GHashTable *presence_hash; + + presence_hash = construct_presence_hash (self, contact_handles); + g_signal_emit (self, signals[PRESENCE_UPDATE], 0, presence_hash); + g_hash_table_destroy (presence_hash); +} + +/** + * emit_one_presence_update: + * Convenience function for calling emit_presence_update with one handle. + */ + +static void +emit_one_presence_update (GabbleConnection *self, + GabbleHandle handle) +{ + GArray *handles = g_array_sized_new (FALSE, FALSE, sizeof (GabbleHandle), 1); + + g_array_insert_val (handles, 0, handle); + emit_presence_update (self, handles); + g_array_free (handles, TRUE); +} + +/** + * signal_own_presence: + * @self: A #GabbleConnection + * @error: pointer in which to return a GError in case of failure. + * + * Signal the user's stored presence to the jabber server + * + * Retuns: FALSE if an error occurred + */ +static gboolean +signal_own_presence (GabbleConnection *self, GError **error) +{ + + GabblePresence *presence; + LmMessage *message; + LmMessageNode *node; + gboolean ret; + gchar *ext_string = NULL; + GSList *features, *i; + GabbleConnectionPrivate *priv; + + if ( NULL == self ) + { + g_debug ("%s: accesing after dereferenced connection", G_STRFUNC); + return FALSE; + } + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + presence = gabble_presence_cache_get (self->presence_cache, self->self_handle); + message = gabble_presence_as_message (presence, priv->resource); + node = lm_message_get_node (message); + + if (presence->status == GABBLE_PRESENCE_HIDDEN) + { + if ((self->features & GABBLE_CONNECTION_FEATURES_PRESENCE_INVISIBLE) != 0) + lm_message_node_set_attribute (node, "type", "invisible"); + } + + features = capabilities_get_features (presence->caps); + + for (i = features; NULL != i; i = i->next) + { + const Feature *feat = (const Feature *) i->data; + + if ((NULL != feat->bundle) && g_strdiff (VERSION, feat->bundle)) + { + if (NULL != ext_string) + { + gchar *tmp = ext_string; + ext_string = g_strdup_printf ("%s %s", ext_string, feat->bundle); + g_free (tmp); + } + else + { + ext_string = g_strdup (feat->bundle); + } + } + } + + node = lm_message_node_add_child (node, "c", NULL); + lm_message_node_set_attributes ( + node, + "xmlns", NS_CAPS, + "node", NS_GABBLE_CAPS, + "ver", VERSION, + NULL); + + if (NULL != ext_string) + lm_message_node_set_attribute (node, "ext", ext_string); + + ret = _gabble_connection_send (self, message, error); + + lm_message_unref (message); + + g_free (ext_string); + g_slist_free (features); + + return ret; +} + +static LmMessage *_lm_iq_message_make_result (LmMessage *iq_message); + +/** + * _gabble_connection_send_iq_result + * + * Function used to acknowledge an IQ stanza. + */ +void +_gabble_connection_acknowledge_set_iq (GabbleConnection *conn, + LmMessage *iq) +{ + LmMessage *result; + + g_assert (LM_MESSAGE_TYPE_IQ == lm_message_get_type (iq)); + g_assert (LM_MESSAGE_SUB_TYPE_SET == lm_message_get_sub_type (iq)); + + result = _lm_iq_message_make_result (iq); + + if (NULL != result) + { + _gabble_connection_send (conn, result, NULL); + lm_message_unref (result); + } +} + + +/** + * _gabble_connection_send_iq_error + * + * Function used to acknowledge an IQ stanza with an error. + */ +void +_gabble_connection_send_iq_error (GabbleConnection *conn, + LmMessage *message, + GabbleXmppError error, + const gchar *errmsg) +{ + const gchar *to, *id; + LmMessage *msg; + LmMessageNode *iq_node; + + iq_node = lm_message_get_node (message); + to = lm_message_node_get_attribute (iq_node, "from"); + id = lm_message_node_get_attribute (iq_node, "id"); + + if (id == NULL) + { + NODE_DEBUG (iq_node, "can't acknowledge IQ with no id"); + return; + } + + msg = lm_message_new_with_sub_type (to, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_ERROR); + + lm_message_node_set_attribute (msg->node, "id", id); + + lm_message_node_steal_children (msg->node, iq_node); + + gabble_xmpp_error_to_node (error, msg->node, errmsg); + + _gabble_connection_send (conn, msg, NULL); + + lm_message_unref (msg); +} + +static LmMessage * +_lm_iq_message_make_result (LmMessage *iq_message) +{ + LmMessage *result; + LmMessageNode *iq, *result_iq; + const gchar *from_jid, *id; + + g_assert (lm_message_get_type (iq_message) == LM_MESSAGE_TYPE_IQ); + g_assert (lm_message_get_sub_type (iq_message) == LM_MESSAGE_SUB_TYPE_GET || + lm_message_get_sub_type (iq_message) == LM_MESSAGE_SUB_TYPE_SET); + + iq = lm_message_get_node (iq_message); + id = lm_message_node_get_attribute (iq, "id"); + + if (id == NULL) + { + NODE_DEBUG (iq, "can't acknowledge IQ with no id"); + return NULL; + } + + from_jid = lm_message_node_get_attribute (iq, "from"); + + result = lm_message_new_with_sub_type (from_jid, LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_RESULT); + result_iq = lm_message_get_node (result); + lm_message_node_set_attribute (result_iq, "id", id); + + return result; +} + +/** + * connection_iq_disco_cb + * + * Called by loudmouth when we get an incoming . This handler handles + * disco-related IQs. + */ +static LmHandlerResult +connection_iq_disco_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + LmMessage *result; + LmMessageNode *iq, *result_iq, *query, *result_query; + const gchar *node, *suffix; + GSList *features; + GSList *i; + GabblePresence *pres; + + if ( NULL == conn ) + { + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + if (lm_message_get_sub_type (message) != LM_MESSAGE_SUB_TYPE_GET) + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + + iq = lm_message_get_node (message); + query = lm_message_node_get_child_with_namespace (iq, "query", + NS_DISCO_INFO); + + if (!query) + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + + node = lm_message_node_get_attribute (query, "node"); + + if (node && ( + 0 != strncmp (node, NS_GABBLE_CAPS "#", strlen (NS_GABBLE_CAPS) + 1) || + strlen(node) < strlen (NS_GABBLE_CAPS) + 2)) + { + NODE_DEBUG (iq, "got iq disco query with unexpected node attribute"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + if (node == NULL) + suffix = NULL; + else + suffix = node + strlen (NS_GABBLE_CAPS) + 1; + + result = _lm_iq_message_make_result (message); + result_iq = lm_message_get_node (result); + result_query = lm_message_node_add_child (result_iq, "query", NULL); + lm_message_node_set_attribute (result_query, "xmlns", NS_DISCO_INFO); + + pres = gabble_presence_cache_get (conn->presence_cache, conn->self_handle); + gabble_debug (DEBUG_FLAG, "got disco request for bundle %s, caps are %x", node, pres->caps); + features = capabilities_get_features (pres->caps); + + g_debug("%s: caps now %u", G_STRFUNC, pres->caps); + + for (i = features; NULL != i; i = i->next) + { + const Feature *feature = (const Feature *) i->data; + + if (NULL == node || !g_strdiff (suffix, feature->bundle)) + { + LmMessageNode *node = lm_message_node_add_child (result_query, + "feature", NULL); + lm_message_node_set_attribute (node, "var", feature->ns); + } + } + + NODE_DEBUG (result_iq, "sending disco response"); + + if (!lm_connection_send (conn->lmconn, result, NULL)) + gabble_debug (DEBUG_FLAG, "sending disco response failed"); + + g_slist_free (features); + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +/** + * connection_iq_unknown_cb + * + * Called by loudmouth when we get an incoming . This handler is + * at a lower priority than the others, and should reply with an error + * about unsupported get/set attempts. + */ +static LmHandlerResult +connection_iq_unknown_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + + g_assert (connection == conn->lmconn); + + NODE_DEBUG (message->node, "got unknown iq"); + + switch (lm_message_get_sub_type (message)) + { + case LM_MESSAGE_SUB_TYPE_GET: + case LM_MESSAGE_SUB_TYPE_SET: + _gabble_connection_send_iq_error (conn, message, + XMPP_ERROR_SERVICE_UNAVAILABLE, NULL); + break; + default: + break; + } + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +/** + * connection_stream_error_cb + * + * Called by loudmouth when we get stream error, which means that + * we're about to close the connection. The message contains the reason + * for the connection hangup. + */ +static LmHandlerResult +connection_stream_error_cb (LmMessageHandler *handler, + LmConnection *connection, + LmMessage *message, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + LmMessageNode *conflict_node; + + g_assert (connection == conn->lmconn); + + NODE_DEBUG (message->node, "got stream error"); + + conflict_node = lm_message_node_get_child (message->node, "conflict"); + if (conflict_node) + { + gabble_debug (DEBUG_FLAG, "found conflict node, emiting status change"); + + /* Another client with the same resource just + * appeared, we're going down. */ + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_NAME_IN_USE); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; +} + + +/** + * connection_ssl_cb + * + * If we're doing old SSL, this function gets called if the certificate + * is dodgy. + */ +static LmSSLResponse +connection_ssl_cb (LmSSL *lmssl, + LmSSLStatus status, + gpointer data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (data); + + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + const char *reason; + TpConnectionStatusReason tp_reason; + + g_message("inside ssl_cb\n"); + switch (status) { + case LM_SSL_STATUS_NO_CERT_FOUND: + reason = "The server doesn't provide a certificate."; + tp_reason = TP_CONN_STATUS_REASON_CERT_NOT_PROVIDED; + break; + case LM_SSL_STATUS_UNTRUSTED_CERT: + reason = "The certificate can not be trusted."; + tp_reason = TP_CONN_STATUS_REASON_CERT_UNTRUSTED; + break; + case LM_SSL_STATUS_CERT_EXPIRED: + reason = "The certificate has expired."; + tp_reason = TP_CONN_STATUS_REASON_CERT_EXPIRED; + break; + case LM_SSL_STATUS_CERT_NOT_ACTIVATED: + reason = "The certificate has not been activated."; + tp_reason = TP_CONN_STATUS_REASON_CERT_NOT_ACTIVATED; + break; + case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH: + reason = "The server hostname doesn't match the one in the certificate."; + tp_reason = TP_CONN_STATUS_REASON_CERT_HOSTNAME_MISMATCH; + break; + case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: + reason = "The fingerprint doesn't match the expected value."; + tp_reason = TP_CONN_STATUS_REASON_CERT_FINGERPRINT_MISMATCH; + break; + case LM_SSL_STATUS_GENERIC_ERROR: + reason = "An unknown SSL error occurred."; + tp_reason = TP_CONN_STATUS_REASON_CERT_OTHER_ERROR; + break; + default: + g_assert_not_reached(); + reason = "Unknown SSL error code from Loudmouth."; + tp_reason = TP_CONN_STATUS_REASON_ENCRYPTION_ERROR; + break; + } + + gabble_debug (DEBUG_FLAG, "called: %s", reason); + + if (priv->ignore_ssl_errors) + { + return LM_SSL_RESPONSE_CONTINUE; + } + else + { + priv->ssl_error = tp_reason; + return LM_SSL_RESPONSE_STOP; + } +} + +static void +do_auth (GabbleConnection *conn) +{ + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + GError *error = NULL; + + gabble_debug (DEBUG_FLAG, "authenticating with username: %s, password: , resource: %s", + priv->username, priv->resource); + + if (!lm_connection_authenticate (conn->lmconn, priv->username, priv->password, + priv->resource, connection_auth_cb, + conn, NULL, &error)) + { + gabble_debug (DEBUG_FLAG, "failed: %s", error->message); + g_error_free (error); + + /* the reason this function can fail is through network errors, + * authentication failures are reported to our auth_cb */ + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_NETWORK_ERROR); + } +} + +static void +registration_finished_cb (GabbleRegister *reg, + gboolean success, + gint err_code, + const gchar *err_msg, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + + g_message("registration_finished_cb\n"); + if (conn->status != TP_CONN_STATUS_CONNECTING) + { + g_assert (conn->status == TP_CONN_STATUS_DISCONNECTED); + return; + } + + gabble_debug (DEBUG_FLAG, "%s", (success) ? "succeeded" : "failed"); + + g_object_unref (reg); + + if (success) + { + do_auth (conn); + } + else + { + gabble_debug (DEBUG_FLAG, "err_code = %d, err_msg = '%s'", + err_code, err_msg); + + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + (err_code == InvalidArgument) ? TP_CONN_STATUS_REASON_NAME_IN_USE : + TP_CONN_STATUS_REASON_AUTHENTICATION_FAILED); + } +} + +static void +do_register (GabbleConnection *conn) +{ + GabbleRegister *reg; + + reg = gabble_register_new (conn); + + g_signal_connect (reg, "finished", (GCallback) registration_finished_cb, + conn); + + gabble_register_start (reg); +} + +/** + * connection_open_cb + * + * Stage 2 of connecting, this function is called by loudmouth after the + * result of the non-blocking lm_connection_open call is known. It makes + * a request to authenticate the user with the server, or optionally + * registers user on the server first. + */ +static void +connection_open_cb (LmConnection *lmconn, + gboolean success, + gpointer data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (data); + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + g_message("[connection_open_cb]"); + if ((conn->status != TP_CONN_STATUS_CONNECTING) && + (conn->status != TP_CONN_STATUS_NEW)) + { + g_assert (conn->status == TP_CONN_STATUS_DISCONNECTED); + return; + } + + g_assert (priv); + g_assert (lmconn == conn->lmconn); + + if (!success) + { + if (lm_connection_get_proxy (lmconn)) + { + g_message ("failed, retrying without proxy"); + + lm_connection_set_proxy (lmconn, NULL); + + if (do_connect (conn, NULL)) + { + return; + } + } + else + { + g_message ("failed"); + } + + if (priv->ssl_error) + { + g_message ("[connection_open_cb] priv->ssl_error"); + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + priv->ssl_error); + } + else + { + g_message ("[connection_open_cb] reached else"); + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_NETWORK_ERROR); + } + + return; + } + + if (!priv->do_register) + do_auth (conn); + else + do_register (conn); +} + +/** + * connection_auth_cb + * + * Stage 3 of connecting, this function is called by loudmouth after the + * result of the non-blocking lm_connection_authenticate call is known. + * It sends a discovery request to find the server's features. + */ +static void +connection_auth_cb (LmConnection *lmconn, + gboolean success, + gpointer data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (data); + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + GError *error = NULL; + + g_message("In connection_auth_cb\n"); + + if (conn->status != TP_CONN_STATUS_CONNECTING) + { + g_assert (conn->status == TP_CONN_STATUS_DISCONNECTED); + return; + } + + g_assert (priv); + g_assert (lmconn == conn->lmconn); + + if (!success) + { + gabble_debug (DEBUG_FLAG, "failed"); + g_message("Inside !success\n"); + + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_AUTHENTICATION_FAILED); + + return; + } + + if (!gabble_disco_request_with_timeout (conn->disco, GABBLE_DISCO_TYPE_INFO, + priv->stream_server, NULL, 5000, + connection_disco_cb, conn, + G_OBJECT (conn), &error)) + { + gabble_debug (DEBUG_FLAG, "sending disco request failed: %s", + error->message); + g_message("sending disco request failed\n"); + + g_error_free (error); + + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_NETWORK_ERROR); + } +} + +/** + * connection_disco_cb + * + * Stage 4 of connecting, this function is called by GabbleDisco after the + * result of the non-blocking server feature discovery call is known. It sends + * the user's initial presence to the server, marking them as available, + * and requests the roster. + */ +static void +connection_disco_cb (GabbleDisco *disco, + GabbleDiscoRequest *request, + const gchar *jid, + const gchar *node, + LmMessageNode *result, + GError *disco_error, + gpointer user_data) +{ + GabbleConnection *conn = user_data; + GabbleConnectionPrivate *priv; + GError *error = NULL; + + g_message("Inside connection_disco_cb\n"); + + if ( NULL == conn ) + { + return; + } + if (conn->status != TP_CONN_STATUS_CONNECTING) + { + g_assert (conn->status == TP_CONN_STATUS_DISCONNECTED); + return; + } + + g_assert (GABBLE_IS_CONNECTION (conn)); + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + if (disco_error) + { + g_message("got disco error, setting no features\n"); + gabble_debug (DEBUG_FLAG, "got disco error, setting no features: %s", disco_error->message); + } + else + { + LmMessageNode *iter; + + NODE_DEBUG (result, "got"); + + for (iter = result->children; iter != NULL; iter = iter->next) + { + if (0 == strcmp (iter->name, "feature")) + { + const gchar *var = lm_message_node_get_attribute (iter, "var"); + + if (var == NULL) + continue; + + if (0 == strcmp (var, NS_GOOGLE_JINGLE_INFO)) + conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_JINGLE_INFO; + else if (0 == strcmp (var, NS_GOOGLE_ROSTER)) + conn->features |= GABBLE_CONNECTION_FEATURES_GOOGLE_ROSTER; + else if (0 == strcmp (var, NS_PRESENCE_INVISIBLE)) + conn->features |= GABBLE_CONNECTION_FEATURES_PRESENCE_INVISIBLE; + else if (0 == strcmp (var, NS_PRIVACY)) + conn->features |= GABBLE_CONNECTION_FEATURES_PRIVACY; + else if (0 == strcmp (var, NS_SEARCH)) + conn->features |= GABBLE_CONNECTION_FEATURES_SEARCH; + } + } + + gabble_debug (DEBUG_FLAG, "set features flags to %d", conn->features); + } + + g_message("before signal_own_presence\n"); + + /* send presence to the server to indicate availability */ + if (!signal_own_presence (conn, &error)) + { + gabble_debug (DEBUG_FLAG, "sending initial presence failed: %s", error->message); + g_message("sending initial presence failed\n"); + goto ERROR; + } + + g_message("after signal_own_presence\n"); + + /* go go gadget on-line */ + connection_status_change (conn, TP_CONN_STATUS_CONNECTED, TP_CONN_STATUS_REASON_REQUESTED); + + if (conn->features & GABBLE_CONNECTION_FEATURES_GOOGLE_JINGLE_INFO) + { + jingle_info_discover_servers (conn); + } + + if(conn->features & GABBLE_CONNECTION_FEATURES_SEARCH) + { + get_search_keys_info(conn,jid); + } + + return; + +ERROR: + if (error) + g_error_free (error); + + connection_status_change (conn, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_NETWORK_ERROR); + + return; +} + + +static GHashTable * +get_statuses_arguments() +{ + +#ifndef EMULATOR + static GHashTable *arguments = NULL; +#endif + + + if (arguments == NULL) + { + arguments = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_insert (arguments, "message", "s"); + g_hash_table_insert (arguments, "priority", "n"); + } + + return arguments; +} + +/**************************************************************************** + * D-BUS EXPORTED METHODS * + ****************************************************************************/ + + +/** + * gabble_connection_add_status + * + * Implements D-Bus method AddStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_add_status (GabbleConnection *self, + const gchar *status, + GHashTable *parms, + GError **error) +{ + g_assert (GABBLE_IS_CONNECTION (self)); + + ERROR_IF_NOT_CONNECTED (self, error); + + g_set_error (error, TELEPATHY_ERRORS, NotImplemented, + "Only one status is possible at a time with this protocol"); + + return FALSE; +} + +static void +_emit_capabilities_changed (GabbleConnection *conn, + GabbleHandle handle, + GabblePresenceCapabilities old_caps, + GabblePresenceCapabilities new_caps) +{ + GPtrArray *caps_arr; + const CapabilityConversionData *ccd; + guint i; + + if (old_caps == new_caps) + return; + + caps_arr = g_ptr_array_new (); + + for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) + { + if (ccd->c2tf_fn (old_caps | new_caps)) + { + GValue caps_monster_struct = {0, }; + guint old_tpflags = ccd->c2tf_fn (old_caps); + guint old_caps = old_tpflags ? + TP_CONN_CAPABILITY_FLAG_CREATE | + TP_CONN_CAPABILITY_FLAG_INVITE : 0; + guint new_tpflags = ccd->c2tf_fn (new_caps); + guint new_caps = new_tpflags ? + TP_CONN_CAPABILITY_FLAG_CREATE | + TP_CONN_CAPABILITY_FLAG_INVITE : 0; + + if (0 == (old_tpflags ^ new_tpflags)) + continue; + + g_value_init (&caps_monster_struct, + TP_CAPABILITIES_CHANGED_MONSTER_TYPE); + g_value_take_boxed (&caps_monster_struct, + dbus_g_type_specialized_construct + (TP_CAPABILITIES_CHANGED_MONSTER_TYPE)); + + dbus_g_type_struct_set (&caps_monster_struct, + 0, handle, + 1, ccd->iface, + 2, old_caps, + 3, new_caps, + 4, old_tpflags, + 5, new_tpflags, + G_MAXUINT); + + g_ptr_array_add (caps_arr, g_value_get_boxed (&caps_monster_struct)); + } + } + + if (caps_arr->len) + g_signal_emit (conn, signals[CAPABILITIES_CHANGED], 0, caps_arr); + + for (i = 0; i < caps_arr->len; i++) + { + g_boxed_free (TP_CAPABILITIES_CHANGED_MONSTER_TYPE, + g_ptr_array_index (caps_arr, i)); + } + g_ptr_array_free (caps_arr, TRUE); +} + +static void +connection_capabilities_update_cb (GabblePresenceCache *cache, + GabbleHandle handle, + GabblePresenceCapabilities old_caps, + GabblePresenceCapabilities new_caps, + gpointer user_data) +{ + GabbleConnection *conn = GABBLE_CONNECTION (user_data); + + _emit_capabilities_changed (conn, handle, old_caps, new_caps); +} + +/** + * gabble_connection_advertise_capabilities + * + * Implements D-Bus method AdvertiseCapabilities + * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_advertise_capabilities (GabbleConnection *self, + const GPtrArray *add, + const gchar **remove, + GPtrArray **ret, + GError **error) +{ + guint i; + GabblePresence *pres; + GabblePresenceCapabilities add_caps = 0, remove_caps = 0, caps, save_caps; + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (self); + const CapabilityConversionData *ccd; + + ERROR_IF_NOT_CONNECTED (self, error); + + pres = gabble_presence_cache_get (self->presence_cache, self->self_handle); + gabble_debug (DEBUG_FLAG, "caps before: %x", pres->caps); + + for (i = 0; i < add->len; i++) + { + GValue iface_flags_pair = {0, }; + gchar *iface; + guint flags; + + g_value_init (&iface_flags_pair, TP_CAPABILITY_PAIR_TYPE); + g_value_set_static_boxed (&iface_flags_pair, g_ptr_array_index (add, i)); + + dbus_g_type_struct_get (&iface_flags_pair, + 0, &iface, + 1, &flags, + G_MAXUINT); + + for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) + if (g_str_equal (iface, ccd->iface)) + add_caps |= ccd->tf2c_fn (flags); + } + + for (i = 0; NULL != remove[i]; i++) + { + for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) + if (g_str_equal (remove[i], ccd->iface)) + remove_caps |= ccd->tf2c_fn (~0); + } + + pres = gabble_presence_cache_get (self->presence_cache, self->self_handle); + save_caps = caps = pres->caps; + + caps |= add_caps; + caps ^= (caps & remove_caps); + + gabble_debug (DEBUG_FLAG, "caps to add: %x", add_caps); + gabble_debug (DEBUG_FLAG, "caps to remove: %x", remove_caps); + gabble_debug (DEBUG_FLAG, "caps after: %x", caps); + + if (caps ^ save_caps) + { + gabble_debug (DEBUG_FLAG, "before != after, changing"); + gabble_presence_set_capabilities (pres, priv->resource, caps, priv->caps_serial++); + gabble_debug (DEBUG_FLAG, "set caps: %x", pres->caps); + } + + *ret = g_ptr_array_new (); + + for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) + { + if (ccd->c2tf_fn (pres->caps)) + { + GValue iface_flags_pair = {0, }; + + g_value_init (&iface_flags_pair, TP_CAPABILITY_PAIR_TYPE); + g_value_take_boxed (&iface_flags_pair, + dbus_g_type_specialized_construct (TP_CAPABILITY_PAIR_TYPE)); + + dbus_g_type_struct_set (&iface_flags_pair, + 0, ccd->iface, + 1, ccd->c2tf_fn (pres->caps), + G_MAXUINT); + + g_ptr_array_add (*ret, g_value_get_boxed (&iface_flags_pair)); + } + } + + if (caps ^ save_caps) + { + if (!signal_own_presence (self, error)) + return FALSE; + + _emit_capabilities_changed (self, self->self_handle, save_caps, caps); + } + + return TRUE; +} + +/** + * gabble_connection_clear_status + * + * Implements D-Bus method ClearStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_clear_status (GabbleConnection *self, + GError **error) +{ + GabbleConnectionPrivate *priv; + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error); + + gabble_presence_cache_update (self->presence_cache, self->self_handle, + priv->resource, GABBLE_PRESENCE_AVAILABLE, NULL, priv->priority); + emit_one_presence_update (self, self->self_handle); + return signal_own_presence (self, error); +} + + +/** + * gabble_connection_connect + * Implements D-Bus method Connect + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ + +gboolean +gabble_connection_connect (GabbleConnection *self, + GError **error) +{ + g_assert(GABBLE_IS_CONNECTION (self)); + + if (self->status == TP_CONN_STATUS_NEW) + return _gabble_connection_connect (self, error); + return TRUE; +} + + +/** + * gabble_connection_disconnect + * + * Implements D-Bus method Disconnect + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_disconnect (GabbleConnection *self, + GError **error) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + g_message("gabble_connection_disconnect: going to change status to TP_CONN_STATUS_DISCONNECTED ") ; + connection_status_change (self, + TP_CONN_STATUS_DISCONNECTED, + TP_CONN_STATUS_REASON_REQUESTED); + + return TRUE; +} + + +/** + * gabble_connection_get_alias_flags + * + * Implements D-Bus method GetAliasFlags + * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_alias_flags (GabbleConnection *self, + guint *ret, + GError **error) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + *ret = TP_CONN_ALIAS_FLAG_USER_SET; + + return TRUE; +} + +//#ifndef EMULATOR +static const gchar *assumed_caps[] = +{ + TP_IFACE_CHANNEL_TYPE_TEXT, + TP_IFACE_CHANNEL_INTERFACE_GROUP, + NULL +}; +//#endif + + +/** + * gabble_connection_get_capabilities + * + * Implements D-Bus method GetCapabilities + * on interface org.freedesktop.Telepathy.Connection.Interface.Capabilities + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_capabilities (GabbleConnection *self, + const GArray *handles, + GPtrArray **ret, + GError **error) +{ + guint i; + + ERROR_IF_NOT_CONNECTED (self, error); + + if (!gabble_handles_are_valid (self->handles, + TP_HANDLE_TYPE_CONTACT, + handles, + TRUE, + error)) + { + return FALSE; + } + + *ret = g_ptr_array_new (); + + for (i = 0; i < handles->len; i++) + { + GabbleHandle handle = g_array_index (handles, guint, i); + GabblePresence *pres; + const CapabilityConversionData *ccd; + guint typeflags; + //#ifndef EMULATOR + const gchar **assumed; + //#else + //gchar **assumed; + //#endif + + if (0 == handle) + { + /* FIXME report the magical channel types available on the connection itself */ + continue; + } + + pres = gabble_presence_cache_get (self->presence_cache, handle); + + if (NULL != pres) + for (ccd = capabilities_conversions; NULL != ccd->iface; ccd++) + { + typeflags = ccd->c2tf_fn (pres->caps); + + if (typeflags) + { + GValue monster = {0, }; + + g_value_init (&monster, TP_GET_CAPABILITIES_MONSTER_TYPE); + g_value_take_boxed (&monster, + dbus_g_type_specialized_construct ( + TP_GET_CAPABILITIES_MONSTER_TYPE)); + + dbus_g_type_struct_set (&monster, + 0, handle, + 1, ccd->iface, + 2, TP_CONN_CAPABILITY_FLAG_CREATE | + TP_CONN_CAPABILITY_FLAG_INVITE, + 3, typeflags, + G_MAXUINT); + + g_ptr_array_add (*ret, g_value_get_boxed (&monster)); + } + } + + for (assumed = assumed_caps; NULL != *assumed; assumed++) + { + GValue monster = {0, }; + + g_value_init (&monster, TP_GET_CAPABILITIES_MONSTER_TYPE); + g_value_take_boxed (&monster, + dbus_g_type_specialized_construct (TP_GET_CAPABILITIES_MONSTER_TYPE)); + + dbus_g_type_struct_set (&monster, + 0, handle, + 1, *assumed, + 2, TP_CONN_CAPABILITY_FLAG_CREATE | + TP_CONN_CAPABILITY_FLAG_INVITE, + 3, 0, + G_MAXUINT); + + g_ptr_array_add (*ret, g_value_get_boxed (&monster)); + } + } + + return TRUE; +} + + +/** + * gabble_connection_get_interfaces + * + * Implements D-Bus method GetInterfaces + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_interfaces (GabbleConnection *self, + gchar ***ret, + GError **error) +{ + const char *interfaces[] = { + TP_IFACE_CONN_INTERFACE_ALIASING, + TP_IFACE_CONN_INTERFACE_CAPABILITIES, + TP_IFACE_CONN_INTERFACE_PRESENCE, + TP_IFACE_PROPERTIES, + TP_IFACE_CONN_INTERFACE_AVATAR, + NULL }; + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + *ret = g_strdupv ((gchar **) interfaces); + + return TRUE; +} + + +/** + * gabble_connection_get_presence + * + * Implements D-Bus method GetPresence + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_get_presence (GabbleConnection *self, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + GHashTable *presence_hash; + GError *error = NULL; + + if (!gabble_handles_are_valid (self->handles, TP_HANDLE_TYPE_CONTACT, + contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + } + + presence_hash = construct_presence_hash (self, contacts); + dbus_g_method_return (context, presence_hash); + g_hash_table_destroy (presence_hash); +} + + +/** + * gabble_connection_get_properties + * + * Implements D-Bus method GetProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_properties (GabbleConnection *self, + const GArray *properties, + GPtrArray **ret, + GError **error) +{ + return gabble_properties_mixin_get_properties (G_OBJECT (self), properties, + ret, error); +} + + +/** + * gabble_connection_get_protocol + * + * Implements D-Bus method GetProtocol + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_protocol (GabbleConnection *self, + gchar **ret, + GError **error) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + *ret = g_strdup (priv->protocol); + + return TRUE; +} + + +/** + * gabble_connection_get_self_handle + * + * Implements D-Bus method GetSelfHandle + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_self_handle (GabbleConnection *self, + guint *ret, + GError **error) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + *ret = self->self_handle; + + return TRUE; +} + + +/** + * gabble_connection_get_status + * + * Implements D-Bus method GetStatus + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_status (GabbleConnection *self, + guint *ret, + GError **error) +{ + g_assert (GABBLE_IS_CONNECTION (self)); + + if (self->status == TP_CONN_STATUS_NEW) + { + *ret = TP_CONN_STATUS_DISCONNECTED; + } + else + { + *ret = self->status; + } + + return TRUE; +} + + +/** + * gabble_connection_get_statuses + * + * Implements D-Bus method GetStatuses + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_get_statuses (GabbleConnection *self, + GHashTable **ret, + GError **error) +{ + GabbleConnectionPrivate *priv; + GValueArray *status; + int i; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + gabble_debug (DEBUG_FLAG, "called."); + + *ret = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) g_value_array_free); + + for (i=0; i < LAST_GABBLE_PRESENCE; i++) + { + /* don't report the invisible presence if the server + * doesn't have the presence-invisible feature */ + if (!status_is_available (self, i)) + continue; + + status = g_value_array_new (5); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth (status, 0), G_TYPE_UINT); + g_value_set_uint (g_value_array_get_nth (status, 0), + gabble_statuses[i].presence_type); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth (status, 1), G_TYPE_BOOLEAN); + g_value_set_boolean (g_value_array_get_nth (status, 1), + gabble_statuses[i].self); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth (status, 2), G_TYPE_BOOLEAN); + g_value_set_boolean (g_value_array_get_nth (status, 2), + gabble_statuses[i].exclusive); + + g_value_array_append (status, NULL); + g_value_init (g_value_array_get_nth (status, 3), + DBUS_TYPE_G_STRING_STRING_HASHTABLE); + g_value_set_static_boxed (g_value_array_get_nth (status, 3), + get_statuses_arguments()); + + g_hash_table_insert (*ret, (gchar*) gabble_statuses[i].name, status); + } + + return TRUE; +} + + +/** + * gabble_connection_hold_handles + * + * Implements D-Bus method HoldHandles + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_hold_handles (GabbleConnection *self, + guint handle_type, + const GArray *handles, + DBusGMethodInvocation *context) +{ + GabbleConnectionPrivate *priv; + GError *error = NULL; + gchar *sender; + guint i; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED_ASYNC (self, error, context) + + if (!gabble_handles_are_valid (self->handles, + handle_type, + handles, + FALSE, + &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + sender = dbus_g_method_get_sender (context); + for (i = 0; i < handles->len; i++) + { + GabbleHandle handle = g_array_index (handles, GabbleHandle, i); + if (!gabble_handle_client_hold (self->handles, sender, handle, + handle_type, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + } + + dbus_g_method_return (context); +} + + +/** + * gabble_connection_inspect_handles + * + * Implements D-Bus method InspectHandles + * on interface org.freedesktop.Telepathy.Connection + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +void +gabble_connection_inspect_handles (GabbleConnection *self, + guint handle_type, + const GArray *handles, + DBusGMethodInvocation *context) +{ + GabbleConnectionPrivate *priv; + GError *error = NULL; + const gchar **ret; + guint i; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED_ASYNC (self, error, context); + + if (!gabble_handles_are_valid (self->handles, + handle_type, + handles, + FALSE, + &error)) + { + dbus_g_method_return_error (context, error); + + g_error_free (error); + + return; + } + + ret = g_new (const gchar *, handles->len + 1); + + for (i = 0; i < handles->len; i++) + { + GabbleHandle handle; + const gchar *tmp; + + handle = g_array_index (handles, GabbleHandle, i); + tmp = gabble_handle_inspect (self->handles, handle_type, handle); + g_assert (tmp != NULL); + + ret[i] = tmp; + } + + ret[i] = NULL; + + dbus_g_method_return (context, ret); + + g_free (ret); +} + +/** + * list_channel_factory_foreach_one: + * @key: iterated key + * @value: iterated value + * @data: data attached to this key/value pair + * + * Called by the exported ListChannels function, this should iterate over + * the handle/channel pairs in a channel factory, and to the GPtrArray in + * the data pointer, add a GValueArray containing the following: + * a D-Bus object path for the channel object on this service + * a D-Bus interface name representing the channel type + * an integer representing the handle type this channel communicates with, or zero + * an integer handle representing the contact, room or list this channel communicates with, or zero + */ +static void +list_channel_factory_foreach_one (TpChannelIface *chan, + gpointer data) +{ + GObject *channel = G_OBJECT (chan); + GPtrArray *channels = (GPtrArray *) data; + gchar *path, *type; + guint handle_type, handle; + GValue entry = { 0, }; + + g_value_init (&entry, TP_CHANNEL_LIST_ENTRY_TYPE); + g_value_take_boxed (&entry, dbus_g_type_specialized_construct + (TP_CHANNEL_LIST_ENTRY_TYPE)); + + g_object_get (channel, + "object-path", &path, + "channel-type", &type, + "handle-type", &handle_type, + "handle", &handle, + NULL); + + dbus_g_type_struct_set (&entry, + 0, path, + 1, type, + 2, handle_type, + 3, handle, + G_MAXUINT); + + g_ptr_array_add (channels, g_value_get_boxed (&entry)); + + g_free (path); + g_free (type); +} + +/** + * gabble_connection_list_channels + * + * Implements D-Bus method ListChannels + * on interface org.freedesktop.Telepathy.Connection + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_list_channels (GabbleConnection *self, + GPtrArray **ret, + GError **error) +{ + GabbleConnectionPrivate *priv; + GPtrArray *channels; + guint i; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + /* I think on average, each factory will have 2 channels :D */ + channels = g_ptr_array_sized_new (priv->channel_factories->len * 2); + + for (i = 0; i < priv->channel_factories->len; i++) + { + TpChannelFactoryIface *factory = g_ptr_array_index + (priv->channel_factories, i); + tp_channel_factory_iface_foreach (factory, + list_channel_factory_foreach_one, channels); + } + + *ret = channels; + + return TRUE; +} + + +/** + * gabble_connection_list_properties + * + * Implements D-Bus method ListProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_list_properties (GabbleConnection *self, + GPtrArray **ret, + GError **error) +{ + return gabble_properties_mixin_list_properties (G_OBJECT (self), ret, error); +} + + +/** + * gabble_connection_release_handles + * + * Implements D-Bus method ReleaseHandles + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_release_handles (GabbleConnection *self, + guint handle_type, + const GArray * handles, + DBusGMethodInvocation *context) +{ + GabbleConnectionPrivate *priv; + char *sender; + GError *error = NULL; + guint i; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED_ASYNC (self, error, context) + + if (!gabble_handles_are_valid (self->handles, + handle_type, + handles, + FALSE, + &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + sender = dbus_g_method_get_sender (context); + for (i = 0; i < handles->len; i++) + { + GabbleHandle handle = g_array_index (handles, GabbleHandle, i); + if (!gabble_handle_client_release (self->handles, sender, handle, + handle_type, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + } + + dbus_g_method_return (context); +} + + +/** + * gabble_connection_remove_status + * + * Implements D-Bus method RemoveStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_remove_status (GabbleConnection *self, + const gchar *status, + GError **error) +{ + GabblePresence *presence; + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + g_assert (GABBLE_IS_CONNECTION (self)); + + ERROR_IF_NOT_CONNECTED (self, error) + + presence = gabble_presence_cache_get (self->presence_cache, + self->self_handle); + + if (strcmp (status, gabble_statuses[presence->status].name) == 0) + { + gabble_presence_cache_update (self->presence_cache, self->self_handle, + priv->resource, GABBLE_PRESENCE_AVAILABLE, NULL, priv->priority); + emit_one_presence_update (self, self->self_handle); + return signal_own_presence (self, error); + } + else + { + g_set_error (error, TELEPATHY_ERRORS, InvalidArgument, + "Attempting to remove non-existent presence."); + return FALSE; + } +} + + +typedef struct _AliasesRequest AliasesRequest; + +struct _AliasesRequest +{ + GabbleConnection *conn; + DBusGMethodInvocation *request_call; + guint pending_vcard_requests; + GArray *contacts; + GabbleVCardManagerRequest **vcard_requests; + gchar **aliases; +}; + + +static AliasesRequest * +aliases_request_new (GabbleConnection *conn, + DBusGMethodInvocation *request_call, + const GArray *contacts) +{ + AliasesRequest *request; + + request = g_slice_new0 (AliasesRequest); + request->conn = conn; + request->request_call = request_call; + request->contacts = g_array_new (FALSE, FALSE, sizeof (GabbleHandle)); + g_array_insert_vals (request->contacts, 0, contacts->data, contacts->len); + request->vcard_requests = + g_new0 (GabbleVCardManagerRequest *, contacts->len); + request->aliases = g_new0 (gchar *, contacts->len + 1); + return request; +} + + +static void +aliases_request_free (AliasesRequest *request) +{ + guint i; + + for (i = 0; i < request->contacts->len; i++) + { + if (request->vcard_requests[i] != NULL) + gabble_vcard_manager_cancel_request (request->conn->vcard_manager, + request->vcard_requests[i]); + } + + g_array_free (request->contacts, TRUE); + g_free (request->vcard_requests); + g_strfreev (request->aliases); + g_slice_free (AliasesRequest, request); +} + + +static gboolean +aliases_request_try_return (AliasesRequest *request) +{ + if (request->pending_vcard_requests == 0) + { + dbus_g_method_return (request->request_call, request->aliases); + return TRUE; + } + + return FALSE; +} + + +static void +aliases_request_vcard_cb (GabbleVCardManager *manager, + GabbleVCardManagerRequest *request, + GabbleHandle handle, + LmMessageNode *vcard, + GError *error, + gpointer user_data) +{ + AliasesRequest *aliases_request = (AliasesRequest *) user_data; + GabbleConnectionAliasSource source; + guint i; + gboolean found = FALSE; + gchar *alias = NULL; + + g_assert (aliases_request->pending_vcard_requests > 0); + + /* The index of the vCard request in the vCard request array is the + * index of the contact/alias in their respective arrays. */ + + for (i = 0; i < aliases_request->contacts->len; i++) + if (aliases_request->vcard_requests[i] == request) + { + found = TRUE; + break; + } + + g_assert (found); + source = _gabble_connection_get_cached_alias (aliases_request->conn, + g_array_index (aliases_request->contacts, GabbleHandle, i), &alias); + g_assert (source != GABBLE_CONNECTION_ALIAS_NONE); + g_assert (NULL != alias); + + aliases_request->pending_vcard_requests--; + aliases_request->vcard_requests[i] = NULL; + aliases_request->aliases[i] = alias; + + if (aliases_request_try_return (aliases_request)) + aliases_request_free (aliases_request); +} + + +/** + * gabble_connection_request_aliases + * + * Implements D-Bus method RequestAliases + * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_request_aliases (GabbleConnection *self, + const GArray *contacts, + DBusGMethodInvocation *context) +{ + guint i; + AliasesRequest *request; + GError *error = NULL; + + g_assert (GABBLE_IS_CONNECTION (self)); + + ERROR_IF_NOT_CONNECTED_ASYNC (self, error, context) + + if (!gabble_handles_are_valid (self->handles, TP_HANDLE_TYPE_CONTACT, + contacts, FALSE, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + request = aliases_request_new (self, context, contacts); + + for (i = 0; i < contacts->len; i++) + { + GabbleHandle handle = g_array_index (contacts, GabbleHandle, i); + GabbleConnectionAliasSource source; + GabbleVCardManagerRequest *vcard_request; + gchar *alias; + + source = _gabble_connection_get_cached_alias (self, handle, &alias); + g_assert (source != GABBLE_CONNECTION_ALIAS_NONE); + g_assert (NULL != alias); + + if (source >= GABBLE_CONNECTION_ALIAS_FROM_VCARD || + gabble_vcard_manager_has_cached_alias (self->vcard_manager, handle)) + { + /* Either the alias we got was from a vCard or better, or we already + * tried getting an alias from a vcard and failed, so there's no + * point trying again. */ + request->aliases[i] = alias; + } + else + { + gabble_debug (DEBUG_FLAG, "requesting vCard for alias of contact %s", + gabble_handle_inspect (self->handles, TP_HANDLE_TYPE_CONTACT, + handle)); + + g_free (alias); + vcard_request = gabble_vcard_manager_request (self->vcard_manager, + handle, 0, aliases_request_vcard_cb, request, G_OBJECT (self), + &error); + + if (NULL != error) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + aliases_request_free (request); + return; + } + + request->vcard_requests[i] = vcard_request; + request->pending_vcard_requests++; + } + } + + if (aliases_request_try_return (request)) + aliases_request_free (request); +} + + +/** + * gabble_connection_request_channel + * + * Implements D-Bus method RequestChannel + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_request_channel (GabbleConnection *self, + const gchar *type, + guint handle_type, + guint handle, + gboolean suppress_handler, + DBusGMethodInvocation *context) +{ + GabbleConnectionPrivate *priv; + TpChannelFactoryRequestStatus status = + TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED; + gchar *object_path = NULL; + GError *error = NULL; + guint i; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED_ASYNC (self, error, context); + + for (i = 0; i < priv->channel_factories->len; i++) + { + TpChannelFactoryIface *factory = g_ptr_array_index + (priv->channel_factories, i); + TpChannelFactoryRequestStatus cur_status; + TpChannelIface *chan = NULL; + ChannelRequest *request = NULL; + + priv->suppress_next_handler = suppress_handler; + + cur_status = tp_channel_factory_iface_request (factory, type, + (TpHandleType) handle_type, handle, &chan, &error); + + priv->suppress_next_handler = FALSE; + + switch (cur_status) + { + case TP_CHANNEL_FACTORY_REQUEST_STATUS_DONE: + g_assert (NULL != chan); + g_object_get (chan, "object-path", &object_path, NULL); + goto OUT; + case TP_CHANNEL_FACTORY_REQUEST_STATUS_QUEUED: + gabble_debug (DEBUG_FLAG, "queueing request, channel_type=%s, handle_type=%u, " + "handle=%u, suppress_handler=%u", type, handle_type, + handle, suppress_handler); + request = channel_request_new (context, type, handle_type, handle, + suppress_handler); + g_ptr_array_add (priv->channel_requests, request); + return; + case TP_CHANNEL_FACTORY_REQUEST_STATUS_ERROR: + /* pass through error */ + goto OUT; + default: + /* always return the most specific error */ + if (cur_status > status) + status = cur_status; + } + } + + switch (status) + { + case TP_CHANNEL_FACTORY_REQUEST_STATUS_INVALID_HANDLE: + gabble_debug (DEBUG_FLAG, "invalid handle %u", handle); + + error = g_error_new (TELEPATHY_ERRORS, InvalidHandle, + "invalid handle %u", handle); + + break; + + case TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_AVAILABLE: + gabble_debug (DEBUG_FLAG, "requested channel is unavailable with " + "handle type %u", handle_type); + + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, + "requested channel is not available with " + "handle type %u", handle_type); + + break; + + case TP_CHANNEL_FACTORY_REQUEST_STATUS_NOT_IMPLEMENTED: + gabble_debug (DEBUG_FLAG, "unsupported channel type %s", type); + + error = g_error_new (TELEPATHY_ERRORS, NotImplemented, + "unsupported channel type %s", type); + + break; + + default: + g_assert_not_reached (); + } + +OUT: + if (NULL != error) + { + g_assert (NULL == object_path); + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + g_assert (NULL != object_path); + dbus_g_method_return (context, object_path); + g_free (object_path); +} + + +static void +hold_and_return_handles (DBusGMethodInvocation *context, + GabbleConnection *conn, + GArray *handles, + guint handle_type) +{ + GError *error = NULL; + gchar *sender = dbus_g_method_get_sender(context); + guint i; + + for (i = 0; i < handles->len; i++) + { + GabbleHandle handle = (GabbleHandle) g_array_index (handles, guint, i); + if (!gabble_handle_client_hold (conn->handles, sender, handle, handle_type, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + } + dbus_g_method_return (context, handles); +} + + +const char * +_gabble_connection_find_conference_server (GabbleConnection *conn) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (conn)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (conn); + + if (priv->conference_server == NULL) + { + /* Find first server that has NS_MUC feature */ + const GabbleDiscoItem *item = gabble_disco_service_find (conn->disco, + NULL, NULL, NS_MUC); + if (item != NULL) + priv->conference_server = item->jid; + } + + if (priv->conference_server == NULL) + priv->conference_server = priv->fallback_conference_server; + + return priv->conference_server; +} + + +static gchar * +_gabble_connection_get_canonical_room_name (GabbleConnection *conn, + const gchar *name) +{ + const gchar *server; + + g_assert (GABBLE_IS_CONNECTION (conn)); + + if (index (name, '@')) + return g_strdup (name); + + server = _gabble_connection_find_conference_server (conn); + + if (server == NULL) + return NULL; + + return g_strdup_printf ("%s@%s", name, server); +} + + +typedef struct _RoomVerifyContext RoomVerifyContext; + +typedef struct { + GabbleConnection *conn; + DBusGMethodInvocation *invocation; + gboolean errored; + guint count; + GArray *handles; + RoomVerifyContext *contexts; +} RoomVerifyBatch; + +struct _RoomVerifyContext { + gchar *jid; + guint index; + RoomVerifyBatch *batch; + GabbleDiscoRequest *request; +}; + +static void +room_verify_batch_free (RoomVerifyBatch *batch) +{ + guint i; + + g_array_free (batch->handles, TRUE); + for (i = 0; i < batch->count; i++) + { + g_free(batch->contexts[i].jid); + } + g_free (batch->contexts); + g_free (batch); +} + +/* Frees the error and the batch. */ +static void +room_verify_batch_raise_error (RoomVerifyBatch *batch, + GError *error) +{ + guint i; + + dbus_g_method_return_error (batch->invocation, error); + g_error_free (error); + batch->errored = TRUE; + for (i = 0; i < batch->count; i++) + { + if (batch->contexts[i].request) + { + gabble_disco_cancel_request(batch->conn->disco, + batch->contexts[i].request); + } + } + room_verify_batch_free (batch); +} + +static RoomVerifyBatch * +room_verify_batch_new (GabbleConnection *conn, + DBusGMethodInvocation *invocation, + guint count, + const gchar **jids) +{ + RoomVerifyBatch *batch = g_new(RoomVerifyBatch, 1); + guint i; + + batch->errored = FALSE; + batch->conn = conn; + batch->invocation = invocation; + batch->count = count; + batch->handles = g_array_sized_new(FALSE, FALSE, sizeof(GabbleHandle), count); + batch->contexts = g_new0(RoomVerifyContext, count); + for (i = 0; i < count; i++) + { + const gchar *name = jids[i]; + gchar *qualified_name; + GabbleHandle handle; + + batch->contexts[i].index = i; + batch->contexts[i].batch = batch; + + qualified_name = _gabble_connection_get_canonical_room_name (conn, name); + + if (!qualified_name) + { + GError *error = NULL; + gabble_debug (DEBUG_FLAG, "requested handle %s contains no conference server", + name); + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, "requested " + "room handle %s does not specify a server, but we have not discovered " + "any local conference servers and no fallback was provided", name); + room_verify_batch_raise_error (batch, error); + return NULL; + } + + batch->contexts[i].jid = qualified_name; + + /* has the handle been verified before? */ + if (gabble_handle_for_room_exists (conn->handles, qualified_name, FALSE)) + { + handle = gabble_handle_for_room (conn->handles, qualified_name); + } + else + { + handle = 0; + } + g_array_append_val (batch->handles, handle); + } + + return batch; +} + +/* If all handles in the array have been disco'd or got from cache, +free the batch and return TRUE. Else return FALSE. */ +static gboolean +room_verify_batch_try_return (RoomVerifyBatch *batch) +{ + guint i; + + for (i = 0; i < batch->count; i++) + { + if (!g_array_index(batch->handles, GabbleHandle, i)) + { + /* we're not ready yet */ + return FALSE; + } + } + + hold_and_return_handles (batch->invocation, batch->conn, batch->handles, TP_HANDLE_TYPE_ROOM); + room_verify_batch_free (batch); + return TRUE; +} + +static void +room_jid_disco_cb (GabbleDisco *disco, + GabbleDiscoRequest *request, + const gchar *jid, + const gchar *node, + LmMessageNode *query_result, + GError *error, + gpointer user_data) +{ + RoomVerifyContext *rvctx = user_data; + RoomVerifyBatch *batch = rvctx->batch; + LmMessageNode *lm_node; + gboolean found = FALSE; + GabbleHandle handle; + + /* stop the request getting cancelled after it's already finished */ + rvctx->request = NULL; + + /* if an error is being handled already, quietly go away */ + if (batch->errored) + { + return; + } + + if (error != NULL) + { + gabble_debug (DEBUG_FLAG, "disco reply error %s", error->message); + + /* disco will free the old error, _raise_error will free the new one */ + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, + "can't retrieve room info: %s", error->message); + + room_verify_batch_raise_error (batch, error); + + return; + } + + for (lm_node = query_result->children; lm_node; lm_node = lm_node->next) + { + const gchar *var; + + if (g_strdiff (lm_node->name, "feature")) + continue; + + var = lm_message_node_get_attribute (lm_node, "var"); + + /* for servers who consider schema compliance to be an optional bonus */ + if (var == NULL) + var = lm_message_node_get_attribute (lm_node, "type"); + + if (!g_strdiff (var, NS_MUC)) + { + found = TRUE; + break; + } + } + + if (!found) + { + gabble_debug (DEBUG_FLAG, "no MUC support for service name in jid %s", rvctx->jid); + + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, "specified server " + "doesn't support MUC"); + + room_verify_batch_raise_error (batch, error); + + return; + } + + handle = gabble_handle_for_room (batch->conn->handles, rvctx->jid); + g_assert (handle != 0); + + gabble_debug (DEBUG_FLAG, "disco reported MUC support for service name in jid %s", rvctx->jid); + g_array_index (batch->handles, GabbleHandle, rvctx->index) = handle; + + /* if this was the last callback to be run, send off the result */ + room_verify_batch_try_return (batch); +} + +/** + * room_jid_verify: + * + * Utility function that verifies that the service name of + * the specified jid exists and reports MUC support. + */ +static gboolean +room_jid_verify (RoomVerifyBatch *batch, + guint index, + DBusGMethodInvocation *context) +{ + gchar *room, *service; + gboolean ret; + GError *error = NULL; + + room = service = NULL; + gabble_decode_jid (batch->contexts[index].jid, &room, &service, NULL); + + g_assert (room && service); + + ret = (gabble_disco_request (batch->conn->disco, GABBLE_DISCO_TYPE_INFO, + service, NULL, room_jid_disco_cb, + batch->contexts + index, + G_OBJECT (batch->conn), &error) != NULL); + if (!ret) + { + room_verify_batch_raise_error (batch, error); + } + + g_free (room); + g_free (service); + + return ret; +} + + +/** + * gabble_connection_request_handles + * + * Implements D-Bus method RequestHandles + * on interface org.freedesktop.Telepathy.Connection + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_request_handles (GabbleConnection *self, + guint handle_type, + const gchar **names, + DBusGMethodInvocation *context) +{ + guint count = 0, i; + const gchar **cur_name; + GError *error = NULL; + GArray *handles = NULL; + RoomVerifyBatch *batch = NULL; + + for (cur_name = names; *cur_name != NULL; cur_name++) + { + count++; + } + + g_assert (GABBLE_IS_CONNECTION (self)); + + ERROR_IF_NOT_CONNECTED_ASYNC (self, error, context) + + if (!gabble_handle_type_is_valid (handle_type, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + + switch (handle_type) + { + case TP_HANDLE_TYPE_CONTACT: + handles = g_array_sized_new(FALSE, FALSE, sizeof(GabbleHandle), count); + + for (i = 0; i < count; i++) + { + GabbleHandle handle; + const gchar *name = names[i]; + + if (!gabble_handle_jid_is_valid (handle_type, name, &error)) + { + dbus_g_method_return_error (context, error); + g_error_free (error); + + g_array_free (handles, TRUE); + return; + } + + handle = gabble_handle_for_contact (self->handles, name, FALSE); + + if (handle == 0) + { + gabble_debug (DEBUG_FLAG, "requested handle %s was invalid", name); + + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, + "requested handle %s was invalid", name); + dbus_g_method_return_error (context, error); + g_error_free (error); + + g_array_free (handles, TRUE); + return; + } + + g_array_append_val(handles, handle); + } + hold_and_return_handles (context, self, handles, handle_type); + g_array_free(handles, TRUE); + break; + + case TP_HANDLE_TYPE_ROOM: + batch = room_verify_batch_new (self, context, count, names); + if (!batch) + { + /* an error occurred while setting up the batch, and we returned error + to dbus */ + return; + } + + /* have all the handles been verified already? If so, nothing to do */ + if (room_verify_batch_try_return (batch)) + { + return; + } + + for (i = 0; i < count; i++) + { + if (!room_jid_verify (batch, i, context)) + { + return; + } + } + + /* we've set the verification process going - the callback will handle + returning or raising error */ + break; + + case TP_HANDLE_TYPE_LIST: + handles = g_array_sized_new(FALSE, FALSE, sizeof(GabbleHandle), count); + + for (i = 0; i < count; i++) + { + GabbleHandle handle; + const gchar *name = names[i]; + + handle = gabble_handle_for_list (self->handles, name); + + if (handle == 0) + { + gabble_debug (DEBUG_FLAG, "requested list channel %s not available", name); + + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, + "requested list channel %s not available", + name); + dbus_g_method_return_error (context, error); + g_error_free (error); + + g_array_free (handles, TRUE); + return; + } + g_array_append_val(handles, handle); + } + hold_and_return_handles (context, self, handles, handle_type); + g_array_free(handles, TRUE); + break; + + default: + gabble_debug (DEBUG_FLAG, "unimplemented handle type %u", handle_type); + + error = g_error_new (TELEPATHY_ERRORS, NotAvailable, + "unimplemented handle type %u", handle_type); + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + + +/** + * gabble_connection_request_presence + * + * Implements D-Bus method RequestPresence + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_request_presence (GabbleConnection *self, + const GArray *contacts, + GError **error) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + if (!gabble_handles_are_valid (self->handles, TP_HANDLE_TYPE_CONTACT, + contacts, FALSE, error)) + return FALSE; + + if (contacts->len) + emit_presence_update (self, contacts); + + return TRUE; +} + + +struct _i_hate_g_hash_table_foreach +{ + GabbleConnection *conn; + GError **error; + gboolean retval; +}; + +static void +setaliases_foreach (gpointer key, gpointer value, gpointer user_data) +{ + struct _i_hate_g_hash_table_foreach *data = + (struct _i_hate_g_hash_table_foreach *) user_data; + GabbleHandle handle = GPOINTER_TO_INT (key); + gchar *alias = (gchar *) value; + GError *error = NULL; + + if (!gabble_handle_is_valid (data->conn->handles, TP_HANDLE_TYPE_CONTACT, + handle, &error)) + { + data->retval = FALSE; + } + else if (data->conn->self_handle == handle) + { + /* only alter the roster if we're already there, e.g. because someone + * added us with another client + */ + if (gabble_roster_handle_has_entry (data->conn->roster, handle) + && !gabble_roster_handle_set_name (data->conn->roster, handle, + alias, data->error)) + { + data->retval = FALSE; + } + } + else if (!gabble_roster_handle_set_name (data->conn->roster, handle, alias, + data->error)) + { + data->retval = FALSE; + } + + if (data->conn->self_handle == handle) + { + /* User has done SetAliases on themselves - patch their vCard. + * FIXME: because SetAliases is currently synchronous, we ignore errors + * here, and just let the request happen in the background + */ + gabble_vcard_manager_edit (data->conn->vcard_manager, + 0, NULL, NULL, G_OBJECT(data->conn), NULL, + "NICKNAME", alias, NULL); + } + + if (NULL != error) + { + if (NULL == *(data->error)) + { + *(data->error) = error; + } + else + { + g_error_free (error); + } + } +} + +/** + * gabble_connection_set_aliases + * + * Implements D-Bus method SetAliases + * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_set_aliases (GabbleConnection *self, + GHashTable *aliases, + GError **error) +{ + GabbleConnectionPrivate *priv; + struct _i_hate_g_hash_table_foreach data = { NULL, NULL, TRUE }; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + data.conn = self; + data.error = error; + + g_hash_table_foreach (aliases, setaliases_foreach, &data); + + return data.retval; +} + + +/** + * gabble_connection_set_last_activity_time + * + * Implements D-Bus method SetLastActivityTime + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns false. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_set_last_activity_time (GabbleConnection *self, + guint time, + GError **error) +{ + GabbleConnectionPrivate *priv; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + return TRUE; +} + + +static void +setstatuses_foreach (gpointer key, gpointer value, gpointer user_data) +{ + struct _i_hate_g_hash_table_foreach *data = + (struct _i_hate_g_hash_table_foreach*) user_data; + GabbleConnectionPrivate *priv = GABBLE_CONNECTION_GET_PRIVATE (data->conn); + + int i; + + for (i = 0; i < LAST_GABBLE_PRESENCE; i++) + { + if (0 == strcmp (gabble_statuses[i].name, (const gchar*) key)) + break; + } + + if (i < LAST_GABBLE_PRESENCE) + { + GHashTable *args = (GHashTable *)value; + GValue *message = g_hash_table_lookup (args, "message"); + GValue *priority = g_hash_table_lookup (args, "priority"); + const gchar *status = NULL; + gint8 prio = priv->priority; + + if (!status_is_available (data->conn, i)) + { + gabble_debug (DEBUG_FLAG, "requested status %s is not available", (const gchar *) key); + g_set_error (data->error, TELEPATHY_ERRORS, NotAvailable, + "requested status '%s' is not available on this connection", + (const gchar *) key); + data->retval = FALSE; + return; + } + + if (message) + { + if (!G_VALUE_HOLDS_STRING (message)) + { + gabble_debug (DEBUG_FLAG, "got a status message which was not a string"); + g_set_error (data->error, TELEPATHY_ERRORS, InvalidArgument, + "Status argument 'message' requires a string"); + data->retval = FALSE; + return; + } + status = g_value_get_string (message); + } + + if (priority) + { + if (!G_VALUE_HOLDS_INT (priority)) + { + gabble_debug (DEBUG_FLAG, "got a priority value which was not a signed integer"); + g_set_error (data->error, TELEPATHY_ERRORS, InvalidArgument, + "Status argument 'priority' requires a signed integer"); + data->retval = FALSE; + return; + } + prio = CLAMP (g_value_get_int (priority), G_MININT8, G_MAXINT8); + } + + gabble_presence_cache_update (data->conn->presence_cache, data->conn->self_handle, priv->resource, i, status, prio); + emit_one_presence_update (data->conn, data->conn->self_handle); + data->retval = signal_own_presence (data->conn, data->error); + } + else + { + gabble_debug (DEBUG_FLAG, "got unknown status identifier %s", (const gchar *) key); + g_set_error (data->error, TELEPATHY_ERRORS, InvalidArgument, + "unknown status identifier: %s", (const gchar *) key); + data->retval = FALSE; + } +} + +/** + * gabble_connection_set_properties + * + * Implements D-Bus method SetProperties + * on interface org.freedesktop.Telepathy.Properties + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ +void +gabble_connection_set_properties (GabbleConnection *self, + const GPtrArray *properties, + DBusGMethodInvocation *context) +{ + gabble_properties_mixin_set_properties (G_OBJECT (self), properties, context); +} + +/** + * gabble_connection_set_status + * + * Implements D-Bus method SetStatus + * on interface org.freedesktop.Telepathy.Connection.Interface.Presence + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_set_status (GabbleConnection *self, + GHashTable *statuses, + GError **error) +{ + GabbleConnectionPrivate *priv; + struct _i_hate_g_hash_table_foreach data = { NULL, NULL, TRUE }; + + g_assert (GABBLE_IS_CONNECTION (self)); + + priv = GABBLE_CONNECTION_GET_PRIVATE (self); + + ERROR_IF_NOT_CONNECTED (self, error) + + if (g_hash_table_size (statuses) != 1) + { + gabble_debug (DEBUG_FLAG, "got more than one status"); + g_set_error (error, TELEPATHY_ERRORS, InvalidArgument, + "Only one status may be set at a time in this protocol"); + return FALSE; + } + + data.conn = self; + data.error = error; + g_hash_table_foreach (statuses, setstatuses_foreach, &data); + + return data.retval; +} + +/** + * gabble_connection_build_avatar + * + * Implements D-Bus method SetAvatar + * on interface org.freedesktop.Telepathy.Connection.Avatar + * + * @context: The D-Bus invocation context to use to return values + * or throw an error. + */ + +LmMessage* gabble_connection_build_avatar(GabbleConnection *self, + const GArray* bin_image, gchar* mime ) + { + int i = 0; + LmMessage *message; + LmMessageNode *lm_node,*photo_node; + LmMessageHandler* handler = NULL; + char* base64 = NULL; + GError* error = NULL; + //NULL values to be checked ? never returns NULL? + if ( bin_image ) + { + if ( self->self_avatar_sha1 ) + { + free( self->self_avatar_sha1 ); + self->self_avatar_sha1 = NULL; + } + self->self_avatar_sha1 = sha1_hex( bin_image->data, bin_image->len ); + base64 = base64_encode ( bin_image->len, bin_image->data ); + } + + message = lm_message_new_with_sub_type ( NULL, + LM_MESSAGE_TYPE_IQ, + LM_MESSAGE_SUB_TYPE_SET ); + lm_node = lm_message_node_add_child (message->node, "vCard", NULL); + lm_message_node_set_attribute (lm_node, "xmlns", NS_VCARD_TEMP); + photo_node = lm_message_node_add_child (lm_node, "PHOTO", NULL); + lm_message_node_add_child (photo_node, "TYPE", mime); + lm_message_node_add_child (photo_node, "BINVAL", base64); + g_free(base64); + return message; + } + +/** + * gabble_connection_set_avatar + * + * Implements D-Bus method SetAvatar + * on interface org.freedesktop.Telepathy.Connection + * + */ +gboolean +gabble_connection_set_avatar( GabbleConnection *self, const GArray* bin_image, gchar* mime_type, gchar**avatar_sha1, + GError** err ) + { + + gboolean ret = FALSE; + LmMessage *message; + + + //There is no need to check bin_image and mime for NULL.. NULL is a valid value for those + message = gabble_connection_build_avatar( self, bin_image, mime_type ); + + ret = _gabble_connection_send ( self, message, err ); + + if ( *err ) + { + *avatar_sha1 = NULL; + //ret is already false.. so false returned + } + else { + if ( self->self_avatar_sha1 ) + { + *avatar_sha1 = g_strdup( self->self_avatar_sha1 ); + } + ret = TRUE; + } + + lm_message_unref( message ); + + return ret; + } + +/** + * gabble_connection_clear_avatar + * + * Implements D-Bus method ClearAvatar + * on interface org.freedesktop.Telepathy.Connection.Interface.Avatar + * + * @error: Used to return a pointer to a GError detailing any error + * that occurred, D-Bus will throw the error only if this + * function returns FALSE. + * + * Returns: TRUE if successful, FALSE if an error was thrown. + */ +gboolean +gabble_connection_clear_avatar( GabbleConnection *self, + GError **error) + { + LmMessage *message; + gboolean ret = TRUE; + + + //Build the avatar xml with NULL as image data and NULL as mimetype + message = gabble_connection_build_avatar( self, + NULL, NULL ); + + ret = _gabble_connection_send ( self, message, error ); + + if ( self->self_avatar_sha1 ) + { + free( self->self_avatar_sha1 ); + self->self_avatar_sha1 = NULL; + } + + lm_message_unref( message ); + + return ret; + }