]> git.0d.be Git - empathy.git/blob - libempathy/empathy-chatroom-manager.c
Merge branch 'gnome-3-8'
[empathy.git] / libempathy / empathy-chatroom-manager.c
1 /*
2  * Copyright (C) 2004-2007 Imendio AB
3  * Copyright (C) 2007-2010 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 #include "empathy-chatroom-manager.h"
26
27 #include <sys/stat.h>
28
29 #include "empathy-client-factory.h"
30 #include "empathy-utils.h"
31
32 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
33 #include "empathy-debug.h"
34
35 #define CHATROOMS_XML_FILENAME "chatrooms.xml"
36 #define CHATROOMS_DTD_RESOURCENAME "/org/gnome/Empathy/empathy-chatroom-manager.dtd"
37 #define SAVE_TIMER 4
38
39 static EmpathyChatroomManager *chatroom_manager_singleton = NULL;
40
41 static void observe_channels_cb (TpSimpleObserver *observer,
42     TpAccount *account,
43     TpConnection *connection,
44     GList *channels,
45     TpChannelDispatchOperation *dispatch_operation,
46     GList *requests,
47     TpObserveChannelsContext *context,
48     gpointer user_data);
49
50 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatroomManager)
51 typedef struct
52 {
53   GList *chatrooms;
54   gchar *file;
55   TpAccountManager *account_manager;
56
57   /* source id of the autosave timer */
58   gint save_timer_id;
59   gboolean ready;
60   GFileMonitor *monitor;
61   gboolean writing;
62
63   TpBaseClient *observer;
64 } EmpathyChatroomManagerPriv;
65
66 enum {
67   CHATROOM_ADDED,
68   CHATROOM_REMOVED,
69   LAST_SIGNAL
70 };
71
72 static guint signals[LAST_SIGNAL];
73
74 /* properties */
75 enum
76 {
77   PROP_FILE = 1,
78   PROP_READY,
79   LAST_PROPERTY
80 };
81
82 G_DEFINE_TYPE (EmpathyChatroomManager, empathy_chatroom_manager, G_TYPE_OBJECT);
83
84 /*
85  * API to save/load and parse the chatrooms file.
86  */
87
88 static gboolean
89 chatroom_manager_file_save (EmpathyChatroomManager *manager)
90 {
91   EmpathyChatroomManagerPriv *priv;
92   xmlDocPtr doc;
93   xmlNodePtr root;
94   GList *l;
95
96   priv = GET_PRIV (manager);
97
98   priv->writing = TRUE;
99
100   doc = xmlNewDoc ((const xmlChar *) "1.0");
101   root = xmlNewNode (NULL, (const xmlChar *) "chatrooms");
102   xmlDocSetRootElement (doc, root);
103
104   for (l = priv->chatrooms; l; l = l->next)
105     {
106       EmpathyChatroom *chatroom;
107       xmlNodePtr       node;
108       const gchar     *account_id;
109
110       chatroom = l->data;
111
112       if (!empathy_chatroom_is_favorite (chatroom))
113         continue;
114
115       account_id = tp_proxy_get_object_path (empathy_chatroom_get_account (
116             chatroom));
117
118       node = xmlNewChild (root, NULL, (const xmlChar *) "chatroom", NULL);
119       xmlNewTextChild (node, NULL, (const xmlChar *) "name",
120         (const xmlChar *) empathy_chatroom_get_name (chatroom));
121       xmlNewTextChild (node, NULL, (const xmlChar *) "room",
122         (const xmlChar *) empathy_chatroom_get_room (chatroom));
123       xmlNewTextChild (node, NULL, (const xmlChar *) "account",
124         (const xmlChar *) account_id);
125       xmlNewTextChild (node, NULL, (const xmlChar *) "auto_connect",
126         empathy_chatroom_get_auto_connect (chatroom) ?
127         (const xmlChar *) "yes" : (const xmlChar *) "no");
128       xmlNewTextChild (node, NULL, (const xmlChar *) "always_urgent",
129         empathy_chatroom_is_always_urgent (chatroom) ?
130         (const xmlChar *) "yes" : (const xmlChar *) "no");
131     }
132
133   /* Make sure the XML is indented properly */
134   xmlIndentTreeOutput = 1;
135
136   DEBUG ("Saving file:'%s'", priv->file);
137   xmlSaveFormatFileEnc (priv->file, doc, "utf-8", 1);
138   xmlFreeDoc (doc);
139
140   xmlMemoryDump ();
141
142   priv->writing = FALSE;
143   return TRUE;
144 }
145
146 static gboolean
147 save_timeout (EmpathyChatroomManager *self)
148 {
149   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
150
151   priv->save_timer_id = 0;
152   chatroom_manager_file_save (self);
153
154   return FALSE;
155 }
156
157 static void
158 reset_save_timeout (EmpathyChatroomManager *self)
159 {
160   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
161
162   if (priv->save_timer_id > 0)
163     g_source_remove (priv->save_timer_id);
164
165   priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
166       (GSourceFunc) save_timeout, self);
167 }
168
169 static void
170 chatroom_changed_cb (EmpathyChatroom *chatroom,
171     GParamSpec *spec,
172     EmpathyChatroomManager *self)
173 {
174   reset_save_timeout (self);
175 }
176
177 static void
178 add_chatroom (EmpathyChatroomManager *self,
179     EmpathyChatroom *chatroom)
180 {
181   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
182
183   priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
184
185   /* Watch only those properties which are exported in the save file */
186   g_signal_connect (chatroom, "notify::name",
187       G_CALLBACK (chatroom_changed_cb), self);
188   g_signal_connect (chatroom, "notify::room",
189       G_CALLBACK (chatroom_changed_cb), self);
190   g_signal_connect (chatroom, "notify::account",
191       G_CALLBACK (chatroom_changed_cb), self);
192   g_signal_connect (chatroom, "notify::auto-connect",
193       G_CALLBACK (chatroom_changed_cb), self);
194   g_signal_connect (chatroom, "notify::always_urgent",
195       G_CALLBACK (chatroom_changed_cb), self);
196   g_signal_connect (chatroom, "notify::favorite",
197       G_CALLBACK (chatroom_changed_cb), self);
198 }
199
200 static void
201 chatroom_manager_parse_chatroom (EmpathyChatroomManager *manager,
202     xmlNodePtr node)
203 {
204   EmpathyChatroom *chatroom = NULL;
205   TpAccount *account;
206   xmlNodePtr child;
207   gchar *str;
208   gchar *name;
209   gchar *room;
210   gchar *account_id;
211   gboolean auto_connect;
212   gboolean always_urgent;
213   EmpathyClientFactory *factory;
214   GError *error = NULL;
215
216   /* default values. */
217   name = NULL;
218   room = NULL;
219   auto_connect = TRUE;
220   always_urgent = FALSE;
221   account_id = NULL;
222
223   for (child = node->children; child; child = child->next)
224     {
225       gchar *tag;
226
227       if (xmlNodeIsText (child))
228         continue;
229
230       tag = (gchar *) child->name;
231       str = (gchar *) xmlNodeGetContent (child);
232
233       if (strcmp (tag, "name") == 0)
234         {
235           name = g_strdup (str);
236         }
237       else if (strcmp (tag, "room") == 0)
238         {
239           room = g_strdup (str);
240         }
241       else if (strcmp (tag, "auto_connect") == 0)
242         {
243           if (strcmp (str, "yes") == 0)
244             auto_connect = TRUE;
245           else
246             auto_connect = FALSE;
247         }
248       else if (!tp_strdiff (tag, "always_urgent"))
249         {
250           if (strcmp (str, "yes") == 0)
251             always_urgent = TRUE;
252           else
253             always_urgent = FALSE;
254         }
255       else if (strcmp (tag, "account") == 0)
256         {
257           account_id = g_strdup (str);
258         }
259
260       xmlFree (str);
261     }
262
263   /* account has to be a valid Account object path */
264   if (!tp_dbus_check_valid_object_path (account_id, NULL) ||
265       !g_str_has_prefix (account_id, TP_ACCOUNT_OBJECT_PATH_BASE))
266     goto out;
267
268   factory = empathy_client_factory_dup ();
269
270   account = tp_simple_client_factory_ensure_account (
271           TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, &error);
272   g_object_unref (factory);
273
274   if (account == NULL)
275     {
276       DEBUG ("Failed to create account: %s", error->message);
277       g_error_free (error);
278
279       g_free (name);
280       g_free (room);
281       g_free (account_id);
282       return;
283     }
284
285   chatroom = empathy_chatroom_new_full (account, room, name, auto_connect);
286   empathy_chatroom_set_favorite (chatroom, TRUE);
287   empathy_chatroom_set_always_urgent (chatroom, always_urgent);
288   add_chatroom (manager, chatroom);
289   g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
290
291 out:
292   g_free (name);
293   g_free (room);
294   g_free (account_id);
295   tp_clear_object (&chatroom);
296 }
297
298 static gboolean
299 chatroom_manager_file_parse (EmpathyChatroomManager *manager,
300     const gchar *filename)
301 {
302   EmpathyChatroomManagerPriv *priv;
303   xmlParserCtxtPtr ctxt;
304   xmlDocPtr doc;
305   xmlNodePtr chatrooms;
306   xmlNodePtr node;
307
308   priv = GET_PRIV (manager);
309
310   DEBUG ("Attempting to parse file:'%s'...", filename);
311
312   ctxt = xmlNewParserCtxt ();
313
314   /* Parse and validate the file. */
315   doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
316   if (doc == NULL)
317     {
318       g_warning ("Failed to parse file:'%s'", filename);
319       xmlFreeParserCtxt (ctxt);
320       return FALSE;
321     }
322
323   if (!empathy_xml_validate_from_resource (doc, CHATROOMS_DTD_RESOURCENAME))
324     {
325       g_warning ("Failed to validate file:'%s'", filename);
326       xmlFreeDoc (doc);
327       xmlFreeParserCtxt (ctxt);
328       return FALSE;
329     }
330
331   /* The root node, chatrooms. */
332   chatrooms = xmlDocGetRootElement (doc);
333
334   for (node = chatrooms->children; node; node = node->next)
335     {
336       if (strcmp ((gchar *) node->name, "chatroom") == 0)
337         chatroom_manager_parse_chatroom (manager, node);
338     }
339
340   DEBUG ("Parsed %d chatrooms", g_list_length (priv->chatrooms));
341
342   xmlFreeDoc (doc);
343   xmlFreeParserCtxt (ctxt);
344
345   return TRUE;
346 }
347
348 static gboolean
349 chatroom_manager_get_all (EmpathyChatroomManager *manager)
350 {
351   EmpathyChatroomManagerPriv *priv;
352
353   priv = GET_PRIV (manager);
354
355   /* read file in */
356   if (g_file_test (priv->file, G_FILE_TEST_EXISTS) &&
357       !chatroom_manager_file_parse (manager, priv->file))
358     return FALSE;
359
360   if (!priv->ready)
361     {
362       priv->ready = TRUE;
363       g_object_notify (G_OBJECT (manager), "ready");
364     }
365
366   return TRUE;
367 }
368
369 static void
370 empathy_chatroom_manager_get_property (GObject *object,
371     guint property_id,
372     GValue *value,
373     GParamSpec *pspec)
374 {
375   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
376   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
377
378   switch (property_id)
379     {
380       case PROP_FILE:
381         g_value_set_string (value, priv->file);
382         break;
383       case PROP_READY:
384         g_value_set_boolean (value, priv->ready);
385         break;
386       default:
387         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
388         break;
389     }
390 }
391
392 static void
393 empathy_chatroom_manager_set_property (GObject *object,
394     guint property_id,
395     const GValue *value,
396     GParamSpec *pspec)
397 {
398   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
399   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
400
401   switch (property_id)
402     {
403       case PROP_FILE:
404         g_free (priv->file);
405         priv->file = g_value_dup_string (value);
406         break;
407       default:
408         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
409         break;
410     }
411 }
412
413 static void
414 chatroom_manager_dispose (GObject *object)
415 {
416   EmpathyChatroomManagerPriv *priv;
417
418   priv = GET_PRIV (object);
419
420   tp_clear_object (&priv->observer);
421   tp_clear_object (&priv->monitor);
422
423   (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->dispose) (object);
424 }
425
426 static void
427 clear_chatrooms (EmpathyChatroomManager *self)
428 {
429   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
430   GList *l, *tmp;
431
432   tmp = priv->chatrooms;
433
434   /* Unreffing the chatroom may result in destroying the underlying
435    * EmpathyTpChat which will fire the invalidated signal and so make us
436    * re-call this function. We already set priv->chatrooms to NULL so we won't
437    * try to destroy twice the same objects. */
438   priv->chatrooms = NULL;
439
440   for (l = tmp; l != NULL; l = g_list_next (l))
441     {
442       EmpathyChatroom *chatroom = l->data;
443
444       g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
445           self);
446       g_signal_emit (self, signals[CHATROOM_REMOVED], 0, chatroom);
447
448       g_object_unref (chatroom);
449     }
450
451   g_list_free (tmp);
452 }
453
454 static void
455 chatroom_manager_finalize (GObject *object)
456 {
457   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
458   EmpathyChatroomManagerPriv *priv;
459
460   priv = GET_PRIV (object);
461
462   g_object_unref (priv->account_manager);
463
464   if (priv->save_timer_id > 0)
465     {
466       /* have to save before destroy the object */
467       g_source_remove (priv->save_timer_id);
468       priv->save_timer_id = 0;
469       chatroom_manager_file_save (self);
470     }
471
472   clear_chatrooms (self);
473
474   g_free (priv->file);
475
476   (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->finalize) (object);
477 }
478
479 static void
480 file_changed_cb (GFileMonitor *monitor,
481     GFile *file,
482     GFile *other_file,
483     GFileMonitorEvent event_type,
484     gpointer user_data)
485 {
486   EmpathyChatroomManager *self = user_data;
487   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
488
489   if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
490     return;
491
492   if (priv->writing)
493     return;
494
495   DEBUG ("chatrooms file changed; reloading list");
496
497   clear_chatrooms (self);
498   chatroom_manager_get_all (self);
499 }
500
501 static void
502 account_manager_ready_cb (GObject *source_object,
503     GAsyncResult *result,
504     gpointer user_data)
505 {
506   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (user_data);
507   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
508   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
509   GError *error = NULL;
510   GFile *file = NULL;
511
512   if (!tp_proxy_prepare_finish (manager, result, &error))
513     {
514       DEBUG ("Failed to prepare account manager: %s", error->message);
515       g_error_free (error);
516       goto out;
517     }
518
519   chatroom_manager_get_all (self);
520
521   /* Set up file monitor */
522   file = g_file_new_for_path (priv->file);
523
524   priv->monitor = g_file_monitor (file, 0, NULL, &error);
525   if (priv->monitor == NULL)
526     {
527       DEBUG ("Failed to create file monitor on %s: %s", priv->file,
528           error->message);
529
530       g_error_free (error);
531       goto out;
532     }
533
534   g_signal_connect (priv->monitor, "changed", G_CALLBACK (file_changed_cb),
535       self);
536
537 out:
538   tp_clear_object (&file);
539   g_object_unref (self);
540 }
541
542 static GObject *
543 empathy_chatroom_manager_constructor (GType type,
544     guint n_props,
545     GObjectConstructParam *props)
546 {
547   GObject *obj;
548   EmpathyChatroomManager *self;
549   EmpathyChatroomManagerPriv *priv;
550   GError *error = NULL;
551
552   if (chatroom_manager_singleton != NULL)
553     return g_object_ref (chatroom_manager_singleton);
554
555   /* Parent constructor chain */
556   obj = G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->
557         constructor (type, n_props, props);
558
559   self = EMPATHY_CHATROOM_MANAGER (obj);
560   priv = GET_PRIV (self);
561
562   priv->ready = FALSE;
563
564   chatroom_manager_singleton = self;
565   g_object_add_weak_pointer (obj, (gpointer) &chatroom_manager_singleton);
566
567   priv->account_manager = tp_account_manager_dup ();
568
569   tp_proxy_prepare_async (priv->account_manager, NULL,
570       account_manager_ready_cb, g_object_ref (self));
571
572   if (priv->file == NULL)
573     {
574       /* Set the default file path */
575       gchar *dir;
576
577       dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
578       if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
579         g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
580
581       priv->file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
582       g_free (dir);
583     }
584
585   /* Setup a room observer */
586   priv->observer = tp_simple_observer_new_with_am (priv->account_manager, TRUE,
587       "Empathy.ChatroomManager", TRUE, observe_channels_cb, self, NULL);
588
589   tp_base_client_take_observer_filter (priv->observer, tp_asv_new (
590       TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
591         TP_IFACE_CHANNEL_TYPE_TEXT,
592       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
593         TP_HANDLE_TYPE_ROOM,
594       NULL));
595
596   if (!tp_base_client_register (priv->observer, &error))
597     {
598       g_critical ("Failed to register Observer: %s", error->message);
599
600       g_error_free (error);
601     }
602
603   return obj;
604 }
605
606 static void
607 empathy_chatroom_manager_class_init (EmpathyChatroomManagerClass *klass)
608 {
609   GObjectClass *object_class = G_OBJECT_CLASS (klass);
610   GParamSpec *param_spec;
611
612   object_class->constructor = empathy_chatroom_manager_constructor;
613   object_class->get_property = empathy_chatroom_manager_get_property;
614   object_class->set_property = empathy_chatroom_manager_set_property;
615   object_class->dispose = chatroom_manager_dispose;
616   object_class->finalize = chatroom_manager_finalize;
617
618   param_spec = g_param_spec_string (
619       "file",
620       "path of the favorite file",
621       "The path of the XML file containing user's favorites",
622       NULL,
623       G_PARAM_CONSTRUCT_ONLY |
624       G_PARAM_READWRITE |
625       G_PARAM_STATIC_NAME |
626       G_PARAM_STATIC_NICK |
627       G_PARAM_STATIC_BLURB);
628   g_object_class_install_property (object_class, PROP_FILE, param_spec);
629
630   param_spec = g_param_spec_boolean (
631       "ready",
632       "whether the manager is ready yet",
633       "whether the manager is ready yet",
634       FALSE,
635       G_PARAM_READABLE);
636   g_object_class_install_property (object_class, PROP_READY, param_spec);
637
638   signals[CHATROOM_ADDED] = g_signal_new ("chatroom-added",
639       G_TYPE_FROM_CLASS (klass),
640       G_SIGNAL_RUN_LAST,
641       0, NULL, NULL,
642       g_cclosure_marshal_generic,
643       G_TYPE_NONE,
644       1, EMPATHY_TYPE_CHATROOM);
645
646   signals[CHATROOM_REMOVED] = g_signal_new ("chatroom-removed",
647       G_TYPE_FROM_CLASS (klass),
648       G_SIGNAL_RUN_LAST,
649       0, NULL, NULL,
650       g_cclosure_marshal_generic,
651       G_TYPE_NONE,
652       1, EMPATHY_TYPE_CHATROOM);
653
654   g_type_class_add_private (object_class, sizeof (EmpathyChatroomManagerPriv));
655 }
656
657 static void
658 empathy_chatroom_manager_init (EmpathyChatroomManager *manager)
659 {
660   EmpathyChatroomManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
661       EMPATHY_TYPE_CHATROOM_MANAGER, EmpathyChatroomManagerPriv);
662
663   manager->priv = priv;
664 }
665
666 EmpathyChatroomManager *
667 empathy_chatroom_manager_dup_singleton (const gchar *file)
668 {
669   return EMPATHY_CHATROOM_MANAGER (g_object_new (EMPATHY_TYPE_CHATROOM_MANAGER,
670       "file", file, NULL));
671 }
672
673 gboolean
674 empathy_chatroom_manager_add (EmpathyChatroomManager *manager,
675     EmpathyChatroom *chatroom)
676 {
677   g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), FALSE);
678   g_return_val_if_fail (EMPATHY_IS_CHATROOM (chatroom), FALSE);
679
680   /* don't add more than once */
681   if (!empathy_chatroom_manager_find (manager,
682       empathy_chatroom_get_account (chatroom),
683       empathy_chatroom_get_room (chatroom)))
684     {
685       add_chatroom (manager, chatroom);
686
687       if (empathy_chatroom_is_favorite (chatroom))
688         reset_save_timeout (manager);
689
690       g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
691       return TRUE;
692     }
693
694   return FALSE;
695 }
696
697 static void
698 chatroom_manager_remove_link (EmpathyChatroomManager *manager,
699     GList *l)
700 {
701   EmpathyChatroomManagerPriv *priv;
702   EmpathyChatroom *chatroom;
703
704   priv = GET_PRIV (manager);
705
706   chatroom = l->data;
707
708   if (empathy_chatroom_is_favorite (chatroom))
709     reset_save_timeout (manager);
710
711   priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
712
713   g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, chatroom);
714   g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb, manager);
715
716   g_object_unref (chatroom);
717 }
718
719 void
720 empathy_chatroom_manager_remove (EmpathyChatroomManager *manager,
721     EmpathyChatroom        *chatroom)
722 {
723   EmpathyChatroomManagerPriv *priv;
724   GList *l;
725
726   g_return_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager));
727   g_return_if_fail (EMPATHY_IS_CHATROOM (chatroom));
728
729   priv = GET_PRIV (manager);
730
731   for (l = priv->chatrooms; l; l = l->next)
732     {
733       EmpathyChatroom *this_chatroom;
734
735       this_chatroom = l->data;
736
737       if (this_chatroom == chatroom ||
738           empathy_chatroom_equal (chatroom, this_chatroom))
739         {
740           chatroom_manager_remove_link (manager, l);
741           break;
742         }
743     }
744 }
745
746 EmpathyChatroom *
747 empathy_chatroom_manager_find (EmpathyChatroomManager *manager,
748     TpAccount *account,
749     const gchar *room)
750 {
751   EmpathyChatroomManagerPriv *priv;
752   GList *l;
753
754   g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
755   g_return_val_if_fail (room != NULL, NULL);
756
757   priv = GET_PRIV (manager);
758
759   for (l = priv->chatrooms; l; l = l->next)
760     {
761       EmpathyChatroom *chatroom;
762       TpAccount *this_account;
763       const gchar    *this_room;
764
765       chatroom = l->data;
766       this_account = empathy_chatroom_get_account (chatroom);
767       this_room = empathy_chatroom_get_room (chatroom);
768
769       if (this_account && this_room && account == this_account
770           && strcmp (this_room, room) == 0)
771         return chatroom;
772     }
773
774   return NULL;
775 }
776
777 EmpathyChatroom *
778 empathy_chatroom_manager_ensure_chatroom (EmpathyChatroomManager *manager,
779     TpAccount *account,
780     const gchar *room,
781     const gchar *name)
782 {
783   EmpathyChatroom *chatroom;
784
785   chatroom = empathy_chatroom_manager_find (manager, account, room);
786
787   if (chatroom)
788     {
789       return g_object_ref (chatroom);
790     }
791   else
792     {
793       chatroom = empathy_chatroom_new_full (account,
794         room,
795         name,
796         FALSE);
797       empathy_chatroom_manager_add (manager, chatroom);
798       return chatroom;
799     }
800 }
801
802 GList *
803 empathy_chatroom_manager_get_chatrooms (EmpathyChatroomManager *manager,
804     TpAccount *account)
805 {
806   EmpathyChatroomManagerPriv *priv;
807   GList *chatrooms, *l;
808
809   g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
810
811   priv = GET_PRIV (manager);
812
813   if (!account)
814     return g_list_copy (priv->chatrooms);
815
816   chatrooms = NULL;
817   for (l = priv->chatrooms; l; l = l->next)
818     {
819       EmpathyChatroom *chatroom;
820
821       chatroom = l->data;
822
823       if (account == empathy_chatroom_get_account (chatroom))
824         chatrooms = g_list_append (chatrooms, chatroom);
825     }
826
827   return chatrooms;
828 }
829
830 static void
831 chatroom_manager_chat_invalidated_cb (EmpathyTpChat *chat,
832   guint domain,
833   gint code,
834   gchar *message,
835   gpointer manager)
836 {
837   EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
838   GList *l;
839
840   for (l = priv->chatrooms; l; l = l->next)
841     {
842       EmpathyChatroom *chatroom = l->data;
843
844       if (empathy_chatroom_get_tp_chat (chatroom) != chat)
845         continue;
846
847       empathy_chatroom_set_tp_chat (chatroom, NULL);
848
849       if (!empathy_chatroom_is_favorite (chatroom))
850         {
851           /* Remove the chatroom from the list, unless it's in the list of
852            * favourites..
853            * FIXME this policy should probably not be in libempathy */
854           chatroom_manager_remove_link (manager, l);
855         }
856
857       break;
858     }
859 }
860
861 static void
862 observe_channels_cb (TpSimpleObserver *observer,
863     TpAccount *account,
864     TpConnection *connection,
865     GList *channels,
866     TpChannelDispatchOperation *dispatch_operation,
867     GList *requests,
868     TpObserveChannelsContext *context,
869     gpointer user_data)
870 {
871   EmpathyChatroomManager *self = user_data;
872   GList *l;
873
874   for (l = channels; l != NULL; l = g_list_next (l))
875     {
876       EmpathyTpChat *tp_chat = l->data;
877       const gchar *roomname;
878       EmpathyChatroom *chatroom;
879
880       if (tp_proxy_get_invalidated ((TpChannel *) tp_chat) != NULL)
881         continue;
882
883       if (!EMPATHY_IS_TP_CHAT (tp_chat))
884         continue;
885
886       roomname = empathy_tp_chat_get_id (tp_chat);
887       chatroom = empathy_chatroom_manager_find (self, account, roomname);
888
889       if (chatroom == NULL)
890         {
891           chatroom = empathy_chatroom_new_full (account, roomname, roomname,
892             FALSE);
893           empathy_chatroom_manager_add (self, chatroom);
894           g_object_unref (chatroom);
895         }
896
897       empathy_chatroom_set_tp_chat (chatroom, tp_chat);
898
899       g_signal_connect (tp_chat, "invalidated",
900         G_CALLBACK (chatroom_manager_chat_invalidated_cb),
901         self);
902     }
903
904   tp_observe_channels_context_accept (context);
905 }