]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-tp-chat.c
Merge branch 'people-nearby-fake-group-613558'
[empathy.git] / libempathy / empathy-tp-chat.c
index aea125469fe2252a1eb53420f840f9bac7b8d136..83582741008d486dc97a45ab4f2a8751aa2808d5 100644 (file)
 
 #include <string.h>
 
-#include <telepathy-glib/channel.h>
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/util.h>
-#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/telepathy-glib.h>
+
+#include <extensions/extensions.h>
 
 #include "empathy-tp-chat.h"
 #include "empathy-tp-contact-factory.h"
 #include "empathy-contact-monitor.h"
 #include "empathy-contact-list.h"
+#include "empathy-dispatcher.h"
 #include "empathy-marshal.h"
 #include "empathy-time.h"
 #include "empathy-utils.h"
@@ -60,6 +60,7 @@ typedef struct {
         * (channel doesn't implement the Password interface) */
        gboolean               got_password_flags;
        gboolean               ready;
+       gboolean               can_upgrade_to_muc;
 } EmpathyTpChatPriv;
 
 static void tp_chat_iface_init         (EmpathyContactListIface *iface);
@@ -68,6 +69,7 @@ enum {
        PROP_0,
        PROP_CHANNEL,
        PROP_REMOTE_CONTACT,
+       PROP_PASSWORD_NEEDED,
        PROP_READY,
 };
 
@@ -116,17 +118,54 @@ tp_chat_add (EmpathyContactList *list,
             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));
+       if (tp_proxy_has_interface_by_id (priv->channel,
+               TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
+               TpHandle           handle;
+               GArray             handles = {(gchar *) &handle, 1};
 
-       handle = empathy_contact_get_handle (contact);
-       tp_cli_channel_interface_group_call_add_members (priv->channel, -1,
-                                                        &handles, NULL,
-                                                        NULL, NULL, NULL,
-                                                        NULL);
+               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);
+       } else if (priv->can_upgrade_to_muc) {
+               EmpathyDispatcher *dispatcher;
+               TpConnection      *connection;
+               GHashTable        *props;
+               const char        *object_path;
+               GPtrArray          channels = { (gpointer *) &object_path, 1 };
+               const char        *invitees[2] = { NULL, };
+
+               dispatcher = empathy_dispatcher_dup_singleton ();
+               connection = tp_channel_borrow_connection (priv->channel);
+
+               invitees[0] = empathy_contact_get_id (contact);
+               object_path = tp_proxy_get_object_path (priv->channel);
+
+               props = tp_asv_new (
+                   TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING,
+                       TP_IFACE_CHANNEL_TYPE_TEXT,
+                   TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT,
+                       TP_HANDLE_TYPE_NONE,
+                   EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels",
+                       TP_ARRAY_TYPE_OBJECT_PATH_LIST, &channels,
+                   EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialInviteeIDs",
+                       G_TYPE_STRV, invitees,
+                   /* FIXME: InvitationMessage ? */
+                   NULL);
+
+               /* Although this is a MUC, it's anonymous, so CreateChannel is
+                * valid.
+                * props now belongs to EmpathyDispatcher, don't free it */
+               empathy_dispatcher_create_channel (dispatcher, connection,
+                               props, NULL, NULL);
+
+               g_object_unref (dispatcher);
+       } else {
+               g_warning ("Cannot add to this channel");
+       }
 }
 
 static void
@@ -230,7 +269,8 @@ tp_chat_build_message (EmpathyTpChat *chat,
                       guint          type,
                       guint          timestamp,
                       guint          from_handle,
-                      const gchar   *message_body)
+                      const gchar   *message_body,
+                      TpChannelTextMessageFlags flags)
 {
        EmpathyTpChatPriv *priv;
        EmpathyMessage    *message;
@@ -243,6 +283,7 @@ tp_chat_build_message (EmpathyTpChat *chat,
        empathy_message_set_timestamp (message, timestamp);
        empathy_message_set_id (message, id);
        empathy_message_set_incoming (message, incoming);
+       empathy_message_set_flags (message, flags);
 
        g_queue_push_tail (priv->messages_queue, message);
 
@@ -300,7 +341,8 @@ tp_chat_received_cb (TpChannel   *channel,
                               message_type,
                               timestamp,
                               from_handle,
-                              message_body);
+                              message_body,
+                              message_flags);
 }
 
 static void
@@ -325,7 +367,8 @@ tp_chat_sent_cb (TpChannel   *channel,
                               message_type,
                               timestamp,
                               0,
-                              message_body);
+                              message_body,
+                              0);
 }
 
 static void
@@ -464,7 +507,8 @@ tp_chat_list_pending_messages_cb (TpChannel       *channel,
                                       message_type,
                                       timestamp,
                                       from_handle,
-                                      message_body);
+                                      message_body,
+                                      message_flags);
        }
 
        if (empty_non_text_content_ids != NULL) {
@@ -794,7 +838,7 @@ tp_chat_check_if_ready (EmpathyTpChat *chat)
         * If the chat is protected by a password we can't get these information so
         * consider the chat as ready so it can be presented to the user. */
        if (!empathy_tp_chat_password_needed (chat) && priv->members == NULL &&
-                                                     priv->remote_contact == NULL)
+           priv->remote_contact == NULL)
                return;
 
        DEBUG ("Ready!");
@@ -822,10 +866,6 @@ tp_chat_check_if_ready (EmpathyTpChat *chat)
                                                                           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");
 }
@@ -845,6 +885,14 @@ tp_chat_update_remote_contact (EmpathyTpChat *chat)
                return;
        }
 
+       /* This is an MSN chat, but it's the new style where 1-1 chats don't
+        * have the group interface. If it has the conference interface, then
+        * it is indeed a MUC. */
+       if (tp_proxy_has_interface_by_id (priv->channel,
+                                         EMP_IFACE_QUARK_CHANNEL_INTERFACE_CONFERENCE)) {
+               return;
+       }
+
        /* 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
@@ -949,6 +997,89 @@ chat_lookup_contact (EmpathyTpChat *chat,
        return NULL;
 }
 
+typedef struct
+{
+    TpHandle old_handle;
+    guint reason;
+    gchar *message;
+} ContactRenameData;
+
+static ContactRenameData *
+contact_rename_data_new (TpHandle handle,
+                        guint reason,
+                        const gchar* message)
+{
+       ContactRenameData *data = g_new (ContactRenameData, 1);
+       data->old_handle = handle;
+       data->reason = reason;
+       data->message = g_strdup (message);
+
+       return data;
+}
+
+static void
+contact_rename_data_free (ContactRenameData* data)
+{
+       g_free (data->message);
+       g_free (data);
+}
+
+static void
+tp_chat_got_renamed_contacts_cb (EmpathyTpContactFactory *factory,
+                                 guint                    n_contacts,
+                                 EmpathyContact * const * contacts,
+                                 guint                    n_failed,
+                                 const TpHandle          *failed,
+                                 const GError            *error,
+                                 gpointer                 user_data,
+                                 GObject                 *chat)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (chat);
+       const TpIntSet *members;
+       TpHandle handle;
+       EmpathyContact *old = NULL, *new = NULL;
+       ContactRenameData *rename_data = (ContactRenameData *) user_data;
+
+       if (error) {
+               DEBUG ("Error: %s", error->message);
+               return;
+       }
+
+       /* renamed members can only be delivered one at a time */
+       g_warn_if_fail (n_contacts == 1);
+
+       new = contacts[0];
+
+       members = tp_channel_group_get_members (priv->channel);
+       handle = empathy_contact_get_handle (new);
+
+       old = chat_lookup_contact (EMPATHY_TP_CHAT (chat),
+                                  rename_data->old_handle, TRUE);
+
+       /* Make sure the contact is still member */
+       if (tp_intset_is_member (members, handle)) {
+               priv->members = g_list_prepend (priv->members,
+                       g_object_ref (new));
+
+               if (old != NULL) {
+                       g_signal_emit_by_name (chat, "member-renamed",
+                                              old, new, rename_data->reason,
+                                              rename_data->message);
+                       g_object_unref (old);
+               }
+       }
+
+       if (priv->user == old) {
+               /* We change our nick */
+               g_object_unref (priv->user);
+               priv->user = g_object_ref (new);
+       }
+
+       tp_chat_update_remote_contact (EMPATHY_TP_CHAT (chat));
+       tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
+}
+
+
 static void
 tp_chat_group_members_changed_cb (TpChannel     *self,
                                  gchar         *message,
@@ -964,6 +1095,25 @@ tp_chat_group_members_changed_cb (TpChannel     *self,
        EmpathyContact *contact;
        EmpathyContact *actor_contact = NULL;
        guint i;
+       ContactRenameData *rename_data;
+       TpHandle old_handle;
+
+       /* Contact renamed */
+       if (reason == TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED) {
+               /* there can only be a single 'added' and a single 'removed' handle */
+               g_warn_if_fail (removed->len == 1);
+               g_warn_if_fail (added->len == 1);
+
+               old_handle = g_array_index (removed, guint, 0);
+
+               rename_data = contact_rename_data_new (old_handle, reason, message);
+               empathy_tp_contact_factory_get_from_handles (priv->factory,
+                       added->len, (TpHandle *) added->data,
+                       tp_chat_got_renamed_contacts_cb,
+                       rename_data, (GDestroyNotify) contact_rename_data_free,
+                       G_OBJECT (chat));
+               return;
+       }
 
        if (actor != 0) {
                actor_contact = chat_lookup_contact (chat, actor, FALSE);
@@ -1015,7 +1165,7 @@ tp_chat_got_remote_contact_cb (EmpathyTpContactFactory *factory,
 
        if (error) {
                DEBUG ("Error: %s", error->message);
-               empathy_tp_chat_close (EMPATHY_TP_CHAT (chat));
+               empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
                return;
        }
 
@@ -1036,7 +1186,7 @@ tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
 
        if (error) {
                DEBUG ("Error: %s", error->message);
-               empathy_tp_chat_close (EMPATHY_TP_CHAT (chat));
+               empathy_tp_chat_leave (EMPATHY_TP_CHAT (chat));
                return;
        }
 
@@ -1045,6 +1195,28 @@ tp_chat_got_self_contact_cb (EmpathyTpContactFactory *factory,
        tp_chat_check_if_ready (EMPATHY_TP_CHAT (chat));
 }
 
+static void
+password_flags_changed_cb (TpChannel *channel,
+    guint added,
+    guint removed,
+    gpointer user_data,
+    GObject *weak_object)
+{
+       EmpathyTpChat *self = EMPATHY_TP_CHAT (weak_object);
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       gboolean was_needed, needed;
+
+       was_needed = empathy_tp_chat_password_needed (self);
+
+       priv->password_flags |= added;
+       priv->password_flags ^= removed;
+
+       needed = empathy_tp_chat_password_needed (self);
+
+       if (was_needed != needed)
+               g_object_notify (G_OBJECT (self), "password-needed");
+}
+
 static void
 got_password_flags_cb (TpChannel *proxy,
                             guint password_flags,
@@ -1099,9 +1271,14 @@ tp_chat_constructor (GType                  type,
                        handles->len, (TpHandle *) handles->data,
                        tp_chat_got_added_contacts_cb, NULL, NULL, chat);
 
+               priv->can_upgrade_to_muc = FALSE;
+
                g_signal_connect (priv->channel, "group-members-changed",
                        G_CALLBACK (tp_chat_group_members_changed_cb), chat);
        } else {
+               EmpathyDispatcher *dispatcher = empathy_dispatcher_dup_singleton ();
+               GList *list, *ptr;
+
                /* Get the self contact from the connection's self handle */
                handle = tp_connection_get_self_handle (connection);
                empathy_tp_contact_factory_get_from_handle (priv->factory,
@@ -1113,6 +1290,25 @@ tp_chat_constructor (GType                  type,
                empathy_tp_contact_factory_get_from_handle (priv->factory,
                        handle, tp_chat_got_remote_contact_cb,
                        NULL, NULL, chat);
+
+               list = empathy_dispatcher_find_requestable_channel_classes (
+                       dispatcher, connection,
+                       tp_channel_get_channel_type (priv->channel),
+                       TP_UNKNOWN_HANDLE_TYPE, NULL);
+
+               for (ptr = list; ptr; ptr = ptr->next) {
+                       GValueArray *array = ptr->data;
+                       const char **oprops = g_value_get_boxed (
+                               g_value_array_get_nth (array, 1));
+
+                       if (tp_strv_contains (oprops, EMP_IFACE_CHANNEL_INTERFACE_CONFERENCE ".InitialChannels")) {
+                               priv->can_upgrade_to_muc = TRUE;
+                               break;
+                       }
+               }
+
+               g_list_free (list);
+               g_object_unref (dispatcher);
        }
 
        if (tp_proxy_has_interface_by_id (priv->channel,
@@ -1135,8 +1331,13 @@ tp_chat_constructor (GType                  type,
        if (tp_proxy_has_interface_by_id (priv->channel,
                                          TP_IFACE_QUARK_CHANNEL_INTERFACE_PASSWORD)) {
                priv->got_password_flags = FALSE;
-               tp_cli_channel_interface_password_call_get_password_flags (priv->channel,
-                                                                              -1, got_password_flags_cb, chat, NULL, chat);
+
+               tp_cli_channel_interface_password_connect_to_password_flags_changed
+                       (priv->channel, password_flags_changed_cb, chat, NULL,
+                        G_OBJECT (chat), NULL);
+
+               tp_cli_channel_interface_password_call_get_password_flags
+                       (priv->channel, -1, got_password_flags_cb, chat, NULL, chat);
        } else {
                /* No Password interface, so no need to fetch the password flags */
                priv->got_password_flags = TRUE;
@@ -1151,6 +1352,7 @@ tp_chat_get_property (GObject    *object,
                      GValue     *value,
                      GParamSpec *pspec)
 {
+       EmpathyTpChat *self = EMPATHY_TP_CHAT (object);
        EmpathyTpChatPriv *priv = GET_PRIV (object);
 
        switch (param_id) {
@@ -1163,6 +1365,9 @@ tp_chat_get_property (GObject    *object,
        case PROP_READY:
                g_value_set_boolean (value, priv->ready);
                break;
+       case PROP_PASSWORD_NEEDED:
+               g_value_set_boolean (value, empathy_tp_chat_password_needed (self));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
@@ -1223,6 +1428,14 @@ empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
                                                               FALSE,
                                                               G_PARAM_READABLE));
 
+       g_object_class_install_property (object_class,
+                                        PROP_PASSWORD_NEEDED,
+                                        g_param_spec_boolean ("password-needed",
+                                                              "password needed",
+                                                              "TRUE if a password is needed to join the channel",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+
        /* Signals */
        signals[MESSAGE_RECEIVED] =
                g_signal_new ("message-received",
@@ -1306,7 +1519,7 @@ empathy_tp_chat_new (TpChannel *channel)
                             NULL);
 }
 
-void
+static void
 empathy_tp_chat_close (EmpathyTpChat *chat) {
        EmpathyTpChatPriv *priv = GET_PRIV (chat);
 
@@ -1473,25 +1686,25 @@ out:
 
 void
 empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
-                                     const GList *messages) {
+                                     const GSList *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;
+       GSList *msgs = g_slist_copy ((GSList *) messages);
+       GSList *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);
+       length = g_slist_length ((GSList *) 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)) {
+       for (l = msgs; l != NULL; l = g_slist_next (l)) {
                GList *m;
 
                EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
@@ -1511,7 +1724,14 @@ empathy_tp_chat_acknowledge_messages (EmpathyTpChat *chat,
                acknowledge_messages (chat, message_ids);
 
        g_array_free (message_ids, TRUE);
-       g_list_free (msgs);
+       g_slist_free (msgs);
+}
+
+void
+empathy_tp_chat_acknowledge_all_messages (EmpathyTpChat *chat)
+{
+  empathy_tp_chat_acknowledge_messages (chat,
+    (GSList *) empathy_tp_chat_get_pending_messages (chat));
 }
 
 gboolean
@@ -1521,3 +1741,124 @@ empathy_tp_chat_password_needed (EmpathyTpChat *self)
 
        return priv->password_flags & TP_CHANNEL_PASSWORD_FLAG_PROVIDE;
 }
+
+static void
+provide_password_cb (TpChannel *channel,
+                                     gboolean correct,
+                                     const GError *error,
+                                     gpointer user_data,
+                                     GObject *weak_object)
+{
+       GSimpleAsyncResult *result = user_data;
+
+       if (error != NULL) {
+               g_simple_async_result_set_from_error (result, error);
+       }
+       else if (!correct) {
+               /* The current D-Bus API is a bit weird so re-use the
+                * AuthenticationFailed error */
+               g_simple_async_result_set_error (result, TP_ERRORS,
+                                                TP_ERROR_AUTHENTICATION_FAILED, "Wrong password");
+       }
+
+       g_simple_async_result_complete (result);
+       g_object_unref (result);
+}
+
+void
+empathy_tp_chat_provide_password_async (EmpathyTpChat *self,
+                                                    const gchar *password,
+                                                    GAsyncReadyCallback callback,
+                                                    gpointer user_data)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       GSimpleAsyncResult *result;
+
+       result = g_simple_async_result_new (G_OBJECT (self),
+                                           callback, user_data,
+                                           empathy_tp_chat_provide_password_finish);
+
+       tp_cli_channel_interface_password_call_provide_password
+               (priv->channel, -1, password, provide_password_cb, result,
+                NULL, G_OBJECT (self));
+}
+
+gboolean
+empathy_tp_chat_provide_password_finish (EmpathyTpChat *self,
+                                                     GAsyncResult *result,
+                                                     GError **error)
+{
+       if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result),
+               error))
+               return FALSE;
+
+       g_return_val_if_fail (g_simple_async_result_is_valid (result,
+                                                             G_OBJECT (self), empathy_tp_chat_provide_password_finish), FALSE);
+
+       return TRUE;
+}
+
+/**
+ * empathy_tp_chat_can_add_contact:
+ *
+ * Returns: %TRUE if empathy_contact_list_add() will work for this channel.
+ * That is if this chat is a 1-to-1 channel that can be upgraded to
+ * a MUC using the Conference interface or if the channel is a MUC.
+ */
+gboolean
+empathy_tp_chat_can_add_contact (EmpathyTpChat *self)
+{
+       EmpathyTpChatPriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_TP_CHAT (self), FALSE);
+
+       priv = GET_PRIV (self);
+
+       return priv->can_upgrade_to_muc ||
+               tp_proxy_has_interface_by_id (priv->channel,
+                       TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP);;
+}
+
+static void
+leave_remove_members_cb (TpChannel *proxy,
+                        const GError *error,
+                        gpointer user_data,
+                        GObject *weak_object)
+{
+       EmpathyTpChat *self = user_data;
+
+       if (error == NULL)
+               return;
+
+       DEBUG ("RemoveMembers failed (%s); closing the channel", error->message);
+       empathy_tp_chat_close (self);
+}
+
+void
+empathy_tp_chat_leave (EmpathyTpChat *self)
+{
+       EmpathyTpChatPriv *priv = GET_PRIV (self);
+       TpHandle self_handle;
+       GArray *array;
+
+       if (!tp_proxy_has_interface_by_id (priv->channel,
+               TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP)) {
+               empathy_tp_chat_close (self);
+               return;
+       }
+
+       self_handle = tp_channel_group_get_self_handle (priv->channel);
+       if (self_handle == 0) {
+               /* we are not member of the channel */
+               empathy_tp_chat_close (self);
+               return;
+       }
+
+       array = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), 1);
+       g_array_insert_val (array, 0, self_handle);
+
+       tp_cli_channel_interface_group_call_remove_members (priv->channel, -1, array,
+               "", leave_remove_members_cb, self, NULL, G_OBJECT (self));
+
+       g_array_free (array, TRUE);
+}