summaryrefslogtreecommitdiff
path: root/src/connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/connection.c')
-rw-r--r--src/connection.c4111
1 files changed, 4111 insertions, 0 deletions
diff --git a/src/connection.c b/src/connection.c
new file mode 100644
index 00000000..75e10c95
--- /dev/null
+++ b/src/connection.c
@@ -0,0 +1,4111 @@
+/*
+ * connection.c - Source for SalutConnection
+ * Copyright (C) 2005 Collabora Ltd.
+ *
+ * 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 "connection.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ctype.h>
+
+#include <dbus/dbus-protocol.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/handle-repo.h>
+#include <telepathy-glib/handle-repo-static.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/util.h>
+
+#include <gibber/gibber-namespaces.h>
+
+#include <salut/caps-channel-manager.h>
+
+#include <wocky/wocky-meta-porter.h>
+#include <wocky/wocky-data-form.h>
+#include <wocky/wocky-xep-0115-capabilities.h>
+
+#include "capabilities.h"
+#include "avahi-discovery-client.h"
+#include "capabilities.h"
+#include "caps-hash.h"
+#include "contact-channel.h"
+#include "contact.h"
+#include "contact-manager.h"
+#include "disco.h"
+#include "discovery-client.h"
+#include "im-manager.h"
+#include "muc-manager.h"
+#include "ft-manager.h"
+#include "contact.h"
+#include "roomlist-manager.h"
+#include "presence.h"
+#include "presence-cache.h"
+#include "self.h"
+#include "si-bytestream-manager.h"
+#include "tubes-manager.h"
+#include "util.h"
+
+#include "plugin-loader.h"
+
+#ifdef ENABLE_OLPC
+#include "olpc-activity-manager.h"
+#endif
+
+#include <extensions/extensions.h>
+
+#define DEBUG_FLAG DEBUG_CONNECTION
+#include "debug.h"
+
+#define SALUT_TP_ALIAS_PAIR_TYPE (dbus_g_type_get_struct ("GValueArray", \
+ G_TYPE_UINT, G_TYPE_STRING, G_TYPE_INVALID))
+
+#ifdef ENABLE_OLPC
+
+#define ACTIVITY_PAIR_TYPE \
+ (dbus_g_type_get_struct ("GValueArray", G_TYPE_STRING, G_TYPE_UINT, \
+ G_TYPE_INVALID))
+
+static void
+salut_connection_olpc_buddy_info_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+salut_connection_olpc_activity_properties_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+#endif
+
+static void
+salut_connection_aliasing_service_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+salut_connection_avatar_service_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+salut_conn_contact_caps_iface_init (gpointer, gpointer);
+
+static void salut_conn_future_iface_init (gpointer, gpointer);
+
+#define DISCONNECT_TIMEOUT 5
+
+G_DEFINE_TYPE_WITH_CODE(SalutConnection,
+ salut_connection,
+ TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+ salut_connection_aliasing_service_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+ tp_presence_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ tp_presence_mixin_simple_presence_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS,
+ salut_connection_avatar_service_iface_init);
+ G_IMPLEMENT_INTERFACE
+ (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ salut_conn_contact_caps_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_CONNECTION_FUTURE,
+ salut_conn_future_iface_init);
+#ifdef ENABLE_OLPC
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_OLPC_BUDDY_INFO,
+ salut_connection_olpc_buddy_info_iface_init);
+ G_IMPLEMENT_INTERFACE (SALUT_TYPE_SVC_OLPC_ACTIVITY_PROPERTIES,
+ salut_connection_olpc_activity_properties_iface_init);
+#endif
+ )
+
+#ifdef ENABLE_OLPC
+static gboolean uninvite_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza, gpointer user_data);
+#endif
+
+/* properties */
+enum {
+ PROP_NICKNAME = 1,
+ PROP_FIRST_NAME,
+ PROP_LAST_NAME,
+ PROP_JID,
+ PROP_EMAIL,
+ PROP_PUBLISHED_NAME,
+ PROP_IM_MANAGER,
+ PROP_MUC_MANAGER,
+ PROP_TUBES_MANAGER,
+ PROP_ROOMLIST_MANAGER,
+ PROP_CONTACT_MANAGER,
+ PROP_SELF,
+ PROP_XCM,
+ PROP_SI_BYTESTREAM_MANAGER,
+#ifdef ENABLE_OLPC
+ PROP_OLPC_ACTIVITY_MANAGER,
+#endif
+ PROP_BACKEND,
+ PROP_DNSSD_NAME,
+ LAST_PROP
+};
+
+struct _SalutConnectionPrivate
+{
+ gboolean dispose_has_run;
+
+ /* Connection information */
+ gchar *published_name;
+ gchar *nickname;
+ gchar *first_name;
+ gchar *last_name;
+ gchar *jid;
+ gchar *email;
+#ifdef ENABLE_OLPC
+ gchar *olpc_color;
+ GArray *olpc_key;
+#endif
+
+ /* Discovery client for browsing and resolving */
+ SalutDiscoveryClient *discovery_client;
+
+ /* TpHandler for our presence on the lan */
+ SalutSelf *self;
+ gboolean self_established;
+ SalutPresenceId pre_connect_presence;
+ gchar *pre_connect_message;
+ GabbleCapabilitySet *pre_connect_caps;
+ GPtrArray *pre_connect_data_forms;
+
+ /* Contact manager */
+ SalutContactManager *contact_manager;
+
+ /* IM channel manager */
+ SalutImManager *im_manager;
+
+ /* MUC channel manager */
+ SalutMucManager *muc_manager;
+
+ /* FT channel manager */
+ SalutFtManager *ft_manager;
+
+ /* Roomlist channel manager */
+ SalutRoomlistManager *roomlist_manager;
+
+ /* Tubes channel manager */
+ SalutTubesManager *tubes_manager;
+
+ /* Bytestream manager for stream initiation (XEP-0095) */
+ SalutSiBytestreamManager *si_bytestream_manager;
+
+ /* Sidecars */
+ /* gchar *interface → SalutSidecar */
+ GHashTable *sidecars;
+
+ /* gchar *interface → GList<DBusGMethodInvocation> */
+ GHashTable *pending_sidecars;
+
+#ifdef ENABLE_OLPC
+ SalutOlpcActivityManager *olpc_activity_manager;
+ guint uninvite_handler_id;
+#endif
+
+ /* timer used when trying to properly disconnect */
+ guint disconnect_timer;
+
+ /* Backend type: avahi or dummy */
+ GType backend_type;
+
+ /* DNS-SD name, used for the avahi backend */
+ gchar *dnssd_name;
+};
+
+typedef struct _ChannelRequest ChannelRequest;
+
+struct _ChannelRequest
+{
+ DBusGMethodInvocation *context;
+ gchar *channel_type;
+ guint handle_type;
+ guint handle;
+ gboolean suppress_handler;
+};
+
+static void _salut_connection_disconnect (SalutConnection *self);
+
+static void
+salut_connection_create_handle_repos (TpBaseConnection *self,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]);
+
+static GPtrArray *
+salut_connection_create_channel_factories (TpBaseConnection *self);
+
+static GPtrArray *
+salut_connection_create_channel_managers (TpBaseConnection *self);
+
+static gchar *
+salut_connection_get_unique_connection_name (TpBaseConnection *self);
+
+static void
+salut_connection_shut_down (TpBaseConnection *self);
+
+static gboolean
+salut_connection_start_connecting (TpBaseConnection *self, GError **error);
+
+static void salut_connection_avatars_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash);
+
+static void salut_connection_aliasing_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash);
+
+static void conn_contact_capabilities_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash);
+
+static void connection_capabilities_update_cb (SalutPresenceCache *cache,
+ TpHandle handle, gpointer user_data);
+
+static void
+conn_avatars_properties_getter (GObject *object, GQuark interface, GQuark name,
+ GValue *value, gpointer getter_data);
+
+static const char *mimetypes[] = {
+ "image/png", "image/jpeg", NULL };
+
+#define AVATAR_MIN_PX 0
+#define AVATAR_REC_PX 64
+#define AVATAR_MAX_PX 0
+#define AVATAR_MAX_BYTES G_MAXUINT16
+
+static TpDBusPropertiesMixinPropImpl conn_avatars_properties[] = {
+ { "MinimumAvatarWidth", GUINT_TO_POINTER (AVATAR_MIN_PX), NULL },
+ { "RecommendedAvatarWidth", GUINT_TO_POINTER (AVATAR_REC_PX), NULL },
+ { "MaximumAvatarWidth", GUINT_TO_POINTER (AVATAR_MAX_PX), NULL },
+ { "MinimumAvatarHeight", GUINT_TO_POINTER (AVATAR_MIN_PX), NULL },
+ { "RecommendedAvatarHeight", GUINT_TO_POINTER (AVATAR_REC_PX), NULL },
+ { "MaximumAvatarHeight", GUINT_TO_POINTER (AVATAR_MAX_PX), NULL },
+ { "MaximumAvatarBytes", GUINT_TO_POINTER (AVATAR_MAX_BYTES), NULL },
+ /* special-cased - it's the only one with a non-guint value */
+ { "SupportedAvatarMIMETypes", NULL, NULL },
+ { NULL }
+};
+
+static void
+salut_connection_init (SalutConnection *obj)
+{
+ SalutConnectionPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE(obj, SALUT_TYPE_CONNECTION,
+ SalutConnectionPrivate);
+
+ obj->priv = priv;
+ obj->name = NULL;
+
+ gabble_capabilities_init (obj);
+
+ tp_presence_mixin_init ((GObject *) obj,
+ G_STRUCT_OFFSET (SalutConnection, presence_mixin));
+
+ /* create this now so channel managers can use it when created from
+ * parent->constructor */
+ obj->session = wocky_session_new_ll (NULL);
+ obj->porter = wocky_session_get_porter (obj->session);
+
+ /* allocate any data required by the object here */
+ priv->published_name = g_strdup (g_get_user_name ());
+ priv->nickname = NULL;
+ priv->first_name = NULL;
+ priv->last_name = NULL;
+ priv->jid = NULL;
+ priv->email = NULL;
+#ifdef ENABLE_OLPC
+ priv->olpc_color = NULL;
+ priv->olpc_key = NULL;
+#endif
+
+ priv->discovery_client = NULL;
+ priv->self = NULL;
+ priv->self_established = FALSE;
+
+ priv->pre_connect_presence = SALUT_PRESENCE_AVAILABLE;
+ priv->pre_connect_message = NULL;
+ priv->pre_connect_caps = NULL;
+
+ priv->contact_manager = NULL;
+}
+
+static void
+sidecars_conn_status_changed_cb (SalutConnection *conn,
+ guint status, guint reason, gpointer unused);
+
+static GObject *
+salut_connection_constructor (GType type,
+ guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ SalutConnection *self;
+ SalutConnectionPrivate *priv;
+
+ obj = G_OBJECT_CLASS (salut_connection_parent_class)->
+ constructor (type, n_props, props);
+ self = SALUT_CONNECTION (obj);
+ priv = self->priv;
+
+ self->disco = salut_disco_new (self);
+ self->presence_cache = salut_presence_cache_new (self);
+ g_signal_connect (self->presence_cache, "capabilities-update", G_CALLBACK
+ (connection_capabilities_update_cb), self);
+
+ tp_contacts_mixin_init (obj,
+ G_STRUCT_OFFSET (SalutConnection, contacts_mixin));
+
+ tp_base_connection_register_with_contacts_mixin (TP_BASE_CONNECTION (obj));
+ tp_presence_mixin_simple_presence_register_with_contacts_mixin (obj);
+
+ tp_contacts_mixin_add_contact_attributes_iface (obj,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ salut_connection_avatars_fill_contact_attributes);
+
+ tp_contacts_mixin_add_contact_attributes_iface (obj,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ salut_connection_aliasing_fill_contact_attributes);
+
+ tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (self),
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ conn_contact_capabilities_fill_contact_attributes);
+
+ priv->sidecars = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ priv->pending_sidecars = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_list_free);
+
+ g_signal_connect (self, "status-changed",
+ (GCallback) sidecars_conn_status_changed_cb, NULL);
+
+ return obj;
+}
+
+static void
+salut_connection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutConnection *self = SALUT_CONNECTION(object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_NICKNAME:
+ g_value_set_string (value, priv->nickname);
+ break;
+ case PROP_FIRST_NAME:
+ g_value_set_string (value, priv->first_name);
+ break;
+ case PROP_LAST_NAME:
+ g_value_set_string (value, priv->last_name);
+ break;
+ case PROP_JID:
+ g_value_set_string (value, priv->jid);
+ break;
+ case PROP_EMAIL:
+ g_value_set_string (value, priv->email);
+ break;
+ case PROP_PUBLISHED_NAME:
+ g_value_set_string (value, priv->published_name);
+ break;
+ case PROP_IM_MANAGER:
+ g_value_set_object (value, priv->im_manager);
+ break;
+ case PROP_MUC_MANAGER:
+ g_value_set_object (value, priv->muc_manager);
+ break;
+ case PROP_TUBES_MANAGER:
+ g_value_set_object (value, priv->tubes_manager);
+ break;
+ case PROP_ROOMLIST_MANAGER:
+ g_value_set_object (value, priv->roomlist_manager);
+ break;
+ case PROP_CONTACT_MANAGER:
+ g_value_set_object (value, priv->contact_manager);
+ break;
+ case PROP_SELF:
+ g_value_set_object (value, priv->self);
+ break;
+ case PROP_SI_BYTESTREAM_MANAGER:
+ g_value_set_object (value, priv->si_bytestream_manager);
+ break;
+#ifdef ENABLE_OLPC
+ case PROP_OLPC_ACTIVITY_MANAGER:
+ g_value_set_object (value, priv->olpc_activity_manager);
+ break;
+#endif
+ case PROP_BACKEND:
+ g_value_set_gtype (value, priv->backend_type);
+ break;
+ case PROP_DNSSD_NAME:
+ g_value_set_string (value, priv->dnssd_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+salut_connection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutConnection *self = SALUT_CONNECTION(object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_NICKNAME:
+ g_free (priv->nickname);
+ priv->nickname = g_value_dup_string (value);
+ break;
+ case PROP_FIRST_NAME:
+ g_free (priv->first_name);
+ priv->first_name = g_value_dup_string (value);
+ break;
+ case PROP_LAST_NAME:
+ g_free (priv->last_name);
+ priv->last_name = g_value_dup_string (value);
+ break;
+ case PROP_JID:
+ g_free (priv->jid);
+ priv->jid = g_value_dup_string (value);
+ break;
+ case PROP_EMAIL:
+ g_free (priv->email);
+ priv->email = g_value_dup_string (value);
+ break;
+ case PROP_PUBLISHED_NAME:
+ g_free (priv->published_name);
+ priv->published_name = g_value_dup_string (value);
+ break;
+ case PROP_BACKEND:
+ priv->backend_type = g_value_get_gtype (value);
+ /* Create the backend object */
+ priv->discovery_client = g_object_new (priv->backend_type,
+ "dnssd-name", priv->dnssd_name,
+ NULL);
+ g_assert (priv->discovery_client != NULL);
+ break;
+ case PROP_DNSSD_NAME:
+ priv->dnssd_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void salut_connection_dispose (GObject *object);
+static void salut_connection_finalize (GObject *object);
+
+/* presence bits and pieces */
+
+static const TpPresenceStatusOptionalArgumentSpec presence_args[] = {
+ { "message", "s" },
+ { NULL }
+};
+
+/* keep these in the same order as SalutPresenceId... */
+static const TpPresenceStatusSpec presence_statuses[] = {
+ { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE,
+ presence_args },
+ { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, presence_args },
+ { "dnd", TP_CONNECTION_PRESENCE_TYPE_BUSY, TRUE, presence_args },
+ { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+ { NULL }
+};
+/* ... and these too (declared in presence.h) */
+const char *salut_presence_status_txt_names[] = {
+ "avail",
+ "away",
+ "dnd",
+ "offline",
+ NULL
+};
+
+static gboolean
+is_presence_status_available (GObject *obj,
+ guint index_)
+{
+ return (index_ < SALUT_PRESENCE_OFFLINE);
+}
+
+static GHashTable *
+make_presence_opt_args (SalutPresenceId presence, const gchar *message)
+{
+ GHashTable *ret;
+ GValue *value;
+
+ /* Omit missing or empty messages from the hash table.
+ * Also, offline has no message in Salut, it wouldn't make sense. */
+ if (presence == SALUT_PRESENCE_OFFLINE || message == NULL ||
+ *message == '\0')
+ {
+ return NULL;
+ }
+
+ ret = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tp_g_value_slice_free);
+
+ value = g_slice_new0 (GValue);
+ g_value_init (value, G_TYPE_STRING);
+ g_value_set_string (value, message);
+ g_hash_table_insert (ret, "message", value);
+
+ return ret;
+}
+
+static GHashTable *
+get_contact_statuses (GObject *obj,
+ const GArray *handles,
+ GError **error)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ GHashTable *ret;
+ guint i;
+
+ if (!tp_handles_are_valid (handle_repo, handles, FALSE, error))
+ {
+ return NULL;
+ }
+
+ ret = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, (GDestroyNotify) tp_presence_status_free);
+
+ for (i = 0; i < handles->len; i++)
+ {
+ TpHandle handle = g_array_index (handles, TpHandle, i);
+ TpPresenceStatus *ps = tp_presence_status_new
+ (SALUT_PRESENCE_OFFLINE, NULL);
+ const gchar *message = NULL;
+
+ if (handle == base->self_handle)
+ {
+ ps->index = priv->self->status;
+ message = priv->self->status_message;
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact
+ (priv->contact_manager, handle);
+
+ if (contact != NULL)
+ {
+ ps->index = contact->status;
+ message = contact->status_message;
+ g_object_unref (contact);
+ }
+ }
+
+ ps->optional_arguments = make_presence_opt_args (ps->index, message);
+
+ g_hash_table_insert (ret, GUINT_TO_POINTER (handle), ps);
+ }
+
+ return ret;
+}
+
+static void
+set_self_presence (SalutConnection *self,
+ SalutPresenceId presence,
+ const gchar *message,
+ GError **error)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+
+ if (priv->self == NULL || !priv->self_established)
+ {
+ g_free (priv->pre_connect_message);
+ priv->pre_connect_presence = presence;
+ priv->pre_connect_message = g_strdup (message);
+ return;
+ }
+
+ if (salut_self_set_presence (priv->self, presence, message, error))
+ {
+ TpPresenceStatus ps = { priv->self->status,
+ make_presence_opt_args (priv->self->status,
+ priv->self->status_message) };
+
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self,
+ base->self_handle, &ps);
+
+ if (ps.optional_arguments != NULL)
+ g_hash_table_destroy (ps.optional_arguments);
+ }
+}
+
+static gboolean
+set_own_status (GObject *obj,
+ const TpPresenceStatus *status,
+ GError **error)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ GError *err = NULL;
+ const GValue *value;
+ const gchar *message = NULL;
+ SalutPresenceId presence = SALUT_PRESENCE_AVAILABLE;
+
+ if (status != NULL)
+ {
+ /* mixin has already validated the index */
+ presence = status->index;
+
+ if (status->optional_arguments != NULL)
+ {
+ value = g_hash_table_lookup (status->optional_arguments, "message");
+ if (value)
+ {
+ /* TpPresenceMixin should validate this for us, but doesn't */
+ if (!G_VALUE_HOLDS_STRING (value))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Status argument 'message' requires a string");
+ return FALSE;
+ }
+ message = g_value_get_string (value);
+ }
+ }
+ }
+
+ set_self_presence (self, presence, message, &err);
+
+ if (err != NULL)
+ {
+ *error = g_error_new_literal (TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ err->message);
+ }
+
+ return TRUE;
+}
+
+static const gchar *interfaces [] = {
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+ TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+ TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+ SALUT_IFACE_CONNECTION_FUTURE,
+#ifdef ENABLE_OLPC
+ SALUT_IFACE_OLPC_BUDDY_INFO,
+ SALUT_IFACE_OLPC_ACTIVITY_PROPERTIES,
+#endif
+ NULL };
+
+const gchar * const *
+salut_connection_get_implemented_interfaces (void)
+{
+ return interfaces;
+}
+
+static void
+salut_connection_class_init (SalutConnectionClass *salut_connection_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_connection_class);
+ TpBaseConnectionClass *tp_connection_class =
+ TP_BASE_CONNECTION_CLASS(salut_connection_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ conn_avatars_properties_getter,
+ NULL,
+ conn_avatars_properties,
+ },
+ { NULL }
+ };
+
+ object_class->get_property = salut_connection_get_property;
+ object_class->set_property = salut_connection_set_property;
+
+ g_type_class_add_private (salut_connection_class,
+ sizeof (SalutConnectionPrivate));
+
+ object_class->constructor = salut_connection_constructor;
+
+ object_class->dispose = salut_connection_dispose;
+ object_class->finalize = salut_connection_finalize;
+
+ tp_connection_class->create_handle_repos =
+ salut_connection_create_handle_repos;
+ tp_connection_class->create_channel_factories =
+ salut_connection_create_channel_factories;
+ tp_connection_class->create_channel_managers =
+ salut_connection_create_channel_managers;
+ tp_connection_class->get_unique_connection_name =
+ salut_connection_get_unique_connection_name;
+ tp_connection_class->shut_down =
+ salut_connection_shut_down;
+ tp_connection_class->start_connecting =
+ salut_connection_start_connecting;
+ tp_connection_class->interfaces_always_present = interfaces;
+
+ salut_connection_class->properties_mixin.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutConnectionClass, properties_mixin));
+
+ tp_presence_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutConnectionClass, presence_mixin),
+ is_presence_status_available, get_contact_statuses, set_own_status,
+ presence_statuses);
+
+ tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutConnectionClass, contacts_mixin));
+
+ param_spec = g_param_spec_string ("nickname", "nickname",
+ "Nickname used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_NICKNAME, param_spec);
+
+ param_spec = g_param_spec_string ("first-name", "First name",
+ "First name used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_FIRST_NAME, param_spec);
+
+ param_spec = g_param_spec_string ("last-name", "Last name",
+ "Last name used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_LAST_NAME, param_spec);
+
+ param_spec = g_param_spec_string ("email", "E-mail address",
+ "E-mail address used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_EMAIL, param_spec);
+
+ param_spec = g_param_spec_string ("jid", "Jabber id",
+ "Jabber id used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_JID, param_spec);
+
+ param_spec = g_param_spec_string ("published-name", "Published name",
+ "Username used in the published data", NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_PUBLISHED_NAME,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "im-manager",
+ "SalutImManager object",
+ "The Salut IM Manager associated with this Salut Connection",
+ SALUT_TYPE_IM_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_IM_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "muc-manager",
+ "SalutMucManager object",
+ "The Salut MUC Manager associated with this Salut Connection",
+ SALUT_TYPE_MUC_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUC_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "tubes-manager",
+ "SalutTubesManager object",
+ "The Salut Tubes Manager associated with this Salut Connection",
+ SALUT_TYPE_TUBES_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TUBES_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "roomlist-manager",
+ "SalutRoomlistManager object",
+ "The Salut Roomlist Manager associated with this Salut Connection",
+ SALUT_TYPE_ROOMLIST_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_ROOMLIST_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "contact-manager",
+ "SalutContactManager object",
+ "The Salut Contact Manager associated with this Salut Connection",
+ SALUT_TYPE_CONTACT_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CONTACT_MANAGER,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "self",
+ "SalutSelf object",
+ "The Salut Self object associated with this Salut Connection",
+ SALUT_TYPE_SELF,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SELF,
+ param_spec);
+
+ param_spec = g_param_spec_object (
+ "si-bytestream-manager",
+ "SalutSiBytestreamManager object",
+ "The Salut SI Bytestream Manager associated with this Salut Connection",
+ SALUT_TYPE_SI_BYTESTREAM_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SI_BYTESTREAM_MANAGER,
+ param_spec);
+
+#ifdef ENABLE_OLPC
+ param_spec = g_param_spec_object (
+ "olpc-activity-manager",
+ "SalutOlpcActivityManager object",
+ "The OLPC activity Manager associated with this Salut Connection",
+ SALUT_TYPE_OLPC_ACTIVITY_MANAGER,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_OLPC_ACTIVITY_MANAGER,
+ param_spec);
+#endif
+
+ param_spec = g_param_spec_gtype (
+ "backend-type",
+ "backend type",
+ "a G_TYPE_GTYPE of the backend to use",
+ G_TYPE_NONE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_BACKEND,
+ param_spec);
+
+ param_spec = g_param_spec_string ("dnssd-name", "DNS-SD name",
+ "The DNS-SD name of the protocol", "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_DNSSD_NAME,
+ param_spec);
+}
+
+void
+salut_connection_dispose (GObject *object)
+{
+ SalutConnection *self = SALUT_CONNECTION (object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (self->disco != NULL)
+ {
+ g_object_unref (self->disco);
+ self->disco = NULL;
+ }
+
+ if (self->presence_cache)
+ {
+ g_object_unref (self->presence_cache);
+ self->presence_cache = NULL;
+ }
+
+ if (priv->pre_connect_message != NULL)
+ {
+ g_free (priv->pre_connect_message);
+ priv->pre_connect_message = NULL;
+ }
+
+ if (priv->pre_connect_caps != NULL)
+ {
+ gabble_capability_set_free (priv->pre_connect_caps);
+ priv->pre_connect_caps = NULL;
+ }
+
+ if (priv->pre_connect_data_forms != NULL)
+ {
+ g_ptr_array_unref (priv->pre_connect_data_forms);
+ priv->pre_connect_data_forms = NULL;
+ }
+
+ if (priv->self)
+ {
+ g_object_unref (priv->self);
+ priv->self = NULL;
+ }
+
+#ifdef ENABLE_OLPC
+ {
+ wocky_porter_unregister_handler (self->porter, priv->uninvite_handler_id);
+ priv->uninvite_handler_id = 0;
+ }
+
+ if (priv->olpc_activity_manager != NULL)
+ {
+ g_object_unref (priv->olpc_activity_manager);
+ priv->olpc_activity_manager = NULL;
+ }
+#endif
+
+ if (self->session != NULL)
+ {
+ g_object_unref (self->session);
+ self->session = NULL;
+ self->porter = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ g_signal_handlers_disconnect_matched (priv->discovery_client,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ priv->discovery_client = NULL;
+ }
+
+ if (priv->si_bytestream_manager != NULL)
+ {
+ g_object_unref (priv->si_bytestream_manager);
+ priv->si_bytestream_manager = NULL;
+ }
+
+ g_warn_if_fail (g_hash_table_size (priv->sidecars) == 0);
+ tp_clear_pointer (&priv->sidecars, g_hash_table_unref);
+
+ g_warn_if_fail (g_hash_table_size (priv->pending_sidecars) == 0);
+ tp_clear_pointer (&priv->pending_sidecars, g_hash_table_unref);
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (salut_connection_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_connection_parent_class)->dispose (object);
+}
+
+void
+salut_connection_finalize (GObject *object)
+{
+ SalutConnection *self = SALUT_CONNECTION (object);
+ SalutConnectionPrivate *priv = self->priv;
+
+ /* free any data held directly by the object here */
+ tp_presence_mixin_finalize (object);
+ g_free (self->name);
+ g_free (priv->published_name);
+ g_free (priv->first_name);
+ g_free (priv->last_name);
+ g_free (priv->email);
+ g_free (priv->jid);
+#ifdef ENABLE_OLPC
+ if (priv->olpc_key != NULL)
+ g_array_free (priv->olpc_key, TRUE);
+ g_free (priv->olpc_color);
+#endif
+ g_free (priv->dnssd_name);
+
+ tp_contacts_mixin_finalize (G_OBJECT(self));
+
+ gabble_capabilities_finalize (self);
+
+ DEBUG("Finalizing connection");
+
+ G_OBJECT_CLASS (salut_connection_parent_class)->finalize (object);
+}
+
+static void
+_contact_manager_contact_status_changed (SalutConnection *self,
+ SalutContact *contact, TpHandle handle)
+{
+ TpPresenceStatus ps = { contact->status,
+ make_presence_opt_args (contact->status, contact->status_message) };
+
+ tp_presence_mixin_emit_one_presence_update ((GObject *) self, handle,
+ &ps);
+
+ if (ps.optional_arguments != NULL)
+ g_hash_table_destroy (ps.optional_arguments);
+}
+
+static gboolean
+announce_self_caps (SalutConnection *self,
+ GError **error)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ gchar *caps_hash;
+ gboolean ret;
+
+ caps_hash = caps_hash_compute_from_self_presence (priv->self);
+
+ ret = salut_self_set_caps (priv->self, GIBBER_TELEPATHY_NS_CAPS, "sha-1",
+ caps_hash, error);
+
+ if (ret)
+ {
+ salut_presence_cache_learn_caps (self->presence_cache,
+ GIBBER_TELEPATHY_NS_CAPS, caps_hash, salut_self_get_caps (priv->self),
+ wocky_xep_0115_capabilities_get_data_forms (WOCKY_XEP_0115_CAPABILITIES (priv->self)));
+ }
+
+ g_free (caps_hash);
+ return ret;
+}
+
+static void
+_self_established_cb (SalutSelf *s, gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION (data);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION (self), TP_HANDLE_TYPE_CONTACT);
+ GError *error = NULL;
+
+ priv->self_established = TRUE;
+
+ g_free (self->name);
+ self->name = g_strdup (s->name);
+
+ base->self_handle = tp_handle_ensure (handle_repo, self->name, NULL, NULL);
+
+ wocky_session_set_jid (self->session, self->name);
+
+ set_self_presence (self, priv->pre_connect_presence,
+ priv->pre_connect_message, &error);
+
+ if (error != NULL)
+ {
+ DEBUG ("Failed to set presence from pre-connection: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_free (priv->pre_connect_message);
+ priv->pre_connect_message = NULL;
+
+ if (!salut_contact_manager_start (priv->contact_manager, &error))
+ {
+ DEBUG ("failed to start contact manager: %s", error->message);
+ g_clear_error (&error);
+
+ tp_base_connection_change_status ( TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return;
+ }
+
+ if (!salut_roomlist_manager_start (priv->roomlist_manager, &error))
+ {
+ DEBUG ("failed to start roomlist manager: %s", error->message);
+ g_clear_error (&error);
+
+ tp_base_connection_change_status ( TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return;
+ }
+
+#ifdef ENABLE_OLPC
+ if (!salut_olpc_activity_manager_start (priv->olpc_activity_manager, &error))
+ {
+ DEBUG ("failed to start olpc activity manager: %s", error->message);
+ g_clear_error (&error);
+
+ tp_base_connection_change_status ( TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return;
+ }
+#endif
+
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_CONNECTED,
+ TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
+}
+
+
+static void
+_self_failed_cb (SalutSelf *s, GError *error, gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION (data);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+
+ /* FIXME better error handling */
+ tp_base_connection_change_status (base, TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED);
+}
+
+static void
+discovery_client_running (SalutConnection *self)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ guint16 port;
+
+ priv->self = salut_discovery_client_create_self (priv->discovery_client,
+ self, priv->nickname, priv->first_name, priv->last_name, priv->jid,
+ priv->email, priv->published_name,
+#ifdef ENABLE_OLPC
+ priv->olpc_key, priv->olpc_color
+#else
+ NULL, NULL
+#endif
+ );
+
+ if (priv->pre_connect_caps != NULL)
+ {
+ salut_self_take_caps (priv->self, priv->pre_connect_caps);
+ priv->pre_connect_caps = NULL;
+ }
+
+ if (priv->pre_connect_data_forms != NULL)
+ {
+ salut_self_take_data_forms (priv->self, priv->pre_connect_data_forms);
+ priv->pre_connect_data_forms = NULL;
+ }
+
+ g_signal_connect (priv->self, "established",
+ G_CALLBACK(_self_established_cb), self);
+ g_signal_connect (priv->self, "failure",
+ G_CALLBACK(_self_failed_cb), self);
+
+ wocky_session_start (self->session);
+
+ port = wocky_meta_porter_get_port (WOCKY_META_PORTER (self->porter));
+
+ if (!announce_self_caps (self, &error))
+ {
+ DEBUG ("Can't announce our capabilities: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (port == 0 || !salut_self_announce (priv->self, port, &error))
+ {
+ DEBUG ("failed to announce: %s",
+ error != NULL ? error->message : "(no error message)");
+
+ tp_base_connection_change_status (
+ TP_BASE_CONNECTION (self),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ /* Create the bytestream manager */
+ priv->si_bytestream_manager = salut_si_bytestream_manager_new (self,
+ salut_discovery_client_get_host_name_fqdn (priv->discovery_client));
+}
+
+static void
+_discovery_client_state_changed_cb (SalutDiscoveryClient *client,
+ SalutDiscoveryClientState state,
+ SalutConnection *self)
+{
+ SalutConnectionPrivate *priv = self->priv;
+
+ g_assert (client == priv->discovery_client);
+
+ if (state == SALUT_DISCOVERY_CLIENT_STATE_CONNECTED)
+ {
+ discovery_client_running (self);
+ }
+ else if (state == SALUT_DISCOVERY_CLIENT_STATE_DISCONNECTED)
+ {
+ /* FIXME better error messages */
+ /* FIXME instead of full disconnect we could handle the avahi restart */
+ DEBUG ("discovery client got disconnected");
+ tp_base_connection_change_status (TP_BASE_CONNECTION (self),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ }
+}
+/* public functions */
+static void
+_salut_connection_disconnect (SalutConnection *self)
+{
+ SalutConnectionPrivate *priv = self->priv;
+
+ if (priv->self)
+ {
+ g_object_unref (priv->self);
+ priv->self = NULL;
+ }
+
+ if (priv->discovery_client != NULL)
+ {
+ g_object_unref (priv->discovery_client);
+ g_signal_handlers_disconnect_matched (priv->discovery_client,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+ priv->discovery_client = NULL;
+ }
+}
+
+
+/* Aliasing interface */
+/**
+ * salut_connection_get_alias_flags
+ *
+ * Implements D-Bus method GetAliasFlags
+ * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing
+ *
+ */
+static void
+salut_connection_get_alias_flags (TpSvcConnectionInterfaceAliasing *self,
+ DBusGMethodInvocation *context)
+{
+ /* Aliases are set by the contacts
+ * Actually we concat the first and lastname property */
+
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+ 0);
+}
+
+static const gchar *
+salut_connection_get_alias (SalutConnection *self, TpHandle handle)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ const gchar *alias;
+
+ if (handle == base->self_handle)
+ {
+ alias = salut_self_get_alias (priv->self);
+ }
+ else
+ {
+ SalutContact *contact;
+ contact = salut_contact_manager_get_contact (priv->contact_manager,
+ handle);
+
+ if (contact == NULL)
+ {
+ alias = tp_handle_inspect (contact_repo, handle);
+ }
+ else
+ {
+ alias = salut_contact_get_alias (contact);
+ g_object_unref (contact);
+ }
+ }
+
+ return alias;
+}
+
+/**
+ * salut_connection_request_aliases
+ *
+ * Implements D-Bus method RequestAliases
+ * on interface org.freedesktop.Telepathy.Connection.Interface.Aliasing
+ *
+ */
+static void
+salut_connection_request_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ const GArray *contacts, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ guint i;
+ const gchar **aliases;
+ GError *error = NULL;
+ TpHandleRepoIface *contact_handles =
+ tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
+
+ DEBUG ("Alias requested");
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_handles, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ aliases = g_new0 (const gchar *, contacts->len + 1);
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ aliases[i] = salut_connection_get_alias (self, handle);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+ aliases);
+
+ g_free (aliases);
+ return;
+}
+
+static void
+salut_connection_get_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ const GArray *contacts, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ guint i;
+ GError *error = NULL;
+ GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+ (gchar *) salut_connection_get_alias (self, handle));
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+
+ g_hash_table_destroy (result);
+}
+
+static void
+salut_connection_aliasing_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ GValue *val = tp_g_value_slice_new (G_TYPE_STRING);
+
+ g_value_set_string (val, salut_connection_get_alias (self, handle));
+
+ tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING"/alias", val);
+ }
+}
+
+/**
+ * salut_connection_get_handle_contact_capabilities
+ *
+ * Add capabilities of handle to the given GPtrArray
+ */
+static void
+salut_connection_get_handle_contact_capabilities (SalutConnection *self,
+ TpHandle handle, GPtrArray *arr)
+{
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (self);
+ TpChannelManagerIter iter;
+ TpChannelManager *manager;
+ const GabbleCapabilitySet *set;
+ SalutContact *contact = NULL;
+
+ if (handle == base_conn->self_handle)
+ {
+ if (self->priv->self == NULL)
+ return;
+
+ set = salut_self_get_caps (self->priv->self);
+ }
+ else
+ {
+ contact = salut_contact_manager_get_contact (
+ self->priv->contact_manager, handle);
+
+ if (contact == NULL)
+ return;
+
+ set = contact->caps;
+ }
+
+ tp_base_connection_channel_manager_iter_init (&iter, base_conn);
+
+ while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+ {
+ /* all channel managers must implement the capability interface */
+ g_assert (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager));
+
+ gabble_caps_channel_manager_get_contact_capabilities (
+ GABBLE_CAPS_CHANNEL_MANAGER (manager), handle, set, arr);
+ }
+
+ tp_clear_object (&contact);
+}
+
+static void
+conn_contact_capabilities_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ guint i;
+ GPtrArray *array = NULL;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ if (array == NULL)
+ array = g_ptr_array_new ();
+
+ salut_connection_get_handle_contact_capabilities (self, handle, array);
+
+ if (array->len > 0)
+ {
+ GValue *val = tp_g_value_slice_new (
+ TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST);
+
+ g_value_take_boxed (val, array);
+ tp_contacts_mixin_set_contact_attribute (attributes_hash,
+ handle,
+ TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES"/capabilities",
+ val);
+
+ array = NULL;
+ }
+ }
+
+ if (array != NULL)
+ g_ptr_array_free (array, TRUE);
+}
+
+static void
+salut_connection_set_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ GHashTable *aliases, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ const gchar *alias = g_hash_table_lookup (aliases,
+ GUINT_TO_POINTER (base->self_handle));
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (alias == NULL || g_hash_table_size (aliases) != 1)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "In Salut you can only set your own alias" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ DEBUG("Setting my alias to: %s", alias);
+
+ if (!salut_self_set_alias (priv->self, alias, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
+}
+
+static void
+_contact_manager_contact_alias_changed (SalutConnection *self,
+ SalutContact *contact, TpHandle handle)
+{
+ GPtrArray *aliases;
+ GValue entry = {0, };
+
+ g_value_init (&entry, SALUT_TP_ALIAS_PAIR_TYPE);
+ g_value_take_boxed (&entry,
+ dbus_g_type_specialized_construct (SALUT_TP_ALIAS_PAIR_TYPE));
+
+ dbus_g_type_struct_set (&entry,
+ 0, handle, 1, salut_contact_get_alias (contact), G_MAXUINT);
+ aliases = g_ptr_array_sized_new (1);
+ g_ptr_array_add (aliases, g_value_get_boxed (&entry));
+
+ DEBUG("Emitting AliasesChanged");
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (self, aliases);
+
+ g_value_unset (&entry);
+ g_ptr_array_free (aliases, TRUE);
+}
+
+static void
+salut_connection_aliasing_service_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass =
+ (TpSvcConnectionInterfaceAliasingClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x \
+ (klass, salut_connection_##x)
+ IMPLEMENT (get_alias_flags);
+ IMPLEMENT (request_aliases);
+ IMPLEMENT (get_aliases);
+ IMPLEMENT (set_aliases);
+#undef IMPLEMENT
+}
+
+/* Avatar service implementation */
+static void
+_contact_manager_contact_avatar_changed (SalutConnection *self,
+ SalutContact *contact, TpHandle handle)
+{
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ (guint)handle, contact->avatar_token);
+}
+
+static void
+salut_connection_clear_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!salut_self_set_avatar (priv->self, NULL, 0, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+ tp_svc_connection_interface_avatars_return_from_clear_avatar (context);
+}
+
+static void
+salut_connection_set_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *avatar, const gchar *mime_type,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ GError *error = NULL;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!salut_self_set_avatar (priv->self, (guint8 *) avatar->data,
+ avatar->len, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+ base->self_handle, priv->self->avatar_token);
+ tp_svc_connection_interface_avatars_return_from_set_avatar (context,
+ priv->self->avatar_token);
+}
+
+
+static void
+salut_connection_get_avatar_tokens (TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *contacts, DBusGMethodInvocation *context)
+{
+ guint i;
+ gchar **ret;
+ GError *err = NULL;
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ ret = g_new0(gchar *, contacts->len + 1);
+
+ for (i = 0; i < contacts->len ; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ if (base->self_handle == handle)
+ {
+ ret[i] = priv->self->avatar_token;
+ }
+ else
+ {
+ SalutContact *contact;
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager,
+ handle);
+ if (contact != NULL)
+ {
+ ret[i] = contact->avatar_token;
+ g_object_unref (contact);
+ }
+ }
+ if (ret[i] == NULL)
+ ret[i] = "";
+ }
+
+ tp_svc_connection_interface_avatars_return_from_get_avatar_tokens (context,
+ (const gchar **)ret);
+
+ g_free (ret);
+}
+
+static void
+salut_connection_get_known_avatar_tokens (
+ TpSvcConnectionInterfaceAvatars *iface, const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ guint i;
+ GHashTable *ret;
+ GError *err = NULL;
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ ret = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+
+ for (i = 0; i < contacts->len ; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ gchar *tokens = NULL;
+
+ if (base->self_handle == handle)
+ {
+ tokens = g_strdup (priv->self->avatar_token);
+ }
+ else
+ {
+ SalutContact *contact;
+ contact =
+ salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact != NULL)
+ {
+ if (contact->avatar_token != NULL)
+ tokens = g_strdup (contact->avatar_token);
+ else
+ /* We always know the tokens, if it's unset then it's "" */
+ tokens = g_strdup ("");
+ g_object_unref (contact);
+ }
+ }
+
+ if (tokens != NULL)
+ g_hash_table_insert (ret, GUINT_TO_POINTER (handle), tokens);
+ }
+
+ tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens (
+ context, ret);
+
+ g_hash_table_destroy (ret);
+}
+
+static void
+salut_connection_avatars_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ guint i;
+ SalutConnection *self = SALUT_CONNECTION (obj);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ SalutConnectionPrivate *priv = self->priv;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+ gchar *token = NULL;
+
+ if (base->self_handle == handle)
+ {
+ token = g_strdup (priv->self->avatar_token);
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact (
+ priv->contact_manager, handle);
+ if (contact != NULL)
+ {
+ if (contact->avatar_token != NULL)
+ token = g_strdup (contact->avatar_token);
+ else
+ /* We always know the tokens, if it's unset then it's "" */
+ token = g_strdup ("");
+
+ g_object_unref (contact);
+ }
+ }
+
+ if (token != NULL)
+ {
+ GValue *val = tp_g_value_slice_new (G_TYPE_STRING);
+
+ g_value_take_string (val, token);
+
+ tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS"/token", val);
+ }
+ }
+}
+
+
+static void
+_request_avatars_cb (SalutContact *contact, guint8 *avatar, gsize size,
+ gpointer user_data)
+{
+ GArray *arr;
+
+ if (avatar == NULL)
+ return;
+
+ arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8), size);
+ arr = g_array_append_vals (arr, avatar, size);
+
+ tp_svc_connection_interface_avatars_emit_avatar_retrieved (
+ (GObject *) user_data, contact->handle,
+ contact->avatar_token, arr, "");
+
+ g_array_free (arr, TRUE);
+}
+
+static void
+salut_connection_request_avatars (
+ TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ guint i;
+ GError *err = NULL;
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (handle_repo, contacts, FALSE, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ for (i = 0; i < contacts->len ; i++)
+ {
+ TpHandle handle = g_array_index (contacts, TpHandle, i);
+
+ if (base->self_handle == handle)
+ {
+ GArray *arr;
+
+ if (priv->self->avatar != NULL)
+ {
+ arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8),
+ priv->self->avatar_size);
+ arr = g_array_append_vals (arr, priv->self->avatar,
+ priv->self->avatar_size);
+
+ tp_svc_connection_interface_avatars_emit_avatar_retrieved (
+ (GObject *) self, base->self_handle,
+ priv->self->avatar_token, arr, "");
+ g_array_free (arr, TRUE);
+ }
+ }
+ else
+ {
+ SalutContact *contact;
+ contact =
+ salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact != NULL)
+ {
+ salut_contact_get_avatar (contact, _request_avatars_cb, self);
+ g_object_unref (contact);
+ }
+ }
+ }
+
+ tp_svc_connection_interface_avatars_return_from_request_avatars (context);
+}
+
+static void
+_request_avatar_cb (SalutContact *contact, guint8 *avatar, gsize size,
+ gpointer user_data)
+{
+ DBusGMethodInvocation *context = (DBusGMethodInvocation *) user_data;
+
+ GError *err = NULL;
+ GArray *arr;
+
+ if (size == 0)
+ {
+ err = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Unable to get avatar");
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ arr = g_array_sized_new (FALSE, FALSE, sizeof (guint8), size);
+ arr = g_array_append_vals (arr, avatar, size);
+ tp_svc_connection_interface_avatars_return_from_request_avatar (context,
+ arr, "");
+ g_array_free (arr, TRUE);
+}
+
+static void
+salut_connection_request_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ guint handle, DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ SalutContact *contact;
+ GError *err = NULL;
+ TpHandleRepoIface *handle_repo;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ handle_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handle_is_valid (handle_repo, handle, &err))
+ {
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ return;
+ }
+
+ if (handle == base->self_handle)
+ {
+ _request_avatar_cb (NULL, priv->self->avatar, priv->self->avatar_size,
+ context);
+ return;
+ }
+
+ contact = salut_contact_manager_get_contact (priv->contact_manager, handle);
+ if (contact == NULL || contact->avatar_token == NULL)
+ {
+ err = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "No known avatar");
+ dbus_g_method_return_error (context, err);
+ g_error_free (err);
+ if (contact != NULL)
+ {
+ g_object_unref (contact);
+ }
+ return;
+ }
+ salut_contact_get_avatar (contact, _request_avatar_cb, context);
+ g_object_unref (contact);
+}
+
+static void
+conn_avatars_properties_getter (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer getter_data)
+{
+ GQuark q_mime_types = g_quark_from_static_string (
+ "SupportedAvatarMIMETypes");
+
+ if (name == q_mime_types)
+ {
+ g_value_set_static_boxed (value, mimetypes);
+ }
+ else
+ {
+ g_value_set_uint (value, GPOINTER_TO_UINT (getter_data));
+ }
+}
+
+static void
+salut_connection_get_avatar_requirements (
+ TpSvcConnectionInterfaceAvatars *iface, DBusGMethodInvocation *context)
+{
+ tp_svc_connection_interface_avatars_return_from_get_avatar_requirements (
+ context, mimetypes, AVATAR_MIN_PX, AVATAR_MIN_PX, AVATAR_MAX_PX,
+ AVATAR_MAX_PX, AVATAR_MAX_BYTES);
+}
+
+static void
+salut_connection_avatar_service_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAvatarsClass *klass =
+ (TpSvcConnectionInterfaceAvatarsClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_avatars_implement_##x \
+ (klass, salut_connection_##x)
+ IMPLEMENT (get_avatar_requirements);
+ IMPLEMENT (get_avatar_tokens);
+ IMPLEMENT (get_known_avatar_tokens);
+ IMPLEMENT (request_avatar);
+ IMPLEMENT (request_avatars);
+ IMPLEMENT (set_avatar);
+ IMPLEMENT (clear_avatar);
+#undef IMPLEMENT
+}
+
+static void
+salut_free_enhanced_contact_capabilities (GPtrArray *caps)
+{
+ guint i;
+
+ for (i = 0; i < caps->len; i++)
+ {
+ GValue monster = {0, };
+
+ g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS);
+ g_value_take_boxed (&monster, g_ptr_array_index (caps, i));
+ g_value_unset (&monster);
+ }
+
+ g_ptr_array_free (caps, TRUE);
+}
+
+/**
+ * salut_connection_get_contact_capabilities
+ *
+ * Implements D-Bus method GetContactCapabilities
+ * on interface
+ * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities
+ */
+static void
+salut_connection_get_contact_capabilities (
+ TpSvcConnectionInterfaceContactCapabilities *iface,
+ const GArray *handles,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ guint i;
+ GHashTable *ret;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handles_are_valid (contact_handles, handles, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ ret = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify) salut_free_enhanced_contact_capabilities);
+
+ for (i = 0; i < handles->len; i++)
+ {
+ GPtrArray *arr = g_ptr_array_new ();
+ TpHandle handle = g_array_index (handles, TpHandle, i);
+
+ salut_connection_get_handle_contact_capabilities (self, handle, arr);
+
+ g_hash_table_insert (ret, GINT_TO_POINTER (handle), arr);
+ }
+
+ tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities
+ (context, ret);
+
+ g_hash_table_destroy (ret);
+}
+
+
+static void
+_emit_contact_capabilities_changed (SalutConnection *conn,
+ TpHandle handle)
+{
+ GPtrArray *ret = g_ptr_array_new ();
+ GHashTable *caps = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ salut_connection_get_handle_contact_capabilities (conn, handle, ret);
+ g_hash_table_insert (caps, GUINT_TO_POINTER (handle), ret);
+
+ tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed (
+ conn, caps);
+
+ salut_free_enhanced_contact_capabilities (ret);
+ g_hash_table_destroy (caps);
+}
+
+static void
+connection_capabilities_update_cb (SalutPresenceCache *cache,
+ TpHandle handle,
+ gpointer user_data)
+{
+ SalutConnection *conn = SALUT_CONNECTION (user_data);
+
+ g_assert (SALUT_IS_CONNECTION (user_data));
+
+ _emit_contact_capabilities_changed (conn, handle);
+}
+
+static gboolean
+data_forms_equal (GPtrArray *one,
+ GPtrArray *two)
+{
+ guint i;
+
+ /* These data form lists come from the channel managers returning
+ * from represent_client so they'll be created new every time
+ * represent_client is called. As a result, we can't just look at
+ * the object pointers, like how we can in the presence cache. */
+
+ if (one->len != two->len)
+ return FALSE;
+
+ for (i = 0; i < one->len; i++)
+ {
+ WockyDataForm *form = g_ptr_array_index (one, i);
+ WockyDataFormField *type_field;
+ const gchar *type;
+ guint j;
+ gboolean found = FALSE;
+
+ type_field = g_hash_table_lookup (form->fields, "FORM_TYPE");
+ type = g_value_get_string (type_field->default_value);
+
+ for (j = 0; j < two->len; j++)
+ {
+ WockyDataForm *two_form = g_ptr_array_index (two, i);
+ WockyDataFormField *two_type;
+
+ two_type = g_hash_table_lookup (two_form->fields,
+ "FORM_TYPE");
+
+ if (!tp_strdiff (g_value_get_string (two_type->default_value), type))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * salut_connection_update_capabilities
+ *
+ * Implements D-Bus method UpdateCapabilities
+ * on interface
+ * org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities
+ */
+static void
+salut_connection_update_capabilities (
+ TpSvcConnectionInterfaceContactCapabilities *iface,
+ const GPtrArray *clients,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ SalutConnectionPrivate *priv = self->priv;
+ GabbleCapabilitySet *before = NULL, *after;
+ GPtrArray *before_forms = NULL, *after_forms;
+ TpChannelManagerIter iter;
+ TpChannelManager *manager;
+ guint i;
+ GError *error = NULL;
+
+ /* these are the caps we were advertising before UpdateCapabilities
+ * was called. we'll only have created the salut self once we've
+ * connected */
+ if (priv->self != NULL)
+ {
+ before = gabble_capability_set_copy (salut_self_get_caps (priv->self));
+ before_forms = g_ptr_array_ref (
+ (GPtrArray *) wocky_xep_0115_capabilities_get_data_forms (
+ WOCKY_XEP_0115_CAPABILITIES (priv->self)));
+ }
+
+ tp_base_connection_channel_manager_iter_init (&iter, base);
+
+ while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+ {
+ /* all channel managers must implement the capability interface */
+ g_assert (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager));
+
+ gabble_caps_channel_manager_reset_capabilities (
+ GABBLE_CAPS_CHANNEL_MANAGER (manager));
+ }
+
+ DEBUG ("enter");
+
+ /* we're going to reset our self caps to the bare caps that we
+ * advertise and then add to it after iterating the clients. */
+ after = salut_dup_self_advertised_caps ();
+ after_forms = g_ptr_array_new ();
+
+ for (i = 0; i < clients->len; i++)
+ {
+ GValueArray *va = g_ptr_array_index (clients, i);
+ const gchar *client_name = g_value_get_string (va->values + 0);
+ const GPtrArray *filters = g_value_get_boxed (va->values + 1);
+ const gchar * const * cap_tokens = g_value_get_boxed (va->values + 2);
+
+ /* We pass the client through to the caps channel managers
+ * because it allows them to update their view on which clients
+ * are still around. */
+
+ tp_base_connection_channel_manager_iter_init (&iter, base);
+
+ while (tp_base_connection_channel_manager_iter_next (&iter, &manager))
+ {
+ /* all channel managers must implement the capability interface */
+ g_assert (GABBLE_IS_CAPS_CHANNEL_MANAGER (manager));
+
+ gabble_caps_channel_manager_represent_client (
+ GABBLE_CAPS_CHANNEL_MANAGER (manager), client_name, filters,
+ cap_tokens, after, after_forms);
+ }
+ }
+
+ if (priv->self != NULL)
+ {
+ /* we've connected and have a SalutSelf, so give the caps to it
+ * right now */
+ salut_self_take_caps (priv->self, after);
+ salut_self_take_data_forms (priv->self, after_forms);
+ }
+ else
+ {
+ if (priv->pre_connect_caps != NULL)
+ gabble_capability_set_free (priv->pre_connect_caps);
+ if (priv->pre_connect_data_forms != NULL)
+ g_ptr_array_unref (priv->pre_connect_data_forms);
+
+ priv->pre_connect_caps = after;
+ priv->pre_connect_data_forms = after_forms;
+ }
+
+ if ((before != NULL && !gabble_capability_set_equals (before, after))
+ || (before_forms != NULL && !data_forms_equal (before_forms, after_forms)))
+ {
+ if (DEBUGGING)
+ {
+ gchar *dump = gabble_capability_set_dump (after, " ");
+ DEBUG ("updated caps:\n%s", dump);
+ g_free (dump);
+ }
+
+ if (!announce_self_caps (self, &error))
+ {
+ gabble_capability_set_free (before);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ _emit_contact_capabilities_changed (self, base->self_handle);
+ }
+
+ /* after now belongs to SalutSelf, or priv->pre_connect_caps */
+ if (before != NULL)
+ gabble_capability_set_free (before);
+
+ if (before_forms != NULL)
+ g_ptr_array_unref (before_forms);
+
+ tp_svc_connection_interface_contact_capabilities_return_from_update_capabilities (
+ context);
+}
+
+static void
+salut_conn_contact_caps_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcConnectionInterfaceContactCapabilitiesClass *klass =
+ (TpSvcConnectionInterfaceContactCapabilitiesClass *) g_iface;
+
+#define IMPLEMENT(x) \
+ tp_svc_connection_interface_contact_capabilities_implement_##x (\
+ klass, salut_connection_##x)
+ IMPLEMENT(get_contact_capabilities);
+ IMPLEMENT(update_capabilities);
+#undef IMPLEMENT
+}
+
+
+#ifdef ENABLE_OLPC
+static GValue *
+new_gvalue (GType type)
+{
+ GValue *result = g_slice_new0 (GValue);
+ g_value_init (result, type);
+ return result;
+}
+
+static GHashTable *
+get_properties_hash (const GArray *key, const gchar *color, const gchar *jid,
+ const gchar *ip4, const gchar *ip6)
+{
+ GHashTable *properties;
+ GValue *gvalue;
+
+ properties = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ if (key != NULL)
+ {
+ gvalue = new_gvalue (DBUS_TYPE_G_UCHAR_ARRAY);
+ g_value_set_boxed (gvalue, key);
+ g_hash_table_insert (properties, "key", gvalue);
+ }
+
+ if (color != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, color);
+ g_hash_table_insert (properties, "color", gvalue);
+ }
+
+ if (jid != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, jid);
+ g_hash_table_insert (properties, "jid", gvalue);
+ }
+
+ if (ip4 != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, ip4);
+ g_hash_table_insert (properties, "ip4-address", gvalue);
+ }
+
+ if (ip6 != NULL)
+ {
+ gvalue = new_gvalue (G_TYPE_STRING);
+ g_value_set_string (gvalue, ip6);
+ g_hash_table_insert (properties, "ip6-address", gvalue);
+ }
+
+ return properties;
+}
+
+static void
+emit_properties_changed (SalutConnection *connection,
+ TpHandle handle,
+ const GArray *key,
+ const gchar *color,
+ const gchar *jid,
+ const gchar *ip4,
+ const gchar *ip6)
+{
+ GHashTable *properties;
+ properties = get_properties_hash (key, color, jid, ip4, ip6);
+
+ salut_svc_olpc_buddy_info_emit_properties_changed (connection,
+ handle, properties);
+
+ g_hash_table_destroy (properties);
+}
+
+static void
+append_activity (SalutOlpcActivity *activity,
+ gpointer user_data)
+{
+ GPtrArray *arr = user_data;
+ GType type = ACTIVITY_PAIR_TYPE;
+ GValue gvalue = {0};
+
+ g_value_init (&gvalue, type);
+ g_value_take_boxed (&gvalue,
+ dbus_g_type_specialized_construct (type));
+
+ dbus_g_type_struct_set (&gvalue,
+ 0, activity->id,
+ 1, activity->room,
+ G_MAXUINT);
+ g_ptr_array_add (arr, g_value_get_boxed (&gvalue));
+}
+
+static void
+free_olpc_activities (GPtrArray *arr)
+{
+ GType type = ACTIVITY_PAIR_TYPE;
+ guint i;
+
+ for (i = 0; i < arr->len; i++)
+ g_boxed_free (type, arr->pdata[i]);
+
+ g_ptr_array_free (arr, TRUE);
+}
+
+static void
+_contact_manager_contact_olpc_activities_changed (SalutConnection *self,
+ SalutContact *contact,
+ TpHandle handle)
+{
+ GPtrArray *activities = g_ptr_array_new ();
+
+ DEBUG ("called for %u", handle);
+
+ salut_contact_foreach_olpc_activity (contact, append_activity, activities);
+ salut_svc_olpc_buddy_info_emit_activities_changed (self,
+ handle, activities);
+ free_olpc_activities (activities);
+}
+
+static void
+_contact_manager_contact_olpc_properties_changed (SalutConnection *self,
+ SalutContact *contact,
+ TpHandle handle)
+{
+ emit_properties_changed (self, handle, contact->olpc_key,
+ contact->olpc_color, contact->jid, contact->olpc_ip4, contact->olpc_ip6);
+}
+
+static gboolean
+check_handle (TpHandleRepoIface *handle_repo,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ GError *error = NULL;
+
+ if (!tp_handle_is_valid (handle_repo, handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+check_contact (TpBaseConnection *base,
+ TpHandle contact,
+ DBusGMethodInvocation *context)
+{
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+ base, TP_HANDLE_TYPE_CONTACT);
+
+ return check_handle (contact_repo, contact, context);
+}
+
+static gboolean
+check_room (TpBaseConnection *base,
+ TpHandle contact,
+ DBusGMethodInvocation *context)
+{
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ base, TP_HANDLE_TYPE_ROOM);
+
+ return check_handle (room_repo, contact, context);
+}
+
+static void
+salut_connection_olpc_get_properties (SalutSvcOLPCBuddyInfo *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ GHashTable *properties = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!check_contact (base, handle, context))
+ return;
+
+ if (handle == base->self_handle)
+ {
+ properties = get_properties_hash (priv->self->olpc_key,
+ priv->self->olpc_color, priv->self->jid, NULL, NULL);
+ }
+ else
+ {
+ SalutContact *contact;
+ contact = salut_contact_manager_get_contact (priv->contact_manager,
+ handle);
+ if (contact == NULL)
+ {
+ /* FIXME: should this be InvalidHandle? */
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Unknown contact" };
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+ properties = get_properties_hash (contact->olpc_key, contact->olpc_color,
+ contact->jid, contact->olpc_ip4, contact->olpc_ip6);
+ g_object_unref (contact);
+ }
+
+ salut_svc_olpc_buddy_info_return_from_get_properties (context, properties);
+ g_hash_table_destroy (properties);
+}
+
+
+static gboolean
+find_unknown_properties (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ gchar **valid_props = (gchar **) user_data;
+ int i;
+ for (i = 0; valid_props[i] != NULL; i++)
+ {
+ if (!tp_strdiff (key, valid_props[i]))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+salut_connection_olpc_set_properties (SalutSvcOLPCBuddyInfo *iface,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+
+ GError *error = NULL;
+ /* Only a few known properties, so handle it quite naively */
+ const gchar *known_properties[] = { "color", "key", "jid", "ip4-address",
+ "ip6-address", NULL };
+ const gchar *color = NULL;
+ const GArray *key = NULL;
+ const gchar *jid = NULL;
+ const GValue *val;
+
+ /* this function explicitly supports being called when DISCONNECTED
+ * or CONNECTING */
+
+ if (g_hash_table_find (properties, find_unknown_properties, known_properties)
+ != NULL)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unknown property given");
+ goto error;
+ }
+
+ val = (const GValue *) g_hash_table_lookup (properties, "color");
+ if (val != NULL)
+ {
+ if (G_VALUE_TYPE (val) != G_TYPE_STRING)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value should be of type s");
+ goto error;
+ }
+ else
+ {
+ int len;
+ gboolean correct = TRUE;
+
+ color = g_value_get_string (val);
+
+ /* be very anal about the color format */
+ len = strlen (color);
+ if (len != 15)
+ {
+ correct = FALSE;
+ }
+ else
+ {
+ int i;
+ for (i = 0 ; i < len ; i++)
+ {
+ switch (i)
+ {
+ case 0:
+ case 8:
+ correct = (color[i] == '#');
+ break;
+ case 7:
+ correct = (color[i] == ',');
+ break;
+ default:
+ correct = isxdigit (color[i]);
+ break;
+ }
+ }
+ }
+
+ if (!correct)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value has an incorrect format");
+ goto error;
+ }
+ }
+ }
+
+ if ((val = (const GValue *) g_hash_table_lookup (properties, "key")) != NULL)
+ {
+ if (G_VALUE_TYPE (val) != DBUS_TYPE_G_UCHAR_ARRAY)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Key value should be of type ay");
+ goto error;
+ }
+ else
+ {
+ key = g_value_get_boxed (val);
+ if (key->len == 0)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Key value of length 0 not allowed");
+ goto error;
+ }
+ }
+ }
+
+ val = g_hash_table_lookup (properties, "jid");
+ if (val != NULL)
+ {
+ if (G_VALUE_TYPE (val) != G_TYPE_STRING)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "JID value should be of type s");
+ goto error;
+ }
+
+ jid = g_value_get_string (val);
+
+ if (strchr (jid, '@') == NULL)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "JID value has an incorrect format");
+ goto error;
+ }
+ }
+
+ if (priv->self)
+ {
+ if (!salut_self_set_olpc_properties (priv->self, key, color, jid,
+ &error))
+ goto error;
+ }
+ else
+ {
+ /* queue it up for later */
+ if (key)
+ {
+ if (priv->olpc_key == NULL)
+ {
+ priv->olpc_key = g_array_sized_new (FALSE, FALSE, sizeof (guint8),
+ key->len);
+ }
+ else
+ {
+ g_array_remove_range (priv->olpc_key, 0, priv->olpc_key->len);
+ }
+ g_array_append_vals (priv->olpc_key, key->data, key->len);
+ }
+ if (color)
+ {
+ g_free (priv->olpc_color);
+ priv->olpc_color = g_strdup (color);
+ }
+ if (jid)
+ {
+ g_free (priv->jid);
+ priv->jid = g_strdup (jid);
+ }
+ }
+
+ salut_svc_olpc_buddy_info_return_from_set_properties (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+salut_connection_olpc_get_current_activity (SalutSvcOLPCBuddyInfo *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ SalutConnectionPrivate *priv = self->priv;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ DEBUG ("called for %u", handle);
+
+ if (!check_contact (base, handle, context))
+ return;
+
+ if (handle == base->self_handle)
+ {
+ DEBUG ("Returning my own cur.act.: %s -> %u",
+ priv->self->olpc_cur_act ? priv->self->olpc_cur_act : "",
+ priv->self->olpc_cur_act_room);
+ salut_svc_olpc_buddy_info_return_from_get_current_activity (context,
+ priv->self->olpc_cur_act ? priv->self->olpc_cur_act : "",
+ priv->self->olpc_cur_act_room);
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact
+ (priv->contact_manager, handle);
+
+ if (contact == NULL)
+ {
+ /* FIXME: should this be InvalidHandle? */
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Unknown contact" };
+ DEBUG ("Returning error: unknown contact");
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ DEBUG ("Returning buddy %u cur.act.: %s -> %u", handle,
+ contact->olpc_cur_act ? contact->olpc_cur_act : "",
+ contact->olpc_cur_act_room);
+ salut_svc_olpc_buddy_info_return_from_get_current_activity (context,
+ contact->olpc_cur_act ? contact->olpc_cur_act : "",
+ contact->olpc_cur_act_room);
+ g_object_unref (contact);
+ }
+}
+
+static void
+salut_connection_olpc_set_current_activity (SalutSvcOLPCBuddyInfo *iface,
+ const gchar *activity_id,
+ TpHandle room_handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ DEBUG ("called");
+
+ if (activity_id[0] == '\0')
+ {
+ if (room_handle != 0)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "If activity ID is empty, room handle must be 0" };
+
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+ }
+ else
+ {
+ if (!check_room (base, room_handle, context))
+ return;
+ }
+
+ if (!salut_self_set_olpc_current_activity (priv->self, activity_id,
+ room_handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ salut_svc_olpc_buddy_info_return_from_set_current_activity (context);
+}
+
+static void
+salut_connection_olpc_get_activities (SalutSvcOLPCBuddyInfo *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GPtrArray *arr;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ DEBUG ("called for %u", handle);
+
+ if (!check_contact (base, handle, context))
+ return;
+
+ if (handle == base->self_handle)
+ {
+ arr = g_ptr_array_new ();
+ salut_self_foreach_olpc_activity (priv->self, append_activity, arr);
+ }
+ else
+ {
+ SalutContact *contact = salut_contact_manager_get_contact
+ (priv->contact_manager, handle);
+
+ if (contact == NULL)
+ {
+ /* FIXME: should this be InvalidHandle? */
+ GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Unknown contact" };
+ DEBUG ("Returning error: unknown contact");
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ arr = g_ptr_array_new ();
+ salut_contact_foreach_olpc_activity (contact, append_activity, arr);
+ g_object_unref (contact);
+ }
+
+ salut_svc_olpc_buddy_info_return_from_get_activities (context, arr);
+ free_olpc_activities (arr);
+}
+
+static void
+salut_connection_olpc_set_activities (SalutSvcOLPCBuddyInfo *iface,
+ const GPtrArray *activities,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_ROOM);
+ GHashTable *room_to_act_id = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal, NULL, (GDestroyNotify) g_free);
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ for (i = 0; i < activities->len; i++)
+ {
+ GValue pair = {0};
+ gchar *activity;
+ guint room_handle;
+
+ g_value_init (&pair, ACTIVITY_PAIR_TYPE);
+ g_value_set_static_boxed (&pair, g_ptr_array_index (activities, i));
+ dbus_g_type_struct_get (&pair,
+ 0, &activity,
+ 1, &room_handle,
+ G_MAXUINT);
+
+ if (activity[0] == '\0')
+ {
+ GError e = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Invalid empty activity ID" };
+
+ DEBUG ("%s", e.message);
+ dbus_g_method_return_error (context, &e);
+ g_free (activity);
+ goto finally;
+ }
+
+ if (!tp_handle_is_valid (room_repo, room_handle, &error))
+ {
+ DEBUG ("Invalid room handle %u: %s", room_handle, error->message);
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ g_free (activity);
+ goto finally;
+ }
+
+ g_hash_table_insert (room_to_act_id, GUINT_TO_POINTER (room_handle),
+ activity);
+ }
+
+ if (!salut_self_set_olpc_activities (priv->self, room_to_act_id, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ }
+ else
+ {
+ salut_svc_olpc_buddy_info_return_from_set_activities (context);
+ }
+
+finally:
+ g_hash_table_destroy (room_to_act_id);
+}
+
+static void
+salut_connection_olpc_add_activity (SalutSvcOLPCBuddyInfo *iface,
+ const gchar *id,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!salut_self_add_olpc_activity (priv->self, id, handle, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ }
+ else
+ {
+ salut_svc_olpc_buddy_info_return_from_set_activities (context);
+ }
+}
+
+static void
+salut_connection_olpc_buddy_info_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutSvcOLPCBuddyInfoClass *klass =
+ (SalutSvcOLPCBuddyInfoClass *) g_iface;
+#define IMPLEMENT(x) salut_svc_olpc_buddy_info_implement_##x (klass, \
+ salut_connection_olpc_##x)
+ IMPLEMENT(set_properties);
+ IMPLEMENT(get_properties);
+ IMPLEMENT(set_activities);
+ IMPLEMENT(add_activity);
+ IMPLEMENT(get_activities);
+ IMPLEMENT(set_current_activity);
+ IMPLEMENT(get_current_activity);
+#undef IMPLEMENT
+}
+
+static void
+salut_connection_act_get_properties (SalutSvcOLPCActivityProperties *iface,
+ TpHandle handle,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_ROOM);
+ GHashTable *properties = NULL;
+ GError *error = NULL;
+ SalutOlpcActivity *activity;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!tp_handle_is_valid (room_repo, handle, &error))
+ goto error;
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (
+ priv->olpc_activity_manager, handle);
+ if (activity == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Activity unknown: %u", handle);
+ goto error;
+ }
+
+ properties = salut_olpc_activity_create_properties_table (activity);
+
+ salut_svc_olpc_buddy_info_return_from_get_properties (context, properties);
+ g_hash_table_destroy (properties);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static gboolean
+check_color (const gchar *color)
+{
+ int len, i;
+
+ /* be very anal about the color format */
+ len = strlen (color);
+ if (len != 15)
+ return FALSE;
+
+ for (i = 0 ; i < len ; i++)
+ {
+ switch (i)
+ {
+ case 0:
+ case 8:
+ if (color[i] != '#')
+ return FALSE;
+ break;
+ case 7:
+ if (color[i] != ',')
+ return FALSE;
+ break;
+ default:
+ if (!isxdigit (color[i]))
+ return FALSE;
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+/* returned strings are only valid as long as the hash table isn't modified */
+static gboolean
+extract_properties_from_hash (GHashTable *properties,
+ const gchar **id,
+ const gchar **color,
+ const gchar **name,
+ const gchar **type,
+ const gchar **tags,
+ gboolean *is_private,
+ GError **error)
+{
+ GValue *activity_id_val, *color_val, *activity_name_val, *activity_type_val,
+ *tags_val, *is_private_val;
+
+ /* activity ID */
+ activity_id_val = g_hash_table_lookup (properties, "id");
+ if (activity_id_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_id_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Activity ID value should be of type s");
+ return FALSE;
+ }
+
+ if (id != NULL)
+ *id = g_value_get_string (activity_id_val);
+ }
+
+ /* color */
+ color_val = g_hash_table_lookup (properties, "color");
+ if (color_val != NULL)
+ {
+ if (G_VALUE_TYPE (color_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value should be of type s");
+ return FALSE;
+ }
+
+ if (color != NULL)
+ {
+ *color = g_value_get_string (color_val);
+
+ if (!check_color (*color))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Color value has an incorrect format");
+ return FALSE;
+ }
+ }
+ }
+
+ /* name */
+ activity_name_val = g_hash_table_lookup (properties, "name");
+ if (activity_name_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_name_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "name value should be of type s");
+ return FALSE;
+ }
+
+ if (name != NULL)
+ *name = g_value_get_string (activity_name_val);
+ }
+
+ /* type */
+ activity_type_val = g_hash_table_lookup (properties, "type");
+ if (activity_type_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_type_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "type value should be of type s");
+ return FALSE;
+ }
+
+ if (type != NULL)
+ {
+ *type = g_value_get_string (activity_type_val);
+
+ if (*type[0] == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "type value must be non-empty");
+ return FALSE;
+ }
+ }
+ }
+
+ /* tags */
+ tags_val = g_hash_table_lookup (properties, "tags");
+ if (tags_val != NULL)
+ {
+ if (G_VALUE_TYPE (activity_type_val) != G_TYPE_STRING)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "tags value should be of type s");
+ return FALSE;
+ }
+
+ if (type != NULL)
+ *tags = g_value_get_string (tags_val);
+ }
+
+ /* is_private */
+ is_private_val = g_hash_table_lookup (properties, "private");
+ if (is_private_val != NULL)
+ {
+ if (G_VALUE_TYPE (is_private_val) != G_TYPE_BOOLEAN)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "private value should be of type b");
+ return FALSE;
+ }
+
+ if (is_private != NULL)
+ *is_private = g_value_get_boolean (is_private_val);
+ }
+
+ return TRUE;
+}
+
+static void
+salut_connection_act_set_properties (SalutSvcOLPCActivityProperties *iface,
+ TpHandle handle,
+ GHashTable *properties,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+ const gchar *known_properties[] = { "color", "name", "type", "private",
+ "tags", NULL };
+ const gchar *color = NULL, *name = NULL, *type = NULL, *tags = NULL;
+ gboolean is_private = TRUE;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ if (!check_room (base, handle, context))
+ return;
+
+ if (g_hash_table_find (properties, find_unknown_properties, known_properties)
+ != NULL)
+ {
+ error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Unknown property given");
+ goto error;
+ }
+
+ if (!extract_properties_from_hash (properties, NULL, &color, &name, &type,
+ &tags, &is_private, &error))
+ goto error;
+
+ if (!salut_self_set_olpc_activity_properties (priv->self, handle, color,
+ name, type, tags, is_private, &error))
+ goto error;
+
+ salut_svc_olpc_activity_properties_return_from_set_properties (context);
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+typedef struct
+{
+ SalutContact *inviter;
+ SalutOlpcActivity *activity;
+} muc_ready_ctx;
+
+static muc_ready_ctx *
+muc_ready_ctx_new (SalutContact *inviter,
+ SalutOlpcActivity *activity)
+{
+ muc_ready_ctx *ctx = g_slice_new (muc_ready_ctx);
+ ctx->inviter = inviter;
+ g_object_ref (inviter);
+ ctx->activity = activity;
+ g_object_ref (activity);
+ return ctx;
+}
+
+static void
+muc_ready_ctx_free (muc_ready_ctx *ctx)
+{
+ if (ctx == NULL)
+ return;
+
+ g_object_unref (ctx->inviter);
+ g_object_unref (ctx->activity);
+ g_slice_free (muc_ready_ctx, ctx);
+}
+
+static void
+muc_ready_cb (SalutMucChannel *muc,
+ muc_ready_ctx *ctx)
+{
+ /* We joined the muc so have to forget about invites */
+ salut_contact_left_activity (ctx->inviter, ctx->activity);
+
+ DEBUG ("forget invite received from %s", ctx->inviter->name);
+ g_signal_handlers_disconnect_matched (muc, G_SIGNAL_MATCH_DATA, 0, 0, NULL,
+ NULL, ctx);
+ muc_ready_ctx_free (ctx);
+}
+
+static void
+muc_closed_cb (SalutMucChannel *muc,
+ muc_ready_ctx *ctx)
+{
+ /* FIXME: should we call left_private_activity here too ? */
+
+ g_signal_handlers_disconnect_matched (muc, G_SIGNAL_MATCH_DATA, 0, 0, NULL,
+ NULL, ctx);
+ muc_ready_ctx_free (ctx);
+}
+
+void
+salut_connection_olpc_observe_invitation (SalutConnection *self,
+ TpHandle room,
+ TpHandle inviter_handle,
+ WockyNode *invite_node)
+{
+ SalutConnectionPrivate *priv = self->priv;
+ WockyNode *props_node;
+ GHashTable *properties;
+ const gchar *activity_id, *color = NULL, *activity_name = NULL,
+ *activity_type = NULL, *tags = NULL;
+ SalutContact *inviter;
+ SalutOlpcActivity *activity;
+ SalutMucChannel *muc;
+ muc_ready_ctx *ctx;
+
+ props_node = wocky_node_get_child_ns (invite_node, "properties",
+ GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS);
+
+ if (props_node == NULL)
+ return;
+
+ inviter = salut_contact_manager_get_contact (priv->contact_manager,
+ inviter_handle);
+ if (inviter == NULL)
+ return;
+
+ properties = salut_wocky_node_extract_properties (props_node,
+ "property");
+
+ if (!extract_properties_from_hash (properties, &activity_id, &color,
+ &activity_name, &activity_type, &tags, NULL, NULL))
+ return;
+
+ activity = salut_olpc_activity_manager_got_invitation (
+ priv->olpc_activity_manager,
+ room, inviter, activity_id, activity_name, activity_type,
+ color, tags);
+
+ muc = salut_muc_manager_get_text_channel (priv->muc_manager, room);
+ g_assert (muc != NULL);
+
+ ctx = muc_ready_ctx_new (inviter, activity);
+ g_signal_connect (muc, "ready", G_CALLBACK (muc_ready_cb), ctx);
+ g_signal_connect (muc, "closed", G_CALLBACK (muc_closed_cb), ctx);
+
+ g_object_unref (muc);
+ g_hash_table_destroy (properties);
+ g_object_unref (inviter);
+}
+
+static void
+salut_connection_act_get_activity (SalutSvcOLPCActivityProperties *iface,
+ const gchar *activity_id,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *self = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = (TpBaseConnection *) self;
+ GError *error = NULL;
+ SalutOlpcActivity *activity;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ activity = salut_olpc_activity_manager_get_activity_by_id (
+ priv->olpc_activity_manager, activity_id);
+ if (activity == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Activity unknown: %s", activity_id);
+ goto error;
+ }
+
+ salut_svc_olpc_activity_properties_return_from_get_activity (context,
+ activity->room);
+
+ return;
+
+error:
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+}
+
+static void
+salut_connection_olpc_activity_properties_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutSvcOLPCActivityPropertiesClass *klass =
+ (SalutSvcOLPCActivityPropertiesClass *) g_iface;
+#define IMPLEMENT(x) salut_svc_olpc_activity_properties_implement_##x \
+ (klass, salut_connection_act_##x)
+ IMPLEMENT(set_properties);
+ IMPLEMENT(get_properties);
+ IMPLEMENT(get_activity);
+#undef IMPLEMENT
+}
+#endif
+
+gchar *
+salut_normalize_non_empty (const gchar *id,
+ GError **error)
+{
+ g_return_val_if_fail (id != NULL, NULL);
+
+ if (*id == '\0')
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "Salut handle names may not be the empty string");
+ return NULL;
+ }
+
+ return g_strdup (id);
+}
+
+static gchar *
+handle_normalize_require_nonempty (TpHandleRepoIface *repo G_GNUC_UNUSED,
+ const gchar *id,
+ gpointer context G_GNUC_UNUSED,
+ GError **error)
+{
+ return salut_normalize_non_empty (id, error);
+}
+
+/* Connection baseclass function implementations */
+static void
+salut_connection_create_handle_repos (TpBaseConnection *self,
+ TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+ static const char *list_handle_strings[] = {
+ "publish",
+ "subscribe",
+ "known",
+ NULL
+ };
+
+ repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_CONTACT, handle_normalize_require_nonempty, NULL);
+
+ repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new
+ (TP_HANDLE_TYPE_ROOM, handle_normalize_require_nonempty, NULL);
+
+ repos[TP_HANDLE_TYPE_LIST] = tp_static_handle_repo_new (TP_HANDLE_TYPE_LIST,
+ list_handle_strings);
+}
+
+static void
+_contact_manager_contact_change_cb (SalutContactManager *mgr,
+ SalutContact *contact, int changes, gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION(data);
+ TpHandleRepoIface *handle_repo = tp_base_connection_get_handles (
+ TP_BASE_CONNECTION(self), TP_HANDLE_TYPE_CONTACT);
+ TpHandle handle;
+
+ handle = tp_handle_lookup (handle_repo, contact->name, NULL, NULL);
+
+ if (changes & SALUT_CONTACT_ALIAS_CHANGED)
+ {
+ _contact_manager_contact_alias_changed (self, contact, handle);
+ }
+
+ if (changes & SALUT_CONTACT_STATUS_CHANGED)
+ {
+ _contact_manager_contact_status_changed (self, contact, handle);
+ }
+
+ if (changes & SALUT_CONTACT_AVATAR_CHANGED)
+ {
+ _contact_manager_contact_avatar_changed (self, contact, handle);
+ }
+
+#ifdef ENABLE_OLPC
+ if (changes & SALUT_CONTACT_OLPC_PROPERTIES)
+ _contact_manager_contact_olpc_properties_changed (self, contact, handle);
+
+ if (changes & SALUT_CONTACT_OLPC_CURRENT_ACTIVITY)
+ salut_svc_olpc_buddy_info_emit_current_activity_changed (self,
+ handle, contact->olpc_cur_act, contact->olpc_cur_act_room);
+
+ if (changes & SALUT_CONTACT_OLPC_ACTIVITIES)
+ _contact_manager_contact_olpc_activities_changed (self, contact, handle);
+#endif
+}
+
+#ifdef ENABLE_OLPC
+static void
+_olpc_activity_manager_activity_modified_cb (SalutOlpcActivityManager *mgr,
+ SalutOlpcActivity *activity, SalutConnection *self)
+{
+ GHashTable *properties;
+
+ properties = salut_olpc_activity_create_properties_table (activity);
+ salut_svc_olpc_activity_properties_emit_activity_properties_changed (
+ self, activity->room, properties);
+
+ g_hash_table_destroy (properties);
+}
+
+gboolean
+salut_connection_olpc_observe_muc_stanza (SalutConnection *self,
+ TpHandle room, TpHandle sender, WockyStanza *stanza)
+{
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ SalutConnectionPrivate *priv = self->priv;
+ WockyNode *props_node;
+ GHashTable *properties;
+ const gchar *activity_id, *color = NULL, *activity_name = NULL,
+ *activity_type = NULL, *tags = NULL;
+ gboolean is_private = FALSE;
+ SalutOlpcActivity *activity;
+
+ props_node = wocky_node_get_child_ns (node, "properties",
+ GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS);
+
+ if (props_node == NULL)
+ return FALSE;
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (
+ priv->olpc_activity_manager, room);
+
+ if (activity == NULL)
+ {
+ DEBUG ("no activity in room %d", room);
+ return FALSE;
+ }
+
+ properties = salut_wocky_node_extract_properties (props_node,
+ "property");
+
+ if (!extract_properties_from_hash (properties, &activity_id, &color,
+ &activity_name, &activity_type, &tags, &is_private, NULL))
+ return TRUE;
+
+ salut_olpc_activity_update (activity, room, activity_id, activity_name,
+ activity_type, color, tags, is_private);
+
+ g_hash_table_destroy (properties);
+
+ return TRUE;
+}
+
+static gboolean
+uninvite_stanza_callback (WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutConnection *self = SALUT_CONNECTION (user_data);
+ SalutConnectionPrivate *priv = self->priv;
+ TpHandleRepoIface *room_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *) self, TP_HANDLE_TYPE_ROOM);
+ WockyNode *node;
+ TpHandle room_handle;
+ const gchar *room, *activity_id;
+ SalutOlpcActivity *activity;
+ WockyNode *top_node = wocky_stanza_get_top_node (stanza);
+ SalutContact *contact = SALUT_CONTACT (wocky_stanza_get_from_contact (stanza));
+
+ node = wocky_node_get_child_ns (top_node, "uninvite",
+ GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS);
+ g_assert (node != NULL);
+
+ room = wocky_node_get_attribute (node, "room");
+ if (room == NULL)
+ {
+ DEBUG ("No room attribute");
+ return FALSE;
+ }
+
+ room_handle = tp_handle_lookup (room_repo, room, NULL, NULL);
+ if (room_handle == 0)
+ {
+ DEBUG ("room %s unknown", room);
+ return FALSE;
+ }
+
+ activity_id = wocky_node_get_attribute (node, "id");
+ if (activity_id == NULL)
+ {
+ DEBUG ("No id attribute");
+ return FALSE;
+ }
+
+ DEBUG ("received uninvite from %s", contact->name);
+
+ activity = salut_olpc_activity_manager_get_activity_by_room (
+ priv->olpc_activity_manager, room_handle);
+
+ if (activity == NULL)
+ return FALSE;
+
+ salut_contact_left_activity (contact, activity);
+
+ return TRUE;
+}
+
+#endif
+
+static GPtrArray *
+salut_connection_create_channel_factories (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+ GPtrArray *factories = g_ptr_array_sized_new (4);
+
+ /* Create the contact manager */
+ priv->contact_manager = salut_discovery_client_create_contact_manager (
+ priv->discovery_client, self);
+ g_signal_connect (priv->contact_manager, "contact-change",
+ G_CALLBACK (_contact_manager_contact_change_cb), self);
+
+#ifdef ENABLE_OLPC
+ priv->uninvite_handler_id = wocky_porter_register_handler_from_anyone (
+ self->porter,
+ WOCKY_STANZA_TYPE_MESSAGE, WOCKY_STANZA_SUB_TYPE_NONE,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ uninvite_stanza_callback, self,
+ '(', "uninvite",
+ ':', GIBBER_TELEPATHY_NS_OLPC_ACTIVITY_PROPS,
+ ')', NULL);
+
+ /* create the OLPC activity manager */
+ priv->olpc_activity_manager =
+ salut_discovery_client_create_olpc_activity_manager (
+ priv->discovery_client, self);
+ g_signal_connect (priv->olpc_activity_manager, "activity-modified",
+ G_CALLBACK (_olpc_activity_manager_activity_modified_cb), self);
+#endif
+
+ return factories;
+}
+
+#ifdef ENABLE_OLPC
+static void
+muc_channel_closed_cb (SalutMucChannel *chan,
+ SalutOlpcActivity *activity)
+{
+ SalutConnection *conn;
+ SalutConnectionPrivate *priv;
+ TpBaseConnection *base;
+ GPtrArray *activities = g_ptr_array_new ();
+
+ g_signal_handlers_disconnect_by_func (chan,
+ G_CALLBACK (muc_channel_closed_cb), activity);
+
+ g_object_get (activity,
+ "connection", &conn,
+ NULL);
+
+ priv = conn->priv;
+ base = (TpBaseConnection *) conn;
+
+ salut_self_remove_olpc_activity (priv->self, activity);
+
+ salut_self_foreach_olpc_activity (priv->self, append_activity, activities);
+ salut_svc_olpc_buddy_info_emit_activities_changed (conn, base->self_handle,
+ activities);
+ free_olpc_activities (activities);
+
+ /* we were holding a ref since the channel was opened */
+ g_object_unref (activity);
+
+ g_object_unref (conn);
+}
+
+static void
+muc_manager_new_channels_cb (TpChannelManager *channel_manager,
+ GHashTable *channels,
+ SalutConnection *conn)
+{
+ SalutConnectionPrivate *priv = conn->priv;
+ GHashTableIter iter;
+ gpointer chan;
+
+ g_hash_table_iter_init (&iter, channels);
+ while (g_hash_table_iter_next (&iter, &chan, NULL))
+ {
+ SalutOlpcActivity *activity;
+ TpHandle room_handle;
+
+ if (!SALUT_IS_MUC_CHANNEL (chan))
+ return;
+
+ g_object_get (chan,
+ "handle", &room_handle,
+ NULL);
+
+ /* ref the activity as long as we have a channel open */
+ activity = salut_olpc_activity_manager_ensure_activity_by_room (
+ priv->olpc_activity_manager,
+ room_handle);
+
+ g_signal_connect (chan, "closed", G_CALLBACK (muc_channel_closed_cb),
+ activity);
+ }
+}
+#endif
+
+static void
+add_to_array (gpointer data,
+ gpointer user_data)
+{
+ g_ptr_array_add (user_data, data);
+}
+
+static GPtrArray *
+salut_connection_create_channel_managers (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+ GPtrArray *managers = g_ptr_array_sized_new (1);
+ GPtrArray *tmp;
+ SalutPluginLoader *loader;
+
+ /* FIXME: The second and third arguments depend on create_channel_factories
+ * being called before this; should telepathy-glib guarantee that or
+ * should we be defensive?
+ */
+ priv->im_manager = salut_im_manager_new (self, priv->contact_manager);
+
+ priv->ft_manager = salut_ft_manager_new (self, priv->contact_manager);
+
+ priv->muc_manager = salut_discovery_client_create_muc_manager (
+ priv->discovery_client, self);
+
+ priv->roomlist_manager = salut_discovery_client_create_roomlist_manager (
+ priv->discovery_client, self);
+
+#if 0
+ priv->tubes_manager = salut_tubes_manager_new (self, priv->contact_manager);
+#endif
+
+ g_ptr_array_add (managers, priv->im_manager);
+ g_ptr_array_add (managers, priv->contact_manager);
+ g_ptr_array_add (managers, priv->ft_manager);
+ g_ptr_array_add (managers, priv->muc_manager);
+ g_ptr_array_add (managers, priv->roomlist_manager);
+#if 0
+ g_ptr_array_add (managers, priv->tubes_manager);
+#endif
+
+#ifdef ENABLE_OLPC
+ g_signal_connect (TP_CHANNEL_MANAGER (priv->muc_manager), "new-channels",
+ G_CALLBACK (muc_manager_new_channels_cb), self);
+#endif
+
+ /* plugin channel managers */
+ loader = salut_plugin_loader_dup ();
+ tmp = salut_plugin_loader_create_channel_managers (loader, base);
+ g_object_unref (loader);
+
+ g_ptr_array_foreach (tmp, add_to_array, managers);
+ g_ptr_array_free (tmp, TRUE);
+
+ return managers;
+}
+
+
+static gchar *
+salut_connection_get_unique_connection_name (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+
+ return g_strdup (priv->published_name);
+}
+
+static void
+force_close_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SalutConnection *self = SALUT_CONNECTION (user_data);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ GError *error = NULL;
+
+ if (!wocky_porter_force_close_finish (WOCKY_PORTER (source),
+ res, &error))
+ {
+ DEBUG ("force close failed: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ DEBUG ("connection properly closed (forced)");
+ }
+
+ tp_base_connection_finish_shutdown (base);
+
+ g_object_unref (self);
+}
+
+static void
+closed_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ SalutConnection *self = SALUT_CONNECTION (user_data);
+ SalutConnectionPrivate *priv = self->priv;
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+ GError *error = NULL;
+ gboolean force_called = priv->disconnect_timer == 0;
+
+ if (priv->disconnect_timer != 0)
+ {
+ /* stop the timer */
+ g_source_remove (priv->disconnect_timer);
+ priv->disconnect_timer = 0;
+ }
+
+ if (!wocky_porter_close_finish (WOCKY_PORTER (source), res, &error))
+ {
+ DEBUG ("close failed: %s", error->message);
+
+ if (g_error_matches (error, WOCKY_PORTER_ERROR,
+ WOCKY_PORTER_ERROR_FORCIBLY_CLOSED))
+ {
+ /* Close operation has been aborted because a force_close operation
+ * has been started. tp_base_connection_finish_shutdown will be
+ * called once this force_close operation is completed so we don't
+ * do it here. */
+
+ g_error_free (error);
+ goto out;
+ }
+
+ g_error_free (error);
+ }
+ else
+ {
+ DEBUG ("connection properly closed");
+ }
+
+ if (!force_called)
+ tp_base_connection_finish_shutdown (base);
+
+out:
+ g_object_unref (self);
+}
+
+static gboolean
+disconnect_timeout_cb (gpointer data)
+{
+ SalutConnection *self = SALUT_CONNECTION (data);
+ SalutConnectionPrivate *priv = self->priv;
+
+ DEBUG ("Close operation timed out. Force closing");
+ priv->disconnect_timer = 0;
+
+ wocky_porter_force_close_async (self->porter, NULL, force_close_cb, g_object_ref (self));
+ return FALSE;
+}
+
+static void
+salut_connection_shut_down (TpBaseConnection *base)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+
+ _salut_connection_disconnect (self);
+
+ if (self->session != NULL)
+ {
+ DEBUG ("connection may still be open; closing it: %p", self);
+
+ g_assert (priv->disconnect_timer == 0);
+ priv->disconnect_timer = g_timeout_add_seconds (DISCONNECT_TIMEOUT,
+ disconnect_timeout_cb, self);
+
+ wocky_porter_close_async (self->porter, NULL, closed_cb, g_object_ref (self));
+ return;
+ }
+
+ DEBUG ("session is not alive; clean up the base connection");
+ tp_base_connection_finish_shutdown (base);
+}
+
+static gboolean
+salut_connection_start_connecting (TpBaseConnection *base, GError **error)
+{
+ SalutConnection *self = SALUT_CONNECTION (base);
+ SalutConnectionPrivate *priv = self->priv;
+ GError *client_error = NULL;
+
+ g_signal_connect (priv->discovery_client, "state-changed",
+ G_CALLBACK (_discovery_client_state_changed_cb), self);
+
+ if (!salut_discovery_client_start (priv->discovery_client, &client_error))
+ {
+ *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Unable to initialize the avahi client: %s",
+ client_error->message);
+ DEBUG ("%s", (*error)->message);
+ g_error_free (client_error);
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ tp_base_connection_change_status (
+ TP_BASE_CONNECTION (base),
+ TP_CONNECTION_STATUS_DISCONNECTED,
+ TP_CONNECTION_STATUS_REASON_NETWORK_ERROR);
+ return FALSE;
+}
+
+/* sidecar stuff */
+static gchar *
+make_sidecar_path (
+ SalutConnection *conn,
+ const gchar *sidecar_iface)
+{
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
+
+ return g_strdelimit (
+ g_strdup_printf ("%s/Sidecar/%s", base_conn->object_path, sidecar_iface),
+ ".", '/');
+}
+
+static gchar *
+connection_install_sidecar (
+ SalutConnection *conn,
+ SalutSidecar *sidecar,
+ const gchar *sidecar_iface)
+{
+ SalutConnectionPrivate *priv = conn->priv;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ (TpBaseConnection *) conn);
+ gchar *path = make_sidecar_path (conn, sidecar_iface);
+
+ tp_dbus_daemon_register_object (bus, path, G_OBJECT (sidecar));
+ g_hash_table_insert (priv->sidecars, g_strdup (sidecar_iface),
+ g_object_ref (sidecar));
+
+ return path;
+}
+
+typedef struct {
+ SalutConnection *conn;
+ gchar *sidecar_iface;
+} Grr;
+
+static Grr *
+grr_new (
+ SalutConnection *conn,
+ const gchar *sidecar_iface)
+{
+ Grr *grr = g_slice_new (Grr);
+
+ grr->conn = g_object_ref (conn);
+ grr->sidecar_iface = g_strdup (sidecar_iface);
+
+ return grr;
+}
+
+static void
+grr_free (Grr *grr)
+{
+ g_object_unref (grr->conn);
+ g_free (grr->sidecar_iface);
+
+ g_slice_free (Grr, grr);
+}
+
+static void
+create_sidecar_cb (
+ GObject *loader_obj,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SalutPluginLoader *loader = SALUT_PLUGIN_LOADER (loader_obj);
+ Grr *ctx = user_data;
+ SalutConnection *conn = ctx->conn;
+ SalutConnectionPrivate *priv = conn->priv;
+ const gchar *sidecar_iface = ctx->sidecar_iface;
+ SalutSidecar *sidecar;
+ GList *contexts;
+ GError *error = NULL;
+
+ sidecar = salut_plugin_loader_create_sidecar_finish (loader, result, &error);
+ contexts = g_hash_table_lookup (priv->pending_sidecars, sidecar_iface);
+
+ if (contexts == NULL)
+ {
+ /* We never use the empty list as a value in pending_sidecars, so this
+ * must mean we've disconnected and already returned. Jettison the
+ * sidecar!
+ */
+ DEBUG ("creating sidecar %s %s after connection closed; jettisoning!",
+ sidecar_iface, (sidecar != NULL ? "succeeded" : "failed"));
+ goto out;
+ }
+
+ if (sidecar != NULL)
+ {
+ const gchar *actual_iface = salut_sidecar_get_interface (sidecar);
+
+ if (tp_strdiff (ctx->sidecar_iface, actual_iface))
+ {
+ /* TODO: maybe this lives in the loader? It knows what the plugin is
+ * called. */
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "A buggy plugin created a %s sidecar when asked to create %s",
+ actual_iface, ctx->sidecar_iface);
+ }
+ }
+ else /* sidecar == NULL */
+ {
+ /* If creating the sidecar failed, 'error' should have been set */
+ g_return_if_fail (error != NULL);
+ }
+
+ if (error == NULL)
+ {
+ gchar *path = connection_install_sidecar (ctx->conn, sidecar,
+ ctx->sidecar_iface);
+ GHashTable *props = salut_sidecar_get_immutable_properties (sidecar);
+ GList *l;
+
+ for (l = contexts; l != NULL; l = l->next)
+ salut_svc_connection_future_return_from_ensure_sidecar (l->data,
+ path, props);
+
+ g_hash_table_unref (props);
+ g_free (path);
+ }
+ else
+ {
+ g_list_foreach (contexts, (GFunc) dbus_g_method_return_error, error);
+ }
+
+ g_hash_table_remove (ctx->conn->priv->pending_sidecars, ctx->sidecar_iface);
+
+out:
+ tp_clear_object (&sidecar);
+ g_clear_error (&error);
+
+ grr_free (ctx);
+}
+
+static void
+salut_connection_ensure_sidecar (
+ SalutSvcConnectionFUTURE *iface,
+ const gchar *sidecar_iface,
+ DBusGMethodInvocation *context)
+{
+ SalutConnection *conn = SALUT_CONNECTION (iface);
+ SalutConnectionPrivate *priv = conn->priv;
+ TpBaseConnection *base_conn = TP_BASE_CONNECTION (conn);
+ SalutSidecar *sidecar;
+ gpointer key, value;
+ GError *error = NULL;
+
+ if (base_conn->status == TP_CONNECTION_STATUS_DISCONNECTED)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_DISCONNECTED,
+ "This connection has already disconnected" };
+
+ DEBUG ("already disconnected, declining request for %s", sidecar_iface);
+ dbus_g_method_return_error (context, &e);
+ return;
+ }
+
+ if (!tp_dbus_check_valid_interface_name (sidecar_iface, &error))
+ {
+ error->domain = TP_ERRORS;
+ error->code = TP_ERROR_INVALID_ARGUMENT;
+ DEBUG ("%s is malformed: %s", sidecar_iface, error->message);
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ sidecar = g_hash_table_lookup (priv->sidecars, sidecar_iface);
+
+ if (sidecar != NULL)
+ {
+ gchar *path = make_sidecar_path (conn, sidecar_iface);
+ GHashTable *props = salut_sidecar_get_immutable_properties (sidecar);
+
+ DEBUG ("sidecar %s already exists at %s", sidecar_iface, path);
+ salut_svc_connection_future_return_from_ensure_sidecar (context, path,
+ props);
+
+ g_free (path);
+ g_hash_table_unref (props);
+ return;
+ }
+
+ if (g_hash_table_lookup_extended (priv->pending_sidecars, sidecar_iface,
+ &key, &value))
+ {
+ GList *contexts = value;
+
+ DEBUG ("already awaiting %s, joining a queue of %u", sidecar_iface,
+ g_list_length (contexts));
+
+ contexts = g_list_prepend (contexts, context);
+ g_hash_table_steal (priv->pending_sidecars, key);
+ g_hash_table_insert (priv->pending_sidecars, key, contexts);
+ return;
+ }
+
+ DEBUG ("enqueuing first request for %s", sidecar_iface);
+ g_hash_table_insert (priv->pending_sidecars, g_strdup (sidecar_iface),
+ g_list_prepend (NULL, context));
+
+ if (base_conn->status == TP_CONNECTION_STATUS_CONNECTED)
+ {
+ SalutPluginLoader *loader = salut_plugin_loader_dup ();
+
+ DEBUG ("requesting %s from the plugin loader", sidecar_iface);
+ salut_plugin_loader_create_sidecar_async (loader, sidecar_iface, conn,
+ conn->session, create_sidecar_cb, grr_new (conn, sidecar_iface));
+ g_object_unref (loader);
+ }
+ else
+ {
+ DEBUG ("not yet connected; waiting.");
+ }
+}
+
+static void
+sidecars_conn_status_changed_cb (
+ SalutConnection *conn,
+ guint status,
+ guint reason,
+ gpointer unused)
+{
+ SalutConnectionPrivate *priv = conn->priv;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ (TpBaseConnection *) conn);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (status == TP_CONNECTION_STATUS_DISCONNECTED)
+ {
+ g_hash_table_iter_init (&iter, priv->sidecars);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ DEBUG ("removing %s from the bus", salut_sidecar_get_interface (value));
+ tp_dbus_daemon_unregister_object (bus, G_OBJECT (value));
+ }
+
+ g_hash_table_iter_init (&iter, priv->pending_sidecars);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const gchar *sidecar_iface = key;
+ GList *contexts = value;
+ GError *error = g_error_new (TP_ERRORS, TP_ERROR_CANCELLED,
+ "Disconnected before %s could be created", sidecar_iface);
+
+ DEBUG ("failing all %u requests for %s", g_list_length (contexts),
+ sidecar_iface);
+ g_list_foreach (contexts, (GFunc) dbus_g_method_return_error, error);
+ g_error_free (error);
+ }
+
+ g_hash_table_remove_all (priv->sidecars);
+ g_hash_table_remove_all (priv->pending_sidecars);
+ }
+ else if (status == TP_CONNECTION_STATUS_CONNECTED)
+ {
+ SalutPluginLoader *loader = salut_plugin_loader_dup ();
+
+ DEBUG ("connected; requesting sidecars from plugins");
+ g_hash_table_iter_init (&iter, priv->pending_sidecars);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ const gchar *sidecar_iface = key;
+
+ DEBUG ("requesting %s from the plugin loader", sidecar_iface);
+ salut_plugin_loader_create_sidecar_async (loader, sidecar_iface, conn,
+ conn->session, create_sidecar_cb, grr_new (conn, sidecar_iface));
+ }
+
+ g_object_unref (loader);
+ }
+}
+
+static void
+salut_conn_future_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ SalutSvcConnectionFUTUREClass *klass = g_iface;
+
+#define IMPLEMENT(x) \
+ salut_svc_connection_future_implement_##x (\
+ klass, salut_connection_##x)
+ IMPLEMENT(ensure_sidecar);
+#undef IMPLEMENT
+}
+
+WockySession *
+salut_connection_get_session (SalutConnection *connection)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (connection), NULL);
+
+ return connection->session;
+}
+
+const gchar *
+salut_connection_get_name (SalutConnection *connection)
+{
+ g_return_val_if_fail (SALUT_IS_CONNECTION (connection), NULL);
+
+ return connection->name;
+}