]> git.0d.be Git - empathy.git/blob - libempathy/empathy-tp-contact-list.c
Wait for the TpGroup to be ready before using it
[empathy.git] / libempathy / empathy-tp-contact-list.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  * 
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26 #include <glib/gi18n.h>
27
28 #include <libtelepathy/tp-conn.h>
29 #include <libtelepathy/tp-chan.h>
30 #include <libtelepathy/tp-chan-type-contact-list-gen.h>
31 #include <telepathy-glib/util.h>
32 #include <telepathy-glib/dbus.h>
33
34 #include "empathy-tp-contact-list.h"
35 #include "empathy-contact-list.h"
36 #include "empathy-tp-group.h"
37 #include "empathy-debug.h"
38 #include "empathy-utils.h"
39
40 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
41                        EMPATHY_TYPE_TP_CONTACT_LIST, EmpathyTpContactListPriv))
42
43 #define DEBUG_DOMAIN "TpContactList"
44
45 struct _EmpathyTpContactListPriv {
46         TpConn         *tp_conn;
47         McAccount      *account;
48         MissionControl *mc;
49         const gchar    *protocol_group;
50
51         EmpathyTpGroup *publish;
52         EmpathyTpGroup *subscribe;
53         GList          *members;
54         GList          *pendings;
55
56         GList          *groups;
57         GHashTable     *contacts_groups;
58 };
59
60 typedef enum {
61         TP_CONTACT_LIST_TYPE_PUBLISH,
62         TP_CONTACT_LIST_TYPE_SUBSCRIBE,
63         TP_CONTACT_LIST_TYPE_UNKNOWN
64 } TpContactListType;
65
66 static void empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass);
67 static void empathy_tp_contact_list_init       (EmpathyTpContactList      *list);
68 static void tp_contact_list_iface_init         (EmpathyContactListIface   *iface);
69
70 enum {
71         DESTROY,
72         LAST_SIGNAL
73 };
74
75 static guint signals[LAST_SIGNAL];
76
77 G_DEFINE_TYPE_WITH_CODE (EmpathyTpContactList, empathy_tp_contact_list, G_TYPE_OBJECT,
78                          G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CONTACT_LIST,
79                                                 tp_contact_list_iface_init));
80
81 static void
82 tp_contact_list_group_destroy_cb (EmpathyTpGroup       *group,
83                                   EmpathyTpContactList *list)
84 {
85         EmpathyTpContactListPriv *priv = GET_PRIV (list);
86
87         empathy_debug (DEBUG_DOMAIN, "Group destroyed: %s",
88                        empathy_tp_group_get_name (group));
89
90         priv->groups = g_list_remove (priv->groups, group);
91         g_object_unref (group);
92 }
93
94 static void
95 tp_contact_list_group_member_added_cb (EmpathyTpGroup       *group,
96                                        EmpathyContact       *contact,
97                                        EmpathyContact       *actor,
98                                        guint                 reason,
99                                        const gchar          *message,
100                                        EmpathyTpContactList *list)
101 {
102         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
103         const gchar               *group_name;
104         GList                    **groups;
105
106         if (!g_list_find (priv->members, contact)) {
107                 return;
108         }
109
110         groups = g_hash_table_lookup (priv->contacts_groups, contact);
111         if (!groups) {
112                 groups = g_slice_new0 (GList*);
113                 g_hash_table_insert (priv->contacts_groups,
114                                      g_object_ref (contact),
115                                      groups);
116         }
117
118         group_name = empathy_tp_group_get_name (group);
119         if (!g_list_find_custom (*groups, group_name, (GCompareFunc) strcmp)) {
120                 empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) added to group %s",
121                                empathy_contact_get_id (contact),
122                                empathy_contact_get_handle (contact),
123                                group_name);
124                 *groups = g_list_prepend (*groups, g_strdup (group_name));
125                 g_signal_emit_by_name (list, "groups-changed", contact,
126                                        group_name,
127                                        TRUE);
128         }
129 }
130
131 static void
132 tp_contact_list_group_member_removed_cb (EmpathyTpGroup       *group,
133                                          EmpathyContact       *contact,
134                                          EmpathyContact       *actor,
135                                          guint                 reason,
136                                          const gchar          *message,
137                                          EmpathyTpContactList *list)
138 {
139         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
140         const gchar               *group_name;
141         GList                    **groups, *l;
142
143         if (!g_list_find (priv->members, contact)) {
144                 return;
145         }
146
147         groups = g_hash_table_lookup (priv->contacts_groups, contact);
148         if (!groups) {
149                 return;
150         }
151
152         group_name = empathy_tp_group_get_name (group);
153         if ((l = g_list_find_custom (*groups, group_name, (GCompareFunc) strcmp))) {
154                 empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) removed from group %s",
155                                empathy_contact_get_id (contact),
156                                empathy_contact_get_handle (contact),
157                                group_name);
158                 *groups = g_list_delete_link (*groups, l);
159                 g_signal_emit_by_name (list, "groups-changed", contact,
160                                        group_name,
161                                        FALSE);
162         }
163 }
164
165 static EmpathyTpGroup *
166 tp_contact_list_find_group (EmpathyTpContactList *list,
167                             const gchar          *group)
168 {
169         EmpathyTpContactListPriv *priv = GET_PRIV (list);
170         GList                    *l;
171
172         for (l = priv->groups; l; l = l->next) {
173                 if (!tp_strdiff (group, empathy_tp_group_get_name (l->data))) {
174                         return l->data;
175                 }
176         }
177         return NULL;
178 }
179
180 static TpContactListType
181 tp_contact_list_get_type (EmpathyTpContactList *list,
182                           EmpathyTpGroup       *group)
183 {
184         const gchar *name;
185
186         name = empathy_tp_group_get_name (group);
187         if (!tp_strdiff (name, "subscribe")) {
188                 return TP_CONTACT_LIST_TYPE_SUBSCRIBE;
189         } else if (!tp_strdiff (name, "publish")) {
190                 return TP_CONTACT_LIST_TYPE_PUBLISH;
191         }
192
193         return TP_CONTACT_LIST_TYPE_UNKNOWN;
194 }
195
196 static void
197 tp_contact_list_add_member (EmpathyTpContactList *list,
198                             EmpathyContact       *contact,
199                             EmpathyContact       *actor,
200                             guint                 reason,
201                             const gchar          *message)
202 {
203         EmpathyTpContactListPriv *priv = GET_PRIV (list);
204         GList                    *l;
205
206         /* Add to the list and emit signal */
207         priv->members = g_list_prepend (priv->members, g_object_ref (contact));
208         g_signal_emit_by_name (list, "members-changed",
209                                contact, actor, reason, message,
210                                TRUE);
211
212         /* This contact is now member, implicitly accept pending. */
213         if (g_list_find (priv->pendings, contact)) {
214                 empathy_tp_group_add_member (priv->publish, contact, "");
215         }
216
217         /* Update groups of the contact */
218         for (l = priv->groups; l; l = l->next) {
219                 if (empathy_tp_group_is_member (l->data, contact)) {
220                         tp_contact_list_group_member_added_cb (l->data, contact,
221                                                                NULL, 0, NULL, 
222                                                                list);
223                 }
224         }
225 }
226
227 static void
228 tp_contact_list_added_cb (EmpathyTpGroup       *group,
229                           EmpathyContact       *contact,
230                           EmpathyContact       *actor,
231                           guint                 reason,
232                           const gchar          *message,
233                           EmpathyTpContactList *list)
234 {
235         EmpathyTpContactListPriv *priv = GET_PRIV (list);
236         TpContactListType         list_type;
237
238         list_type = tp_contact_list_get_type (list, group);
239         empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) added to list type %d",
240                       empathy_contact_get_id (contact),
241                       empathy_contact_get_handle (contact),
242                       list_type);
243
244         /* We now get the presence of that contact, add it to members */
245         if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE &&
246             !g_list_find (priv->members, contact)) {
247                 tp_contact_list_add_member (list, contact, actor, reason, message);
248         }
249
250         /* We now send our presence to that contact, remove it from pendings */
251         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH &&
252             g_list_find (priv->pendings, contact)) {
253                 g_signal_emit_by_name (list, "pendings-changed",
254                                        contact, actor, reason, message,
255                                        FALSE);
256                 priv->pendings = g_list_remove (priv->pendings, contact);
257                 g_object_unref (contact);
258         }
259 }
260
261 static void
262 tp_contact_list_removed_cb (EmpathyTpGroup       *group,
263                             EmpathyContact       *contact,
264                             EmpathyContact       *actor,
265                             guint                 reason,
266                             const gchar          *message,
267                             EmpathyTpContactList *list)
268 {
269         EmpathyTpContactListPriv *priv = GET_PRIV (list);
270         TpContactListType         list_type;
271
272         list_type = tp_contact_list_get_type (list, group);
273         empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) removed from list type %d",
274                       empathy_contact_get_id (contact),
275                       empathy_contact_get_handle (contact),
276                       list_type);
277
278         /* This contact refuses to send us his presence, remove from members. */
279         if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE &&
280             g_list_find (priv->members, contact)) {
281                 g_signal_emit_by_name (list, "members-changed",
282                                        contact, actor, reason, message,
283                                        FALSE);
284                 priv->members = g_list_remove (priv->members, contact);
285                 g_object_unref (contact);
286         }
287
288         /* We refuse to send our presence to that contact, remove from pendings */
289         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH &&
290             g_list_find (priv->pendings, contact)) {
291                 g_signal_emit_by_name (list, "pendings-changed",
292                                        contact, actor, reason, message,
293                                        FALSE);
294                 priv->pendings = g_list_remove (priv->pendings, contact);
295                 g_object_unref (contact);
296         }
297 }
298
299 static void
300 tp_contact_list_pending_cb (EmpathyTpGroup       *group,
301                             EmpathyContact       *contact,
302                             EmpathyContact       *actor,
303                             guint                 reason,
304                             const gchar          *message,
305                             EmpathyTpContactList *list)
306 {
307         EmpathyTpContactListPriv *priv = GET_PRIV (list);
308         TpContactListType         list_type;
309
310         list_type = tp_contact_list_get_type (list, group);
311         empathy_debug (DEBUG_DOMAIN, "Contact %s (%d) pending in list type %d",
312                       empathy_contact_get_id (contact),
313                       empathy_contact_get_handle (contact),
314                       list_type);
315
316         /* We want this contact in our contact list but we don't get its 
317          * presence yet. Add to members anyway. */
318         if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE &&
319             !g_list_find (priv->members, contact)) {
320                 tp_contact_list_add_member (list, contact, actor, reason, message);
321         }
322
323         /* This contact wants our presence, auto accept if he is member,
324          * otherwise he is pending. */
325         if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH &&
326             !g_list_find (priv->pendings, contact)) {
327                 if (g_list_find (priv->members, contact)) {
328                         empathy_tp_group_add_member (priv->publish, contact, "");
329                 } else {
330                         priv->pendings = g_list_prepend (priv->pendings,
331                                                          g_object_ref (contact));
332                         g_signal_emit_by_name (list, "pendings-changed",
333                                                contact, actor, reason, message,
334                                                TRUE);
335                 }
336         }
337 }
338
339 static void
340 tp_contact_list_group_ready_cb (EmpathyTpGroup       *group,
341                                 gpointer              unused,
342                                 EmpathyTpContactList *list)
343 {
344         EmpathyTpContactListPriv *priv = GET_PRIV (list);
345         guint                     handle_type;
346         TpChan                   *tp_chan;
347
348         tp_chan = empathy_tp_group_get_channel (group);
349         handle_type = tp_chan->handle_type;
350
351         if (handle_type == TP_HANDLE_TYPE_LIST) {
352                 TpContactListType  list_type;
353                 GList             *contacts, *l;
354
355                 list_type = tp_contact_list_get_type (list, group);
356                 if (list_type == TP_CONTACT_LIST_TYPE_PUBLISH && !priv->publish) {
357                         priv->publish = g_object_ref (group);
358
359                         /* Publish is the list of contacts to who we send our
360                          * presence. Makes no sense to be in remote-pending */
361                         g_signal_connect (group, "local-pending",
362                                           G_CALLBACK (tp_contact_list_pending_cb),
363                                           list);
364
365                         contacts = empathy_tp_group_get_local_pendings (group);
366                         for (l = contacts; l; l = l->next) {
367                                 EmpathyPendingInfo *info = l->data;
368
369                                 tp_contact_list_pending_cb (group,
370                                                             info->member,
371                                                             info->actor,
372                                                             0,
373                                                             info->message,
374                                                             list);
375                                 empathy_pending_info_free (info);
376                         }
377                         g_list_free (contacts);
378                 }
379                 else if (list_type == TP_CONTACT_LIST_TYPE_SUBSCRIBE && !priv->subscribe) {
380                         priv->subscribe = g_object_ref (group);
381
382                         /* Subscribe is the list of contacts from who we
383                          * receive presence. Makes no sense to be in
384                          * local-pending */
385                         g_signal_connect (group, "remote-pending",
386                                           G_CALLBACK (tp_contact_list_pending_cb),
387                                           list);
388
389                         contacts = empathy_tp_group_get_remote_pendings (group);
390                         for (l = contacts; l; l = l->next) {
391                                 tp_contact_list_pending_cb (group,
392                                                             l->data,
393                                                             NULL, 0,
394                                                             NULL, list);
395                                 g_object_unref (l->data);
396                         }
397                         g_list_free (contacts);
398                 } else {
399                         empathy_debug (DEBUG_DOMAIN,
400                                       "Type of contact list channel unknown "
401                                       "or aleady have that list: %s",
402                                       empathy_tp_group_get_name (group));
403                         goto OUT;
404                 }
405                 empathy_debug (DEBUG_DOMAIN,
406                                "New contact list channel of type: %d",
407                                list_type);
408
409                 g_signal_connect (group, "member-added",
410                                   G_CALLBACK (tp_contact_list_added_cb),
411                                   list);
412                 g_signal_connect (group, "member-removed",
413                                   G_CALLBACK (tp_contact_list_removed_cb),
414                                   list);
415
416                 contacts = empathy_tp_group_get_members (group);
417                 for (l = contacts; l; l = l->next) {
418                         tp_contact_list_added_cb (group,
419                                                   l->data,
420                                                   NULL, 0, NULL,
421                                                   list);
422                         g_object_unref (l->data);
423                 }
424                 g_list_free (contacts);
425         }
426         else if (handle_type == TP_HANDLE_TYPE_GROUP) {
427                 const gchar *group_name;
428                 GList       *contacts, *l;
429
430                 /* Check if already exists */
431                 group_name = empathy_tp_group_get_name (group);
432                 if (tp_contact_list_find_group (list, group_name)) {
433                         goto OUT;
434                 }
435
436                 empathy_debug (DEBUG_DOMAIN, "New server-side group channel: %s",
437                                group_name);
438
439                 priv->groups = g_list_prepend (priv->groups, g_object_ref (group));
440
441                 g_signal_connect (group, "member-added",
442                                   G_CALLBACK (tp_contact_list_group_member_added_cb),
443                                   list);
444                 g_signal_connect (group, "member-removed",
445                                   G_CALLBACK (tp_contact_list_group_member_removed_cb),
446                                   list);
447                 g_signal_connect (group, "destroy",
448                                   G_CALLBACK (tp_contact_list_group_destroy_cb),
449                                   list);
450
451                 contacts = empathy_tp_group_get_members (group);
452                 for (l = contacts; l; l = l->next) {
453                         tp_contact_list_group_member_added_cb (group, l->data,
454                                                                NULL, 0, NULL,
455                                                                list);
456                         g_object_unref (l->data);
457                 }
458                 g_list_free (contacts);
459         } else {
460                 empathy_debug (DEBUG_DOMAIN,
461                                "Unknown handle type (%d) for contact list channel",
462                                handle_type);
463         }
464
465 OUT:
466         g_object_unref (group);
467         g_object_unref (list);
468 }
469
470 static void
471 tp_contact_list_newchannel_cb (DBusGProxy           *proxy,
472                                const gchar          *object_path,
473                                const gchar          *channel_type,
474                                TpHandleType          handle_type,
475                                guint                 channel_handle,
476                                gboolean              suppress_handler,
477                                EmpathyTpContactList *list)
478 {
479         EmpathyTpContactListPriv *priv = GET_PRIV (list);
480         EmpathyTpGroup           *group;
481         TpChan                   *new_chan;
482         const gchar              *bus_name;
483
484         if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 ||
485             suppress_handler ||
486             (handle_type != TP_HANDLE_TYPE_LIST &&
487              handle_type != TP_HANDLE_TYPE_GROUP)) {
488                 return;
489         }
490
491         bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn));
492         new_chan = tp_chan_new (tp_get_bus (),
493                                 bus_name,
494                                 object_path,
495                                 channel_type,
496                                 handle_type,
497                                 channel_handle);
498         g_return_if_fail (TELEPATHY_IS_CHAN (new_chan));
499
500         group = empathy_tp_group_new (priv->account, new_chan);
501         g_object_unref (new_chan);
502
503         /* We give a ref of group and list to the callback */
504         if (empathy_tp_group_is_ready (group)) {
505                 tp_contact_list_group_ready_cb (group, NULL, g_object_ref (list));
506         } else {
507                 g_signal_connect (group, "notify::ready",
508                                   G_CALLBACK (tp_contact_list_group_ready_cb),
509                                   g_object_ref (list));
510         }
511 }
512
513 static void
514 tp_contact_list_destroy_cb (TpConn               *tp_conn,
515                             EmpathyTpContactList *list)
516 {
517         EmpathyTpContactListPriv *priv = GET_PRIV (list);
518         GList                    *l;
519
520         empathy_debug (DEBUG_DOMAIN, "Account disconnected or CM crashed");
521
522         /* DBus proxie should NOT be used anymore */
523         g_object_unref (priv->tp_conn);
524         priv->tp_conn = NULL;
525
526         /* Remove all contacts */
527         for (l = priv->members; l; l = l->next) {
528                 g_signal_emit_by_name (list, "members-changed", l->data,
529                                        NULL, 0, NULL,
530                                        FALSE);
531                 g_object_unref (l->data);
532         }
533         for (l = priv->pendings; l; l = l->next) {
534                 g_signal_emit_by_name (list, "pendings-changed", l->data,
535                                        NULL, 0, NULL,
536                                        FALSE);
537                 g_object_unref (l->data);
538         }
539         g_list_free (priv->members);
540         g_list_free (priv->pendings);
541         priv->members = NULL;
542         priv->pendings = NULL;
543
544         /* Tell the world to not use us anymore */
545         g_signal_emit (list, signals[DESTROY], 0);
546 }
547
548 static void
549 tp_contact_list_disconnect (EmpathyTpContactList *list)
550 {
551         EmpathyTpContactListPriv *priv = GET_PRIV (list);
552
553         if (priv->tp_conn) {
554                 g_signal_handlers_disconnect_by_func (priv->tp_conn,
555                                                       tp_contact_list_destroy_cb,
556                                                       list);
557                 dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
558                                                 G_CALLBACK (tp_contact_list_newchannel_cb),
559                                                 list);
560         }
561 }
562
563 static void
564 tp_contact_list_status_changed_cb (MissionControl           *mc,
565                                    TpConnectionStatus        status,
566                                    McPresence                presence,
567                                    TpConnectionStatusReason  reason,
568                                    const gchar              *unique_name,
569                                    EmpathyTpContactList     *list)
570 {
571         EmpathyTpContactListPriv *priv = GET_PRIV (list);
572         McAccount                *account;
573
574         account = mc_account_lookup (unique_name);
575         if (status != TP_CONNECTION_STATUS_CONNECTED &&
576             empathy_account_equal (account, priv->account)) {
577                 /* We are disconnected */
578                 tp_contact_list_disconnect (list);
579                 tp_contact_list_destroy_cb (priv->tp_conn, list);
580         }
581
582         g_object_unref (account);
583 }
584
585 static void
586 tp_contact_list_group_list_free (GList **groups)
587 {
588         g_list_foreach (*groups, (GFunc) g_free, NULL);
589         g_list_free (*groups);
590         g_slice_free (GList*, groups);
591 }
592
593 static void
594 tp_contact_list_finalize (GObject *object)
595 {
596         EmpathyTpContactListPriv *priv;
597         EmpathyTpContactList     *list;
598
599         list = EMPATHY_TP_CONTACT_LIST (object);
600         priv = GET_PRIV (list);
601
602         empathy_debug (DEBUG_DOMAIN, "finalize: %p", object);
603
604         tp_contact_list_disconnect (list);
605
606         if (priv->mc) {
607                 dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
608                                                 "AccountStatusChanged",
609                                                 G_CALLBACK (tp_contact_list_status_changed_cb),
610                                                 list);
611                 g_object_unref (priv->mc);
612         }
613
614         if (priv->subscribe) {
615                 g_object_unref (priv->subscribe);
616         }
617         if (priv->publish) {
618                 g_object_unref (priv->publish);
619         }
620         if (priv->account) {
621                 g_object_unref (priv->account);
622         }
623         if (priv->tp_conn) {
624                 g_object_unref (priv->tp_conn);
625         }
626
627         g_hash_table_destroy (priv->contacts_groups);
628         g_list_foreach (priv->groups, (GFunc) g_object_unref, NULL);
629         g_list_free (priv->groups);
630         g_list_foreach (priv->members, (GFunc) g_object_unref, NULL);
631         g_list_free (priv->members);
632         g_list_foreach (priv->pendings, (GFunc) g_object_unref, NULL);
633         g_list_free (priv->pendings);
634
635         G_OBJECT_CLASS (empathy_tp_contact_list_parent_class)->finalize (object);
636 }
637
638 static void
639 empathy_tp_contact_list_class_init (EmpathyTpContactListClass *klass)
640 {
641         GObjectClass *object_class = G_OBJECT_CLASS (klass);
642
643         object_class->finalize = tp_contact_list_finalize;
644
645         signals[DESTROY] =
646                 g_signal_new ("destroy",
647                               G_TYPE_FROM_CLASS (klass),
648                               G_SIGNAL_RUN_LAST,
649                               0,
650                               NULL, NULL,
651                               g_cclosure_marshal_VOID__VOID,
652                               G_TYPE_NONE,
653                               0);
654
655         g_type_class_add_private (object_class, sizeof (EmpathyTpContactListPriv));
656 }
657
658 static void
659 empathy_tp_contact_list_init (EmpathyTpContactList *list)
660 {
661 }
662
663 static void
664 tp_contact_list_setup (EmpathyTpContactList *list)
665 {
666         EmpathyTpContactListPriv *priv = GET_PRIV (list);
667         GPtrArray                *channels;
668         guint                     i;
669         GError                   *error = NULL;
670
671         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
672
673         /* Get existing channels */
674         if (!tp_conn_list_channels (DBUS_G_PROXY (priv->tp_conn),
675                                     &channels,
676                                     &error)) {
677                 empathy_debug (DEBUG_DOMAIN,
678                               "Failed to get list of open channels: %s",
679                               error ? error->message : "No error given");
680                 g_clear_error (&error);
681                 return;
682         }
683
684         for (i = 0; i < channels->len; i++) {
685                 GValueArray  *chan_struct;
686                 const gchar  *object_path;
687                 const gchar  *chan_iface;
688                 TpHandleType  handle_type;
689                 guint         handle;
690
691                 chan_struct = g_ptr_array_index (channels, i);
692                 object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
693                 chan_iface = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
694                 handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
695                 handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
696
697                 tp_contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
698                                                object_path, chan_iface,
699                                                handle_type, handle,
700                                                FALSE,
701                                                list);
702
703                 g_value_array_free (chan_struct);
704         }
705         g_ptr_array_free (channels, TRUE);
706 }
707
708 EmpathyTpContactList *
709 empathy_tp_contact_list_new (McAccount *account)
710 {
711         EmpathyTpContactListPriv *priv;
712         EmpathyTpContactList     *list;
713         MissionControl           *mc;
714         TpConn                   *tp_conn = NULL;
715         McProfile                *profile;
716         const gchar              *protocol_name;
717
718         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
719
720         mc = empathy_mission_control_new ();
721
722         /* status==0 means CONNECTED */
723         if (mission_control_get_connection_status (mc, account, NULL) == 0) {
724                 tp_conn = mission_control_get_connection (mc, account, NULL);
725         }
726         if (!tp_conn) {
727                 /* The account is not connected, nothing to do. */
728                 g_object_unref (mc);
729                 return NULL;
730         }
731
732         list = g_object_new (EMPATHY_TYPE_TP_CONTACT_LIST, NULL);
733         priv = GET_PRIV (list);
734
735         priv->tp_conn = tp_conn;
736         priv->account = g_object_ref (account);
737         priv->mc = mc;
738         priv->contacts_groups = g_hash_table_new_full (g_direct_hash,
739                                                        g_direct_equal,
740                                                        (GDestroyNotify) g_object_unref,
741                                                        (GDestroyNotify) tp_contact_list_group_list_free);
742
743         /* Check for protocols that does not support contact groups. We can
744          * put all contacts into a special group in that case.
745          * FIXME: Default group should be an information in the profile */
746         profile = mc_account_get_profile (account);
747         protocol_name = mc_profile_get_protocol_name (profile);
748         if (strcmp (protocol_name, "local-xmpp") == 0) {
749                 priv->protocol_group = _("People nearby");
750         }
751         g_object_unref (profile);
752
753         /* Connect signals */
754         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc),
755                                      "AccountStatusChanged",
756                                      G_CALLBACK (tp_contact_list_status_changed_cb),
757                                      list, NULL);
758         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
759                                      G_CALLBACK (tp_contact_list_newchannel_cb),
760                                      list, NULL);
761         g_signal_connect (priv->tp_conn, "destroy",
762                           G_CALLBACK (tp_contact_list_destroy_cb),
763                           list);
764
765         tp_contact_list_setup (list);
766
767         return list;
768 }
769
770 McAccount *
771 empathy_tp_contact_list_get_account (EmpathyTpContactList *list)
772 {
773         EmpathyTpContactListPriv *priv;
774
775         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
776
777         priv = GET_PRIV (list);
778
779         return priv->account;
780 }
781
782 static void
783 tp_contact_list_add (EmpathyContactList *list,
784                      EmpathyContact     *contact,
785                      const gchar        *message)
786 {
787         EmpathyTpContactListPriv *priv = GET_PRIV (list);
788
789         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
790
791         empathy_tp_group_add_member (priv->subscribe, contact, message);
792         if (g_list_find (priv->pendings, contact)) {
793                 empathy_tp_group_add_member (priv->publish, contact, message);          
794         }
795 }
796
797 static void
798 tp_contact_list_remove (EmpathyContactList *list,
799                         EmpathyContact     *contact,
800                         const gchar        *message)
801 {
802         EmpathyTpContactListPriv *priv = GET_PRIV (list);
803
804         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
805
806         empathy_tp_group_remove_member (priv->subscribe, contact, message);
807         empathy_tp_group_remove_member (priv->publish, contact, message);               
808 }
809
810 static GList *
811 tp_contact_list_get_members (EmpathyContactList *list)
812 {
813         EmpathyTpContactListPriv *priv = GET_PRIV (list);
814
815         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
816
817         g_list_foreach (priv->members, (GFunc) g_object_ref, NULL);
818         return g_list_copy (priv->members);
819 }
820
821 static GList *
822 tp_contact_list_get_pendings (EmpathyContactList *list)
823 {
824         EmpathyTpContactListPriv *priv = GET_PRIV (list);
825
826         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
827
828         g_list_foreach (priv->pendings, (GFunc) g_object_ref, NULL);
829         return g_list_copy (priv->pendings);
830 }
831
832 static GList *
833 tp_contact_list_get_all_groups (EmpathyContactList *list)
834 {
835         EmpathyTpContactListPriv *priv = GET_PRIV (list);
836         GList                    *groups = NULL, *l;
837
838         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
839
840         if (priv->protocol_group) {
841                 groups = g_list_prepend (groups, g_strdup (priv->protocol_group));
842         }
843
844         for (l = priv->groups; l; l = l->next) {
845                 const gchar *name;
846
847                 name = empathy_tp_group_get_name (l->data);
848                 groups = g_list_prepend (groups, g_strdup (name));
849         }
850
851         return groups;
852 }
853
854 static GList *
855 tp_contact_list_get_groups (EmpathyContactList *list,
856                             EmpathyContact     *contact)
857 {
858         EmpathyTpContactListPriv  *priv = GET_PRIV (list);
859         GList                    **groups;
860         GList                     *ret = NULL, *l;
861
862         g_return_val_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list), NULL);
863
864         if (priv->protocol_group) {
865                 ret = g_list_prepend (ret, g_strdup (priv->protocol_group));
866         }
867
868         groups = g_hash_table_lookup (priv->contacts_groups, contact);
869         if (!groups) {
870                 return ret;
871         }
872
873         for (l = *groups; l; l = l->next) {
874                 ret = g_list_prepend (ret, g_strdup (l->data));
875         }
876
877
878         return ret;
879 }
880
881 static EmpathyTpGroup *
882 tp_contact_list_get_group (EmpathyTpContactList *list,
883                            const gchar          *group)
884 {
885         EmpathyTpContactListPriv *priv = GET_PRIV (list);
886         EmpathyTpGroup           *tp_group;
887         gchar                    *object_path;
888         guint                     handle;
889         GArray                   *handles;
890         const char               *names[2] = {group, NULL};
891         GError                   *error = NULL;
892
893         tp_group = tp_contact_list_find_group (list, group);
894         if (tp_group) {
895                 return tp_group;
896         }
897
898         empathy_debug (DEBUG_DOMAIN, "creating new group: %s", group);
899
900         if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
901                                       TP_HANDLE_TYPE_GROUP,
902                                       names,
903                                       &handles,
904                                       &error)) {
905                 empathy_debug (DEBUG_DOMAIN,
906                               "Failed to RequestHandles: %s",
907                               error ? error->message : "No error given");
908                 g_clear_error (&error);
909                 return NULL;
910         }
911         handle = g_array_index (handles, guint, 0);
912         g_array_free (handles, TRUE);
913
914         if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn),
915                                       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
916                                       TP_HANDLE_TYPE_GROUP,
917                                       handle,
918                                       TRUE,
919                                       &object_path,
920                                       &error)) {
921                 empathy_debug (DEBUG_DOMAIN,
922                               "Failed to RequestChannel: %s",
923                               error ? error->message : "No error given");
924                 g_clear_error (&error);
925                 return NULL;
926         }
927
928         tp_contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
929                                        object_path,
930                                        TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
931                                        TP_HANDLE_TYPE_GROUP,
932                                        handle, FALSE,
933                                        list);
934         g_free (object_path);
935
936         return tp_contact_list_find_group (list, group);
937 }
938
939 static void
940 tp_contact_list_add_to_group (EmpathyContactList *list,
941                               EmpathyContact     *contact,
942                               const gchar        *group)
943 {
944         EmpathyTpGroup *tp_group;
945
946         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
947
948         tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list),
949                                               group);
950
951         empathy_tp_group_add_member (tp_group, contact, "");
952 }
953
954 static void
955 tp_contact_list_remove_from_group (EmpathyContactList *list,
956                                    EmpathyContact     *contact,
957                                    const gchar        *group)
958 {
959         EmpathyTpGroup *tp_group;
960
961         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
962
963         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
964                                                group);
965
966         if (tp_group) {
967                 empathy_tp_group_remove_member (tp_group, contact, "");
968         }
969 }
970
971 static void
972 tp_contact_list_rename_group (EmpathyContactList *list,
973                               const gchar        *old_group,
974                               const gchar        *new_group)
975 {
976         EmpathyTpGroup *tp_group;
977         GList          *members;
978
979         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
980
981         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
982                                                old_group);
983         if (!tp_group) {
984                 return;
985         }
986
987         empathy_debug (DEBUG_DOMAIN, "rename group %s to %s", old_group, new_group);
988
989         /* Remove all members from the old group */
990         members = empathy_tp_group_get_members (tp_group);
991         empathy_tp_group_remove_members (tp_group, members, "");
992         empathy_tp_group_close (tp_group);
993
994         /* Add all members to the new group */
995         tp_group = tp_contact_list_get_group (EMPATHY_TP_CONTACT_LIST (list),
996                                               new_group);
997         empathy_tp_group_add_members (tp_group, members, "");
998
999         g_list_foreach (members, (GFunc) g_object_unref, NULL);
1000         g_list_free (members);
1001 }
1002
1003 static void
1004 tp_contact_list_remove_group (EmpathyContactList *list,
1005                               const gchar *group)
1006 {
1007         EmpathyTpGroup *tp_group;
1008         GList          *members;
1009
1010         g_return_if_fail (EMPATHY_IS_TP_CONTACT_LIST (list));
1011
1012         tp_group = tp_contact_list_find_group (EMPATHY_TP_CONTACT_LIST (list),
1013                                                group);
1014         
1015         if (!tp_group) {
1016                 return;
1017         }
1018
1019         empathy_debug (DEBUG_DOMAIN, "remove group %s", group);
1020
1021         /* Remove all members of the group */
1022         members = empathy_tp_group_get_members (tp_group);
1023         empathy_tp_group_remove_members (tp_group, members, "");
1024         empathy_tp_group_close (tp_group);
1025
1026         g_list_foreach (members, (GFunc) g_object_unref, NULL);
1027         g_list_free (members);
1028 }
1029
1030 static void
1031 tp_contact_list_iface_init (EmpathyContactListIface *iface)
1032 {
1033         iface->add               = tp_contact_list_add;
1034         iface->remove            = tp_contact_list_remove;
1035         iface->get_members       = tp_contact_list_get_members;
1036         iface->get_pendings      = tp_contact_list_get_pendings;
1037         iface->get_all_groups    = tp_contact_list_get_all_groups;
1038         iface->get_groups        = tp_contact_list_get_groups;
1039         iface->add_to_group      = tp_contact_list_add_to_group;
1040         iface->remove_from_group = tp_contact_list_remove_from_group;
1041         iface->rename_group      = tp_contact_list_rename_group;
1042         iface->remove_group      = tp_contact_list_remove_group;
1043 }
1044