]> git.0d.be Git - empathy.git/blob - libempathy/gossip-chatroom-manager.c
Add all properties described by TP spec for the Text channel on
[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 (gossip_account_equal (account, this_account) &&
233                     strcmp (this_room, room) == 0) {
234                         return chatroom;
235                 }
236         }
237
238         return NULL;
239 }
240
241 GList *
242 gossip_chatroom_manager_get_chatrooms (GossipChatroomManager *manager,
243                                        McAccount             *account)
244 {
245         GossipChatroomManagerPriv *priv;
246         GList                     *chatrooms, *l;
247
248         g_return_val_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager), NULL);
249
250         priv = GET_PRIV (manager);
251
252         if (!account) {
253                 return g_list_copy (priv->chatrooms);
254         }
255
256         chatrooms = NULL;
257         for (l = priv->chatrooms; l; l = l->next) {
258                 GossipChatroom *chatroom;
259
260                 chatroom = l->data;
261
262                 if (gossip_account_equal (account,
263                                           gossip_chatroom_get_account (chatroom))) {
264                         chatrooms = g_list_append (chatrooms, chatroom);
265                 }
266         }
267
268         return chatrooms;
269 }
270
271 guint
272 gossip_chatroom_manager_get_count (GossipChatroomManager *manager,
273                                    McAccount             *account)
274 {
275         GossipChatroomManagerPriv *priv;
276         GList                     *l;
277         guint                      count = 0;
278
279         g_return_val_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager), 0);
280
281         priv = GET_PRIV (manager);
282
283         if (!account) {
284                 return g_list_length (priv->chatrooms);
285         }
286
287         for (l = priv->chatrooms; l; l = l->next) {
288                 GossipChatroom *chatroom;
289
290                 chatroom = l->data;
291
292                 if (gossip_account_equal (account,
293                                           gossip_chatroom_get_account (chatroom))) {
294                         count++;
295                 }
296         }
297
298         return count;
299 }
300
301 void
302 gossip_chatroom_manager_store (GossipChatroomManager *manager)
303 {
304         g_return_if_fail (GOSSIP_IS_CHATROOM_MANAGER (manager));
305
306         chatroom_manager_file_save (manager);
307 }
308
309 /*
310  * API to save/load and parse the chatrooms file.
311  */
312
313 static gboolean
314 chatroom_manager_get_all (GossipChatroomManager *manager)
315 {
316         GossipChatroomManagerPriv *priv;
317         gchar                     *dir;
318         gchar                     *file_with_path = NULL;
319
320         priv = GET_PRIV (manager);
321
322         dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
323         if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
324                 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
325         }
326
327         file_with_path = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
328         g_free (dir);
329
330         /* read file in */
331         if (g_file_test (file_with_path, G_FILE_TEST_EXISTS) &&
332             !chatroom_manager_file_parse (manager, file_with_path)) {
333                 g_free (file_with_path);
334                 return FALSE;
335         }
336
337         g_free (file_with_path);
338
339         return TRUE;
340 }
341
342 static void
343 chatroom_manager_file_changed_cb (GnomeVFSMonitorHandle    *handle,
344                                   const gchar              *monitor_uri,
345                                   const gchar              *info_uri,
346                                   GnomeVFSMonitorEventType  event_type,
347                                   GossipChatroomManager    *manager)
348 {
349         GossipChatroomManagerPriv *priv;
350         GList                     *l;
351
352         priv = GET_PRIV (manager);
353
354         gossip_debug (DEBUG_DOMAIN, "Reload file: %s", monitor_uri);
355
356         /* FIXME: This is not optimised */
357         for (l = priv->chatrooms; l; l = l->next) {
358                 g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, l->data);
359                 g_object_unref (l->data);
360         }
361         g_list_free (priv->chatrooms);
362         priv->chatrooms = NULL;
363
364         chatroom_manager_get_all (manager);
365 }
366
367 static gboolean
368 chatroom_manager_file_parse (GossipChatroomManager *manager,
369                              const gchar           *filename)
370 {
371         GossipChatroomManagerPriv *priv;
372         xmlParserCtxtPtr           ctxt;
373         xmlDocPtr                  doc;
374         xmlNodePtr                 chatrooms;
375         xmlNodePtr                 node;
376
377         priv = GET_PRIV (manager);
378
379         /* Do not monitor this file twice if it's already monitored */
380         if (!g_hash_table_lookup (priv->monitors, filename)) {
381                 GnomeVFSMonitorHandle *handle;
382
383                 gnome_vfs_monitor_add (&handle,
384                                        filename,
385                                        GNOME_VFS_MONITOR_FILE,
386                                        (GnomeVFSMonitorCallback) chatroom_manager_file_changed_cb,
387                                        manager);
388
389                 g_hash_table_insert (priv->monitors, g_strdup (filename), handle);
390         }
391
392         gossip_debug (DEBUG_DOMAIN, "Attempting to parse file:'%s'...", filename);
393
394         ctxt = xmlNewParserCtxt ();
395
396         /* Parse and validate the file. */
397         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
398         if (!doc) {
399                 g_warning ("Failed to parse file:'%s'", filename);
400                 xmlFreeParserCtxt (ctxt);
401                 return FALSE;
402         }
403
404         if (!gossip_xml_validate (doc, CHATROOMS_DTD_FILENAME)) {
405                 g_warning ("Failed to validate file:'%s'", filename);
406                 xmlFreeDoc(doc);
407                 xmlFreeParserCtxt (ctxt);
408                 return FALSE;
409         }
410
411         /* The root node, chatrooms. */
412         chatrooms = xmlDocGetRootElement (doc);
413
414         for (node = chatrooms->children; node; node = node->next) {
415                 if (strcmp ((gchar *) node->name, "chatroom") == 0) {
416                         chatroom_manager_parse_chatroom (manager, node);
417                 }
418         }
419
420         gossip_debug (DEBUG_DOMAIN,
421                       "Parsed %d chatrooms",
422                       g_list_length (priv->chatrooms));
423
424         xmlFreeDoc(doc);
425         xmlFreeParserCtxt (ctxt);
426
427         return TRUE;
428 }
429
430 static void
431 chatroom_manager_parse_chatroom (GossipChatroomManager *manager,
432                                  xmlNodePtr             node)
433 {
434         GossipChatroomManagerPriv *priv;
435         GossipChatroom            *chatroom;
436         McAccount                 *account;
437         xmlNodePtr                 child;
438         gchar                     *str;
439         gchar                     *name;
440         gchar                     *room;
441         gchar                     *account_id;
442         gboolean                   auto_connect;
443
444         priv = GET_PRIV (manager);
445
446         /* default values. */
447         name = NULL;
448         room = NULL;
449         auto_connect = TRUE;
450         account_id = NULL;
451
452         for (child = node->children; child; child = child->next) {
453                 gchar *tag;
454
455                 if (xmlNodeIsText (child)) {
456                         continue;
457                 }
458
459                 tag = (gchar *) child->name;
460                 str = (gchar *) xmlNodeGetContent (child);
461
462                 if (strcmp (tag, "name") == 0) {
463                         name = g_strdup (str);
464                 }
465                 else if (strcmp (tag, "room") == 0) {
466                         room = g_strdup (str);
467                 }
468                 else if (strcmp (tag, "auto_connect") == 0) {
469                         if (strcmp (str, "yes") == 0) {
470                                 auto_connect = TRUE;
471                         } else {
472                                 auto_connect = FALSE;
473                         }
474                 }
475                 else if (strcmp (tag, "account") == 0) {
476                         account_id = g_strdup (str);
477                 }
478
479                 xmlFree (str);
480         }
481
482         account = mc_account_lookup (account_id);
483         if (!account) {
484                 g_free (name);
485                 g_free (room);
486                 g_free (account_id);
487                 return;
488         }
489
490         chatroom = gossip_chatroom_new_full (account, room, name, auto_connect);
491         priv->chatrooms = g_list_prepend (priv->chatrooms, chatroom);
492         g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
493
494         g_object_unref (account);
495         g_free (name);
496         g_free (room);
497         g_free (account_id);
498 }
499
500 static gboolean
501 chatroom_manager_file_save (GossipChatroomManager *manager)
502 {
503         GossipChatroomManagerPriv *priv;
504         xmlDocPtr                  doc;
505         xmlNodePtr                 root;
506         GList                     *l;
507         gchar                     *dir;
508         gchar                     *file;
509
510         priv = GET_PRIV (manager);
511
512         dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
513         if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
514                 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
515         }
516
517         file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
518         g_free (dir);
519
520         doc = xmlNewDoc ("1.0");
521         root = xmlNewNode (NULL, "chatrooms");
522         xmlDocSetRootElement (doc, root);
523
524         for (l = priv->chatrooms; l; l = l->next) {
525                 GossipChatroom *chatroom;
526                 xmlNodePtr      node;
527                 const gchar    *account_id;
528
529                 chatroom = l->data;
530                 account_id = mc_account_get_unique_name (gossip_chatroom_get_account (chatroom));
531
532                 node = xmlNewChild (root, NULL, "chatroom", NULL);
533                 xmlNewTextChild (node, NULL, "name", gossip_chatroom_get_name (chatroom));
534                 xmlNewTextChild (node, NULL, "room", gossip_chatroom_get_room (chatroom));
535                 xmlNewTextChild (node, NULL, "account", account_id);
536                 xmlNewTextChild (node, NULL, "auto_connect", gossip_chatroom_get_auto_connect (chatroom) ? "yes" : "no");
537         }
538
539         /* Make sure the XML is indented properly */
540         xmlIndentTreeOutput = 1;
541
542         gossip_debug (DEBUG_DOMAIN, "Saving file:'%s'", file);
543         xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
544         xmlFreeDoc (doc);
545
546         xmlCleanupParser ();
547         xmlMemoryDump ();
548
549         g_free (file);
550
551         return TRUE;
552 }