]> git.0d.be Git - empathy.git/blob - libempathy/empathy-chatroom-manager.c
Fix some coding style
[empathy.git] / libempathy / empathy-chatroom-manager.c
1 /*
2  * Copyright (C) 2004-2007 Imendio AB
3  * Copyright (C) 2007-2009 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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, 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-tp-chat.h"
34 #include "empathy-chatroom-manager.h"
35 #include "empathy-utils.h"
36
37 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
38 #include "empathy-debug.h"
39
40 #define CHATROOMS_XML_FILENAME "chatrooms.xml"
41 #define CHATROOMS_DTD_FILENAME "empathy-chatroom-manager.dtd"
42 #define SAVE_TIMER 4
43
44 static EmpathyChatroomManager *chatroom_manager_singleton = NULL;
45
46 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatroomManager)
47 typedef struct
48 {
49   GList *chatrooms;
50   gchar *file;
51   /* source id of the autosave timer */
52   gint save_timer_id;
53 } EmpathyChatroomManagerPriv;
54
55 enum {
56   CHATROOM_ADDED,
57   CHATROOM_REMOVED,
58   LAST_SIGNAL
59 };
60
61 static guint signals[LAST_SIGNAL];
62
63 /* properties */
64 enum
65 {
66   PROP_FILE = 1,
67   LAST_PROPERTY
68 };
69
70 G_DEFINE_TYPE (EmpathyChatroomManager, empathy_chatroom_manager, G_TYPE_OBJECT);
71
72 static void
73 empathy_chatroom_manager_get_property (GObject *object,
74                                        guint property_id,
75                                        GValue *value,
76                                        GParamSpec *pspec)
77 {
78   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
79   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
80
81   switch (property_id)
82     {
83       case PROP_FILE:
84         g_value_set_string (value, priv->file);
85         break;
86       default:
87         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
88         break;
89     }
90 }
91
92 static void
93 empathy_chatroom_manager_set_property (GObject *object,
94                                        guint property_id,
95                                        const GValue *value,
96                                        GParamSpec *pspec)
97 {
98   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
99   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
100
101   switch (property_id)
102     {
103       case PROP_FILE:
104         g_free (priv->file);
105         priv->file = g_value_dup_string (value);
106         break;
107       default:
108         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
109         break;
110     }
111 }
112
113 static GObject *
114 empathy_chatroom_manager_constructor (GType type,
115                                       guint n_props,
116                                       GObjectConstructParam *props)
117 {
118   GObject *obj;
119   EmpathyChatroomManager *self;
120   EmpathyChatroomManagerPriv *priv;
121
122   if (chatroom_manager_singleton != NULL)
123     return g_object_ref (chatroom_manager_singleton);
124
125   /* Parent constructor chain */
126   obj = G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->
127         constructor (type, n_props, props);
128
129   self = EMPATHY_CHATROOM_MANAGER (obj);
130   priv = GET_PRIV (self);
131
132   chatroom_manager_singleton = self;
133   g_object_add_weak_pointer (obj, (gpointer) &chatroom_manager_singleton);
134
135   if (priv->file == NULL)
136     {
137       /* Set the default file path */
138       gchar *dir;
139
140       dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
141       if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
142         g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
143
144       priv->file = g_build_filename (dir, CHATROOMS_XML_FILENAME, NULL);
145       g_free (dir);
146     }
147
148   chatroom_manager_get_all (self);
149   return obj;
150 }
151
152 static void
153 empathy_chatroom_manager_class_init (EmpathyChatroomManagerClass *klass)
154 {
155   GObjectClass *object_class = G_OBJECT_CLASS (klass);
156   GParamSpec *param_spec;
157
158   object_class->constructor = empathy_chatroom_manager_constructor;
159   object_class->get_property = empathy_chatroom_manager_get_property;
160   object_class->set_property = empathy_chatroom_manager_set_property;
161         object_class->finalize = chatroom_manager_finalize;
162
163   param_spec = g_param_spec_string (
164       "file",
165       "path of the favorite file",
166       "The path of the XML file containing user's favorites",
167       NULL,
168       G_PARAM_CONSTRUCT_ONLY |
169       G_PARAM_READWRITE |
170       G_PARAM_STATIC_NAME |
171       G_PARAM_STATIC_NICK |
172       G_PARAM_STATIC_BLURB);
173   g_object_class_install_property (object_class, PROP_FILE, param_spec);
174
175   signals[CHATROOM_ADDED] = g_signal_new ("chatroom-added",
176       G_TYPE_FROM_CLASS (klass),
177       G_SIGNAL_RUN_LAST,
178       0, NULL, NULL,
179       g_cclosure_marshal_VOID__OBJECT,
180       G_TYPE_NONE,
181       1, EMPATHY_TYPE_CHATROOM);
182
183   signals[CHATROOM_REMOVED] = g_signal_new ("chatroom-removed",
184       G_TYPE_FROM_CLASS (klass),
185       G_SIGNAL_RUN_LAST,
186       0, NULL, NULL,
187       g_cclosure_marshal_VOID__OBJECT,
188       G_TYPE_NONE,
189       1, EMPATHY_TYPE_CHATROOM);
190
191   g_type_class_add_private (object_class, sizeof (EmpathyChatroomManagerPriv));
192 }
193
194 static void
195 empathy_chatroom_manager_init (EmpathyChatroomManager *manager)
196 {
197   EmpathyChatroomManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
198       EMPATHY_TYPE_CHATROOM_MANAGER, EmpathyChatroomManagerPriv);
199
200   manager->priv = priv;
201 }
202
203 static void
204 chatroom_changed_cb (EmpathyChatroom *chatroom,
205                      GParamSpec *spec,
206                      EmpathyChatroomManager *self)
207 {
208   reset_save_timeout (self);
209 }
210
211 static void
212 chatroom_manager_finalize (GObject *object)
213 {
214   EmpathyChatroomManager *self = EMPATHY_CHATROOM_MANAGER (object);
215   EmpathyChatroomManagerPriv *priv;
216   GList *l;
217
218   priv = GET_PRIV (object);
219
220   if (priv->save_timer_id > 0)
221     {
222       /* have to save before destroy the object */
223       g_source_remove (priv->save_timer_id);
224       priv->save_timer_id = 0;
225       chatroom_manager_file_save (self);
226     }
227
228   for (l = priv->chatrooms; l != NULL; l = g_list_next (l))
229     {
230       EmpathyChatroom *chatroom = l->data;
231
232       g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
233           self);
234
235       g_object_unref (chatroom);
236     }
237
238   g_list_free (priv->chatrooms);
239   g_free (priv->file);
240
241   (G_OBJECT_CLASS (empathy_chatroom_manager_parent_class)->finalize) (object);
242 }
243
244 EmpathyChatroomManager *
245 empathy_chatroom_manager_dup_singleton (const gchar *file)
246 {
247   return EMPATHY_CHATROOM_MANAGER (g_object_new (EMPATHY_TYPE_CHATROOM_MANAGER,
248       "file", file, NULL));
249 }
250
251 static gboolean
252 save_timeout (EmpathyChatroomManager *self)
253 {
254   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
255
256   priv->save_timer_id = 0;
257   chatroom_manager_file_save (self);
258
259   return FALSE;
260 }
261
262 static void
263 reset_save_timeout (EmpathyChatroomManager *self)
264 {
265   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
266
267   if (priv->save_timer_id > 0)
268     {
269       g_source_remove (priv->save_timer_id);
270     }
271
272   priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
273       (GSourceFunc) save_timeout, self);
274 }
275
276 static void
277 add_chatroom (EmpathyChatroomManager *self,
278               EmpathyChatroom *chatroom)
279 {
280   EmpathyChatroomManagerPriv *priv = GET_PRIV (self);
281
282   priv->chatrooms = g_list_prepend (priv->chatrooms, g_object_ref (chatroom));
283
284   g_signal_connect (chatroom, "notify",
285       G_CALLBACK (chatroom_changed_cb), self);
286 }
287
288 gboolean
289 empathy_chatroom_manager_add (EmpathyChatroomManager *manager,
290                              EmpathyChatroom        *chatroom)
291 {
292   EmpathyChatroomManagerPriv *priv;
293
294   g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), FALSE);
295   g_return_val_if_fail (EMPATHY_IS_CHATROOM (chatroom), FALSE);
296
297   priv = GET_PRIV (manager);
298
299   /* don't add more than once */
300   if (!empathy_chatroom_manager_find (manager,
301       empathy_chatroom_get_account (chatroom),
302       empathy_chatroom_get_room (chatroom)))
303     {
304       gboolean favorite;
305
306       g_object_get (chatroom, "favorite", &favorite, NULL);
307       add_chatroom (manager, chatroom);
308
309       if (favorite)
310         reset_save_timeout (manager);
311
312       g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
313       return TRUE;
314     }
315
316   return FALSE;
317 }
318
319 void
320 empathy_chatroom_manager_remove (EmpathyChatroomManager *manager,
321                                  EmpathyChatroom        *chatroom)
322 {
323   EmpathyChatroomManagerPriv *priv;
324   GList *l;
325
326   g_return_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager));
327   g_return_if_fail (EMPATHY_IS_CHATROOM (chatroom));
328
329   priv = GET_PRIV (manager);
330
331   for (l = priv->chatrooms; l; l = l->next)
332     {
333       EmpathyChatroom *this_chatroom;
334
335       this_chatroom = l->data;
336
337       if (this_chatroom == chatroom ||
338           empathy_chatroom_equal (chatroom, this_chatroom))
339         {
340           gboolean favorite;
341
342           priv->chatrooms = g_list_delete_link (priv->chatrooms, l);
343           g_object_get (chatroom, "favorite", &favorite, NULL);
344           if (favorite)
345             reset_save_timeout (manager);
346
347           g_signal_emit (manager, signals[CHATROOM_REMOVED], 0, this_chatroom);
348           g_signal_handlers_disconnect_by_func (chatroom, chatroom_changed_cb,
349               manager);
350
351           g_object_unref (this_chatroom);
352           break;
353         }
354     }
355 }
356
357 EmpathyChatroom *
358 empathy_chatroom_manager_find (EmpathyChatroomManager *manager,
359                                McAccount *account,
360                                const gchar *room)
361 {
362         EmpathyChatroomManagerPriv *priv;
363         GList                     *l;
364
365         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
366         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
367         g_return_val_if_fail (room != NULL, NULL);
368
369         priv = GET_PRIV (manager);
370
371         for (l = priv->chatrooms; l; l = l->next) {
372                 EmpathyChatroom *chatroom;
373                 McAccount      *this_account;
374                 const gchar    *this_room;
375
376                 chatroom = l->data;
377                 this_account = empathy_chatroom_get_account (chatroom);
378                 this_room = empathy_chatroom_get_room (chatroom);
379
380                 if (this_account && this_room &&
381                     empathy_account_equal (account, this_account) &&
382                     strcmp (this_room, room) == 0) {
383                         return chatroom;
384                 }
385         }
386
387         return NULL;
388 }
389
390 GList *
391 empathy_chatroom_manager_get_chatrooms (EmpathyChatroomManager *manager,
392                                        McAccount             *account)
393 {
394         EmpathyChatroomManagerPriv *priv;
395         GList                     *chatrooms, *l;
396
397         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), NULL);
398
399         priv = GET_PRIV (manager);
400
401         if (!account) {
402                 return g_list_copy (priv->chatrooms);
403         }
404
405         chatrooms = NULL;
406         for (l = priv->chatrooms; l; l = l->next) {
407                 EmpathyChatroom *chatroom;
408
409                 chatroom = l->data;
410
411                 if (empathy_account_equal (account,
412                                           empathy_chatroom_get_account (chatroom))) {
413                         chatrooms = g_list_append (chatrooms, chatroom);
414                 }
415         }
416
417         return chatrooms;
418 }
419
420 guint
421 empathy_chatroom_manager_get_count (EmpathyChatroomManager *manager,
422                                    McAccount             *account)
423 {
424         EmpathyChatroomManagerPriv *priv;
425         GList                     *l;
426         guint                      count = 0;
427
428         g_return_val_if_fail (EMPATHY_IS_CHATROOM_MANAGER (manager), 0);
429
430         priv = GET_PRIV (manager);
431
432         if (!account) {
433                 return g_list_length (priv->chatrooms);
434         }
435
436         for (l = priv->chatrooms; l; l = l->next) {
437                 EmpathyChatroom *chatroom;
438
439                 chatroom = l->data;
440
441                 if (empathy_account_equal (account,
442                                            empathy_chatroom_get_account (chatroom))) {
443                         count++;
444                 }
445         }
446
447         return count;
448 }
449
450 /*
451  * API to save/load and parse the chatrooms file.
452  */
453
454 static gboolean
455 chatroom_manager_get_all (EmpathyChatroomManager *manager)
456 {
457         EmpathyChatroomManagerPriv *priv;
458
459         priv = GET_PRIV (manager);
460
461         /* read file in */
462         if (g_file_test (priv->file, G_FILE_TEST_EXISTS) &&
463             !chatroom_manager_file_parse (manager, priv->file)) {
464                 return FALSE;
465         }
466
467         return TRUE;
468 }
469
470 static gboolean
471 chatroom_manager_file_parse (EmpathyChatroomManager *manager,
472                              const gchar           *filename)
473 {
474         EmpathyChatroomManagerPriv *priv;
475         xmlParserCtxtPtr           ctxt;
476         xmlDocPtr                  doc;
477         xmlNodePtr                 chatrooms;
478         xmlNodePtr                 node;
479
480         priv = GET_PRIV (manager);
481
482         DEBUG ("Attempting to parse file:'%s'...", filename);
483
484         ctxt = xmlNewParserCtxt ();
485
486         /* Parse and validate the file. */
487         doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
488         if (!doc) {
489                 g_warning ("Failed to parse file:'%s'", filename);
490                 xmlFreeParserCtxt (ctxt);
491                 return FALSE;
492         }
493
494         if (!empathy_xml_validate (doc, CHATROOMS_DTD_FILENAME)) {
495                 g_warning ("Failed to validate file:'%s'", filename);
496                 xmlFreeDoc(doc);
497                 xmlFreeParserCtxt (ctxt);
498                 return FALSE;
499         }
500
501         /* The root node, chatrooms. */
502         chatrooms = xmlDocGetRootElement (doc);
503
504         for (node = chatrooms->children; node; node = node->next) {
505                 if (strcmp ((gchar *) node->name, "chatroom") == 0) {
506                         chatroom_manager_parse_chatroom (manager, node);
507                 }
508         }
509
510         DEBUG ("Parsed %d chatrooms", g_list_length (priv->chatrooms));
511
512         xmlFreeDoc(doc);
513         xmlFreeParserCtxt (ctxt);
514
515         return TRUE;
516 }
517
518 static void
519 chatroom_manager_parse_chatroom (EmpathyChatroomManager *manager,
520                                  xmlNodePtr             node)
521 {
522         EmpathyChatroomManagerPriv *priv;
523         EmpathyChatroom            *chatroom;
524         McAccount                 *account;
525         xmlNodePtr                 child;
526         gchar                     *str;
527         gchar                     *name;
528         gchar                     *room;
529         gchar                     *account_id;
530         gboolean                   auto_connect;
531
532         priv = GET_PRIV (manager);
533
534         /* default values. */
535         name = NULL;
536         room = NULL;
537         auto_connect = TRUE;
538         account_id = NULL;
539
540         for (child = node->children; child; child = child->next) {
541                 gchar *tag;
542
543                 if (xmlNodeIsText (child)) {
544                         continue;
545                 }
546
547                 tag = (gchar *) child->name;
548                 str = (gchar *) xmlNodeGetContent (child);
549
550                 if (strcmp (tag, "name") == 0) {
551                         name = g_strdup (str);
552                 }
553                 else if (strcmp (tag, "room") == 0) {
554                         room = g_strdup (str);
555                 }
556                 else if (strcmp (tag, "auto_connect") == 0) {
557                         if (strcmp (str, "yes") == 0) {
558                                 auto_connect = TRUE;
559                         } else {
560                                 auto_connect = FALSE;
561                         }
562                 }
563                 else if (strcmp (tag, "account") == 0) {
564                         account_id = g_strdup (str);
565                 }
566
567                 xmlFree (str);
568         }
569
570         account = mc_account_lookup (account_id);
571         if (!account) {
572                 g_free (name);
573                 g_free (room);
574                 g_free (account_id);
575                 return;
576         }
577
578         chatroom = empathy_chatroom_new_full (account, room, name, auto_connect);
579         g_object_set (chatroom, "favorite", TRUE, NULL);
580         add_chatroom (manager, chatroom);
581         g_signal_emit (manager, signals[CHATROOM_ADDED], 0, chatroom);
582
583         g_object_unref (account);
584         g_free (name);
585         g_free (room);
586         g_free (account_id);
587 }
588
589 static gboolean
590 chatroom_manager_file_save (EmpathyChatroomManager *manager)
591 {
592         EmpathyChatroomManagerPriv *priv;
593         xmlDocPtr                  doc;
594         xmlNodePtr                 root;
595         GList                     *l;
596
597         priv = GET_PRIV (manager);
598
599         doc = xmlNewDoc ("1.0");
600         root = xmlNewNode (NULL, "chatrooms");
601         xmlDocSetRootElement (doc, root);
602
603         for (l = priv->chatrooms; l; l = l->next) {
604                 EmpathyChatroom *chatroom;
605                 xmlNodePtr       node;
606                 const gchar     *account_id;
607                 gboolean         favorite;
608
609                 chatroom = l->data;
610
611                 g_object_get (chatroom, "favorite", &favorite, NULL);
612                 if (!favorite) {
613                         continue;
614                 }
615
616                 account_id = mc_account_get_unique_name (empathy_chatroom_get_account (chatroom));
617
618                 node = xmlNewChild (root, NULL, "chatroom", NULL);
619                 xmlNewTextChild (node, NULL, "name", empathy_chatroom_get_name (chatroom));
620                 xmlNewTextChild (node, NULL, "room", empathy_chatroom_get_room (chatroom));
621                 xmlNewTextChild (node, NULL, "account", account_id);
622                 xmlNewTextChild (node, NULL, "auto_connect",
623                         empathy_chatroom_get_auto_connect (chatroom) ? "yes" : "no");
624         }
625
626         /* Make sure the XML is indented properly */
627         xmlIndentTreeOutput = 1;
628
629         DEBUG ("Saving file:'%s'", priv->file);
630         xmlSaveFormatFileEnc (priv->file, doc, "utf-8", 1);
631         xmlFreeDoc (doc);
632
633         xmlCleanupParser ();
634         xmlMemoryDump ();
635
636         return TRUE;
637 }
638
639 static void
640 chatroom_manager_chat_destroyed_cb (EmpathyTpChat *chat,
641   gpointer user_data)
642 {
643   EmpathyChatroomManager *manager = EMPATHY_CHATROOM_MANAGER (user_data);
644   McAccount *account = empathy_tp_chat_get_account (chat);
645   EmpathyChatroom *chatroom;
646   const gchar *roomname;
647   gboolean favorite;
648
649   roomname = empathy_tp_chat_get_id (chat);
650   chatroom = empathy_chatroom_manager_find (manager, account, roomname);
651
652   if (chatroom == NULL)
653     return;
654
655   g_object_set (chatroom, "tp-chat", NULL, NULL);
656   g_object_get (chatroom, "favorite", &favorite, NULL);
657
658   if (!favorite)
659     {
660       /* Remove the chatroom from the list, unless it's in the list of
661        * favourites..
662        * FIXME this policy should probably not be in libempathy */
663       empathy_chatroom_manager_remove (manager, chatroom);
664     }
665 }
666
667 static void
668 chatroom_manager_observe_channel_cb (EmpathyDispatcher *dispatcher,
669   EmpathyDispatchOperation *operation, gpointer user_data)
670 {
671   EmpathyChatroomManager *manager = EMPATHY_CHATROOM_MANAGER (user_data);
672   EmpathyChatroom *chatroom;
673   TpChannel *channel;
674   EmpathyTpChat *chat;
675   const gchar *roomname;
676   GQuark channel_type;
677   TpHandleType handle_type;
678   McAccount *account;
679
680   channel_type = empathy_dispatch_operation_get_channel_type_id (operation);
681
682   /* Observe Text channels to rooms only */
683   if (channel_type != TP_IFACE_QUARK_CHANNEL_TYPE_TEXT)
684     return;
685
686   channel = empathy_dispatch_operation_get_channel (operation);
687   tp_channel_get_handle (channel, &handle_type);
688
689   if (handle_type != TP_HANDLE_TYPE_ROOM)
690     return;
691
692   chat = EMPATHY_TP_CHAT (
693     empathy_dispatch_operation_get_channel_wrapper (operation));
694   account = empathy_tp_chat_get_account (chat);
695
696   roomname = empathy_tp_chat_get_id (chat);
697
698   chatroom = empathy_chatroom_manager_find (manager, account, roomname);
699
700   if (chatroom == NULL)
701     {
702       chatroom = empathy_chatroom_new_full (account, roomname, roomname,
703         FALSE);
704       g_object_set (G_OBJECT (chatroom), "tp-chat", chat, NULL);
705       empathy_chatroom_manager_add (manager, chatroom);
706       g_object_unref (chatroom);
707     }
708   else
709     {
710       g_object_set (G_OBJECT (chatroom), "tp-chat", chat, NULL);
711     }
712
713   /* A TpChat is always destroyed as it only gets unreffed after the channel
714    * has been invalidated in the dispatcher..  */
715   g_signal_connect (chat, "destroy",
716     G_CALLBACK (chatroom_manager_chat_destroyed_cb),
717     manager);
718 }
719
720 void
721 empathy_chatroom_manager_observe (EmpathyChatroomManager *manager,
722   EmpathyDispatcher *dispatcher)
723 {
724   g_signal_connect (dispatcher, "observe",
725     G_CALLBACK (chatroom_manager_observe_channel_cb), manager);
726 }