]> git.0d.be Git - empathy.git/blob - libempathy/gossip-chatroom-manager.c
f85d5407e8eef66cf1e640b0325e10537bcd76d6
[empathy.git] / libempathy / gossip-chatroom-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2004-2007 Imendio AB
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Xavier Claessens <xclaesse@gmail.com>
22  *          Martyn Russell <martyn@imendio.com>
23  */
24
25 #include "config.h"
26
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30
31 #include <libxml/parser.h>
32 #include <libxml/tree.h>
33
34 #include <libgnomevfs/gnome-vfs.h>
35
36 #include "gossip-debug.h"
37 #include "gossip-chatroom-manager.h"
38 #include "gossip-utils.h"
39
40 #define DEBUG_DOMAIN "ChatroomManager"
41
42 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHATROOM_MANAGER, GossipChatroomManagerPriv))
43
44 #define CHATROOMS_XML_FILENAME "chatrooms.xml"
45 #define CHATROOMS_DTD_FILENAME "gossip-chatroom-manager.dtd"
46
47 struct _GossipChatroomManagerPriv {
48         GList      *chatrooms;
49         GHashTable *monitors;
50 };
51
52 static void     gossip_chatroom_manager_class_init (GossipChatroomManagerClass *klass);
53 static void     gossip_chatroom_manager_init       (GossipChatroomManager      *manager);
54 static void     chatroom_manager_finalize          (GObject                    *object);
55 static gboolean chatroom_manager_get_all           (GossipChatroomManager      *manager);
56 static void     chatroom_manager_file_changed_cb   (GnomeVFSMonitorHandle      *handle,
57                                                     const gchar                *monitor_uri,
58                                                     const gchar                *info_uri,
59                                                     GnomeVFSMonitorEventType    event_type,
60                                                     GossipChatroomManager      *manager);
61 static gboolean chatroom_manager_file_parse        (GossipChatroomManager      *manager,
62                                                     const gchar                *filename);
63 static void     chatroom_manager_parse_chatroom    (GossipChatroomManager      *manager,
64                                                     xmlNodePtr                  node);
65 static gboolean chatroom_manager_file_save         (GossipChatroomManager      *manager);
66
67 enum {
68         CHATROOM_ADDED,
69         CHATROOM_REMOVED,
70         LAST_SIGNAL
71 };
72
73 static guint signals[LAST_SIGNAL];
74
75 G_DEFINE_TYPE (GossipChatroomManager, gossip_chatroom_manager, G_TYPE_OBJECT);
76
77 static void
78 gossip_chatroom_manager_class_init (GossipChatroomManagerClass *klass)
79 {
80         GObjectClass *object_class = G_OBJECT_CLASS (klass);
81
82         object_class->finalize = chatroom_manager_finalize;
83
84         signals[CHATROOM_ADDED] =
85                 g_signal_new ("chatroom-added",
86                               G_TYPE_FROM_CLASS (klass),
87                               G_SIGNAL_RUN_LAST,
88                               0,
89                               NULL, NULL,
90                               g_cclosure_marshal_VOID__OBJECT,
91                               G_TYPE_NONE,
92                               1, GOSSIP_TYPE_CHATROOM);
93         signals[CHATROOM_REMOVED] =
94                 g_signal_new ("chatroom-removed",
95                               G_TYPE_FROM_CLASS (klass),
96                               G_SIGNAL_RUN_LAST,
97                               0,
98                               NULL, NULL,
99                               g_cclosure_marshal_VOID__OBJECT,
100                               G_TYPE_NONE,
101                               1, GOSSIP_TYPE_CHATROOM);
102
103         g_type_class_add_private (object_class,
104                                   sizeof (GossipChatroomManagerPriv));
105 }
106
107 static void
108 gossip_chatroom_manager_init (GossipChatroomManager *manager)
109 {
110         GossipChatroomManagerPriv *priv;
111
112         priv = GET_PRIV (manager);
113
114         priv->monitors = g_hash_table_new_full (g_str_hash,
115                                                 g_str_equal,
116                                                 (GDestroyNotify) g_free,
117                                                 (GDestroyNotify) gnome_vfs_monitor_cancel);
118 }
119
120 static void
121 chatroom_manager_finalize (GObject *object)
122 {
123         GossipChatroomManagerPriv *priv;
124
125         priv = GET_PRIV (object);
126
127         g_list_foreach (priv->chatrooms, (GFunc) g_object_unref, NULL);
128         g_list_free (priv->chatrooms);
129         g_hash_table_destroy (priv->monitors);
130
131         (G_OBJECT_CLASS (gossip_chatroom_manager_parent_class)->finalize) (object);
132 }
133
134 GossipChatroomManager *
135 gossip_chatroom_manager_new (void)
136 {
137         static GossipChatroomManager *manager = NULL;
138
139         if (!manager) {
140                 GossipChatroomManagerPriv *priv;
141
142                 manager = g_object_new (GOSSIP_TYPE_CHATROOM_MANAGER, NULL);
143                 priv = GET_PRIV (manager);
144                 chatroom_manager_get_all (manager);
145         
146                 g_object_add_weak_pointer (G_OBJECT (manager), (gpointer) &manager);
147         } else {
148                 g_object_ref (manager);
149         }
150
151         return manager;
152 }
153
154 gboolean
155 gossip_chatroom_manager_add (GossipChatroomManager *manager,
156                              GossipChatroom        *chatroom)
157 {
158         GossipChatroomManagerPriv *priv;
159
160         g_return_val_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager), FALSE);
161         g_return_val_if_fail (GOSSIP_IS_CHATROOM (chatroom), FALSE);
162
163         priv = GET_PRIV (manager);
164
165         /* don't add more than once */
166         if (!gossip_chatroom_manager_find (manager,
167                                            gossip_chatroom_get_account (chatroom),
168                                            gossip_chatroom_get_room (chatroom))) {
169                 priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
170                 chatroom_manager_file_save (manager);
171
172                 g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
173
174                 return TRUE;
175         }
176
177         return FALSE;
178 }
179
180 void
181 gossip_chatroom_manager_remove (GossipChatroomManager *manager,
182                                 GossipChatroom        *chatroom)
183 {
184         GossipChatroomManagerPriv *priv;
185         GList                     *l;
186
187         g_return_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager));
188         g_return_if_fail (GOSSIP_IS_CHATROOM (chatroom));
189
190         priv = GET_PRIV (manager);
191
192         for (l = priv->chatrooms; l; l = l->next) {
193                 GossipChatroom *this_chatroom;
194
195                 this_chatroom = l->data;
196
197                 if (gossip_chatroom_equal (chatroom, this_chatroom)) {
198                         priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
199
200                         chatroom_manager_file_save (manager);
201
202                         g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, this_chatroom);
203                         g_object_unref (this_chatroom);
204                         break;
205                 }
206         }
207 }
208
209 GossipChatroom *
210 gossip_chatroom_manager_find (GossipChatroomManager *manager,
211                               McAccount             *account,
212                               const gchar           *room)
213 {
214         GossipChatroomManagerPriv *priv;
215         GList                     *l;
216
217         g_return_val_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager), NULL);
218         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
219         g_return_val_if_fail (room != NULL, NULL);
220
221         priv = GET_PRIV (manager);
222
223         for (l = priv->chatrooms; l; l = l->next) {
224                 GossipChatroom *chatroom;
225                 McAccount      *this_account;
226                 const gchar    *this_room;
227
228                 chatroom = l->data;
229                 this_account = gossip_chatroom_get_account (chatroom);
230                 this_room = gossip_chatroom_get_room (chatroom);
231
232                 if (this_account && this_room &&
233                     gossip_account_equal (account, this_account) &&
234                     strcmp (this_room, room) == 0) {
235                         return chatroom;
236                 }
237         }
238
239         return NULL;
240 }
241
242 GList *
243 gossip_chatroom_manager_get_chatrooms (GossipChatroomManager *manager,
244                                        McAccount             *account)
245 {
246         GossipChatroomManagerPriv *priv;
247         GList                     *chatrooms, *l;
248
249         g_return_val_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager), NULL);
250
251         priv = GET_PRIV (manager);
252
253         if (!account) {
254                 return g_list_copy (priv->chatrooms);
255         }
256
257         chatrooms = NULL;
258         for (l = priv->chatrooms; l; l = l->next) {
259                 GossipChatroom *chatroom;
260
261                 chatroom = l->data;
262
263                 if (gossip_account_equal (account,
264                                           gossip_chatroom_get_account (chatroom))) {
265                         chatrooms = g_list_append (chatrooms, chatroom);
266                 }
267         }
268
269         return chatrooms;
270 }
271
272 guint
273 gossip_chatroom_manager_get_count (GossipChatroomManager *manager,
274                                    McAccount             *account)
275 {
276         GossipChatroomManagerPriv *priv;
277         GList                     *l;
278         guint                      count = 0;
279
280         g_return_val_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager), 0);
281
282         priv = GET_PRIV (manager);
283
284         if (!account) {
285                 return g_list_length (priv->chatrooms);
286         }
287
288         for (l = priv->chatrooms; l; l = l->next) {
289                 GossipChatroom *chatroom;
290
291                 chatroom = l->data;
292
293                 if (gossip_account_equal (account,
294                                           gossip_chatroom_get_account (chatroom))) {
295                         count++;
296                 }
297         }
298
299         return count;
300 }
301
302 void
303 gossip_chatroom_manager_store (GossipChatroomManager *manager)
304 {
305         g_return_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager));
306
307         chatroom_manager_file_save (manager);
308 }
309
310 /*
311  * API to save/load and parse the chatrooms file.
312  */
313
314 static gboolean
315 chatroom_manager_get_all (GossipChatroomManager *manager)
316 {
317         GossipChatroomManagerPriv *priv;
318         gchar                     *dir;
319         gchar                     *file_with_path = NULL;
320
321         priv = GET_PRIV (manager);
322
323         dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
324         if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
325                 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
326         }
327
328         file_with_path = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
329         g_free (dir);
330
331         /* read file in */
332         if (g_file_test (file_with_path, G_FILE_TEST_EXISTS) &&
333             !chatroom_manager_file_parse (manager, file_with_path)) {
334                 g_free (file_with_path);
335                 return FALSE;
336         }
337
338         g_free (file_with_path);
339
340         return TRUE;
341 }
342
343 static void
344 chatroom_manager_file_changed_cb (GnomeVFSMonitorHandle    *handle,
345                                   const gchar              *monitor_uri,
346                                   const gchar              *info_uri,
347                                   GnomeVFSMonitorEventType  event_type,
348                                   GossipChatroomManager    *manager)
349 {
350         GossipChatroomManagerPriv *priv;
351         GList                     *l;
352
353         priv = GET_PRIV (manager);
354
355         gossip_debug (DEBUG_DOMAIN, "Reload file: %s", monitor_uri);
356
357         /* FIXME: This is not optimised */
358         for (l = priv->chatrooms; l; l = l->next) {
359                 g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, l->data);
360                 g_object_unref (l->data);
361         }
362         g_list_free (priv->chatrooms);
363         priv->chatrooms = NULL;
364
365         chatroom_manager_get_all (manager);
366 }
367
368 static gboolean
369 chatroom_manager_file_parse (GossipChatroomManager *manager,
370                              const gchar           *filename)
371 {
372         GossipChatroomManagerPriv *priv;
373         xmlParserCtxtPtr           ctxt;
374         xmlDocPtr                  doc;
375         xmlNodePtr                 chatrooms;
376         xmlNodePtr                 node;
377
378         priv = GET_PRIV (manager);
379
380         /* Do not monitor this file twice if it's already monitored */
381         if (!g_hash_table_lookup (priv->monitors, filename)) {
382                 GnomeVFSMonitorHandle *handle;
383
384                 gnome_vfs_monitor_add (&handle,
385                                        filename,
386                                        GNOME_VFS_MONITOR_FILE,
387                                        (GnomeVFSMonitorCallback) chatroom_manager_file_changed_cb,
388                                        manager);
389
390                 g_hash_table_insert (priv->monitors, g_strdup (filename), handle);
391         }
392
393         gossip_debug (DEBUG_DOMAIN, "Attempting to parse file:'%s'...", filename);
394
395         ctxt = xmlNewParserCtxt ();
396
397         /* Parse and validate the file. */
398         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
399         if (!doc) {
400                 g_warning ("Failed to parse file:'%s'", filename);
401                 xmlFreeParserCtxt (ctxt);
402                 return FALSE;
403         }
404
405         if (!gossip_xml_validate (doc, CHATROOMS_DTD_FILENAME)) {
406                 g_warning ("Failed to validate file:'%s'", filename);
407                 xmlFreeDoc(doc);
408                 xmlFreeParserCtxt (ctxt);
409                 return FALSE;
410         }
411
412         /* The root node, chatrooms. */
413         chatrooms = xmlDocGetRootElement (doc);
414
415         for (node = chatrooms->children; node; node = node->next) {
416                 if (strcmp ((gchar *) node->name, "chatroom") == 0) {
417                         chatroom_manager_parse_chatroom (manager, node);
418                 }
419         }
420
421         gossip_debug (DEBUG_DOMAIN,
422                       "Parsed %d chatrooms",
423                       g_list_length (priv->chatrooms));
424
425         xmlFreeDoc(doc);
426         xmlFreeParserCtxt (ctxt);
427
428         return TRUE;
429 }
430
431 static void
432 chatroom_manager_parse_chatroom (GossipChatroomManager *manager,
433                                  xmlNodePtr             node)
434 {
435         GossipChatroomManagerPriv *priv;
436         GossipChatroom            *chatroom;
437         McAccount                 *account;
438         xmlNodePtr                 child;
439         gchar                     *str;
440         gchar                     *name;
441         gchar                     *room;
442         gchar                     *account_id;
443         gboolean                   auto_connect;
444
445         priv = GET_PRIV (manager);
446
447         /* default values. */
448         name = NULL;
449         room = NULL;
450         auto_connect = TRUE;
451         account_id = NULL;
452
453         for (child = node->children; child; child = child->next) {
454                 gchar *tag;
455
456                 if (xmlNodeIsText (child)) {
457                         continue;
458                 }
459
460                 tag = (gchar *) child->name;
461                 str = (gchar *) xmlNodeGetContent (child);
462
463                 if (strcmp (tag, "name") == 0) {
464                         name = g_strdup (str);
465                 }
466                 else if (strcmp (tag, "room") == 0) {
467                         room = g_strdup (str);
468                 }
469                 else if (strcmp (tag, "auto_connect") == 0) {
470                         if (strcmp (str, "yes") == 0) {
471                                 auto_connect = TRUE;
472                         } else {
473                                 auto_connect = FALSE;
474                         }
475                 }
476                 else if (strcmp (tag, "account") == 0) {
477                         account_id = g_strdup (str);
478                 }
479
480                 xmlFree (str);
481         }
482
483         account = mc_account_lookup (account_id);
484         if (!account) {
485                 g_free (name);
486                 g_free (room);
487                 g_free (account_id);
488                 return;
489         }
490
491         chatroom = gossip_chatroom_new_full (account, room, name, auto_connect);
492         priv->chatrooms = g_list_prepend (priv->chatrooms, chatroom);
493         g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
494
495         g_object_unref (account);
496         g_free (name);
497         g_free (room);
498         g_free (account_id);
499 }
500
501 static gboolean
502 chatroom_manager_file_save (GossipChatroomManager *manager)
503 {
504         GossipChatroomManagerPriv *priv;
505         xmlDocPtr                  doc;
506         xmlNodePtr                 root;
507         GList                     *l;
508         gchar                     *dir;
509         gchar                     *file;
510
511         priv = GET_PRIV (manager);
512
513         dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
514         if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
515                 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
516         }
517
518         file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
519         g_free (dir);
520
521         doc = xmlNewDoc ("1.0");
522         root = xmlNewNode (NULL, "chatrooms");
523         xmlDocSetRootElement (doc, root);
524
525         for (l = priv->chatrooms; l; l = l->next) {
526                 GossipChatroom *chatroom;
527                 xmlNodePtr      node;
528                 const gchar    *account_id;
529
530                 chatroom = l->data;
531                 account_id = mc_account_get_unique_name (gossip_chatroom_get_account (chatroom));
532
533                 node = xmlNewChild (root, NULL, "chatroom", NULL);
534                 xmlNewTextChild (node, NULL, "name", gossip_chatroom_get_name (chatroom));
535                 xmlNewTextChild (node, NULL, "room", gossip_chatroom_get_room (chatroom));
536                 xmlNewTextChild (node, NULL, "account", account_id);
537                 xmlNewTextChild (node, NULL, "auto_connect", gossip_chatroom_get_auto_connect (chatroom) ? "yes" : "no");
538         }
539
540         /* Make sure the XML is indented properly */
541         xmlIndentTreeOutput = 1;
542
543         gossip_debug (DEBUG_DOMAIN, "Saving file:'%s'", file);
544         xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
545         xmlFreeDoc (doc);
546
547         xmlCleanupParser ();
548         xmlMemoryDump ();
549
550         g_free (file);
551
552         return TRUE;
553 }