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