]> git.0d.be Git - empathy.git/blob - libempathy/empathy-chatroom-manager.c
Put configuration data in XDG_CONFIG_DIRS (GNOME bug 494007)
[empathy.git] / libempathy / empathy-chatroom-manager.c
1 /*
2  * Copyright (C) 2004-2007 Imendio AB
3  * Copyright (C) 2007-2009 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  *          Martyn Russell <martyn@imendio.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
32
33 #include "empathy-tp-chat.h"
34 #include "empathy-chatroom-manager.h"
35 #include "empathy-account-manager.h"
36 #include "empathy-utils.h"
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
39 #include "empathy-debug.h"
40
41 #define CHATROOMS_XML_FILENAME "chatrooms.xml"
42 #define CHATROOMS_DTD_FILENAME "empathy-chatroom-manager.dtd"
43 #define SAVE_TIMER 4
44
45 static EmpathyChatroomManager *chatroom_manager_singleton = NULL;
46
47 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatroomManager)
48 typedef struct
49 {
50   GList *chatrooms;
51   gchar *file;
52   EmpathyAccountManager *account_manager;
53   /* source id of the autosave timer */
54   gint save_timer_id;
55 } EmpathyChatroomManagerPriv;
56
57 enum {
58   CHATROOM_ADDED,
59   CHATROOM_REMOVED,
60   LAST_SIGNAL
61 };
62
63 static guint signals[LAST_SIGNAL];
64
65 /* properties */
66 enum
67 {
68   PROP_FILE = 1,
69   LAST_PROPERTY
70 };
71
72 G_DEFINE_TYPE (EmpathyChatroomManager, empathy_chatroom_manager, G_TYPE_OBJECT);
73
74 /*
75  * API to save/load and parse the chatrooms file.
76  */
77
78 static gboolean
79 chatroom_manager_file_save (EmpathyChatroomManager *manager)
80 {
81         EmpathyChatroomManagerPriv *priv;
82         xmlDocPtr                  doc;
83         xmlNodePtr                 root;
84         GList                     *l;
85
86         priv = GET_PRIV (manager);
87
88         doc = xmlNewDoc ("1.0");
89         root = xmlNewNode (NULL, "chatrooms");
90         xmlDocSetRootElement (doc, root);
91
92         for (l = priv->chatrooms; l; l = l->next) {
93                 EmpathyChatroom *chatroom;
94                 xmlNodePtr       node;
95                 const gchar     *account_id;
96
97                 chatroom = l->data;
98
99                 if (!empathy_chatroom_is_favorite (chatroom)) {
100                         continue;
101                 }
102
103                 account_id = empathy_account_get_unique_name (
104                   empathy_chatroom_get_account (chatroom));
105
106                 node = xmlNewChild (root, NULL, "chatroom", NULL);
107                 xmlNewTextChild (node, NULL, "name", empathy_chatroom_get_name (chatroom));
108                 xmlNewTextChild (node, NULL, "room", empathy_chatroom_get_room (chatroom));
109                 xmlNewTextChild (node, NULL, "account", account_id);
110                 xmlNewTextChild (node, NULL, "auto_connect",
111                         empathy_chatroom_get_auto_connect (chatroom) ? "yes" : "no");
112         }
113
114         /* Make sure the XML is indented properly */
115         xmlIndentTreeOutput = 1;
116
117         DEBUG ("Saving file:'%s'", priv->file);
118         xmlSaveFormatFileEnc (priv->file, doc, "utf-8", 1);
119         xmlFreeDoc (doc);
120
121         xmlCleanupParser ();
122         xmlMemoryDump ();
123
124         return TRUE;
125 }
126
127 static gboolean
128 save_timeout (EmpathyChatroomManager *self)
129 {
130   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
131
132   priv->save_timer_id = 0;
133   chatroom_manager_file_save (self);
134
135   return FALSE;
136 }
137
138 static void
139 reset_save_timeout (EmpathyChatroomManager *self)
140 {
141   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
142
143   if (priv->save_timer_id > 0)
144     {
145       g_source_remove (priv->save_timer_id);
146     }
147
148   priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
149       (GSourceFunc) save_timeout, self);
150 }
151
152 static void
153 chatroom_changed_cb (EmpathyChatroom *chatroom,
154                      GParamSpec *spec,
155                      EmpathyChatroomManager *self)
156 {
157   reset_save_timeout (self);
158 }
159
160 static void
161 add_chatroom (EmpathyChatroomManager *self,
162               EmpathyChatroom *chatroom)
163 {
164   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
165
166   priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
167
168   g_signal_connect (chatroom, "notify",
169       G_CALLBACK (chatroom_changed_cb), self);
170 }
171
172 static void
173 chatroom_manager_parse_chatroom (EmpathyChatroomManager *manager,
174                                  xmlNodePtr             node)
175 {
176         EmpathyChatroomManagerPriv *priv;
177         EmpathyChatroom            *chatroom;
178         EmpathyAccount             *account;
179         xmlNodePtr                 child;
180         gchar                     *str;
181         gchar                     *name;
182         gchar                     *room;
183         gchar                     *account_id;
184         gboolean                   auto_connect;
185
186         priv = GET_PRIV (manager);
187
188         /* default values. */
189         name = NULL;
190         room = NULL;
191         auto_connect = TRUE;
192         account_id = NULL;
193
194         for (child = node->children; child; child = child->next) {
195                 gchar *tag;
196
197                 if (xmlNodeIsText (child)) {
198                         continue;
199                 }
200
201                 tag = (gchar *) child->name;
202                 str = (gchar *) xmlNodeGetContent (child);
203
204                 if (strcmp (tag, "name") == 0) {
205                         name = g_strdup (str);
206                 }
207                 else if (strcmp (tag, "room") == 0) {
208                         room = g_strdup (str);
209                 }
210                 else if (strcmp (tag, "auto_connect") == 0) {
211                         if (strcmp (str, "yes") == 0) {
212                                 auto_connect = TRUE;
213                         } else {
214                                 auto_connect = FALSE;
215                         }
216                 }
217                 else if (strcmp (tag, "account") == 0) {
218                         account_id = g_strdup (str);
219                 }
220
221                 xmlFree (str);
222         }
223
224         account = empathy_account_manager_lookup (priv->account_manager, account_id);
225         if (!account) {
226                 g_free (name);
227                 g_free (room);
228                 g_free (account_id);
229                 return;
230         }
231
232         chatroom = empathy_chatroom_new_full (account, room, name, auto_connect);
233         empathy_chatroom_set_favorite (chatroom, TRUE);
234         add_chatroom (manager, chatroom);
235         g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
236
237         g_object_unref (account);
238         g_free (name);
239         g_free (room);
240         g_free (account_id);
241 }
242
243 static gboolean
244 chatroom_manager_file_parse (EmpathyChatroomManager *manager,
245                              const gchar           *filename)
246 {
247         EmpathyChatroomManagerPriv *priv;
248         xmlParserCtxtPtr           ctxt;
249         xmlDocPtr                  doc;
250         xmlNodePtr                 chatrooms;
251         xmlNodePtr                 node;
252
253         priv = GET_PRIV (manager);
254
255         DEBUG ("Attempting to parse file:'%s'...", filename);
256
257         ctxt = xmlNewParserCtxt ();
258
259         /* Parse and validate the file. */
260         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
261         if (!doc) {
262                 g_warning ("Failed to parse file:'%s'", filename);
263                 xmlFreeParserCtxt (ctxt);
264                 return FALSE;
265         }
266
267         if (!empathy_xml_validate (doc, CHATROOMS_DTD_FILENAME)) {
268                 g_warning ("Failed to validate file:'%s'", filename);
269                 xmlFreeDoc (doc);
270                 xmlFreeParserCtxt (ctxt);
271                 return FALSE;
272         }
273
274         /* The root node, chatrooms. */
275         chatrooms = xmlDocGetRootElement (doc);
276
277         for (node = chatrooms->children; node; node = node->next) {
278                 if (strcmp ((gchar *) node->name, "chatroom") == 0) {
279                         chatroom_manager_parse_chatroom (manager, node);
280                 }
281         }
282
283         DEBUG ("Parsed %d chatrooms", g_list_length (priv->chatrooms));
284
285         xmlFreeDoc (doc);
286         xmlFreeParserCtxt (ctxt);
287
288         return TRUE;
289 }
290
291 static gboolean
292 chatroom_manager_get_all (EmpathyChatroomManager *manager)
293 {
294         EmpathyChatroomManagerPriv *priv;
295
296         priv = GET_PRIV (manager);
297
298         /* read file in */
299         if (g_file_test (priv->file, G_FILE_TEST_EXISTS) &&
300             !chatroom_manager_file_parse (manager, priv->file)) {
301                 return FALSE;
302         }
303
304         return TRUE;
305 }
306
307 static void
308 empathy_chatroom_manager_get_property (GObject *object,
309                                        guint property_id,
310                                        GValue *value,
311                                        GParamSpec *pspec)
312 {
313   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
314   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
315
316   switch (property_id)
317     {
318       case PROP_FILE:
319         g_value_set_string (value, priv->file);
320         break;
321       default:
322         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
323         break;
324     }
325 }
326
327 static void
328 empathy_chatroom_manager_set_property (GObject *object,
329                                        guint property_id,
330                                        const GValue *value,
331                                        GParamSpec *pspec)
332 {
333   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
334   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
335
336   switch (property_id)
337     {
338       case PROP_FILE:
339         g_free (priv->file);
340         priv->file = g_value_dup_string (value);
341         break;
342       default:
343         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
344         break;
345     }
346 }
347
348 static void
349 chatroom_manager_finalize (GObject *object)
350 {
351   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
352   EmpathyChatroomManagerPriv *priv;
353   GList *l;
354
355   priv = GET_PRIV (object);
356
357   g_object_unref (priv->account_manager);
358
359   if (priv->save_timer_id > 0)
360     {
361       /* have to save before destroy the object */
362       g_source_remove (priv->save_timer_id);
363       priv->save_timer_id = 0;
364       chatroom_manager_file_save (self);
365     }
366
367   for (l = priv->chatrooms; l != NULL; l = g_list_next (l))
368     {
369       EmpathyChatroom *chatroom = l->data;
370
371       g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
372           self);
373
374       g_object_unref (chatroom);
375     }
376
377   g_list_free (priv->chatrooms);
378   g_free (priv->file);
379
380   (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->finalize) (object);
381 }
382
383 static GObject *
384 empathy_chatroom_manager_constructor (GType type,
385                                       guint n_props,
386                                       GObjectConstructParam *props)
387 {
388   GObject *obj;
389   EmpathyChatroomManager *self;
390   EmpathyChatroomManagerPriv *priv;
391
392   if (chatroom_manager_singleton != NULL)
393     return g_object_ref (chatroom_manager_singleton);
394
395   /* Parent constructor chain */
396   obj = G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->
397         constructor (type, n_props, props);
398
399   self = EMPATHY_CHATROOM_MANAGER (obj);
400   priv = GET_PRIV (self);
401
402   chatroom_manager_singleton = self;
403   g_object_add_weak_pointer (obj, (gpointer) &chatroom_manager_singleton);
404
405   priv->account_manager = empathy_account_manager_dup_singleton ();
406
407   if (priv->file == NULL)
408     {
409       /* Set the default file path */
410       gchar *dir;
411
412       dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
413       if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
414         g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
415
416       priv->file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
417       g_free (dir);
418     }
419
420   chatroom_manager_get_all (self);
421   return obj;
422 }
423
424 static void
425 empathy_chatroom_manager_class_init (EmpathyChatroomManagerClass *klass)
426 {
427   GObjectClass *object_class = G_OBJECT_CLASS (klass);
428   GParamSpec *param_spec;
429
430   object_class->constructor = empathy_chatroom_manager_constructor;
431   object_class->get_property = empathy_chatroom_manager_get_property;
432   object_class->set_property = empathy_chatroom_manager_set_property;
433         object_class->finalize = chatroom_manager_finalize;
434
435   param_spec = g_param_spec_string (
436       "file",
437       "path of the favorite file",
438       "The path of the XML file containing user's favorites",
439       NULL,
440       G_PARAM_CONSTRUCT_ONLY |
441       G_PARAM_READWRITE |
442       G_PARAM_STATIC_NAME |
443       G_PARAM_STATIC_NICK |
444       G_PARAM_STATIC_BLURB);
445   g_object_class_install_property (object_class, PROP_FILE, param_spec);
446
447   signals[CHATROOM_ADDED] = g_signal_new ("chatroom-added",
448       G_TYPE_FROM_CLASS (klass),
449       G_SIGNAL_RUN_LAST,
450       0, NULL, NULL,
451       g_cclosure_marshal_VOID__OBJECT,
452       G_TYPE_NONE,
453       1, EMPATHY_TYPE_CHATROOM);
454
455   signals[CHATROOM_REMOVED] = g_signal_new ("chatroom-removed",
456       G_TYPE_FROM_CLASS (klass),
457       G_SIGNAL_RUN_LAST,
458       0, NULL, NULL,
459       g_cclosure_marshal_VOID__OBJECT,
460       G_TYPE_NONE,
461       1, EMPATHY_TYPE_CHATROOM);
462
463   g_type_class_add_private (object_class, sizeof (EmpathyChatroomManagerPriv));
464 }
465
466 static void
467 empathy_chatroom_manager_init (EmpathyChatroomManager *manager)
468 {
469   EmpathyChatroomManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
470       EMPATHY_TYPE_CHATROOM_MANAGER, EmpathyChatroomManagerPriv);
471
472   manager->priv = priv;
473 }
474
475 EmpathyChatroomManager *
476 empathy_chatroom_manager_dup_singleton (const gchar *file)
477 {
478   return EMPATHY_CHATROOM_MANAGER (g_object_new (EMPATHY_TYPE_CHATROOM_MANAGER,
479       "file", file, NULL));
480 }
481
482 gboolean
483 empathy_chatroom_manager_add (EmpathyChatroomManager *manager,
484                              EmpathyChatroom        *chatroom)
485 {
486   EmpathyChatroomManagerPriv *priv;
487
488   g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), FALSE);
489   g_return_val_if_fail (EMPATHY_IS_CHATROOM (chatroom), FALSE);
490
491   priv = GET_PRIV (manager);
492
493   /* don't add more than once */
494   if (!empathy_chatroom_manager_find (manager,
495       empathy_chatroom_get_account (chatroom),
496       empathy_chatroom_get_room (chatroom)))
497     {
498       add_chatroom (manager, chatroom);
499
500       if (empathy_chatroom_is_favorite (chatroom))
501         reset_save_timeout (manager);
502
503       g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
504       return TRUE;
505     }
506
507   return FALSE;
508 }
509
510 static void
511 chatroom_manager_remove_link (EmpathyChatroomManager *manager,
512                               GList *l)
513 {
514   EmpathyChatroomManagerPriv *priv;
515   EmpathyChatroom *chatroom;
516
517   priv = GET_PRIV (manager);
518
519   chatroom = l->data;
520
521   if (empathy_chatroom_is_favorite (chatroom))
522     reset_save_timeout (manager);
523
524   priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
525
526   g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, chatroom);
527   g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb, manager);
528
529   g_object_unref (chatroom);
530 }
531
532 void
533 empathy_chatroom_manager_remove (EmpathyChatroomManager *manager,
534                                  EmpathyChatroom        *chatroom)
535 {
536   EmpathyChatroomManagerPriv *priv;
537   GList *l;
538
539   g_return_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager));
540   g_return_if_fail (EMPATHY_IS_CHATROOM (chatroom));
541
542   priv = GET_PRIV (manager);
543
544   for (l = priv->chatrooms; l; l = l->next)
545     {
546       EmpathyChatroom *this_chatroom;
547
548       this_chatroom = l->data;
549
550       if (this_chatroom == chatroom ||
551           empathy_chatroom_equal (chatroom, this_chatroom))
552         {
553           chatroom_manager_remove_link (manager, l);
554           break;
555         }
556     }
557 }
558
559 EmpathyChatroom *
560 empathy_chatroom_manager_find (EmpathyChatroomManager *manager,
561                                EmpathyAccount *account,
562                                const gchar *room)
563 {
564         EmpathyChatroomManagerPriv *priv;
565         GList                     *l;
566
567         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
568         g_return_val_if_fail (room != NULL, NULL);
569
570         priv = GET_PRIV (manager);
571
572         for (l = priv->chatrooms; l; l = l->next) {
573                 EmpathyChatroom *chatroom;
574                 EmpathyAccount *this_account;
575                 const gchar    *this_room;
576
577                 chatroom = l->data;
578                 this_account = empathy_chatroom_get_account (chatroom);
579                 this_room = empathy_chatroom_get_room (chatroom);
580
581                 if (this_account && this_room &&
582                     empathy_account_equal (account, this_account) &&
583                     strcmp (this_room, room) == 0) {
584                         return chatroom;
585                 }
586         }
587
588         return NULL;
589 }
590
591 GList *
592 empathy_chatroom_manager_get_chatrooms (EmpathyChatroomManager *manager,
593                                        EmpathyAccount *account)
594 {
595         EmpathyChatroomManagerPriv *priv;
596         GList                     *chatrooms, *l;
597
598         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
599
600         priv = GET_PRIV (manager);
601
602         if (!account) {
603                 return g_list_copy (priv->chatrooms);
604         }
605
606         chatrooms = NULL;
607         for (l = priv->chatrooms; l; l = l->next) {
608                 EmpathyChatroom *chatroom;
609
610                 chatroom = l->data;
611
612                 if (empathy_account_equal (account,
613                                           empathy_chatroom_get_account (chatroom))) {
614                         chatrooms = g_list_append (chatrooms, chatroom);
615                 }
616         }
617
618         return chatrooms;
619 }
620
621 guint
622 empathy_chatroom_manager_get_count (EmpathyChatroomManager *manager,
623                                    EmpathyAccount *account)
624 {
625         EmpathyChatroomManagerPriv *priv;
626         GList                     *l;
627         guint                      count = 0;
628
629         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), 0);
630
631         priv = GET_PRIV (manager);
632
633         if (!account) {
634                 return g_list_length (priv->chatrooms);
635         }
636
637         for (l = priv->chatrooms; l; l = l->next) {
638                 EmpathyChatroom *chatroom;
639
640                 chatroom = l->data;
641
642                 if (empathy_account_equal (account,
643                                            empathy_chatroom_get_account (chatroom))) {
644                         count++;
645                 }
646         }
647
648         return count;
649 }
650
651 static void
652 chatroom_manager_chat_destroyed_cb (EmpathyTpChat *chat,
653   gpointer manager)
654 {
655   EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
656   GList *l;
657
658   for (l = priv->chatrooms; l; l = l->next)
659     {
660       EmpathyChatroom *chatroom = l->data;
661
662       if (empathy_chatroom_get_tp_chat (chatroom) != chat)
663         continue;
664
665       empathy_chatroom_set_tp_chat (chatroom, NULL);
666
667       if (!empathy_chatroom_is_favorite (chatroom))
668         {
669           /* Remove the chatroom from the list, unless it's in the list of
670            * favourites..
671            * FIXME this policy should probably not be in libempathy */
672           chatroom_manager_remove_link (manager, l);
673         }
674
675       break;
676     }
677 }
678
679 static void
680 chatroom_manager_observe_channel_cb (EmpathyDispatcher *dispatcher,
681   EmpathyDispatchOperation *operation, gpointer manager)
682 {
683   EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
684   EmpathyChatroom *chatroom;
685   TpChannel *channel;
686   EmpathyTpChat *chat;
687   const gchar *roomname;
688   GQuark channel_type;
689   TpHandleType handle_type;
690   EmpathyAccount *account;
691   TpConnection *connection;
692
693   channel_type = empathy_dispatch_operation_get_channel_type_id (operation);
694
695   /* Observe Text channels to rooms only */
696   if (channel_type != TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
697     return;
698
699   channel = empathy_dispatch_operation_get_channel (operation);
700   tp_channel_get_handle (channel, &handle_type);
701
702   if (handle_type != TP_HANDLE_TYPE_ROOM)
703     return;
704
705   chat = EMPATHY_TP_CHAT (
706     empathy_dispatch_operation_get_channel_wrapper (operation));
707   connection = empathy_tp_chat_get_connection (chat);
708   account = empathy_account_manager_get_account (priv->account_manager,
709       connection);
710
711   roomname = empathy_tp_chat_get_id (chat);
712
713   chatroom = empathy_chatroom_manager_find (manager, account, roomname);
714
715   if (chatroom == NULL)
716     {
717       chatroom = empathy_chatroom_new_full (account, roomname, roomname,
718         FALSE);
719       empathy_chatroom_set_tp_chat (chatroom, chat);
720       empathy_chatroom_manager_add (manager, chatroom);
721       g_object_unref (chatroom);
722     }
723   else
724     {
725         empathy_chatroom_set_tp_chat (chatroom, chat);
726     }
727
728   /* A TpChat is always destroyed as it only gets unreffed after the channel
729    * has been invalidated in the dispatcher..  */
730   g_signal_connect (chat, "destroy",
731     G_CALLBACK (chatroom_manager_chat_destroyed_cb),
732     manager);
733 }
734
735 void
736 empathy_chatroom_manager_observe (EmpathyChatroomManager *manager,
737   EmpathyDispatcher *dispatcher)
738 {
739   g_signal_connect (dispatcher, "observe",
740     G_CALLBACK (chatroom_manager_observe_channel_cb), manager);
741 }