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