2 * Copyright (C) 2004-2007 Imendio AB
3 * Copyright (C) 2007-2010 Collabora Ltd.
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.
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.
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
20 * Authors: Xavier Claessens <xclaesse@gmail.com>
21 * Martyn Russell <martyn@imendio.com>
25 #include "empathy-chatroom-manager.h"
28 #include <tp-account-widgets/tpaw-utils.h>
30 #include "empathy-client-factory.h"
31 #include "empathy-utils.h"
33 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
34 #include "empathy-debug.h"
36 #define CHATROOMS_XML_FILENAME "chatrooms.xml"
37 #define CHATROOMS_DTD_RESOURCENAME "/org/gnome/Empathy/empathy-chatroom-manager.dtd"
40 static EmpathyChatroomManager *chatroom_manager_singleton = NULL;
42 static void observe_channels_cb (TpSimpleObserver *observer,
44 TpConnection *connection,
46 TpChannelDispatchOperation *dispatch_operation,
48 TpObserveChannelsContext *context,
51 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatroomManager)
56 TpAccountManager *account_manager;
58 /* source id of the autosave timer */
61 GFileMonitor *monitor;
64 TpBaseClient *observer;
65 } EmpathyChatroomManagerPriv;
73 static guint signals[LAST_SIGNAL];
83 G_DEFINE_TYPE (EmpathyChatroomManager, empathy_chatroom_manager, G_TYPE_OBJECT);
86 * API to save/load and parse the chatrooms file.
90 chatroom_manager_file_save (EmpathyChatroomManager *manager)
92 EmpathyChatroomManagerPriv *priv;
97 priv = GET_PRIV (manager);
101 doc = xmlNewDoc ((const xmlChar *) "1.0");
102 root = xmlNewNode (NULL, (const xmlChar *) "chatrooms");
103 xmlDocSetRootElement (doc, root);
105 for (l = priv->chatrooms; l; l = l->next)
107 EmpathyChatroom *chatroom;
109 const gchar *account_id;
113 if (!empathy_chatroom_is_favorite (chatroom))
116 account_id = tp_proxy_get_object_path (empathy_chatroom_get_account (
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");
134 /* Make sure the XML is indented properly */
135 xmlIndentTreeOutput = 1;
137 DEBUG ("Saving file:'%s'", priv->file);
138 xmlSaveFormatFileEnc (priv->file, doc, "utf-8", 1);
143 priv->writing = FALSE;
148 save_timeout (EmpathyChatroomManager *self)
150 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
152 priv->save_timer_id = 0;
153 chatroom_manager_file_save (self);
159 reset_save_timeout (EmpathyChatroomManager *self)
161 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
163 if (priv->save_timer_id > 0)
164 g_source_remove (priv->save_timer_id);
166 priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
167 (GSourceFunc) save_timeout, self);
171 chatroom_changed_cb (EmpathyChatroom *chatroom,
173 EmpathyChatroomManager *self)
175 reset_save_timeout (self);
179 add_chatroom (EmpathyChatroomManager *self,
180 EmpathyChatroom *chatroom)
182 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
184 priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
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);
202 chatroom_manager_parse_chatroom (EmpathyChatroomManager *manager,
205 EmpathyChatroom *chatroom = NULL;
212 gboolean auto_connect;
213 gboolean always_urgent;
214 EmpathyClientFactory *factory;
215 GError *error = NULL;
217 /* default values. */
221 always_urgent = FALSE;
224 for (child = node->children; child; child = child->next)
228 if (xmlNodeIsText (child))
231 tag = (gchar *) child->name;
232 str = (gchar *) xmlNodeGetContent (child);
234 if (strcmp (tag, "name") == 0)
236 name = g_strdup (str);
238 else if (strcmp (tag, "room") == 0)
240 room = g_strdup (str);
242 else if (strcmp (tag, "auto_connect") == 0)
244 if (strcmp (str, "yes") == 0)
247 auto_connect = FALSE;
249 else if (!tp_strdiff (tag, "always_urgent"))
251 if (strcmp (str, "yes") == 0)
252 always_urgent = TRUE;
254 always_urgent = FALSE;
256 else if (strcmp (tag, "account") == 0)
258 account_id = g_strdup (str);
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))
269 factory = empathy_client_factory_dup ();
271 account = tp_simple_client_factory_ensure_account (
272 TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, &error);
273 g_object_unref (factory);
277 DEBUG ("Failed to create account: %s", error->message);
278 g_error_free (error);
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);
296 tp_clear_object (&chatroom);
300 chatroom_manager_file_parse (EmpathyChatroomManager *manager,
301 const gchar *filename)
303 EmpathyChatroomManagerPriv *priv;
304 xmlParserCtxtPtr ctxt;
306 xmlNodePtr chatrooms;
309 priv = GET_PRIV (manager);
311 DEBUG ("Attempting to parse file:'%s'...", filename);
313 ctxt = xmlNewParserCtxt ();
315 /* Parse and validate the file. */
316 doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
319 g_warning ("Failed to parse file:'%s'", filename);
320 xmlFreeParserCtxt (ctxt);
324 if (!tpaw_xml_validate_from_resource (doc, CHATROOMS_DTD_RESOURCENAME))
326 g_warning ("Failed to validate file:'%s'", filename);
328 xmlFreeParserCtxt (ctxt);
332 /* The root node, chatrooms. */
333 chatrooms = xmlDocGetRootElement (doc);
335 for (node = chatrooms->children; node; node = node->next)
337 if (strcmp ((gchar *) node->name, "chatroom") == 0)
338 chatroom_manager_parse_chatroom (manager, node);
341 DEBUG ("Parsed %d chatrooms", g_list_length (priv->chatrooms));
344 xmlFreeParserCtxt (ctxt);
350 chatroom_manager_get_all (EmpathyChatroomManager *manager)
352 EmpathyChatroomManagerPriv *priv;
354 priv = GET_PRIV (manager);
357 if (g_file_test (priv->file, G_FILE_TEST_EXISTS) &&
358 !chatroom_manager_file_parse (manager, priv->file))
364 g_object_notify (G_OBJECT (manager), "ready");
371 empathy_chatroom_manager_get_property (GObject *object,
376 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
377 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
382 g_value_set_string (value, priv->file);
385 g_value_set_boolean (value, priv->ready);
388 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
394 empathy_chatroom_manager_set_property (GObject *object,
399 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
400 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
406 priv->file = g_value_dup_string (value);
409 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
415 chatroom_manager_dispose (GObject *object)
417 EmpathyChatroomManagerPriv *priv;
419 priv = GET_PRIV (object);
421 tp_clear_object (&priv->observer);
422 tp_clear_object (&priv->monitor);
424 (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->dispose) (object);
428 clear_chatrooms (EmpathyChatroomManager *self)
430 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
433 tmp = priv->chatrooms;
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;
441 for (l = tmp; l != NULL; l = g_list_next (l))
443 EmpathyChatroom *chatroom = l->data;
445 g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
447 g_signal_emit (self, signals[CHATROOM_REMOVED], 0, chatroom);
449 g_object_unref (chatroom);
456 chatroom_manager_finalize (GObject *object)
458 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
459 EmpathyChatroomManagerPriv *priv;
461 priv = GET_PRIV (object);
463 g_object_unref (priv->account_manager);
465 if (priv->save_timer_id > 0)
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);
473 clear_chatrooms (self);
477 (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->finalize) (object);
481 file_changed_cb (GFileMonitor *monitor,
484 GFileMonitorEvent event_type,
487 EmpathyChatroomManager *self = user_data;
488 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
490 if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
496 DEBUG ("chatrooms file changed; reloading list");
498 clear_chatrooms (self);
499 chatroom_manager_get_all (self);
503 account_manager_ready_cb (GObject *source_object,
504 GAsyncResult *result,
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;
513 if (!tp_proxy_prepare_finish (manager, result, &error))
515 DEBUG ("Failed to prepare account manager: %s", error->message);
516 g_error_free (error);
520 chatroom_manager_get_all (self);
522 /* Set up file monitor */
523 file = g_file_new_for_path (priv->file);
525 priv->monitor = g_file_monitor (file, 0, NULL, &error);
526 if (priv->monitor == NULL)
528 DEBUG ("Failed to create file monitor on %s: %s", priv->file,
531 g_error_free (error);
535 g_signal_connect (priv->monitor, "changed", G_CALLBACK (file_changed_cb),
539 tp_clear_object (&file);
540 g_object_unref (self);
544 empathy_chatroom_manager_constructor (GType type,
546 GObjectConstructParam *props)
549 EmpathyChatroomManager *self;
550 EmpathyChatroomManagerPriv *priv;
551 GError *error = NULL;
553 if (chatroom_manager_singleton != NULL)
554 return g_object_ref (chatroom_manager_singleton);
556 /* Parent constructor chain */
557 obj = G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->
558 constructor (type, n_props, props);
560 self = EMPATHY_CHATROOM_MANAGER (obj);
561 priv = GET_PRIV (self);
565 chatroom_manager_singleton = self;
566 g_object_add_weak_pointer (obj, (gpointer) &chatroom_manager_singleton);
568 priv->account_manager = tp_account_manager_dup ();
570 tp_proxy_prepare_async (priv->account_manager, NULL,
571 account_manager_ready_cb, g_object_ref (self));
573 if (priv->file == NULL)
575 /* Set the default file path */
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);
582 priv->file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
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);
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,
597 if (!tp_base_client_register (priv->observer, &error))
599 g_critical ("Failed to register Observer: %s", error->message);
601 g_error_free (error);
608 empathy_chatroom_manager_class_init (EmpathyChatroomManagerClass *klass)
610 GObjectClass *object_class = G_OBJECT_CLASS (klass);
611 GParamSpec *param_spec;
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;
619 param_spec = g_param_spec_string (
621 "path of the favorite file",
622 "The path of the XML file containing user's favorites",
624 G_PARAM_CONSTRUCT_ONLY |
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);
631 param_spec = g_param_spec_boolean (
633 "whether the manager is ready yet",
634 "whether the manager is ready yet",
637 g_object_class_install_property (object_class, PROP_READY, param_spec);
639 signals[CHATROOM_ADDED] = g_signal_new ("chatroom-added",
640 G_TYPE_FROM_CLASS (klass),
643 g_cclosure_marshal_generic,
645 1, EMPATHY_TYPE_CHATROOM);
647 signals[CHATROOM_REMOVED] = g_signal_new ("chatroom-removed",
648 G_TYPE_FROM_CLASS (klass),
651 g_cclosure_marshal_generic,
653 1, EMPATHY_TYPE_CHATROOM);
655 g_type_class_add_private (object_class, sizeof (EmpathyChatroomManagerPriv));
659 empathy_chatroom_manager_init (EmpathyChatroomManager *manager)
661 EmpathyChatroomManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
662 EMPATHY_TYPE_CHATROOM_MANAGER, EmpathyChatroomManagerPriv);
664 manager->priv = priv;
667 EmpathyChatroomManager *
668 empathy_chatroom_manager_dup_singleton (const gchar *file)
670 return EMPATHY_CHATROOM_MANAGER (g_object_new (EMPATHY_TYPE_CHATROOM_MANAGER,
671 "file", file, NULL));
675 empathy_chatroom_manager_add (EmpathyChatroomManager *manager,
676 EmpathyChatroom *chatroom)
678 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), FALSE);
679 g_return_val_if_fail (EMPATHY_IS_CHATROOM (chatroom), FALSE);
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)))
686 add_chatroom (manager, chatroom);
688 if (empathy_chatroom_is_favorite (chatroom))
689 reset_save_timeout (manager);
691 g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
699 chatroom_manager_remove_link (EmpathyChatroomManager *manager,
702 EmpathyChatroomManagerPriv *priv;
703 EmpathyChatroom *chatroom;
705 priv = GET_PRIV (manager);
709 if (empathy_chatroom_is_favorite (chatroom))
710 reset_save_timeout (manager);
712 priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
714 g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, chatroom);
715 g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb, manager);
717 g_object_unref (chatroom);
721 empathy_chatroom_manager_remove (EmpathyChatroomManager *manager,
722 EmpathyChatroom *chatroom)
724 EmpathyChatroomManagerPriv *priv;
727 g_return_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager));
728 g_return_if_fail (EMPATHY_IS_CHATROOM (chatroom));
730 priv = GET_PRIV (manager);
732 for (l = priv->chatrooms; l; l = l->next)
734 EmpathyChatroom *this_chatroom;
736 this_chatroom = l->data;
738 if (this_chatroom == chatroom ||
739 empathy_chatroom_equal (chatroom, this_chatroom))
741 chatroom_manager_remove_link (manager, l);
748 empathy_chatroom_manager_find (EmpathyChatroomManager *manager,
752 EmpathyChatroomManagerPriv *priv;
755 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
756 g_return_val_if_fail (room != NULL, NULL);
758 priv = GET_PRIV (manager);
760 for (l = priv->chatrooms; l; l = l->next)
762 EmpathyChatroom *chatroom;
763 TpAccount *this_account;
764 const gchar *this_room;
767 this_account = empathy_chatroom_get_account (chatroom);
768 this_room = empathy_chatroom_get_room (chatroom);
770 if (this_account && this_room && account == this_account
771 && strcmp (this_room, room) == 0)
779 empathy_chatroom_manager_ensure_chatroom (EmpathyChatroomManager *manager,
784 EmpathyChatroom *chatroom;
786 chatroom = empathy_chatroom_manager_find (manager, account, room);
790 return g_object_ref (chatroom);
794 chatroom = empathy_chatroom_new_full (account,
798 empathy_chatroom_manager_add (manager, chatroom);
804 empathy_chatroom_manager_get_chatrooms (EmpathyChatroomManager *manager,
807 EmpathyChatroomManagerPriv *priv;
808 GList *chatrooms, *l;
810 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
812 priv = GET_PRIV (manager);
815 return g_list_copy (priv->chatrooms);
818 for (l = priv->chatrooms; l; l = l->next)
820 EmpathyChatroom *chatroom;
824 if (account == empathy_chatroom_get_account (chatroom))
825 chatrooms = g_list_append (chatrooms, chatroom);
832 chatroom_manager_chat_invalidated_cb (EmpathyTpChat *chat,
838 EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
841 for (l = priv->chatrooms; l; l = l->next)
843 EmpathyChatroom *chatroom = l->data;
845 if (empathy_chatroom_get_tp_chat (chatroom) != chat)
848 empathy_chatroom_set_tp_chat (chatroom, NULL);
850 if (!empathy_chatroom_is_favorite (chatroom))
852 /* Remove the chatroom from the list, unless it's in the list of
854 * FIXME this policy should probably not be in libempathy */
855 chatroom_manager_remove_link (manager, l);
863 observe_channels_cb (TpSimpleObserver *observer,
865 TpConnection *connection,
867 TpChannelDispatchOperation *dispatch_operation,
869 TpObserveChannelsContext *context,
872 EmpathyChatroomManager *self = user_data;
875 for (l = channels; l != NULL; l = g_list_next (l))
877 EmpathyTpChat *tp_chat = l->data;
878 const gchar *roomname;
879 EmpathyChatroom *chatroom;
881 if (tp_proxy_get_invalidated ((TpChannel *) tp_chat) != NULL)
884 if (!EMPATHY_IS_TP_CHAT (tp_chat))
887 roomname = empathy_tp_chat_get_id (tp_chat);
888 chatroom = empathy_chatroom_manager_find (self, account, roomname);
890 if (chatroom == NULL)
892 chatroom = empathy_chatroom_new_full (account, roomname, roomname,
894 empathy_chatroom_manager_add (self, chatroom);
895 g_object_unref (chatroom);
898 empathy_chatroom_set_tp_chat (chatroom, tp_chat);
900 g_signal_connect (tp_chat, "invalidated",
901 G_CALLBACK (chatroom_manager_chat_invalidated_cb),
905 tp_observe_channels_context_accept (context);