summaryrefslogtreecommitdiff
path: root/src/muc-channel.c
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2011-04-28 14:05:57 +0100
committerJonny Lamb <jonny.lamb@collabora.co.uk>2011-04-28 16:08:12 +0100
commit243c0b73d7ed3db43d13e37828bd28cb10c16cd6 (patch)
treebf4b4e584c17bfb04cbcda15663eaea945b0baf6 /src/muc-channel.c
parentc1790e71e42d774923b7c8273ea617a22b69acdb (diff)
downloadtelepathy-salut-243c0b73d7ed3db43d13e37828bd28cb10c16cd6.tar.gz
src: remove annoying salut- prefix
Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
Diffstat (limited to 'src/muc-channel.c')
-rw-r--r--src/muc-channel.c1423
1 files changed, 1423 insertions, 0 deletions
diff --git a/src/muc-channel.c b/src/muc-channel.c
new file mode 100644
index 00000000..1f20e10d
--- /dev/null
+++ b/src/muc-channel.c
@@ -0,0 +1,1423 @@
+/*
+ * muc-channel.c - Source for SalutMucChannel
+ * Copyright (C) 2006,2010 Collabora Ltd.
+ * Copyright (C) 2006 Nokia Corporation
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+
+#define DEBUG_FLAG DEBUG_MUC
+#include "debug.h"
+
+/* Maximum time to wait for others joining the group */
+#define CONNECTED_TIMEOUT 60 * 1000
+
+#include "muc-channel.h"
+
+#include <gibber/gibber-namespaces.h>
+#include <gibber/gibber-transport.h>
+
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/util.h>
+
+#include <gibber/gibber-muc-connection.h>
+
+#include "connection.h"
+#include "contact-manager.h"
+#include "self.h"
+#include "muc-manager.h"
+#include "util.h"
+
+#include "text-helper.h"
+#include "tube-stream.h"
+
+static void channel_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE(SalutMucChannel, salut_muc_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE(TP_TYPE_CHANNEL_IFACE, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+ tp_group_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
+ tp_message_mixin_text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
+ tp_message_mixin_messages_iface_init);
+)
+
+static const char *salut_muc_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_GROUP,
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES,
+ NULL
+};
+
+/* signal enum */
+enum
+{
+ READY,
+ JOIN_ERROR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+/* properties */
+enum
+{
+ PROP_OBJECT_PATH = 1,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_MUC_CONNECTION,
+ PROP_CONNECTION,
+ PROP_NAME,
+ PROP_CREATOR,
+ PROP_INTERFACES,
+ PROP_TARGET_ID,
+ PROP_REQUESTED,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_CHANNEL_PROPERTIES,
+ PROP_CHANNEL_DESTROYED,
+ LAST_PROPERTY
+};
+
+/* private structure */
+typedef struct _SalutMucChannelPrivate SalutMucChannelPrivate;
+
+struct _SalutMucChannelPrivate
+{
+ gboolean dispose_has_run;
+ gchar *object_path;
+ TpHandle handle;
+ SalutSelf *self;
+ GibberMucConnection *muc_connection;
+ gchar *muc_name;
+ gboolean connected;
+ gboolean creator;
+ guint timeout;
+ /* (gchar *) -> (SalutContact *) */
+ GHashTable *senders;
+ SalutMucManager *muc_manager;
+ TpHandle initiator;
+ gboolean requested;
+ gboolean closed;
+};
+
+#define SALUT_MUC_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SALUT_TYPE_MUC_CHANNEL, SalutMucChannelPrivate))
+
+/* Callback functions */
+static gboolean salut_muc_channel_send_stanza (SalutMucChannel *self,
+ guint type,
+ const gchar *token,
+ const gchar *text,
+ WockyStanza *stanza,
+ GError **error);
+static void salut_muc_channel_received_stanza (GibberMucConnection *conn,
+ const gchar *sender,
+ WockyStanza *stanza,
+ gpointer user_data);
+static gboolean
+salut_muc_channel_connect (SalutMucChannel *channel, GError **error);
+static void salut_muc_channel_disconnected (GibberTransport *transport,
+ gpointer user_data);
+static void salut_muc_channel_send (GObject *channel,
+ TpMessage *message,
+ TpMessageSendingFlags flags);
+
+static void
+salut_muc_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SalutMucChannel *chan = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (chan);
+ TpBaseConnection *base_conn = (TpBaseConnection *) chan->connection;
+
+ switch (property_id) {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, priv->object_path);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, priv->muc_name);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint (value, TP_HANDLE_TYPE_ROOM);
+ break;
+ case PROP_HANDLE:
+ g_value_set_uint (value, priv->handle);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, chan->connection);
+ break;
+ case PROP_MUC_CONNECTION:
+ g_value_set_object (value, priv->muc_connection);
+ break;
+ case PROP_CREATOR:
+ g_value_set_boolean (value, priv->creator);
+ break;
+ case PROP_INTERFACES:
+ g_value_set_static_boxed (value, salut_muc_channel_interfaces);
+ break;
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_ROOM);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+ case PROP_INITIATOR_HANDLE:
+ g_value_set_uint (value, priv->initiator);
+ break;
+ case PROP_INITIATOR_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->initiator));
+ }
+ break;
+ case PROP_REQUESTED:
+ g_value_set_boolean (value, priv->requested);
+ break;
+ case PROP_CHANNEL_DESTROYED:
+ /* TODO: this should be FALSE if there are still pending messages, so
+ * the channel manager can respawn the channel.
+ */
+ g_value_set_boolean (value, TRUE);
+ break;
+ case PROP_CHANNEL_PROPERTIES:
+ g_value_take_boxed (value,
+ tp_dbus_properties_mixin_make_properties_hash (object,
+ TP_IFACE_CHANNEL, "TargetHandle",
+ TP_IFACE_CHANNEL, "TargetHandleType",
+ TP_IFACE_CHANNEL, "ChannelType",
+ TP_IFACE_CHANNEL, "TargetID",
+ TP_IFACE_CHANNEL, "InitiatorHandle",
+ TP_IFACE_CHANNEL, "InitiatorID",
+ TP_IFACE_CHANNEL, "Requested",
+ TP_IFACE_CHANNEL, "Interfaces",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessagePartSupportFlags",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "DeliveryReportingSupport",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "SupportedContentTypes",
+ TP_IFACE_CHANNEL_INTERFACE_MESSAGES, "MessageTypes",
+ NULL));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_muc_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SalutMucChannel *chan = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (chan);
+ const gchar *tmp;
+
+ switch (property_id) {
+ case PROP_OBJECT_PATH:
+ g_free (priv->object_path);
+ priv->object_path = g_value_dup_string (value);
+ break;
+ case PROP_NAME:
+ g_free (priv->muc_name);
+ priv->muc_name = g_value_dup_string (value);
+ break;
+ case PROP_HANDLE:
+ priv->handle = g_value_get_uint (value);
+ break;
+ case PROP_CONNECTION:
+ chan->connection = g_value_get_object (value);
+ break;
+ case PROP_MUC_CONNECTION:
+ priv->muc_connection = g_value_get_object (value);
+ break;
+ case PROP_HANDLE_TYPE:
+ g_assert (g_value_get_uint (value) == 0
+ || g_value_get_uint (value) == TP_HANDLE_TYPE_ROOM);
+ break;
+ case PROP_CHANNEL_TYPE:
+ tmp = g_value_get_string (value);
+ g_assert (tmp == NULL
+ || !tp_strdiff (g_value_get_string (value),
+ TP_IFACE_CHANNEL_TYPE_TEXT));
+ break;
+ case PROP_CREATOR:
+ priv->creator = g_value_get_boolean (value);
+ break;
+ case PROP_INITIATOR_HANDLE:
+ priv->initiator = g_value_get_uint (value);
+ break;
+ case PROP_REQUESTED:
+ priv->requested = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+salut_muc_channel_add_self_to_members (SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ TpIntSet *empty;
+ TpIntSet *add;
+
+ priv->connected = TRUE;
+ g_signal_emit (self, signals[READY], 0);
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ /* Now we are connected, move yourself to members */
+ empty = tp_intset_new ();
+ add = tp_intset_new ();
+ tp_intset_add (add, base_connection->self_handle);
+
+ tp_group_mixin_change_members (G_OBJECT (self),
+ "", add, empty, empty, empty, base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+
+ tp_intset_destroy (empty);
+ tp_intset_destroy (add);
+}
+
+static gboolean
+connected_timeout_cb (gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ DEBUG ("Didn't receive muc senders. Timeout expired, "
+ "adding myself as member anyway");
+ salut_muc_channel_add_self_to_members (self);
+ priv->timeout = 0;
+
+ return FALSE;
+}
+
+static void
+muc_connection_connected_cb (GibberMucConnection *connection,
+ SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->creator)
+ {
+ DEBUG ("I created this muc. Adding myself as member now");
+ salut_muc_channel_add_self_to_members (self);
+ }
+ else
+ {
+ DEBUG ("I didn't create this muc. Waiting for senders before adding "
+ "myself as member");
+ priv->timeout = g_timeout_add (CONNECTED_TIMEOUT, connected_timeout_cb,
+ self);
+ }
+
+ salut_muc_channel_publish_service (self);
+}
+
+#define NUM_SUPPORTED_MESSAGE_TYPES 3
+
+static GObject *
+salut_muc_channel_constructor (GType type, guint n_props,
+ GObjectConstructParam *props)
+{
+ GObject *obj;
+ TpDBusDaemon *bus;
+ SalutMucChannel *self;
+ SalutMucChannelPrivate *priv;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *handle_repo;
+ TpHandleRepoIface *contact_repo;
+ TpChannelTextMessageType types[NUM_SUPPORTED_MESSAGE_TYPES] = {
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+ };
+ const gchar * supported_content_types[] = {
+ "text/plain",
+ NULL
+ };
+
+ /* Parent constructor chain */
+ obj = G_OBJECT_CLASS (salut_muc_channel_parent_class)->
+ constructor (type, n_props, props);
+
+ self = SALUT_MUC_CHANNEL (obj);
+ priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ /* Ref our handle */
+ base_conn = TP_BASE_CONNECTION (self->connection);
+
+ handle_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_ROOM);
+
+ tp_handle_ref (handle_repo, priv->handle);
+
+ /* Message mixin initialisation */
+ tp_message_mixin_init (obj, G_STRUCT_OFFSET (SalutMucChannel, message_mixin),
+ base_conn);
+ tp_message_mixin_implement_sending (obj, salut_muc_channel_send,
+ NUM_SUPPORTED_MESSAGE_TYPES, types, 0,
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES |
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES,
+ supported_content_types);
+
+ g_object_get (self->connection, "self", &(priv->self), NULL);
+ g_object_unref (priv->self);
+ g_assert (priv->self != NULL);
+
+ g_assert (priv->muc_connection != NULL);
+
+ priv->connected = FALSE;
+ g_signal_connect (priv->muc_connection, "connected",
+ G_CALLBACK (muc_connection_connected_cb), obj);
+
+ g_object_get (self->connection,
+ "muc-manager", &(priv->muc_manager),
+ NULL);
+ g_assert (priv->muc_manager != NULL);
+
+ /* no need to keep a ref on the muc manager as it keeps a ref on us */
+ g_object_unref (priv->muc_manager);
+
+ /* Connect to the bus */
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+
+ contact_repo = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+ tp_group_mixin_init (obj, G_STRUCT_OFFSET(SalutMucChannel, group),
+ contact_repo, base_conn->self_handle);
+
+ tp_group_mixin_change_flags (obj,
+ TP_CHANNEL_GROUP_FLAG_PROPERTIES |
+ TP_CHANNEL_GROUP_FLAG_CAN_ADD |
+ TP_CHANNEL_GROUP_FLAG_MESSAGE_ADD,
+ 0);
+
+ return obj;
+}
+
+static void
+salut_muc_channel_init (SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ /* allocate any data required by the object here */
+ priv->object_path = NULL;
+ self->connection = NULL;
+ priv->muc_name = NULL;
+ priv->timeout = 0;
+ priv->senders = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+}
+
+static void salut_muc_channel_dispose (GObject *object);
+static void salut_muc_channel_finalize (GObject *object);
+
+static void
+invitation_append_parameter (gpointer key, gpointer value, gpointer data)
+{
+ WockyNode *node = (WockyNode *) data;
+ wocky_node_add_child_with_content (node, (gchar *) key,
+ (gchar *) value);
+}
+
+static WockyStanza *
+create_invitation (SalutMucChannel *self, SalutContact *contact,
+ const gchar *message)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION(self->connection);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+ WockyStanza *msg;
+ WockyNode *invite_node;
+
+ msg = wocky_stanza_build_to_contact (WOCKY_STANZA_TYPE_MESSAGE,
+ WOCKY_STANZA_SUB_TYPE_NORMAL,
+ self->connection->name, WOCKY_CONTACT (contact),
+ '(', "body",
+ '$', "You got a Clique chatroom invitation",
+ ')',
+ '(', "invite",
+ '*', &invite_node,
+ ':', GIBBER_TELEPATHY_NS_CLIQUE,
+ '(', "roomname",
+ '$', tp_handle_inspect (room_repo, priv->handle),
+ ')',
+ ')',
+ NULL);
+
+ if (message != NULL && *message != '\0')
+ {
+ wocky_node_add_child_with_content (invite_node, "reason", message);
+ }
+
+ g_hash_table_foreach (
+ (GHashTable *) gibber_muc_connection_get_parameters (
+ priv->muc_connection),
+ invitation_append_parameter, invite_node);
+
+#ifdef ENABLE_OLPC
+ salut_self_olpc_augment_invitation (priv->self, priv->handle, contact->handle,
+ invite_node);
+#endif
+
+ return msg;
+}
+
+gboolean
+salut_muc_channel_publish_service (SalutMucChannel *self)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ return SALUT_MUC_CHANNEL_GET_CLASS (self)->publish_service (self,
+ priv->muc_connection, priv->muc_name);
+}
+
+typedef struct
+{
+ SalutMucChannel *self;
+ SalutContact *contact;
+} SendInviteData;
+
+static void
+send_invite_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WockyPorter *porter = WOCKY_PORTER (source_object);
+ SendInviteData *data = user_data;
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (
+ data->self->connection);
+ GError *error = NULL;
+ TpHandle handle;
+ TpIntSet *empty, *removed;
+
+ if (wocky_porter_send_finish (porter, result, &error))
+ /* success */
+ goto out;
+
+ /* failure */
+ DEBUG ("Failed to send stanza: %s", error->message);
+ g_clear_error (&error);
+
+ handle = data->contact->handle;
+
+ empty = tp_intset_new ();
+ removed = tp_intset_new ();
+ tp_intset_add (removed, handle);
+
+ tp_group_mixin_change_members (G_OBJECT (data->self), "", empty, removed, empty,
+ empty, base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_ERROR);
+
+ tp_intset_destroy (empty);
+ tp_intset_destroy (removed);
+
+out:
+ g_object_unref (data->contact);
+ g_slice_free (SendInviteData, data);
+}
+
+gboolean
+salut_muc_channel_send_invitation (SalutMucChannel *self,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ WockyStanza *stanza;
+ SalutContactManager *contact_manager = NULL;
+ SalutContact *contact;
+ WockyPorter *porter = self->connection->porter;
+ SendInviteData *data;
+
+ g_object_get (G_OBJECT (self->connection), "contact-manager",
+ &contact_manager, NULL);
+ g_assert (contact_manager != NULL);
+
+ contact = salut_contact_manager_get_contact (contact_manager, handle);
+ g_object_unref (contact_manager);
+
+ if (contact == NULL)
+ {
+ *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Couldn't contact the contact");
+ return FALSE;
+ }
+
+ DEBUG ("request XMPP connection with contact %s", contact->name);
+
+ stanza = create_invitation (self, contact, message);
+
+ data = g_slice_new0 (SendInviteData);
+ data->self = self;
+ data->contact = contact; /* steal the ref */
+
+ wocky_porter_send_async (porter, stanza, NULL, send_invite_cb, data);
+
+ g_object_unref (stanza);
+ return TRUE;
+}
+
+/* FIXME: This is an ugly workaround. See fd.o #15092
+ * We shouldn't export this function */
+gboolean
+salut_muc_channel_add_member (GObject *iface,
+ TpHandle handle,
+ const gchar *message,
+ GError **error)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL(iface);
+#ifdef ENABLE_DEBUG
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+#endif
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ TpIntSet *empty, *remote_pending;
+
+ if (handle == base_connection->self_handle)
+ {
+ /* adding yourself, let's join the muc */
+ TpIntSet *empty_;
+ TpIntSet *add;
+ gboolean ret = TRUE;
+
+ if (tp_handle_set_is_member (self->group.remote_pending,
+ base_connection->self_handle))
+ {
+ /* Already in remote pending, no need to redo */
+ return TRUE;
+ }
+
+ empty_ = tp_intset_new ();
+ add = tp_intset_new ();
+ tp_intset_add (add, handle);
+ /* Add to members */
+
+ if (salut_muc_channel_connect (self, NULL))
+ {
+ /* We are considered as remote-pending while the muc connection
+ * is not connected */
+ tp_group_mixin_change_members (G_OBJECT (self),
+ message, empty_, empty_, empty_, add,
+ base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ }
+ else
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ "Failed to connect to the group");
+ ret = FALSE;
+ }
+
+ tp_intset_destroy (empty_);
+ tp_intset_destroy (add);
+ return ret;
+ }
+
+ /* Adding a contact, let's invite him */
+
+ DEBUG ("Trying to add handle %u to %s", handle ,priv->object_path);
+
+ if (!salut_muc_channel_send_invitation (self, handle, message, error))
+ return FALSE;
+
+ /* Set the contact as remote pending */
+ empty = tp_intset_new ();
+ remote_pending = tp_intset_new ();
+ tp_intset_add (remote_pending, handle);
+ tp_group_mixin_change_members (G_OBJECT(self), "", empty, empty, empty,
+ remote_pending, base_connection->self_handle,
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+ tp_intset_destroy (empty);
+ tp_intset_destroy (remote_pending);
+
+ return TRUE;
+}
+
+static void
+salut_muc_channel_leave (SalutMucChannel *self,
+ TpChannelGroupChangeReason reason,
+ const gchar *message)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->closed)
+ return;
+
+ if (priv->connected)
+ {
+ /* FIXME: send a part-message based on reason and message first,
+ * once we've defined how */
+
+ /* priv->closed will be set in salut_muc_channel_disconnected */
+ gibber_muc_connection_disconnect (priv->muc_connection);
+ }
+ else
+ {
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+}
+
+static gboolean
+salut_muc_channel_remove_member_with_reason (GObject *object,
+ TpHandle handle,
+ const gchar *message,
+ guint reason,
+ GError **error)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (object);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+
+ if (handle != base_connection->self_handle)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Contacts cannot be kicked from Clique chatrooms");
+ return FALSE;
+ }
+
+ salut_muc_channel_leave (self, reason, message);
+ return TRUE;
+}
+
+static void
+salut_muc_channel_class_init (SalutMucChannelClass *salut_muc_channel_class) {
+ GObjectClass *object_class = G_OBJECT_CLASS (salut_muc_channel_class);
+ GParamSpec *param_spec;
+ static TpDBusPropertiesMixinPropImpl channel_props[] = {
+ { "TargetHandleType", "handle-type", NULL },
+ { "TargetHandle", "handle", NULL },
+ { "TargetID", "target-id", NULL },
+ { "ChannelType", "channel-type", NULL },
+ { "Interfaces", "interfaces", NULL },
+ { "Requested", "requested", NULL },
+ { "InitiatorHandle", "initiator-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ g_type_class_add_private (salut_muc_channel_class,
+ sizeof (SalutMucChannelPrivate));
+
+ object_class->dispose = salut_muc_channel_dispose;
+ object_class->finalize = salut_muc_channel_finalize;
+
+ object_class->constructor = salut_muc_channel_constructor;
+ object_class->get_property = salut_muc_channel_get_property;
+ object_class->set_property = salut_muc_channel_set_property;
+
+ g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+ "object-path");
+ g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+ "channel-type");
+ g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+ "handle-type");
+ g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+ g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
+ "channel-destroyed");
+ g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
+ "channel-properties");
+
+
+ param_spec = g_param_spec_string ("target-id", "Target JID",
+ "The string obtained by inspecting this channel's handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+ param_spec = g_param_spec_boolean ("requested", "Requested?",
+ "True if this channel was requested by the local user",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+ param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+ "The contact which invited us to the MUC, or ourselves if we joined of "
+ "our own accord",
+ 0, G_MAXUINT32, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator JID",
+ "The string obtained by inspecting this channel's initiator-handle",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+ param_spec);
+
+ param_spec = g_param_spec_string ("name",
+ "Name of the muc group",
+ "The name of the muc group",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_NAME, param_spec);
+
+ param_spec = g_param_spec_object ("muc-connection",
+ "The GibberMucConnection",
+ "muc connection object",
+ GIBBER_TYPE_MUC_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_MUC_CONNECTION, param_spec);
+ param_spec = g_param_spec_object ("connection",
+ "SalutConnection object",
+ "Salut Connection that owns the"
+ "connection for this IM channel",
+ SALUT_TYPE_CONNECTION,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_CONNECTION, param_spec);
+
+ param_spec = g_param_spec_boolean (
+ "creator",
+ "creator",
+ "Whether or not we created this muc",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class,
+ PROP_CREATOR, param_spec);
+
+ param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+ "Additional Channel.Interface.* interfaces",
+ G_TYPE_STRV,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+
+ signals[READY] = g_signal_new (
+ "ready",
+ G_OBJECT_CLASS_TYPE (salut_muc_channel_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[JOIN_ERROR] = g_signal_new (
+ "join-error",
+ G_OBJECT_CLASS_TYPE (salut_muc_channel_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ salut_muc_channel_class->dbus_props_class.interfaces = prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (SalutMucChannelClass, dbus_props_class));
+
+ tp_group_mixin_class_init (object_class,
+ G_STRUCT_OFFSET(SalutMucChannelClass, group_class),
+ salut_muc_channel_add_member, NULL);
+ tp_group_mixin_init_dbus_properties (object_class);
+
+ tp_group_mixin_class_allow_self_removal (object_class);
+ tp_group_mixin_class_set_remove_with_reason_func (object_class,
+ salut_muc_channel_remove_member_with_reason);
+
+ tp_message_mixin_init_dbus_properties (object_class);
+}
+
+void
+salut_muc_channel_dispose (GObject *object)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ g_signal_handlers_disconnect_matched (priv->muc_connection,
+ G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
+
+ if (priv->muc_connection != NULL)
+ {
+ g_object_unref (priv->muc_connection);
+ priv->muc_connection = NULL;
+ }
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ if (priv->senders != NULL)
+ {
+ g_hash_table_destroy (priv->senders);
+ priv->senders = NULL;
+ }
+
+ if (!priv->closed)
+ {
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ /* release any references held by the object here */
+ if (G_OBJECT_CLASS (salut_muc_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (salut_muc_channel_parent_class)->dispose (object);
+}
+
+void
+salut_muc_channel_finalize (GObject *object)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (object);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+ g_free (priv->muc_name);
+
+ tp_group_mixin_finalize (object);
+ tp_message_mixin_finalize (object);
+
+ G_OBJECT_CLASS (salut_muc_channel_parent_class)->finalize (object);
+}
+
+gboolean
+salut_muc_channel_invited (SalutMucChannel *self, TpHandle inviter,
+ const gchar *stanza, GError **error)
+{
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+#ifdef ENABLE_DEBUG
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+ TpHandleRepoIface *room_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_ROOM);
+#endif
+ gboolean ret = TRUE;
+
+ /* Got invited to this muc channel */
+ DEBUG ("Got an invitation to %s from %s",
+ tp_handle_inspect (room_repo, priv->handle),
+ tp_handle_inspect (contact_repo, inviter));
+
+ /* If we are already a member, no further actions are needed */
+ if (tp_handle_set_is_member (self->group.members,
+ base_connection->self_handle)) {
+ return TRUE;
+ }
+
+ if (inviter == base_connection->self_handle)
+ {
+ /* Invited ourselves, go straight to members */
+ gboolean r;
+ GArray *members = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
+ g_array_append_val (members, base_connection->self_handle);
+ r = tp_group_mixin_add_members (G_OBJECT (self), members, "", error);
+ g_assert (r);
+ g_array_free (members, TRUE);
+ }
+ else
+ {
+ TpIntSet *empty = tp_intset_new ();
+ TpIntSet *local_pending = tp_intset_new ();
+
+ g_assert (stanza != NULL);
+
+ tp_intset_add (local_pending, base_connection->self_handle);
+ tp_group_mixin_change_members (G_OBJECT(self), stanza,
+ empty, empty,
+ local_pending, empty,
+ inviter,
+ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
+ tp_intset_destroy (local_pending);
+ tp_intset_destroy (empty);
+ }
+
+ return ret;
+}
+
+/* Private functions */
+static gboolean
+salut_muc_channel_send_stanza (SalutMucChannel *self, guint type,
+ const gchar *token,
+ const gchar *text,
+ WockyStanza *stanza,
+ GError **error)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (!gibber_muc_connection_send (priv->muc_connection, stanza, error)) {
+ text_helper_report_delivery_error (TP_SVC_CHANNEL (self),
+ TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN, time (NULL), type, token, text);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+salut_muc_channel_add_members (SalutMucChannel *self,
+ GArray *members)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpIntSet *empty, *changes;
+ guint i;
+ SalutContactManager *contact_mgr;
+
+ empty = tp_intset_new ();
+ changes = tp_intset_new ();
+
+ g_object_get (G_OBJECT (self->connection), "contact-manager",
+ &contact_mgr, NULL);
+ g_assert (contact_mgr != NULL);
+
+ for (i = 0; i < members->len; i++)
+ {
+ gchar *sender = g_array_index (members, gchar *, i);
+ SalutContact *contact;
+
+ contact = salut_contact_manager_ensure_contact (contact_mgr, sender);
+
+ g_hash_table_insert (priv->senders, sender, contact);
+ tp_intset_add (changes, contact->handle);
+ }
+
+ tp_group_mixin_change_members (G_OBJECT(self),
+ "",
+ changes,
+ empty,
+ empty, empty,
+ 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (changes);
+ tp_intset_destroy (empty);
+ g_object_unref (contact_mgr);
+}
+
+static void
+salut_muc_channel_remove_members (SalutMucChannel *self,
+ GArray *members)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection =
+ (TpBaseConnection *) (self->connection);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ (base_connection, TP_HANDLE_TYPE_CONTACT);
+ TpIntSet *empty, *changes;
+ guint i;
+
+ empty = tp_intset_new ();
+ changes = tp_intset_new ();
+
+ for (i = 0; i < members->len; i++)
+ {
+ TpHandle handle;
+ gchar *sender = g_array_index (members, gchar *, i);
+
+ handle = tp_handle_lookup (contact_repo, sender, NULL, NULL);
+ if (handle == 0)
+ {
+ DEBUG ("Lost sender (%s), but unknown contact", sender);
+ continue;
+ }
+
+ g_hash_table_remove (priv->senders, sender);
+
+ tp_intset_add (changes, handle);
+ }
+
+ tp_group_mixin_change_members (G_OBJECT(self),
+ "",
+ empty,
+ changes,
+ empty, empty,
+ 0,
+ TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+ tp_intset_destroy (changes);
+ tp_intset_destroy (empty);
+}
+
+static void
+salut_muc_channel_received_stanza (GibberMucConnection *conn,
+ const gchar *sender,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+ TpHandleRepoIface *contact_repo =
+ tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
+
+ const gchar *from, *to, *body, *body_offset;
+ TpChannelTextMessageType msgtype;
+ TpHandle from_handle;
+ WockyNode *node = wocky_stanza_get_top_node (stanza);
+ WockyNode *tubes_node;
+
+ to = wocky_node_get_attribute (node, "to");
+ if (strcmp (to, priv->muc_name)) {
+ DEBUG("Stanza to another muc group, discarding");
+ return;
+ }
+
+ from_handle = tp_handle_lookup (contact_repo, sender, NULL, NULL);
+ if (from_handle == 0)
+ {
+ /* FIXME, unknown contact.. Need some way to handle this safely,
+ * just adding the contact is somewhat scary */
+ DEBUG("Got stanza from unknown contact, discarding");
+ return;
+ }
+
+#ifdef ENABLE_OLPC
+ if (salut_connection_olpc_observe_muc_stanza (self->connection, priv->handle,
+ from_handle, stanza))
+ return;
+#endif
+
+ tubes_node = wocky_node_get_child_ns (node, "tubes",
+ GIBBER_TELEPATHY_NS_TUBES);
+ if (tubes_node != NULL)
+ {
+ SalutTubesChannel *tubes_chan;
+ GPtrArray *tubes;
+ guint i;
+ GHashTable *channels;
+ gboolean created;
+
+ tubes_chan = salut_muc_manager_ensure_tubes_channel (priv->muc_manager,
+ priv->handle, from_handle, &created);
+ g_assert (tubes_chan != NULL);
+
+ channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, NULL);
+
+ if (created)
+ {
+ g_hash_table_insert (channels, tubes_chan, NULL);
+ }
+
+ tubes = salut_tubes_channel_muc_message_received (tubes_chan, sender,
+ stanza);
+
+ for (i = 0; i < tubes->len; i++)
+ {
+ SalutTubeIface *tube;
+
+ tube = g_ptr_array_index (tubes, i);
+ g_hash_table_insert (channels, tube, NULL);
+ }
+
+ if (g_hash_table_size (channels) > 0)
+ {
+ tp_channel_manager_emit_new_channels (priv->muc_manager, channels);
+ }
+
+ g_object_unref (tubes_chan);
+ g_ptr_array_free (tubes, TRUE);
+ g_hash_table_destroy (channels);
+ }
+
+ if (!text_helper_parse_incoming_message (stanza, &from, &msgtype,
+ &body, &body_offset))
+ {
+ DEBUG("Couldn't parse stanza");
+ return;
+ }
+
+ if (body == NULL)
+ {
+ DEBUG("Message with an empty body");
+ return;
+ }
+
+ /* FIXME validate the from and the to */
+ tp_message_mixin_take_received (G_OBJECT (self),
+ text_helper_create_received_message (base_connection, from_handle,
+ time (NULL), msgtype, body_offset));
+}
+
+static void
+salut_muc_channel_new_senders (GibberMucConnection *connection,
+ GArray *senders,
+ gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ TpBaseConnection *base_connection = TP_BASE_CONNECTION (self->connection);
+
+ salut_muc_channel_add_members (self, senders);
+ if (!tp_handle_set_is_member (self->group.members,
+ base_connection->self_handle))
+ {
+ DEBUG ("Got new senders. Adding myself as member");
+ salut_muc_channel_add_self_to_members (self);
+ }
+}
+
+static void
+salut_muc_channel_lost_senders (GibberMucConnection *connection,
+ GArray *senders, gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+
+ salut_muc_channel_remove_members (self, senders);
+}
+
+static gboolean
+salut_muc_channel_connect (SalutMucChannel *channel,
+ GError **error)
+{
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (channel);
+
+ g_signal_connect (priv->muc_connection, "received-stanza",
+ G_CALLBACK (salut_muc_channel_received_stanza), channel);
+
+ g_signal_connect (priv->muc_connection, "disconnected",
+ G_CALLBACK (salut_muc_channel_disconnected), channel);
+
+ g_signal_connect (priv->muc_connection, "new-senders",
+ G_CALLBACK (salut_muc_channel_new_senders), channel);
+
+ g_signal_connect (priv->muc_connection, "lost-senders",
+ G_CALLBACK (salut_muc_channel_lost_senders), channel);
+
+ return gibber_muc_connection_connect (priv->muc_connection, error);
+}
+
+static void
+salut_muc_channel_disconnected (GibberTransport *transport, gpointer user_data)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (user_data);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->timeout != 0)
+ {
+ g_source_remove (priv->timeout);
+ priv->timeout = 0;
+ }
+
+ if (!priv->connected)
+ {
+ /* FIXME the disconnected signal should give us an error */
+ GError error = { TP_ERRORS, TP_ERROR_NETWORK_ERROR,
+ "can't join the muc" };
+ g_signal_emit (self, signals[JOIN_ERROR], 0, &error);
+ }
+
+ if (priv->closed)
+ return;
+
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+}
+
+/* channel interfaces */
+/**
+ * salut_muc_channel_get_interfaces
+ *
+ * Implements D-Bus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_get_interfaces (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_interfaces (context,
+ salut_muc_channel_interfaces);
+}
+
+
+/**
+ * salut_muc_channel_get_handle
+ *
+ * Implements D-Bus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (iface);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_ROOM,
+ priv->handle);
+}
+/**
+ * salut_muc_channel_get_channel_type
+ *
+ * Implements D-Bus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+/**
+ * salut_muc_channel_close
+ *
+ * Implements D-Bus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ *
+ * @error: Used to return a pointer to a GError detailing any error
+ * that occurred, D-Bus will throw the error only if this
+ * function returns FALSE.
+ *
+ * Returns: TRUE if successful, FALSE if an error was thrown.
+ */
+static void
+salut_muc_channel_close (TpSvcChannel *iface, DBusGMethodInvocation *context)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL (iface);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE (self);
+
+ if (priv->closed)
+ {
+ GError already = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Channel already closed"};
+ DEBUG ("channel already closed");
+
+ dbus_g_method_return_error (context, &already);
+ return;
+ }
+
+ salut_muc_channel_leave (self, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, "");
+ tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, salut_muc_channel_##x)
+ IMPLEMENT (close);
+ IMPLEMENT (get_channel_type);
+ IMPLEMENT (get_handle);
+ IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+salut_muc_channel_send (GObject *channel,
+ TpMessage *message,
+ TpMessageSendingFlags flags)
+{
+ SalutMucChannel *self = SALUT_MUC_CHANNEL(channel);
+ SalutMucChannelPrivate *priv = SALUT_MUC_CHANNEL_GET_PRIVATE(self);
+ GError *error = NULL;
+ WockyStanza *stanza = NULL;
+ guint type;
+ gchar *text = NULL;
+ gchar *token = NULL;
+
+ if (!text_helper_validate_tp_message (message, &type, &token, &text, &error))
+ goto error;
+
+ stanza = text_helper_create_message_groupchat (self->connection->name,
+ priv->muc_name, type, text, &error);
+
+ if (stanza == NULL)
+ goto error;
+
+ if (!salut_muc_channel_send_stanza (self, type, token,
+ text, stanza, &error))
+ goto error;
+
+ tp_message_mixin_sent (channel, message, 0, token, NULL);
+ g_free (token);
+ g_object_unref (G_OBJECT (stanza));
+ return;
+
+error:
+ if (stanza != NULL)
+ g_object_unref (G_OBJECT (stanza));
+ tp_message_mixin_sent (channel, message, 0, NULL, error);
+ g_error_free (error);
+ g_free (text);
+ g_free (token);
+ return;
+}
+