2 * Copyright (C) 2004-2007 Imendio AB
3 * Copyright (C) 2007-2009 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>
27 #include <sys/types.h>
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
33 #include <telepathy-glib/account-manager.h>
34 #include <telepathy-glib/interfaces.h>
35 #include <telepathy-glib/util.h>
37 #include "empathy-tp-chat.h"
38 #include "empathy-chatroom-manager.h"
39 #include "empathy-utils.h"
41 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
42 #include "empathy-debug.h"
44 #define CHATROOMS_XML_FILENAME "chatrooms.xml"
45 #define CHATROOMS_DTD_FILENAME "empathy-chatroom-manager.dtd"
48 static EmpathyChatroomManager *chatroom_manager_singleton = NULL;
50 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatroomManager)
55 TpAccountManager *account_manager;
57 /* source id of the autosave timer */
60 } EmpathyChatroomManagerPriv;
68 static guint signals[LAST_SIGNAL];
78 G_DEFINE_TYPE (EmpathyChatroomManager, empathy_chatroom_manager, G_TYPE_OBJECT);
81 * API to save/load and parse the chatrooms file.
85 chatroom_manager_file_save (EmpathyChatroomManager *manager)
87 EmpathyChatroomManagerPriv *priv;
92 priv = GET_PRIV (manager);
94 doc = xmlNewDoc ((const xmlChar *) "1.0");
95 root = xmlNewNode (NULL, (const xmlChar *) "chatrooms");
96 xmlDocSetRootElement (doc, root);
98 for (l = priv->chatrooms; l; l = l->next) {
99 EmpathyChatroom *chatroom;
101 const gchar *account_id;
105 if (!empathy_chatroom_is_favorite (chatroom)) {
109 account_id = tp_proxy_get_object_path (
110 empathy_chatroom_get_account (chatroom));
112 node = xmlNewChild (root, NULL, (const xmlChar *) "chatroom", NULL);
113 xmlNewTextChild (node, NULL, (const xmlChar *) "name",
114 (const xmlChar *) empathy_chatroom_get_name (chatroom));
115 xmlNewTextChild (node, NULL, (const xmlChar *) "room",
116 (const xmlChar *) empathy_chatroom_get_room (chatroom));
117 xmlNewTextChild (node, NULL, (const xmlChar *) "account",
118 (const xmlChar *) account_id);
119 xmlNewTextChild (node, NULL, (const xmlChar *) "auto_connect",
120 empathy_chatroom_get_auto_connect (chatroom) ?
121 (const xmlChar *) "yes" : (const xmlChar *) "no");
122 xmlNewTextChild (node, NULL, (const xmlChar *) "always_urgent",
123 empathy_chatroom_is_always_urgent (chatroom) ?
124 (const xmlChar *) "yes" : (const xmlChar *) "no");
127 /* Make sure the XML is indented properly */
128 xmlIndentTreeOutput = 1;
130 DEBUG ("Saving file:'%s'", priv->file);
131 xmlSaveFormatFileEnc (priv->file, doc, "utf-8", 1);
140 save_timeout (EmpathyChatroomManager *self)
142 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
144 priv->save_timer_id = 0;
145 chatroom_manager_file_save (self);
151 reset_save_timeout (EmpathyChatroomManager *self)
153 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
155 if (priv->save_timer_id > 0)
157 g_source_remove (priv->save_timer_id);
160 priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
161 (GSourceFunc) save_timeout, self);
165 chatroom_changed_cb (EmpathyChatroom *chatroom,
167 EmpathyChatroomManager *self)
169 reset_save_timeout (self);
173 add_chatroom (EmpathyChatroomManager *self,
174 EmpathyChatroom *chatroom)
176 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
178 priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
180 g_signal_connect (chatroom, "notify",
181 G_CALLBACK (chatroom_changed_cb), self);
185 chatroom_manager_parse_chatroom (EmpathyChatroomManager *manager,
188 EmpathyChatroomManagerPriv *priv;
189 EmpathyChatroom *chatroom;
196 gboolean auto_connect;
197 gboolean always_urgent;
199 priv = GET_PRIV (manager);
201 /* default values. */
205 always_urgent = FALSE;
208 for (child = node->children; child; child = child->next) {
211 if (xmlNodeIsText (child)) {
215 tag = (gchar *) child->name;
216 str = (gchar *) xmlNodeGetContent (child);
218 if (strcmp (tag, "name") == 0) {
219 name = g_strdup (str);
221 else if (strcmp (tag, "room") == 0) {
222 room = g_strdup (str);
224 else if (strcmp (tag, "auto_connect") == 0) {
225 if (strcmp (str, "yes") == 0) {
228 auto_connect = FALSE;
231 else if (!tp_strdiff (tag, "always_urgent")) {
232 if (strcmp (str, "yes") == 0) {
233 always_urgent = TRUE;
235 always_urgent = FALSE;
238 else if (strcmp (tag, "account") == 0) {
239 account_id = g_strdup (str);
245 account = tp_account_manager_ensure_account (priv->account_manager,
254 chatroom = empathy_chatroom_new_full (account, room, name, auto_connect);
255 empathy_chatroom_set_favorite (chatroom, TRUE);
256 empathy_chatroom_set_always_urgent (chatroom, always_urgent);
257 add_chatroom (manager, chatroom);
258 g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
266 chatroom_manager_file_parse (EmpathyChatroomManager *manager,
267 const gchar *filename)
269 EmpathyChatroomManagerPriv *priv;
270 xmlParserCtxtPtr ctxt;
272 xmlNodePtr chatrooms;
275 priv = GET_PRIV (manager);
277 DEBUG ("Attempting to parse file:'%s'...", filename);
279 ctxt = xmlNewParserCtxt ();
281 /* Parse and validate the file. */
282 doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
284 g_warning ("Failed to parse file:'%s'", filename);
285 xmlFreeParserCtxt (ctxt);
289 if (!empathy_xml_validate (doc, CHATROOMS_DTD_FILENAME)) {
290 g_warning ("Failed to validate file:'%s'", filename);
292 xmlFreeParserCtxt (ctxt);
296 /* The root node, chatrooms. */
297 chatrooms = xmlDocGetRootElement (doc);
299 for (node = chatrooms->children; node; node = node->next) {
300 if (strcmp ((gchar *) node->name, "chatroom") == 0) {
301 chatroom_manager_parse_chatroom (manager, node);
305 DEBUG ("Parsed %d chatrooms", g_list_length (priv->chatrooms));
308 xmlFreeParserCtxt (ctxt);
314 chatroom_manager_get_all (EmpathyChatroomManager *manager)
316 EmpathyChatroomManagerPriv *priv;
318 priv = GET_PRIV (manager);
321 if (g_file_test (priv->file, G_FILE_TEST_EXISTS) &&
322 !chatroom_manager_file_parse (manager, priv->file)) {
327 g_object_notify (G_OBJECT (manager), "ready");
333 empathy_chatroom_manager_get_property (GObject *object,
338 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
339 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
344 g_value_set_string (value, priv->file);
347 g_value_set_boolean (value, priv->ready);
350 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
356 empathy_chatroom_manager_set_property (GObject *object,
361 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
362 EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
368 priv->file = g_value_dup_string (value);
371 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
377 chatroom_manager_finalize (GObject *object)
379 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
380 EmpathyChatroomManagerPriv *priv;
383 priv = GET_PRIV (object);
385 g_object_unref (priv->account_manager);
387 if (priv->save_timer_id > 0)
389 /* have to save before destroy the object */
390 g_source_remove (priv->save_timer_id);
391 priv->save_timer_id = 0;
392 chatroom_manager_file_save (self);
395 for (l = priv->chatrooms; l != NULL; l = g_list_next (l))
397 EmpathyChatroom *chatroom = l->data;
399 g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
402 g_object_unref (chatroom);
405 g_list_free (priv->chatrooms);
408 (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->finalize) (object);
412 account_manager_ready_cb (GObject *source_object,
413 GAsyncResult *result,
416 EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (user_data);
417 TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
418 GError *error = NULL;
420 if (!tp_account_manager_prepare_finish (manager, result, &error))
422 DEBUG ("Failed to prepare account manager: %s", error->message);
423 g_error_free (error);
427 chatroom_manager_get_all (self);
430 g_object_unref (self);
434 empathy_chatroom_manager_constructor (GType type,
436 GObjectConstructParam *props)
439 EmpathyChatroomManager *self;
440 EmpathyChatroomManagerPriv *priv;
442 if (chatroom_manager_singleton != NULL)
443 return g_object_ref (chatroom_manager_singleton);
445 /* Parent constructor chain */
446 obj = G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->
447 constructor (type, n_props, props);
449 self = EMPATHY_CHATROOM_MANAGER (obj);
450 priv = GET_PRIV (self);
454 chatroom_manager_singleton = self;
455 g_object_add_weak_pointer (obj, (gpointer) &chatroom_manager_singleton);
457 priv->account_manager = tp_account_manager_dup ();
459 tp_account_manager_prepare_async (priv->account_manager, NULL,
460 account_manager_ready_cb, g_object_ref (self));
462 if (priv->file == NULL)
464 /* Set the default file path */
467 dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
468 if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
469 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
471 priv->file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
479 empathy_chatroom_manager_class_init (EmpathyChatroomManagerClass *klass)
481 GObjectClass *object_class = G_OBJECT_CLASS (klass);
482 GParamSpec *param_spec;
484 object_class->constructor = empathy_chatroom_manager_constructor;
485 object_class->get_property = empathy_chatroom_manager_get_property;
486 object_class->set_property = empathy_chatroom_manager_set_property;
487 object_class->finalize = chatroom_manager_finalize;
489 param_spec = g_param_spec_string (
491 "path of the favorite file",
492 "The path of the XML file containing user's favorites",
494 G_PARAM_CONSTRUCT_ONLY |
496 G_PARAM_STATIC_NAME |
497 G_PARAM_STATIC_NICK |
498 G_PARAM_STATIC_BLURB);
499 g_object_class_install_property (object_class, PROP_FILE, param_spec);
501 param_spec = g_param_spec_boolean (
503 "whether the manager is ready yet",
504 "whether the manager is ready yet",
507 g_object_class_install_property (object_class, PROP_READY, param_spec);
509 signals[CHATROOM_ADDED] = g_signal_new ("chatroom-added",
510 G_TYPE_FROM_CLASS (klass),
513 g_cclosure_marshal_VOID__OBJECT,
515 1, EMPATHY_TYPE_CHATROOM);
517 signals[CHATROOM_REMOVED] = g_signal_new ("chatroom-removed",
518 G_TYPE_FROM_CLASS (klass),
521 g_cclosure_marshal_VOID__OBJECT,
523 1, EMPATHY_TYPE_CHATROOM);
525 g_type_class_add_private (object_class, sizeof (EmpathyChatroomManagerPriv));
529 empathy_chatroom_manager_init (EmpathyChatroomManager *manager)
531 EmpathyChatroomManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
532 EMPATHY_TYPE_CHATROOM_MANAGER, EmpathyChatroomManagerPriv);
534 manager->priv = priv;
537 EmpathyChatroomManager *
538 empathy_chatroom_manager_dup_singleton (const gchar *file)
540 return EMPATHY_CHATROOM_MANAGER (g_object_new (EMPATHY_TYPE_CHATROOM_MANAGER,
541 "file", file, NULL));
545 empathy_chatroom_manager_add (EmpathyChatroomManager *manager,
546 EmpathyChatroom *chatroom)
548 EmpathyChatroomManagerPriv *priv;
550 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), FALSE);
551 g_return_val_if_fail (EMPATHY_IS_CHATROOM (chatroom), FALSE);
553 priv = GET_PRIV (manager);
555 /* don't add more than once */
556 if (!empathy_chatroom_manager_find (manager,
557 empathy_chatroom_get_account (chatroom),
558 empathy_chatroom_get_room (chatroom)))
560 add_chatroom (manager, chatroom);
562 if (empathy_chatroom_is_favorite (chatroom))
563 reset_save_timeout (manager);
565 g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
573 chatroom_manager_remove_link (EmpathyChatroomManager *manager,
576 EmpathyChatroomManagerPriv *priv;
577 EmpathyChatroom *chatroom;
579 priv = GET_PRIV (manager);
583 if (empathy_chatroom_is_favorite (chatroom))
584 reset_save_timeout (manager);
586 priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
588 g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, chatroom);
589 g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb, manager);
591 g_object_unref (chatroom);
595 empathy_chatroom_manager_remove (EmpathyChatroomManager *manager,
596 EmpathyChatroom *chatroom)
598 EmpathyChatroomManagerPriv *priv;
601 g_return_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager));
602 g_return_if_fail (EMPATHY_IS_CHATROOM (chatroom));
604 priv = GET_PRIV (manager);
606 for (l = priv->chatrooms; l; l = l->next)
608 EmpathyChatroom *this_chatroom;
610 this_chatroom = l->data;
612 if (this_chatroom == chatroom ||
613 empathy_chatroom_equal (chatroom, this_chatroom))
615 chatroom_manager_remove_link (manager, l);
622 empathy_chatroom_manager_find (EmpathyChatroomManager *manager,
626 EmpathyChatroomManagerPriv *priv;
629 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
630 g_return_val_if_fail (room != NULL, NULL);
632 priv = GET_PRIV (manager);
634 for (l = priv->chatrooms; l; l = l->next) {
635 EmpathyChatroom *chatroom;
636 TpAccount *this_account;
637 const gchar *this_room;
640 this_account = empathy_chatroom_get_account (chatroom);
641 this_room = empathy_chatroom_get_room (chatroom);
643 if (this_account && this_room && account == this_account
644 && strcmp (this_room, room) == 0) {
653 empathy_chatroom_manager_ensure_chatroom (EmpathyChatroomManager *manager,
658 EmpathyChatroom *chatroom;
660 chatroom = empathy_chatroom_manager_find (manager, account, room);
663 return g_object_ref (chatroom);
665 chatroom = empathy_chatroom_new_full (account,
669 empathy_chatroom_manager_add (manager, chatroom);
675 empathy_chatroom_manager_get_chatrooms (EmpathyChatroomManager *manager,
678 EmpathyChatroomManagerPriv *priv;
679 GList *chatrooms, *l;
681 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
683 priv = GET_PRIV (manager);
686 return g_list_copy (priv->chatrooms);
690 for (l = priv->chatrooms; l; l = l->next) {
691 EmpathyChatroom *chatroom;
695 if (account == empathy_chatroom_get_account (chatroom)) {
696 chatrooms = g_list_append (chatrooms, chatroom);
704 empathy_chatroom_manager_get_count (EmpathyChatroomManager *manager,
707 EmpathyChatroomManagerPriv *priv;
711 g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), 0);
713 priv = GET_PRIV (manager);
716 return g_list_length (priv->chatrooms);
719 for (l = priv->chatrooms; l; l = l->next) {
720 EmpathyChatroom *chatroom;
724 if (account == empathy_chatroom_get_account (chatroom)) {
733 chatroom_manager_chat_destroyed_cb (EmpathyTpChat *chat,
736 EmpathyChatroomManagerPriv *priv = GET_PRIV (manager);
739 for (l = priv->chatrooms; l; l = l->next)
741 EmpathyChatroom *chatroom = l->data;
743 if (empathy_chatroom_get_tp_chat (chatroom) != chat)
746 empathy_chatroom_set_tp_chat (chatroom, NULL);
748 if (!empathy_chatroom_is_favorite (chatroom))
750 /* Remove the chatroom from the list, unless it's in the list of
752 * FIXME this policy should probably not be in libempathy */
753 chatroom_manager_remove_link (manager, l);
760 /* Called by EmpathyChatManager when we are handling a new group chat */
762 empathy_chatroom_manager_chat_handled (EmpathyChatroomManager *self,
766 EmpathyChatroom *chatroom;
767 const gchar *roomname;
769 roomname = empathy_tp_chat_get_id (chat);
771 chatroom = empathy_chatroom_manager_find (self, account, roomname);
773 if (chatroom == NULL)
775 chatroom = empathy_chatroom_new_full (account, roomname, roomname,
777 empathy_chatroom_set_tp_chat (chatroom, chat);
778 empathy_chatroom_manager_add (self, chatroom);
779 g_object_unref (chatroom);
783 empathy_chatroom_set_tp_chat (chatroom, chat);
786 /* A TpChat is always destroyed as it only gets unreffed after the channel
787 * has been invalidated in the dispatcher.. */
788 g_signal_connect (chat, "destroy",
789 G_CALLBACK (chatroom_manager_chat_destroyed_cb),