X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy%2Fempathy-tp-chat.c;h=b9b731384f5fa7be91dd3e6d8fec7e18feac003d;hp=36ac8a7a854c3193c29a2c78e08ae10a6b094965;hb=20075065ac090531ca887c900bf8b0cb367a0336;hpb=69814b0fd3717aadde21b392c875033865903f07 diff --git a/libempathy/empathy-tp-chat.c b/libempathy/empathy-tp-chat.c index 36ac8a7a..b9b73138 100644 --- a/libempathy/empathy-tp-chat.c +++ b/libempathy/empathy-tp-chat.c @@ -1,297 +1,429 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* - * Copyright (C) 2007 Collabora Ltd. + * Copyright (C) 2007-2008 Collabora Ltd. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. + * 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 program is distributed in the hope that it will be useful, + * 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 - * General Public License for more details. + * Lesser General Public License for more details. * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * 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 * * Authors: Xavier Claessens */ #include -#include -#include -#include -#include +#include + +#include +#include +#include #include "empathy-tp-chat.h" -#include "empathy-contact-manager.h" +#include "empathy-tp-contact-factory.h" +#include "empathy-contact-monitor.h" #include "empathy-contact-list.h" -#include "empathy-session.h" #include "empathy-marshal.h" -#include "gossip-debug.h" -#include "gossip-time.h" - -#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ - EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv)) - -#define DEBUG_DOMAIN "TpChat" - -struct _EmpathyTpChatPriv { - EmpathyContactList *list; - McAccount *account; - gchar *id; +#include "empathy-time.h" +#include "empathy-utils.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_TP | EMPATHY_DEBUG_CHAT +#include "empathy-debug.h" + +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyTpChat) +typedef struct { + gboolean dispose_has_run; + EmpathyTpContactFactory *factory; + EmpathyContactMonitor *contact_monitor; + EmpathyContact *user; + EmpathyContact *remote_contact; + GList *members; + TpChannel *channel; + gboolean listing_pending_messages; + /* Queue of messages not signalled yet */ + GQueue *messages_queue; + /* Queue of messages signalled but not acked yet */ + GQueue *pending_messages_queue; + gboolean had_properties_list; + GPtrArray *properties; + gboolean ready; +} EmpathyTpChatPriv; + +typedef struct { + gchar *name; + guint id; + TpPropertyFlags flags; + GValue *value; +} TpChatProperty; + +static void tp_chat_iface_init (EmpathyContactListIface *iface); - TpChan *tp_chan; - DBusGProxy *text_iface; - DBusGProxy *chat_state_iface; +enum { + PROP_0, + PROP_CHANNEL, + PROP_REMOTE_CONTACT, + PROP_READY, }; -static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass); -static void empathy_tp_chat_init (EmpathyTpChat *chat); -static void tp_chat_finalize (GObject *object); -static void tp_chat_destroy_cb (TpChan *text_chan, - EmpathyTpChat *chat); -static void tp_chat_received_cb (DBusGProxy *text_iface, - guint message_id, - guint timestamp, - guint from_handle, - guint message_type, - guint message_flags, - gchar *message_body, - EmpathyTpChat *chat); -static void tp_chat_sent_cb (DBusGProxy *text_iface, - guint timestamp, - guint message_type, - gchar *message_body, - EmpathyTpChat *chat); -static void tp_chat_state_changed_cb (DBusGProxy *chat_state_iface, - guint handle, - EmpathyTpChatState state, - EmpathyTpChat *chat); -static void tp_chat_emit_message (EmpathyTpChat *chat, - guint type, - guint timestamp, - guint from_handle, - const gchar *message_body); - enum { MESSAGE_RECEIVED, + SEND_ERROR, CHAT_STATE_CHANGED, + PROPERTY_CHANGED, DESTROY, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; -G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT); +G_DEFINE_TYPE_WITH_CODE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST, + tp_chat_iface_init)); + +static void acknowledge_messages (EmpathyTpChat *chat, GArray *ids); static void -empathy_tp_chat_class_init (EmpathyTpChatClass *klass) +tp_chat_invalidated_cb (TpProxy *proxy, + guint domain, + gint code, + gchar *message, + EmpathyTpChat *chat) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = tp_chat_finalize; + EmpathyTpChatPriv *priv = GET_PRIV (chat); - signals[MESSAGE_RECEIVED] = - g_signal_new ("message-received", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, GOSSIP_TYPE_MESSAGE); + DEBUG ("Channel invalidated: %s", message); + g_signal_emit (chat, signals[DESTROY], 0); - signals[CHAT_STATE_CHANGED] = - g_signal_new ("chat-state-changed", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - empathy_marshal_VOID__OBJECT_UINT, - G_TYPE_NONE, - 2, GOSSIP_TYPE_CONTACT, G_TYPE_UINT); + g_object_unref (priv->channel); + priv->channel = NULL; +} - signals[DESTROY] = - g_signal_new ("destroy", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0); +static void +tp_chat_async_cb (TpChannel *proxy, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + if (error) { + DEBUG ("Error %s: %s", (gchar*) user_data, error->message); + } +} - g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv)); +static void +tp_chat_add (EmpathyContactList *list, + EmpathyContact *contact, + const gchar *message) +{ + EmpathyTpChatPriv *priv = GET_PRIV (list); + TpHandle handle; + GArray handles = {(gchar *) &handle, 1}; + + g_return_if_fail (EMPATHY_IS_TP_CHAT (list)); + g_return_if_fail (EMPATHY_IS_CONTACT (contact)); + + handle = empathy_contact_get_handle (contact); + tp_cli_channel_interface_group_call_add_members (priv->channel, -1, + &handles, NULL, + NULL, NULL, NULL, + NULL); } static void -empathy_tp_chat_init (EmpathyTpChat *chat) +tp_chat_remove (EmpathyContactList *list, + EmpathyContact *contact, + const gchar *message) { + EmpathyTpChatPriv *priv = GET_PRIV (list); + TpHandle handle; + GArray handles = {(gchar *) &handle, 1}; + + g_return_if_fail (EMPATHY_IS_TP_CHAT (list)); + g_return_if_fail (EMPATHY_IS_CONTACT (contact)); + + handle = empathy_contact_get_handle (contact); + tp_cli_channel_interface_group_call_remove_members (priv->channel, -1, + &handles, NULL, + NULL, NULL, NULL, + NULL); } +static GList * +tp_chat_get_members (EmpathyContactList *list) +{ + EmpathyTpChatPriv *priv = GET_PRIV (list); + GList *members = NULL; -static void -tp_chat_finalize (GObject *object) + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL); + + if (priv->members) { + members = g_list_copy (priv->members); + g_list_foreach (members, (GFunc) g_object_ref, NULL); + } else { + members = g_list_prepend (members, g_object_ref (priv->user)); + members = g_list_prepend (members, g_object_ref (priv->remote_contact)); + } + + return members; +} + +static EmpathyContactMonitor * +tp_chat_get_monitor (EmpathyContactList *list) { EmpathyTpChatPriv *priv; - EmpathyTpChat *chat; - GError *error = NULL; - chat = EMPATHY_TP_CHAT (object); - priv = GET_PRIV (chat); + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (list), NULL); - if (priv->tp_chan) { - gossip_debug (DEBUG_DOMAIN, "Closing channel..."); + priv = GET_PRIV (list); - g_signal_handlers_disconnect_by_func (priv->tp_chan, - tp_chat_destroy_cb, - object); + if (priv->contact_monitor == NULL) { + priv->contact_monitor = empathy_contact_monitor_new_for_iface (list); + } + + return priv->contact_monitor; +} - if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) { - gossip_debug (DEBUG_DOMAIN, - "Error closing text channel: %s", - error ? error->message : "No error given"); - g_clear_error (&error); +static void +tp_chat_emit_queued_messages (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + EmpathyMessage *message; + + /* Check if we can now emit some queued messages */ + while ((message = g_queue_peek_head (priv->messages_queue)) != NULL) { + if (empathy_message_get_sender (message) == NULL) { + break; } - g_object_unref (priv->tp_chan); - } - if (priv->list) { - g_object_unref (priv->list); - } - if (priv->account) { - g_object_unref (priv->account); + DEBUG ("Queued message ready"); + g_queue_pop_head (priv->messages_queue); + g_queue_push_tail (priv->pending_messages_queue, message); + g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message); } - g_free (priv->id); - - G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object); } -EmpathyTpChat * -empathy_tp_chat_new (McAccount *account, - TpChan *tp_chan) +static void +tp_chat_got_sender_cb (EmpathyTpContactFactory *factory, + GList *contacts, + gpointer message, + GObject *chat) { - EmpathyTpChatPriv *priv; - EmpathyTpChat *chat; - EmpathyContactManager *manager; + empathy_message_set_sender (message, contacts->data); + tp_chat_emit_queued_messages (EMPATHY_TP_CHAT (chat)); +} - g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL); - g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL); +static void +tp_chat_build_message (EmpathyTpChat *chat, + guint id, + guint type, + guint timestamp, + guint from_handle, + const gchar *message_body) +{ + EmpathyTpChatPriv *priv; + EmpathyMessage *message; - chat = g_object_new (EMPATHY_TYPE_TP_CHAT, NULL); priv = GET_PRIV (chat); - manager = empathy_session_get_contact_manager (); - priv->list = empathy_contact_manager_get_list (manager, account); - priv->tp_chan = g_object_ref (tp_chan); - priv->account = g_object_ref (account); - g_object_ref (priv->list); + message = empathy_message_new (message_body); + empathy_message_set_tptype (message, type); + empathy_message_set_receiver (message, priv->user); + empathy_message_set_timestamp (message, timestamp); + empathy_message_set_id (message, id); + g_queue_push_tail (priv->messages_queue, message); - priv->text_iface = tp_chan_get_interface (tp_chan, - TELEPATHY_CHAN_IFACE_TEXT_QUARK); - priv->chat_state_iface = tp_chan_get_interface (tp_chan, - TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK); + if (from_handle == 0) { + empathy_message_set_sender (message, priv->user); + tp_chat_emit_queued_messages (chat); + } else { + empathy_tp_contact_factory_get_from_handles (priv->factory, + 1, &from_handle, + tp_chat_got_sender_cb, + message, NULL, G_OBJECT (chat)); + } +} - g_signal_connect (priv->tp_chan, "destroy", - G_CALLBACK (tp_chat_destroy_cb), - chat); - dbus_g_proxy_connect_signal (priv->text_iface, "Received", - G_CALLBACK (tp_chat_received_cb), - chat, NULL); - dbus_g_proxy_connect_signal (priv->text_iface, "Sent", - G_CALLBACK (tp_chat_sent_cb), - chat, NULL); +static void +tp_chat_received_cb (TpChannel *channel, + guint message_id, + guint timestamp, + guint from_handle, + guint message_type, + guint message_flags, + const gchar *message_body, + gpointer user_data, + GObject *chat_) +{ + EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_); + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + if (priv->channel == NULL) + return; - if (priv->chat_state_iface != NULL) { - dbus_g_proxy_connect_signal (priv->chat_state_iface, - "ChatStateChanged", - G_CALLBACK (tp_chat_state_changed_cb), - chat, NULL); + if (priv->listing_pending_messages) { + return; } + + DEBUG ("Message received: %s", message_body); - return chat; + if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT && + !tp_strdiff (message_body, "")) { + GArray *ids; + + DEBUG ("Empty message with NonTextContent, ignoring and acking."); + + ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + g_array_append_val (ids, message_id); + acknowledge_messages (chat, ids); + g_array_free (ids, TRUE); + + return; + } + + tp_chat_build_message (chat, + message_id, + message_type, + timestamp, + from_handle, + message_body); } -EmpathyTpChat * -empathy_tp_chat_new_with_contact (GossipContact *contact) +static void +tp_chat_sent_cb (TpChannel *channel, + guint timestamp, + guint message_type, + const gchar *message_body, + gpointer user_data, + GObject *chat_) +{ + EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_); + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + if (priv->channel == NULL) + return; + + DEBUG ("Message sent: %s", message_body); + + tp_chat_build_message (chat, + 0, + message_type, + timestamp, + 0, + message_body); +} + +static void +tp_chat_send_error_cb (TpChannel *channel, + guint error_code, + guint timestamp, + guint message_type, + const gchar *message_body, + gpointer user_data, + GObject *chat) { - EmpathyTpChat *chat; - MissionControl *mc; - McAccount *account; - TpConn *tp_conn; - TpChan *text_chan; - const gchar *bus_name; - guint handle; + EmpathyTpChatPriv *priv = GET_PRIV (chat); - g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL); + if (priv->channel == NULL) + return; + + DEBUG ("Message sent error: %s (%d)", message_body, error_code); - mc = empathy_session_get_mission_control (); - account = gossip_contact_get_account (contact); + tp_chat_build_message (EMPATHY_TP_CHAT (chat), + 0, + message_type, + timestamp, + 0, + message_body); +} - if (mission_control_get_connection_status (mc, account, NULL) != 0) { - /* The account is not connected, nothing to do. */ - return NULL; +static void +tp_chat_send_cb (TpChannel *proxy, + const GError *error, + gpointer user_data, + GObject *chat) +{ + EmpathyMessage *message = EMPATHY_MESSAGE (user_data); + + if (error) { + DEBUG ("Error: %s", error->message); + g_signal_emit (chat, signals[SEND_ERROR], 0, message, + TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN); } +} - tp_conn = mission_control_get_connection (mc, account, NULL); - g_return_val_if_fail (tp_conn != NULL, NULL); - bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn)); - handle = gossip_contact_get_handle (contact); +typedef struct { + EmpathyTpChat *chat; + TpChannelChatState state; +} StateChangedData; - text_chan = tp_conn_new_channel (tp_get_bus (), - tp_conn, - bus_name, - TP_IFACE_CHANNEL_TYPE_TEXT, - TP_HANDLE_TYPE_CONTACT, - handle, - TRUE); +static void +tp_chat_state_changed_got_contact_cb (EmpathyTpContactFactory *factory, + GList *contacts, + gpointer user_data, + GObject *chat) +{ + EmpathyContact *contact = contacts->data; + TpChannelChatState state; - chat = empathy_tp_chat_new (account, text_chan); + state = GPOINTER_TO_UINT (user_data); + DEBUG ("Chat state changed for %s (%d): %d", + empathy_contact_get_name (contact), + empathy_contact_get_handle (contact), state); - g_object_unref (tp_conn); - g_object_unref (text_chan); + g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state); +} - return chat; +static void +tp_chat_state_changed_cb (TpChannel *channel, + TpHandle handle, + TpChannelChatState state, + gpointer user_data, + GObject *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + empathy_tp_contact_factory_get_from_handles (priv->factory, 1, &handle, + tp_chat_state_changed_got_contact_cb, GUINT_TO_POINTER (state), + NULL, chat); } -void -empathy_tp_chat_request_pending (EmpathyTpChat *chat) +static void +tp_chat_list_pending_messages_cb (TpChannel *channel, + const GPtrArray *messages_list, + const GError *error, + gpointer user_data, + GObject *chat_) { - EmpathyTpChatPriv *priv; - GPtrArray *messages_list; + EmpathyTpChat *chat = EMPATHY_TP_CHAT (chat_); + EmpathyTpChatPriv *priv = GET_PRIV (chat); guint i; - GError *error = NULL; + GArray *empty_non_text_content_ids = NULL; - g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); + priv->listing_pending_messages = FALSE; - priv = GET_PRIV (chat); + if (priv->channel == NULL) + return; - /* If we do this call async, don't forget to ignore Received signal - * until we get the answer */ - if (!tp_chan_type_text_list_pending_messages (priv->text_iface, - TRUE, - &messages_list, - &error)) { - gossip_debug (DEBUG_DOMAIN, - "Error retrieving pending messages: %s", - error ? error->message : "No error given"); - g_clear_error (&error); + if (error) { + DEBUG ("Error listing pending messages: %s", error->message); return; } for (i = 0; i < messages_list->len; i++) { - GValueArray *message_struct; - const gchar *message_body; - guint message_id; - guint timestamp; - guint from_handle; - guint message_type; - guint message_flags; + GValueArray *message_struct; + const gchar *message_body; + guint message_id; + guint timestamp; + guint from_handle; + guint message_type; + guint message_flags; message_struct = g_ptr_array_index (messages_list, i); @@ -302,233 +434,955 @@ empathy_tp_chat_request_pending (EmpathyTpChat *chat) message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4)); message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5)); - gossip_debug (DEBUG_DOMAIN, "Message pending: %s", message_body); + DEBUG ("Message pending: %s", message_body); + + if (message_flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT && + !tp_strdiff (message_body, "")) { + DEBUG ("Empty message with NonTextContent, ignoring and acking."); - tp_chat_emit_message (chat, - message_type, - timestamp, - from_handle, - message_body); + if (empty_non_text_content_ids == NULL) { + empty_non_text_content_ids = g_array_new (FALSE, FALSE, sizeof (guint)); + } - g_value_array_free (message_struct); + g_array_append_val (empty_non_text_content_ids, message_id); + continue; + } + + tp_chat_build_message (chat, + message_id, + message_type, + timestamp, + from_handle, + message_body); } - g_ptr_array_free (messages_list, TRUE); + if (empty_non_text_content_ids != NULL) { + acknowledge_messages (chat, empty_non_text_content_ids); + g_array_free (empty_non_text_content_ids, TRUE); + } } -void -empathy_tp_chat_send (EmpathyTpChat *chat, - GossipMessage *message) +static void +tp_chat_property_flags_changed_cb (TpProxy *proxy, + const GPtrArray *properties, + gpointer user_data, + GObject *chat) { - EmpathyTpChatPriv *priv; - const gchar *message_body; - GossipMessageType message_type; - GError *error = NULL; - - g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); - g_return_if_fail (GOSSIP_IS_MESSAGE (message)); + EmpathyTpChatPriv *priv = GET_PRIV (chat); + guint i, j; - priv = GET_PRIV (chat); + if (priv->channel == NULL) + return; - message_body = gossip_message_get_body (message); - message_type = gossip_message_get_type (message); + if (!priv->had_properties_list || !properties) { + return; + } - gossip_debug (DEBUG_DOMAIN, "Sending message: %s", message_body); - if (!tp_chan_type_text_send (priv->text_iface, - message_type, - message_body, - &error)) { - gossip_debug (DEBUG_DOMAIN, - "Send Error: %s", - error ? error->message : "No error given"); - g_clear_error (&error); + for (i = 0; i < properties->len; i++) { + GValueArray *prop_struct; + TpChatProperty *property; + guint id; + guint flags; + + prop_struct = g_ptr_array_index (properties, i); + id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0)); + flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 1)); + + for (j = 0; j < priv->properties->len; j++) { + property = g_ptr_array_index (priv->properties, j); + if (property->id == id) { + property->flags = flags; + DEBUG ("property %s flags changed: %d", + property->name, property->flags); + break; + } + } } } -void -empathy_tp_chat_send_state (EmpathyTpChat *chat, - EmpathyTpChatState state) +static void +tp_chat_properties_changed_cb (TpProxy *proxy, + const GPtrArray *properties, + gpointer user_data, + GObject *chat) { - EmpathyTpChatPriv *priv; - GError *error = NULL; + EmpathyTpChatPriv *priv = GET_PRIV (chat); + guint i, j; - g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); + if (priv->channel == NULL) + return; - priv = GET_PRIV (chat); + if (!priv->had_properties_list || !properties) { + return; + } - if (priv->chat_state_iface) { - gossip_debug (DEBUG_DOMAIN, "Set state: %d", state); - if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface, - state, - &error)) { - gossip_debug (DEBUG_DOMAIN, - "Set Chat State Error: %s", - error ? error->message : "No error given"); - g_clear_error (&error); + for (i = 0; i < properties->len; i++) { + GValueArray *prop_struct; + TpChatProperty *property; + guint id; + GValue *src_value; + + prop_struct = g_ptr_array_index (properties, i); + id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0)); + src_value = g_value_get_boxed (g_value_array_get_nth (prop_struct, 1)); + + for (j = 0; j < priv->properties->len; j++) { + property = g_ptr_array_index (priv->properties, j); + if (property->id == id) { + if (property->value) { + g_value_copy (src_value, property->value); + } else { + property->value = tp_g_value_slice_dup (src_value); + } + + DEBUG ("property %s changed", property->name); + g_signal_emit (chat, signals[PROPERTY_CHANGED], 0, + property->name, property->value); + break; + } } } } -const gchar * -empathy_tp_chat_get_id (EmpathyTpChat *chat) +static void +tp_chat_get_properties_cb (TpProxy *proxy, + const GPtrArray *properties, + const GError *error, + gpointer user_data, + GObject *chat) { - EmpathyTpChatPriv *priv; - MissionControl *mc; - TpConn *tp_conn; - GArray *handles; - gchar **names; - GError *error = NULL; + if (error) { + DEBUG ("Error getting properties: %s", error->message); + return; + } - g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL); + tp_chat_properties_changed_cb (proxy, properties, user_data, chat); +} - priv = GET_PRIV (chat); +static void +tp_chat_list_properties_cb (TpProxy *proxy, + const GPtrArray *properties, + const GError *error, + gpointer user_data, + GObject *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + GArray *ids; + guint i; + + if (priv->channel == NULL) + return; - if (priv->id) { - return priv->id; + priv->had_properties_list = TRUE; + + if (error) { + DEBUG ("Error listing properties: %s", error->message); + return; } - mc = empathy_session_get_mission_control (); - tp_conn = mission_control_get_connection (mc, priv->account, NULL); - handles = g_array_new (FALSE, FALSE, sizeof (guint)); - g_array_append_val (handles, priv->tp_chan->handle); + ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), properties->len); + priv->properties = g_ptr_array_sized_new (properties->len); + for (i = 0; i < properties->len; i++) { + GValueArray *prop_struct; + TpChatProperty *property; + + prop_struct = g_ptr_array_index (properties, i); + property = g_slice_new0 (TpChatProperty); + property->id = g_value_get_uint (g_value_array_get_nth (prop_struct, 0)); + property->name = g_value_dup_string (g_value_array_get_nth (prop_struct, 1)); + property->flags = g_value_get_uint (g_value_array_get_nth (prop_struct, 3)); + + DEBUG ("Adding property name=%s id=%d flags=%d", + property->name, property->id, property->flags); + g_ptr_array_add (priv->properties, property); + if (property->flags & TP_PROPERTY_FLAG_READ) { + g_array_append_val (ids, property->id); + } + } + + tp_cli_properties_interface_call_get_properties (proxy, -1, + ids, + tp_chat_get_properties_cb, + NULL, NULL, + chat); + + g_array_free (ids, TRUE); +} + +void +empathy_tp_chat_set_property (EmpathyTpChat *chat, + const gchar *name, + const GValue *value) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + TpChatProperty *property; + guint i; + + for (i = 0; i < priv->properties->len; i++) { + property = g_ptr_array_index (priv->properties, i); + if (!tp_strdiff (property->name, name)) { + GPtrArray *properties; + GValueArray *prop; + GValue id = {0, }; + GValue dest_value = {0, }; + + if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) { + break; + } + + g_value_init (&id, G_TYPE_UINT); + g_value_init (&dest_value, G_TYPE_VALUE); + g_value_set_uint (&id, property->id); + g_value_set_boxed (&dest_value, value); + + prop = g_value_array_new (2); + g_value_array_append (prop, &id); + g_value_array_append (prop, &dest_value); + + properties = g_ptr_array_sized_new (1); + g_ptr_array_add (properties, prop); + + DEBUG ("Set property %s", name); + tp_cli_properties_interface_call_set_properties (priv->channel, -1, + properties, + (tp_cli_properties_interface_callback_for_set_properties) + tp_chat_async_cb, + "Seting property", NULL, + G_OBJECT (chat)); + + g_ptr_array_free (properties, TRUE); + g_value_array_free (prop); + + break; + } + } +} + +static void +tp_chat_dispose (GObject *object) +{ + EmpathyTpChat *self = EMPATHY_TP_CHAT (object); + EmpathyTpChatPriv *priv = GET_PRIV (self); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; - if (!tp_conn_inspect_handles (DBUS_G_PROXY (tp_conn), - priv->tp_chan->handle_type, - handles, - &names, - &error)) { - gossip_debug (DEBUG_DOMAIN, - "Couldn't get id: %s", - error ? error->message : "No error given"); - g_clear_error (&error); - g_array_free (handles, TRUE); - g_object_unref (tp_conn); - - return NULL; + if (priv->channel != NULL) { + g_signal_handlers_disconnect_by_func (priv->channel, + tp_chat_invalidated_cb, self); + g_object_unref (priv->channel); } + priv->channel = NULL; - /* A handle name is unique only for a specific account */ - priv->id = g_strdup_printf ("%s/%s", - mc_account_get_unique_name (priv->account), - *names); + if (priv->remote_contact != NULL) + g_object_unref (priv->remote_contact); + priv->remote_contact = NULL; - g_strfreev (names); - g_object_unref (tp_conn); + if (priv->factory != NULL) + g_object_unref (priv->factory); + priv->factory = NULL; - return priv->id; + if (priv->user != NULL); + g_object_unref (priv->user); + priv->user = NULL; + + if (priv->contact_monitor) + g_object_unref (priv->contact_monitor); + priv->contact_monitor = NULL; + + g_queue_foreach (priv->messages_queue, (GFunc) g_object_unref, NULL); + g_queue_clear (priv->messages_queue); + + g_queue_foreach (priv->pending_messages_queue, + (GFunc) g_object_unref, NULL); + g_queue_clear (priv->pending_messages_queue); + + if (G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose) + G_OBJECT_CLASS (empathy_tp_chat_parent_class)->dispose (object); } static void -tp_chat_destroy_cb (TpChan *text_chan, - EmpathyTpChat *chat) +tp_chat_finalize (GObject *object) { - EmpathyTpChatPriv *priv; + EmpathyTpChatPriv *priv = GET_PRIV (object); + guint i; - priv = GET_PRIV (chat); + DEBUG ("Finalize: %p", object); + + if (priv->properties) { + for (i = 0; i < priv->properties->len; i++) { + TpChatProperty *property; - gossip_debug (DEBUG_DOMAIN, "Channel destroyed"); + property = g_ptr_array_index (priv->properties, i); + g_free (property->name); + if (property->value) { + tp_g_value_slice_free (property->value); + } + g_slice_free (TpChatProperty, property); + } + g_ptr_array_free (priv->properties, TRUE); + } - g_object_unref (priv->tp_chan); - priv->tp_chan = NULL; - priv->text_iface = NULL; - priv->chat_state_iface = NULL; + g_queue_free (priv->messages_queue); + g_queue_free (priv->pending_messages_queue); - g_signal_emit (chat, signals[DESTROY], 0); + G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object); } static void -tp_chat_received_cb (DBusGProxy *text_iface, - guint message_id, - guint timestamp, - guint from_handle, - guint message_type, - guint message_flags, - gchar *message_body, - EmpathyTpChat *chat) +tp_chat_check_if_ready (EmpathyTpChat *chat) { - EmpathyTpChatPriv *priv; - GArray *message_ids; + EmpathyTpChatPriv *priv = GET_PRIV (chat); - priv = GET_PRIV (chat); + if (priv->ready || priv->user == NULL || + (priv->members == NULL && priv->remote_contact == NULL)) { + return; + } + + DEBUG ("Ready!"); + + priv->listing_pending_messages = TRUE; + tp_cli_channel_type_text_call_list_pending_messages (priv->channel, -1, + FALSE, + tp_chat_list_pending_messages_cb, + NULL, NULL, + G_OBJECT (chat)); + + tp_cli_channel_type_text_connect_to_received (priv->channel, + tp_chat_received_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + tp_cli_channel_type_text_connect_to_sent (priv->channel, + tp_chat_sent_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + tp_cli_channel_type_text_connect_to_send_error (priv->channel, + tp_chat_send_error_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel, + tp_chat_state_changed_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + tp_cli_channel_interface_chat_state_connect_to_chat_state_changed (priv->channel, + tp_chat_state_changed_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + priv->ready = TRUE; + g_object_notify (G_OBJECT (chat), "ready"); +} - gossip_debug (DEBUG_DOMAIN, "Message received: %s", message_body); +static void +tp_chat_update_remote_contact (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + EmpathyContact *contact = NULL; + TpHandle self_handle; + TpHandleType handle_type; + GList *l; + + /* If this is a named chatroom, never pretend it is a private chat */ + tp_channel_get_handle (priv->channel, &handle_type); + if (handle_type == TP_HANDLE_TYPE_ROOM) { + return; + } - tp_chat_emit_message (chat, - message_type, - timestamp, - from_handle, - message_body); + /* This is an MSN-like chat where anyone can join the chat at anytime. + * If there is only one non-self contact member, we are in a private + * chat and we set the "remote-contact" property to that contact. If + * there are more, set the "remote-contact" property to NULL and the + * UI will display a contact list. */ + self_handle = tp_channel_group_get_self_handle (priv->channel); + for (l = priv->members; l; l = l->next) { + /* Skip self contact if member */ + if (empathy_contact_get_handle (l->data) == self_handle) { + continue; + } - message_ids = g_array_new (FALSE, FALSE, sizeof (guint)); - g_array_append_val (message_ids, message_id); - tp_chan_type_text_acknowledge_pending_messages (priv->text_iface, - message_ids, NULL); - g_array_free (message_ids, TRUE); + /* We have more than one remote contact, break */ + if (contact != NULL) { + contact = NULL; + break; + } + + /* If we didn't find yet a remote contact, keep this one */ + contact = l->data; + } + + if (priv->remote_contact == contact) { + return; + } + + DEBUG ("Changing remote contact from %p to %p", + priv->remote_contact, contact); + + if (priv->remote_contact) { + g_object_unref (priv->remote_contact); + } + + priv->remote_contact = contact ? g_object_ref (contact) : NULL; + g_object_notify (G_OBJECT (chat), "remote-contact"); } static void -tp_chat_sent_cb (DBusGProxy *text_iface, - guint timestamp, - guint message_type, - gchar *message_body, - EmpathyTpChat *chat) +tp_chat_got_added_contacts_cb (EmpathyTpContactFactory *factory, + GList *contacts, + gpointer user_data, + GObject *chat) { - gossip_debug (DEBUG_DOMAIN, "Message sent: %s", message_body); + EmpathyTpChatPriv *priv = GET_PRIV (chat); + GList *l; + const TpIntSet *members; + TpHandle handle; + EmpathyContact *contact; + + members = tp_channel_group_get_members (priv->channel); + for (l = contacts; l; l = l->next) { + contact = l->data; + handle = empathy_contact_get_handle (contact); + + /* Make sure the contact is still member */ + if (tp_intset_is_member (members, handle)) { + priv->members = g_list_prepend (priv->members, + g_object_ref (contact)); + g_signal_emit_by_name (chat, "members-changed", + contact, NULL, 0, NULL, FALSE); + } + } - tp_chat_emit_message (chat, - message_type, - timestamp, - 0, - message_body); + tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat)); + tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat)); } static void -tp_chat_state_changed_cb (DBusGProxy *chat_state_iface, - guint handle, - EmpathyTpChatState state, - EmpathyTpChat *chat) +tp_chat_group_members_changed_cb (TpChannel *self, + gchar *message, + GArray *added, + GArray *removed, + GArray *local_pending, + GArray *remote_pending, + guint actor, + guint reason, + EmpathyTpChat *chat) { - EmpathyTpChatPriv *priv; - GossipContact *contact; + EmpathyTpChatPriv *priv = GET_PRIV (chat); + EmpathyContact *contact; + TpHandle handle; + guint i; + GList *l; + + /* Remove contacts that are not members anymore */ + for (i = 0; i < removed->len; i++) { + for (l = priv->members; l; l = l->next) { + contact = l->data; + handle = empathy_contact_get_handle (contact); + if (handle == g_array_index (removed, TpHandle, i)) { + priv->members = g_list_delete_link (priv->members, l); + g_signal_emit_by_name (chat, "members-changed", + contact, actor, reason, + message, FALSE); + g_object_unref (contact); + break; + } + } + } - priv = GET_PRIV (chat); + /* Request added contacts */ + if (added->len > 0) { + empathy_tp_contact_factory_get_from_handles (priv->factory, + added->len, (TpHandle*) added->data, + tp_chat_got_added_contacts_cb, NULL, NULL, + G_OBJECT (chat)); + } - contact = empathy_contact_list_get_from_handle (priv->list, handle); + tp_chat_update_remote_contact (chat); +} - g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state); +static void +tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory, + GList *contacts, + gpointer user_data, + GObject *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + priv->remote_contact = g_object_ref (contacts->data); + g_object_notify (chat, "remote-contact"); - g_object_unref (contact); + tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat)); } static void -tp_chat_emit_message (EmpathyTpChat *chat, - guint type, - guint timestamp, - guint from_handle, - const gchar *message_body) +tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory, + GList *contacts, + gpointer user_data, + GObject *chat) { + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + priv->user = g_object_ref (contacts->data); + empathy_contact_set_is_user (priv->user, TRUE); + tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat)); +} + +static void +tp_chat_get_self_handle_cb (TpConnection *connection, + TpHandle self_handle, + const GError *error, + gpointer user_data, + GObject *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + if (error) { + DEBUG ("Error: %s", error->message); + tp_cli_channel_call_close (priv->channel, -1, + NULL, NULL, NULL, NULL); + return; + } + + empathy_tp_contact_factory_get_from_handles (priv->factory, + 1, &self_handle, + tp_chat_got_self_contact_cb, + NULL, NULL, chat); +} + +static GObject * +tp_chat_constructor (GType type, + guint n_props, + GObjectConstructParam *props) +{ + GObject *chat; EmpathyTpChatPriv *priv; - GossipMessage *message; - GossipContact *sender; + TpConnection *connection; + TpHandle handle; + + chat = G_OBJECT_CLASS (empathy_tp_chat_parent_class)->constructor (type, n_props, props); priv = GET_PRIV (chat); - if (from_handle == 0) { - sender = empathy_contact_list_get_own (priv->list); - g_object_ref (sender); + connection = tp_channel_borrow_connection (priv->channel); + priv->factory = empathy_tp_contact_factory_dup_singleton (connection); + g_signal_connect (priv->channel, "invalidated", + G_CALLBACK (tp_chat_invalidated_cb), + chat); + + if (tp_proxy_has_interface_by_id (priv->channel, + TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) { + const TpIntSet *members; + GArray *handles; + + /* Get self contact from the group's self handle */ + handle = tp_channel_group_get_self_handle (priv->channel); + empathy_tp_contact_factory_get_from_handles (priv->factory, + 1, &handle, + tp_chat_got_self_contact_cb, + NULL, NULL, chat); + + /* Get initial member contacts */ + members = tp_channel_group_get_members (priv->channel); + handles = tp_intset_to_array (members); + empathy_tp_contact_factory_get_from_handles (priv->factory, + handles->len, (TpHandle*) handles->data, + tp_chat_got_added_contacts_cb, NULL, NULL, chat); + + g_signal_connect_swapped (priv->channel, "group-members-changed", + G_CALLBACK (tp_chat_group_members_changed_cb), chat); } else { - sender = empathy_contact_list_get_from_handle (priv->list, - from_handle); + /* Get the self contact from the connection's self handle */ + tp_cli_connection_call_get_self_handle (connection, -1, + tp_chat_get_self_handle_cb, NULL, NULL, chat); + + /* Get the remote contact */ + handle = tp_channel_get_handle (priv->channel, NULL); + empathy_tp_contact_factory_get_from_handles (priv->factory, + 1, &handle, tp_chat_got_remote_contact_cb, + NULL, NULL, chat); } - message = gossip_message_new (message_body); - gossip_message_set_type (message, type); - gossip_message_set_sender (message, sender); - gossip_message_set_timestamp (message, (GossipTime) timestamp); + if (tp_proxy_has_interface_by_id (priv->channel, + TP_IFACE_QUARK_PROPERTIES_INTERFACE)) { + tp_cli_properties_interface_call_list_properties (priv->channel, -1, + tp_chat_list_properties_cb, + NULL, NULL, + G_OBJECT (chat)); + tp_cli_properties_interface_connect_to_properties_changed (priv->channel, + tp_chat_properties_changed_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + tp_cli_properties_interface_connect_to_property_flags_changed (priv->channel, + tp_chat_property_flags_changed_cb, + NULL, NULL, + G_OBJECT (chat), NULL); + } + + return chat; +} + +static void +tp_chat_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EmpathyTpChatPriv *priv = GET_PRIV (object); + + switch (param_id) { + case PROP_CHANNEL: + g_value_set_object (value, priv->channel); + break; + case PROP_REMOTE_CONTACT: + g_value_set_object (value, priv->remote_contact); + break; + case PROP_READY: + g_value_set_boolean (value, priv->ready); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + }; +} - g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message); +static void +tp_chat_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EmpathyTpChatPriv *priv = GET_PRIV (object); + + switch (param_id) { + case PROP_CHANNEL: + priv->channel = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + }; +} +static void +empathy_tp_chat_class_init (EmpathyTpChatClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = tp_chat_dispose; + object_class->finalize = tp_chat_finalize; + object_class->constructor = tp_chat_constructor; + object_class->get_property = tp_chat_get_property; + object_class->set_property = tp_chat_set_property; + + g_object_class_install_property (object_class, + PROP_CHANNEL, + g_param_spec_object ("channel", + "telepathy channel", + "The text channel for the chat", + TP_TYPE_CHANNEL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, + PROP_REMOTE_CONTACT, + g_param_spec_object ("remote-contact", + "The remote contact", + "The remote contact if there is no group iface on the channel", + EMPATHY_TYPE_CONTACT, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_READY, + g_param_spec_boolean ("ready", + "Is the object ready", + "This object can't be used until this becomes true", + FALSE, + G_PARAM_READABLE)); + + /* Signals */ + signals[MESSAGE_RECEIVED] = + g_signal_new ("message-received", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, EMPATHY_TYPE_MESSAGE); + + signals[SEND_ERROR] = + g_signal_new ("send-error", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _empathy_marshal_VOID__OBJECT_UINT, + G_TYPE_NONE, + 2, EMPATHY_TYPE_MESSAGE, G_TYPE_UINT); + + signals[CHAT_STATE_CHANGED] = + g_signal_new ("chat-state-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _empathy_marshal_VOID__OBJECT_UINT, + G_TYPE_NONE, + 2, EMPATHY_TYPE_CONTACT, G_TYPE_UINT); + + signals[PROPERTY_CHANGED] = + g_signal_new ("property-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _empathy_marshal_VOID__STRING_BOXED, + G_TYPE_NONE, + 2, G_TYPE_STRING, G_TYPE_VALUE); + + signals[DESTROY] = + g_signal_new ("destroy", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv)); +} + +static void +empathy_tp_chat_init (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat, + EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv); + + chat->priv = priv; + priv->contact_monitor = NULL; + priv->messages_queue = g_queue_new (); + priv->pending_messages_queue = g_queue_new (); +} + +static void +tp_chat_iface_init (EmpathyContactListIface *iface) +{ + iface->add = tp_chat_add; + iface->remove = tp_chat_remove; + iface->get_members = tp_chat_get_members; + iface->get_monitor = tp_chat_get_monitor; +} + +EmpathyTpChat * +empathy_tp_chat_new (TpChannel *channel) +{ + return g_object_new (EMPATHY_TYPE_TP_CHAT, + "channel", channel, + NULL); +} + +void +empathy_tp_chat_close (EmpathyTpChat *chat) { + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + /* If there are still messages left, it'll come back.. + We loose the ordering of sent messages though */ + g_signal_handlers_disconnect_by_func (priv->channel, + tp_chat_invalidated_cb, chat); + + tp_cli_channel_call_close (priv->channel, -1, tp_chat_async_cb, + "closing channel", NULL, NULL); + + g_object_unref (priv->channel); + priv->channel = NULL; + + g_signal_emit (chat, signals[DESTROY], 0); +} + +const gchar * +empathy_tp_chat_get_id (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL); + + return tp_channel_get_identifier (priv->channel); +} + +EmpathyContact * +empathy_tp_chat_get_remote_contact (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL); + g_return_val_if_fail (priv->ready, NULL); + + return priv->remote_contact; +} + +TpChannel * +empathy_tp_chat_get_channel (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL); + + return priv->channel; +} + +TpConnection * +empathy_tp_chat_get_connection (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL); + + return tp_channel_borrow_connection (priv->channel); +} + +gboolean +empathy_tp_chat_is_ready (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), FALSE); + + return priv->ready; +} + +void +empathy_tp_chat_send (EmpathyTpChat *chat, + EmpathyMessage *message) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + const gchar *message_body; + TpChannelTextMessageType message_type; + + g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); + g_return_if_fail (EMPATHY_IS_MESSAGE (message)); + g_return_if_fail (priv->ready); + + message_body = empathy_message_get_body (message); + message_type = empathy_message_get_tptype (message); + + DEBUG ("Sending message: %s", message_body); + tp_cli_channel_type_text_call_send (priv->channel, -1, + message_type, + message_body, + tp_chat_send_cb, + g_object_ref (message), + (GDestroyNotify) g_object_unref, + G_OBJECT (chat)); +} + +void +empathy_tp_chat_set_state (EmpathyTpChat *chat, + TpChannelChatState state) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); + g_return_if_fail (priv->ready); + + DEBUG ("Set state: %d", state); + tp_cli_channel_interface_chat_state_call_set_chat_state (priv->channel, -1, + state, + tp_chat_async_cb, + "setting chat state", + NULL, + G_OBJECT (chat)); +} + + +const GList * +empathy_tp_chat_get_pending_messages (EmpathyTpChat *chat) +{ + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + g_return_val_if_fail (EMPATHY_IS_TP_CHAT (chat), NULL); + g_return_val_if_fail (priv->ready, NULL); + + return priv->pending_messages_queue->head; +} + +static void +acknowledge_messages (EmpathyTpChat *chat, GArray *ids) { + EmpathyTpChatPriv *priv = GET_PRIV (chat); + + tp_cli_channel_type_text_call_acknowledge_pending_messages ( + priv->channel, -1, ids, tp_chat_async_cb, + "acknowledging received message", NULL, G_OBJECT (chat)); +} + +void +empathy_tp_chat_acknowledge_message (EmpathyTpChat *chat, + EmpathyMessage *message) { + EmpathyTpChatPriv *priv = GET_PRIV (chat); + GArray *message_ids; + GList *m; + guint id; + + g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); + g_return_if_fail (priv->ready); + + if (empathy_message_get_sender (message) == priv->user) + goto out; + + message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + + id = empathy_message_get_id (message); + g_array_append_val (message_ids, id); + acknowledge_messages (chat, message_ids); + g_array_free (message_ids, TRUE); + +out: + m = g_queue_find (priv->pending_messages_queue, message); + g_assert (m != NULL); + g_queue_delete_link (priv->pending_messages_queue, m); g_object_unref (message); - g_object_unref (sender); +} + +void +empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat, + const GList *messages) { + EmpathyTpChatPriv *priv = GET_PRIV (chat); + /* Copy messages as the messges list (probably is) our own */ + GList *msgs = g_list_copy ((GList *) messages); + GList *l; + guint length; + GArray *message_ids; + + g_return_if_fail (EMPATHY_IS_TP_CHAT (chat)); + g_return_if_fail (priv->ready); + + length = g_list_length ((GList *)messages); + + if (length == 0) + return; + + message_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), length); + + for (l = msgs; l != NULL; l = g_list_next (l)) { + GList *m; + + EmpathyMessage *message = EMPATHY_MESSAGE (l->data); + + m = g_queue_find (priv->pending_messages_queue, message); + g_assert (m != NULL); + g_queue_delete_link (priv->pending_messages_queue, m); + + if (empathy_message_get_sender (message) != priv->user) { + guint id = empathy_message_get_id (message); + g_array_append_val (message_ids, id); + } + g_object_unref (message); + } + + if (message_ids->len > 0) + acknowledge_messages (chat, message_ids); + + g_array_free (message_ids, TRUE); + g_list_free (msgs); }