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