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