c7b604632fd0b0f0fa07f332414566e1a5771d7c
[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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, 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                 gboolean         favorite;
97
98                 chatroom = l->data;
99
100                 g_object_get (chatroom, "favorite", &favorite, NULL);
101                 if (!favorite) {
102                         continue;
103                 }
104
105                 account_id = mc_account_get_unique_name (empathy_chatroom_get_account (chatroom));
106
107                 node = xmlNewChild (root, NULL, "chatroom", NULL);
108                 xmlNewTextChild (node, NULL, "name", empathy_chatroom_get_name (chatroom));
109                 xmlNewTextChild (node, NULL, "room", empathy_chatroom_get_room (chatroom));
110                 xmlNewTextChild (node, NULL, "account", account_id);
111                 xmlNewTextChild (node, NULL, "auto_connect",
112                         empathy_chatroom_get_auto_connect (chatroom) ? "yes" : "no");
113         }
114
115         /* Make sure the XML is indented properly */
116         xmlIndentTreeOutput = 1;
117
118         DEBUG ("Saving file:'%s'", priv->file);
119         xmlSaveFormatFileEnc (priv->file, doc, "utf-8", 1);
120         xmlFreeDoc (doc);
121
122         xmlCleanupParser ();
123         xmlMemoryDump ();
124
125         return TRUE;
126 }
127
128 static gboolean
129 save_timeout (EmpathyChatroomManager *self)
130 {
131   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
132
133   priv->save_timer_id = 0;
134   chatroom_manager_file_save (self);
135
136   return FALSE;
137 }
138
139 static void
140 reset_save_timeout (EmpathyChatroomManager *self)
141 {
142   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
143
144   if (priv->save_timer_id > 0)
145     {
146       g_source_remove (priv->save_timer_id);
147     }
148
149   priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
150       (GSourceFunc) save_timeout, self);
151 }
152
153 static void
154 chatroom_changed_cb (EmpathyChatroom *chatroom,
155                      GParamSpec *spec,
156                      EmpathyChatroomManager *self)
157 {
158   reset_save_timeout (self);
159 }
160
161 static void
162 add_chatroom (EmpathyChatroomManager *self,
163               EmpathyChatroom *chatroom)
164 {
165   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
166
167   priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
168
169   g_signal_connect (chatroom, "notify",
170       G_CALLBACK (chatroom_changed_cb), self);
171 }
172
173 static void
174 chatroom_manager_parse_chatroom (EmpathyChatroomManager *manager,
175                                  xmlNodePtr             node)
176 {
177         EmpathyChatroomManagerPriv *priv;
178         EmpathyChatroom            *chatroom;
179         McAccount                 *account;
180         xmlNodePtr                 child;
181         gchar                     *str;
182         gchar                     *name;
183         gchar                     *room;
184         gchar                     *account_id;
185         gboolean                   auto_connect;
186
187         priv = GET_PRIV (manager);
188
189         /* default values. */
190         name = NULL;
191         room = NULL;
192         auto_connect = TRUE;
193         account_id = NULL;
194
195         for (child = node->children; child; child = child->next) {
196                 gchar *tag;
197
198                 if (xmlNodeIsText (child)) {
199                         continue;
200                 }
201
202                 tag = (gchar *) child->name;
203                 str = (gchar *) xmlNodeGetContent (child);
204
205                 if (strcmp (tag, "name") == 0) {
206                         name = g_strdup (str);
207                 }
208                 else if (strcmp (tag, "room") == 0) {
209                         room = g_strdup (str);
210                 }
211                 else if (strcmp (tag, "auto_connect") == 0) {
212                         if (strcmp (str, "yes") == 0) {
213                                 auto_connect = TRUE;
214                         } else {
215                                 auto_connect = FALSE;
216                         }
217                 }
218                 else if (strcmp (tag, "account") == 0) {
219                         account_id = g_strdup (str);
220                 }
221
222                 xmlFree (str);
223         }
224
225         account = mc_account_lookup (account_id);
226         if (!account) {
227                 g_free (name);
228                 g_free (room);
229                 g_free (account_id);
230                 return;
231         }
232
233         chatroom = empathy_chatroom_new_full (account, room, name, auto_connect);
234         g_object_set (chatroom, "favorite", TRUE, NULL);
235         add_chatroom (manager, chatroom);
236         g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
237
238         g_object_unref (account);
239         g_free (name);
240         g_free (room);
241         g_free (account_id);
242 }
243
244 static gboolean
245 chatroom_manager_file_parse (EmpathyChatroomManager *manager,
246                              const gchar           *filename)
247 {
248         EmpathyChatroomManagerPriv *priv;
249         xmlParserCtxtPtr           ctxt;
250         xmlDocPtr                  doc;
251         xmlNodePtr                 chatrooms;
252         xmlNodePtr                 node;
253
254         priv = GET_PRIV (manager);
255
256         DEBUG ("Attempting to parse file:'%s'...", filename);
257
258         ctxt = xmlNewParserCtxt ();
259
260         /* Parse and validate the file. */
261         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
262         if (!doc) {
263                 g_warning ("Failed to parse file:'%s'", filename);
264                 xmlFreeParserCtxt (ctxt);
265                 return FALSE;
266         }
267
268         if (!empathy_xml_validate (doc, CHATROOMS_DTD_FILENAME)) {
269                 g_warning ("Failed to validate file:'%s'", filename);
270                 xmlFreeDoc(doc);
271                 xmlFreeParserCtxt (ctxt);
272                 return FALSE;
273         }
274
275         /* The root node, chatrooms. */
276         chatrooms = xmlDocGetRootElement (doc);
277
278         for (node = chatrooms->children; node; node = node->next) {
279                 if (strcmp ((gchar *) node->name, "chatroom") == 0) {
280                         chatroom_manager_parse_chatroom (manager, node);
281                 }
282         }
283
284         DEBUG ("Parsed %d chatrooms", g_list_length (priv->chatrooms));
285
286         xmlFreeDoc(doc);
287         xmlFreeParserCtxt (ctxt);
288
289         return TRUE;
290 }
291
292 static gboolean
293 chatroom_manager_get_all (EmpathyChatroomManager *manager)
294 {
295         EmpathyChatroomManagerPriv *priv;
296
297         priv = GET_PRIV (manager);
298
299         /* read file in */
300         if (g_file_test (priv->file, G_FILE_TEST_EXISTS) &&
301             !chatroom_manager_file_parse (manager, priv->file)) {
302                 return FALSE;
303         }
304
305         return TRUE;
306 }
307
308 static void
309 empathy_chatroom_manager_get_property (GObject *object,
310                                        guint property_id,
311                                        GValue *value,
312                                        GParamSpec *pspec)
313 {
314   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
315   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
316
317   switch (property_id)
318     {
319       case PROP_FILE:
320         g_value_set_string (value, priv->file);
321         break;
322       default:
323         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
324         break;
325     }
326 }
327
328 static void
329 empathy_chatroom_manager_set_property (GObject *object,
330                                        guint property_id,
331                                        const GValue *value,
332                                        GParamSpec *pspec)
333 {
334   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
335   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
336
337   switch (property_id)
338     {
339       case PROP_FILE:
340         g_free (priv->file);
341         priv->file = g_value_dup_string (value);
342         break;
343       default:
344         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
345         break;
346     }
347 }
348
349 static void
350 chatroom_manager_finalize (GObject *object)
351 {
352   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
353   EmpathyChatroomManagerPriv *priv;
354   GList *l;
355
356   priv = GET_PRIV (object);
357
358   g_object_unref (priv->account_manager);
359
360   if (priv->save_timer_id > 0)
361     {
362       /* have to save before destroy the object */
363       g_source_remove (priv->save_timer_id);
364       priv->save_timer_id = 0;
365       chatroom_manager_file_save (self);
366     }
367
368   for (l = priv->chatrooms; l != NULL; l = g_list_next (l))
369     {
370       EmpathyChatroom *chatroom = l->data;
371
372       g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
373           self);
374
375       g_object_unref (chatroom);
376     }
377
378   g_list_free (priv->chatrooms);
379   g_free (priv->file);
380
381   (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->finalize) (object);
382 }
383
384 static GObject *
385 empathy_chatroom_manager_constructor (GType type,
386                                       guint n_props,
387                                       GObjectConstructParam *props)
388 {
389   GObject *obj;
390   EmpathyChatroomManager *self;
391   EmpathyChatroomManagerPriv *priv;
392
393   if (chatroom_manager_singleton != NULL)
394     return g_object_ref (chatroom_manager_singleton);
395
396   /* Parent constructor chain */
397   obj = G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->
398         constructor (type, n_props, props);
399
400   self = EMPATHY_CHATROOM_MANAGER (obj);
401   priv = GET_PRIV (self);
402
403   chatroom_manager_singleton = self;
404   g_object_add_weak_pointer (obj, (gpointer) &chatroom_manager_singleton);
405
406   priv->account_manager = empathy_account_manager_dup_singleton ();
407
408   if (priv->file == NULL)
409     {
410       /* Set the default file path */
411       gchar *dir;
412
413       dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
414       if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
415         g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
416
417       priv->file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
418       g_free (dir);
419     }
420
421   chatroom_manager_get_all (self);
422   return obj;
423 }
424
425 static void
426 empathy_chatroom_manager_class_init (EmpathyChatroomManagerClass *klass)
427 {
428   GObjectClass *object_class = G_OBJECT_CLASS (klass);
429   GParamSpec *param_spec;
430
431   object_class->constructor = empathy_chatroom_manager_constructor;
432   object_class->get_property = empathy_chatroom_manager_get_property;
433   object_class->set_property = empathy_chatroom_manager_set_property;
434         object_class->finalize = chatroom_manager_finalize;
435
436   param_spec = g_param_spec_string (
437       "file",
438       "path of the favorite file",
439       "The path of the XML file containing user's favorites",
440       NULL,
441       G_PARAM_CONSTRUCT_ONLY |
442       G_PARAM_READWRITE |
443       G_PARAM_STATIC_NAME |
444       G_PARAM_STATIC_NICK |
445       G_PARAM_STATIC_BLURB);
446   g_object_class_install_property (object_class, PROP_FILE, param_spec);
447
448   signals[CHATROOM_ADDED] = g_signal_new ("chatroom-added",
449       G_TYPE_FROM_CLASS (klass),
450       G_SIGNAL_RUN_LAST,
451       0, NULL, NULL,
452       g_cclosure_marshal_VOID__OBJECT,
453       G_TYPE_NONE,
454       1, EMPATHY_TYPE_CHATROOM);
455
456   signals[CHATROOM_REMOVED] = g_signal_new ("chatroom-removed",
457       G_TYPE_FROM_CLASS (klass),
458       G_SIGNAL_RUN_LAST,
459       0, NULL, NULL,
460       g_cclosure_marshal_VOID__OBJECT,
461       G_TYPE_NONE,
462       1, EMPATHY_TYPE_CHATROOM);
463
464   g_type_class_add_private (object_class, sizeof (EmpathyChatroomManagerPriv));
465 }
466
467 static void
468 empathy_chatroom_manager_init (EmpathyChatroomManager *manager)
469 {
470   EmpathyChatroomManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
471       EMPATHY_TYPE_CHATROOM_MANAGER, EmpathyChatroomManagerPriv);
472
473   manager->priv = priv;
474 }
475
476 EmpathyChatroomManager *
477 empathy_chatroom_manager_dup_singleton (const gchar *file)
478 {
479   return EMPATHY_CHATROOM_MANAGER (g_object_new (EMPATHY_TYPE_CHATROOM_MANAGER,
480       "file", file, NULL));
481 }
482
483 gboolean
484 empathy_chatroom_manager_add (EmpathyChatroomManager *manager,
485                              EmpathyChatroom        *chatroom)
486 {
487   EmpathyChatroomManagerPriv *priv;
488
489   g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), FALSE);
490   g_return_val_if_fail (EMPATHY_IS_CHATROOM (chatroom), FALSE);
491
492   priv = GET_PRIV (manager);
493
494   /* don't add more than once */
495   if (!empathy_chatroom_manager_find (manager,
496       empathy_chatroom_get_account (chatroom),
497       empathy_chatroom_get_room (chatroom)))
498     {
499       gboolean favorite;
500
501       g_object_get (chatroom, "favorite", &favorite, NULL);
502       add_chatroom (manager, chatroom);
503
504       if (favorite)
505         reset_save_timeout (manager);
506
507       g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
508       return TRUE;
509     }
510
511   return FALSE;
512 }
513
514 void
515 empathy_chatroom_manager_remove (EmpathyChatroomManager *manager,
516                                  EmpathyChatroom        *chatroom)
517 {
518   EmpathyChatroomManagerPriv *priv;
519   GList *l;
520
521   g_return_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager));
522   g_return_if_fail (EMPATHY_IS_CHATROOM (chatroom));
523
524   priv = GET_PRIV (manager);
525
526   for (l = priv->chatrooms; l; l = l->next)
527     {
528       EmpathyChatroom *this_chatroom;
529
530       this_chatroom = l->data;
531
532       if (this_chatroom == chatroom ||
533           empathy_chatroom_equal (chatroom, this_chatroom))
534         {
535           gboolean favorite;
536
537           priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
538           g_object_get (chatroom, "favorite", &favorite, NULL);
539           if (favorite)
540             reset_save_timeout (manager);
541
542           g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, this_chatroom);
543           g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
544               manager);
545
546           g_object_unref (this_chatroom);
547           break;
548         }
549     }
550 }
551
552 EmpathyChatroom *
553 empathy_chatroom_manager_find (EmpathyChatroomManager *manager,
554                                McAccount *account,
555                                const gchar *room)
556 {
557         EmpathyChatroomManagerPriv *priv;
558         GList                     *l;
559
560         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
561         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
562         g_return_val_if_fail (room != NULL, NULL);
563
564         priv = GET_PRIV (manager);
565
566         for (l = priv->chatrooms; l; l = l->next) {
567                 EmpathyChatroom *chatroom;
568                 McAccount      *this_account;
569                 const gchar    *this_room;
570
571                 chatroom = l->data;
572                 this_account = empathy_chatroom_get_account (chatroom);
573                 this_room = empathy_chatroom_get_room (chatroom);
574
575                 if (this_account && this_room &&
576                     empathy_account_equal (account, this_account) &&
577                     strcmp (this_room, room) == 0) {
578                         return chatroom;
579                 }
580         }
581
582         return NULL;
583 }
584
585 GList *
586 empathy_chatroom_manager_get_chatrooms (EmpathyChatroomManager *manager,
587                                        McAccount             *account)
588 {
589         EmpathyChatroomManagerPriv *priv;
590         GList                     *chatrooms, *l;
591
592         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
593
594         priv = GET_PRIV (manager);
595
596         if (!account) {
597                 return g_list_copy (priv->chatrooms);
598         }
599
600         chatrooms = NULL;
601         for (l = priv->chatrooms; l; l = l->next) {
602                 EmpathyChatroom *chatroom;
603
604                 chatroom = l->data;
605
606                 if (empathy_account_equal (account,
607                                           empathy_chatroom_get_account (chatroom))) {
608                         chatrooms = g_list_append (chatrooms, chatroom);
609                 }
610         }
611
612         return chatrooms;
613 }
614
615 guint
616 empathy_chatroom_manager_get_count (EmpathyChatroomManager *manager,
617                                    McAccount             *account)
618 {
619         EmpathyChatroomManagerPriv *priv;
620         GList                     *l;
621         guint                      count = 0;
622
623         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), 0);
624
625         priv = GET_PRIV (manager);
626
627         if (!account) {
628                 return g_list_length (priv->chatrooms);
629         }
630
631         for (l = priv->chatrooms; l; l = l->next) {
632                 EmpathyChatroom *chatroom;
633
634                 chatroom = l->data;
635
636                 if (empathy_account_equal (account,
637                                            empathy_chatroom_get_account (chatroom))) {
638                         count++;
639                 }
640         }
641
642         return count;
643 }
644
645 static void
646 chatroom_manager_chat_destroyed_cb (EmpathyTpChat *chat,
647   gpointer manager)
648 {
649   EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
650   McAccount *account;
651   TpConnection *connection;
652   EmpathyChatroom *chatroom;
653   const gchar *roomname;
654   gboolean favorite;
655
656   connection = empathy_tp_chat_get_connection (chat);
657   account = empathy_account_manager_get_account (priv->account_manager,
658       connection);
659   roomname = empathy_tp_chat_get_id (chat);
660   chatroom = empathy_chatroom_manager_find (manager, account, roomname);
661
662   if (chatroom == NULL)
663     return;
664
665   g_object_set (chatroom, "tp-chat", NULL, NULL);
666   g_object_get (chatroom, "favorite", &favorite, NULL);
667
668   if (!favorite)
669     {
670       /* Remove the chatroom from the list, unless it's in the list of
671        * favourites..
672        * FIXME this policy should probably not be in libempathy */
673       empathy_chatroom_manager_remove (manager, chatroom);
674     }
675 }
676
677 static void
678 chatroom_manager_observe_channel_cb (EmpathyDispatcher *dispatcher,
679   EmpathyDispatchOperation *operation, gpointer manager)
680 {
681   EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
682   EmpathyChatroom *chatroom;
683   TpChannel *channel;
684   EmpathyTpChat *chat;
685   const gchar *roomname;
686   GQuark channel_type;
687   TpHandleType handle_type;
688   McAccount *account;
689   TpConnection *connection;
690
691   channel_type = empathy_dispatch_operation_get_channel_type_id (operation);
692
693   /* Observe Text channels to rooms only */
694   if (channel_type != TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
695     return;
696
697   channel = empathy_dispatch_operation_get_channel (operation);
698   tp_channel_get_handle (channel, &handle_type);
699
700   if (handle_type != TP_HANDLE_TYPE_ROOM)
701     return;
702
703   chat = EMPATHY_TP_CHAT (
704     empathy_dispatch_operation_get_channel_wrapper (operation));
705   connection = empathy_tp_chat_get_connection (chat);
706   account = empathy_account_manager_get_account (priv->account_manager,
707       connection);
708
709   roomname = empathy_tp_chat_get_id (chat);
710
711   chatroom = empathy_chatroom_manager_find (manager, account, roomname);
712
713   if (chatroom == NULL)
714     {
715       chatroom = empathy_chatroom_new_full (account, roomname, roomname,
716         FALSE);
717       g_object_set (G_OBJECT (chatroom), "tp-chat", chat, NULL);
718       empathy_chatroom_manager_add (manager, chatroom);
719       g_object_unref (chatroom);
720     }
721   else
722     {
723       g_object_set (G_OBJECT (chatroom), "tp-chat", chat, NULL);
724     }
725
726   /* A TpChat is always destroyed as it only gets unreffed after the channel
727    * has been invalidated in the dispatcher..  */
728   g_signal_connect (chat, "destroy",
729     G_CALLBACK (chatroom_manager_chat_destroyed_cb),
730     manager);
731 }
732
733 void
734 empathy_chatroom_manager_observe (EmpathyChatroomManager *manager,
735   EmpathyDispatcher *dispatcher)
736 {
737   g_signal_connect (dispatcher, "observe",
738     G_CALLBACK (chatroom_manager_observe_channel_cb), manager);
739 }