]> git.0d.be Git - empathy.git/blob - libempathy/empathy-irc-network-manager.c
Updated Basque language
[empathy.git] / libempathy / empathy-irc-network-manager.c
1 /*
2  * Copyright (C) 2007-2008 Guillaume Desmottes
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Guillaume Desmottes <gdesmott@gnome.org>
19  */
20
21 #include <config.h>
22 #include <string.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <libxml/parser.h>
26 #include <libxml/tree.h>
27
28 #include "empathy-utils.h"
29 #include "empathy-irc-network-manager.h"
30
31 #define DEBUG_FLAG EMPATHY_DEBUG_IRC
32 #include "empathy-debug.h"
33
34 #define IRC_NETWORKS_DTD_FILENAME "empathy-irc-networks.dtd"
35 #define SAVE_TIMER 4
36
37 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIrcNetworkManager)
38 typedef struct {
39   GHashTable *networks;
40
41   gchar *global_file;
42   gchar *user_file;
43   guint last_id;
44
45   /* Do we have to save modifications to the user file ? */
46   gboolean have_to_save;
47   /* Are we loading networks from XML files ? */
48   gboolean loading;
49   /* source id of the autosave timer */
50   gint save_timer_id;
51 } EmpathyIrcNetworkManagerPriv;
52
53 /* properties */
54 enum
55 {
56   PROP_GLOBAL_FILE = 1,
57   PROP_USER_FILE,
58   LAST_PROPERTY
59 };
60
61 G_DEFINE_TYPE (EmpathyIrcNetworkManager, empathy_irc_network_manager,
62     G_TYPE_OBJECT);
63
64 static void irc_network_manager_load_servers (
65     EmpathyIrcNetworkManager *manager);
66 static gboolean irc_network_manager_file_parse (
67     EmpathyIrcNetworkManager *manager, const gchar *filename,
68     gboolean user_defined);
69 static gboolean irc_network_manager_file_save (
70     EmpathyIrcNetworkManager *manager);
71
72 static void
73 empathy_irc_network_manager_get_property (GObject *object,
74                                           guint property_id,
75                                           GValue *value,
76                                           GParamSpec *pspec)
77 {
78   EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object);
79   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
80
81   switch (property_id)
82     {
83       case PROP_GLOBAL_FILE:
84         g_value_set_string (value, priv->global_file);
85         break;
86       case PROP_USER_FILE:
87         g_value_set_string (value, priv->user_file);
88         break;
89       default:
90         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
91         break;
92     }
93 }
94
95 static void
96 empathy_irc_network_manager_set_property (GObject *object,
97                                           guint property_id,
98                                           const GValue *value,
99                                           GParamSpec *pspec)
100 {
101   EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object);
102   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
103
104   switch (property_id)
105     {
106       case PROP_GLOBAL_FILE:
107         g_free (priv->global_file);
108         priv->global_file = g_value_dup_string (value);
109         break;
110       case PROP_USER_FILE:
111         g_free (priv->user_file);
112         priv->user_file = g_value_dup_string (value);
113         break;
114       default:
115         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
116         break;
117     }
118 }
119
120 static GObject *
121 empathy_irc_network_manager_constructor (GType type,
122                                          guint n_props,
123                                          GObjectConstructParam *props)
124 {
125   GObject *obj;
126   EmpathyIrcNetworkManager *self;
127
128   /* Parent constructor chain */
129   obj = G_OBJECT_CLASS (empathy_irc_network_manager_parent_class)->
130         constructor (type, n_props, props);
131
132   self = EMPATHY_IRC_NETWORK_MANAGER (obj);
133   irc_network_manager_load_servers (self);
134
135   return obj;
136 }
137
138 static void
139 empathy_irc_network_manager_finalize (GObject *object)
140 {
141   EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object);
142   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
143
144   if (priv->save_timer_id > 0)
145     {
146       g_source_remove (priv->save_timer_id);
147     }
148
149   if (priv->have_to_save)
150     {
151       irc_network_manager_file_save (self);
152     }
153
154   g_free (priv->global_file);
155   g_free (priv->user_file);
156
157   g_hash_table_destroy (priv->networks);
158
159   G_OBJECT_CLASS (empathy_irc_network_manager_parent_class)->finalize (object);
160 }
161
162 static void
163 empathy_irc_network_manager_init (EmpathyIrcNetworkManager *self)
164 {
165   EmpathyIrcNetworkManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
166       EMPATHY_TYPE_IRC_NETWORK_MANAGER, EmpathyIrcNetworkManagerPriv);
167
168   self->priv = priv;
169
170   priv->networks = g_hash_table_new_full (g_str_hash, g_str_equal,
171       (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref);
172
173   priv->last_id = 0;
174
175   priv->have_to_save = FALSE;
176   priv->loading = FALSE;
177   priv->save_timer_id = 0;
178 }
179
180 static void
181 empathy_irc_network_manager_class_init (EmpathyIrcNetworkManagerClass *klass)
182 {
183   GObjectClass *object_class = G_OBJECT_CLASS (klass);
184   GParamSpec *param_spec;
185
186   object_class->constructor = empathy_irc_network_manager_constructor;
187   object_class->get_property = empathy_irc_network_manager_get_property;
188   object_class->set_property = empathy_irc_network_manager_set_property;
189
190   g_type_class_add_private (object_class, sizeof (EmpathyIrcNetworkManagerPriv));
191
192   object_class->finalize = empathy_irc_network_manager_finalize;
193
194   param_spec = g_param_spec_string (
195       "global-file",
196       "path of the global networks file",
197       "The path of the system-wide filename from which we have to load"
198       " the networks list",
199       NULL,
200       G_PARAM_CONSTRUCT_ONLY |
201       G_PARAM_READWRITE |
202       G_PARAM_STATIC_NAME |
203       G_PARAM_STATIC_NICK |
204       G_PARAM_STATIC_BLURB);
205   g_object_class_install_property (object_class, PROP_GLOBAL_FILE, param_spec);
206
207   param_spec = g_param_spec_string (
208       "user-file",
209       "path of the user networks file",
210       "The path of user's  filename from which we have to load"
211       " the networks list and to which we'll save his modifications",
212       NULL,
213       G_PARAM_CONSTRUCT_ONLY |
214       G_PARAM_READWRITE |
215       G_PARAM_STATIC_NAME |
216       G_PARAM_STATIC_NICK |
217       G_PARAM_STATIC_BLURB);
218   g_object_class_install_property (object_class, PROP_USER_FILE, param_spec);
219 }
220
221 /**
222  * empathy_irc_network_manager_new:
223  * @global_file: the path of the global networks file, or %NULL
224  * @user_file: the path of the user networks file, or %NULL
225  *
226  * Creates a new #EmpathyIrcNetworkManager
227  *
228  * Returns: a new #EmpathyIrcNetworkManager
229  */
230 EmpathyIrcNetworkManager *
231 empathy_irc_network_manager_new (const gchar *global_file,
232                                  const gchar *user_file)
233 {
234   EmpathyIrcNetworkManager *manager;
235
236   manager = g_object_new (EMPATHY_TYPE_IRC_NETWORK_MANAGER,
237       "global-file", global_file,
238       "user-file", user_file,
239       NULL);
240
241   return manager;
242 }
243
244 static gboolean
245 save_timeout (EmpathyIrcNetworkManager *self)
246 {
247   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
248
249   priv->save_timer_id = 0;
250   irc_network_manager_file_save (self);
251
252   return FALSE;
253 }
254
255 static void
256 reset_save_timeout (EmpathyIrcNetworkManager *self)
257 {
258   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
259
260   if (priv->save_timer_id > 0)
261     {
262       g_source_remove (priv->save_timer_id);
263     }
264
265   priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
266       (GSourceFunc) save_timeout, self);
267 }
268
269 static void
270 network_modified (EmpathyIrcNetwork *network,
271                   EmpathyIrcNetworkManager *self)
272 {
273   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
274
275   network->user_defined = TRUE;
276
277   if (!priv->loading)
278     {
279       priv->have_to_save = TRUE;
280       reset_save_timeout (self);
281     }
282 }
283
284 static void
285 add_network (EmpathyIrcNetworkManager *self,
286              EmpathyIrcNetwork *network,
287              const gchar *id)
288 {
289   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
290
291   g_hash_table_insert (priv->networks, g_strdup (id), g_object_ref (network));
292
293   g_signal_connect (network, "modified", G_CALLBACK (network_modified), self);
294 }
295
296 /**
297  * empathy_irc_network_manager_add:
298  * @manager: an #EmpathyIrcNetworkManager
299  * @network: the #EmpathyIrcNetwork to add
300  *
301  * Add an #EmpathyIrcNetwork to the given #EmpathyIrcNetworkManager.
302  *
303  */
304 void
305 empathy_irc_network_manager_add (EmpathyIrcNetworkManager *self,
306                                  EmpathyIrcNetwork *network)
307 {
308   EmpathyIrcNetworkManagerPriv *priv;
309   gchar *id = NULL;
310
311   g_return_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self));
312   g_return_if_fail (EMPATHY_IS_IRC_NETWORK (network));
313
314   priv = GET_PRIV (self);
315
316   /* generate an id for this network */
317   do
318     {
319       g_free (id);
320       id = g_strdup_printf ("id%u", ++priv->last_id);
321     } while (g_hash_table_lookup (priv->networks, id) != NULL &&
322         priv->last_id < G_MAXUINT);
323
324   if (priv->last_id == G_MAXUINT)
325     {
326       DEBUG ("Can't add network: too many networks using a similiar ID");
327       return;
328     }
329
330   DEBUG ("add server with \"%s\" as ID", id);
331
332   network->user_defined = TRUE;
333   add_network (self, network, id);
334
335   priv->have_to_save = TRUE;
336   reset_save_timeout (self);
337
338   g_free (id);
339 }
340
341 /**
342  * empathy_irc_network_manager_remove:
343  * @manager: an #EmpathyIrcNetworkManager
344  * @network: the #EmpathyIrcNetwork to remove
345  *
346  * Remove an #EmpathyIrcNetwork from the given #EmpathyIrcNetworkManager.
347  *
348  */
349 void
350 empathy_irc_network_manager_remove (EmpathyIrcNetworkManager *self,
351                                     EmpathyIrcNetwork *network)
352 {
353   EmpathyIrcNetworkManagerPriv *priv;
354
355   g_return_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self));
356   g_return_if_fail (EMPATHY_IS_IRC_NETWORK (network));
357
358   priv = GET_PRIV (self);
359
360   network->user_defined = TRUE;
361   network->dropped = TRUE;
362
363   priv->have_to_save = TRUE;
364   reset_save_timeout (self);
365 }
366
367 static void
368 append_network_to_list (const gchar *id,
369                         EmpathyIrcNetwork *network,
370                         GSList **list)
371 {
372   if (network->dropped)
373     return;
374
375   *list = g_slist_prepend (*list, g_object_ref (network));
376 }
377
378 /**
379  * empathy_irc_network_manager_get_networks:
380  * @manager: an #EmpathyIrcNetworkManager
381  *
382  * Get the list of #EmpathyIrcNetwork associated with the given
383  * manager.
384  *
385  * Returns: a new #GSList of refed #EmpathyIrcNetwork
386  */
387 GSList *
388 empathy_irc_network_manager_get_networks (EmpathyIrcNetworkManager *self)
389 {
390   EmpathyIrcNetworkManagerPriv *priv;
391   GSList *irc_networks = NULL;
392
393   g_return_val_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self), NULL);
394
395   priv = GET_PRIV (self);
396
397   g_hash_table_foreach (priv->networks, (GHFunc) append_network_to_list,
398       &irc_networks);
399
400   return irc_networks;
401 }
402
403 /*
404  * API to save/load and parse the irc_networks file.
405  */
406
407 static void
408 load_global_file (EmpathyIrcNetworkManager *self)
409 {
410   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
411
412   if (priv->global_file == NULL)
413     return;
414
415   if (!g_file_test (priv->global_file, G_FILE_TEST_EXISTS))
416     {
417       DEBUG ("Global networks file %s doesn't exist", priv->global_file);
418       return;
419     }
420
421   irc_network_manager_file_parse (self, priv->global_file, FALSE);
422 }
423
424 static void
425 load_user_file (EmpathyIrcNetworkManager *self)
426 {
427   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
428
429   if (priv->user_file == NULL)
430     return;
431
432   if (!g_file_test (priv->user_file, G_FILE_TEST_EXISTS))
433     {
434       DEBUG ("User networks file %s doesn't exist", priv->global_file);
435       return;
436     }
437
438   irc_network_manager_file_parse (self, priv->user_file, TRUE);
439 }
440
441 static void
442 irc_network_manager_load_servers (EmpathyIrcNetworkManager *self)
443 {
444   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
445
446   priv->loading = TRUE;
447
448   load_global_file (self);
449   load_user_file (self);
450
451   priv->loading = FALSE;
452   priv->have_to_save = FALSE;
453 }
454
455 static void
456 irc_network_manager_parse_irc_server (EmpathyIrcNetwork *network,
457                                       xmlNodePtr node)
458 {
459   xmlNodePtr server_node;
460
461   for (server_node = node->children; server_node;
462       server_node = server_node->next)
463     {
464       gchar *address = NULL, *port = NULL, *ssl = NULL;
465
466       if (strcmp (server_node->name, "server") != 0)
467         continue;
468
469       address = xmlGetProp (server_node, "address");
470       port = xmlGetProp (server_node, "port");
471       ssl = xmlGetProp (server_node, "ssl");
472
473       if (address != NULL)
474         {
475           gint port_nb = 0;
476           gboolean have_ssl = FALSE;
477           EmpathyIrcServer *server;
478
479           if (port != NULL)
480             port_nb = strtol (port, NULL, 10);
481
482           if (port_nb <= 0 || port_nb > G_MAXUINT16)
483             port_nb = 6667;
484
485           if (ssl == NULL || strcmp (ssl, "TRUE") == 0)
486             have_ssl = TRUE;
487
488           DEBUG ("parsed server %s port %d ssl %d", address, port_nb, have_ssl);
489
490           server = empathy_irc_server_new (address, port_nb, have_ssl);
491           empathy_irc_network_append_server (network, server);
492         }
493
494       if (address)
495         xmlFree (address);
496       if (port)
497         xmlFree (port);
498       if (ssl)
499         xmlFree (ssl);
500     }
501 }
502
503 static void
504 irc_network_manager_parse_irc_network (EmpathyIrcNetworkManager *self,
505                                        xmlNodePtr node,
506                                        gboolean user_defined)
507 {
508   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
509   EmpathyIrcNetwork  *network;
510   xmlNodePtr child;
511   gchar *str;
512   gchar *id, *name;
513
514   id = xmlGetProp (node, "id");
515   if (xmlHasProp (node, "dropped"))
516     {
517       if (!user_defined)
518         {
519           DEBUG ("the 'dropped' attribute shouldn't be used in the global file");
520         }
521
522       network = g_hash_table_lookup (priv->networks, id);
523       if (network != NULL)
524         {
525           network->dropped = TRUE;
526           network->user_defined = TRUE;
527         }
528        xmlFree (id);
529       return;
530     }
531
532   if (!xmlHasProp (node, "name"))
533     return;
534
535   name = xmlGetProp (node, "name");
536   network = empathy_irc_network_new (name);
537
538   if (xmlHasProp (node, "network_charset"))
539     {
540       gchar *charset;
541       charset = xmlGetProp (node, "network_charset");
542       g_object_set (network, "charset", charset, NULL);
543       xmlFree (charset);
544     }
545
546   add_network (self, network, id);
547   DEBUG ("add network %s (id %s)", name, id);
548
549   for (child = node->children; child; child = child->next)
550     {
551       gchar *tag;
552
553       tag = (gchar *) child->name;
554       str = (gchar *) xmlNodeGetContent (child);
555
556       if (!str)
557         continue;
558
559       if (strcmp (tag, "servers") == 0)
560         {
561           irc_network_manager_parse_irc_server (network, child);
562         }
563
564       xmlFree (str);
565     }
566
567   network->user_defined = user_defined;
568   g_object_unref (network);
569   xmlFree (name);
570   xmlFree (id);
571 }
572
573 static gboolean
574 irc_network_manager_file_parse (EmpathyIrcNetworkManager *self,
575                                 const gchar *filename,
576                                 gboolean user_defined)
577 {
578   EmpathyIrcNetworkManagerPriv *priv;
579   xmlParserCtxtPtr ctxt;
580   xmlDocPtr doc;
581   xmlNodePtr networks;
582   xmlNodePtr node;
583
584   priv = GET_PRIV (self);
585
586   DEBUG ("Attempting to parse file:'%s'...", filename);
587
588   ctxt = xmlNewParserCtxt ();
589
590   /* Parse and validate the file. */
591   doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
592   if (!doc)
593     {
594       g_warning ("Failed to parse file:'%s'", filename);
595       xmlFreeParserCtxt (ctxt);
596       return FALSE;
597     }
598
599   if (!empathy_xml_validate (doc, IRC_NETWORKS_DTD_FILENAME)) {
600     g_warning ("Failed to validate file:'%s'", filename);
601     xmlFreeDoc (doc);
602     xmlFreeParserCtxt (ctxt);
603     return FALSE;
604   }
605
606   /* The root node, networks. */
607   networks = xmlDocGetRootElement (doc);
608
609   for (node = networks->children; node; node = node->next)
610     {
611       irc_network_manager_parse_irc_network (self, node, user_defined);
612     }
613
614   xmlFreeDoc (doc);
615   xmlFreeParserCtxt (ctxt);
616
617   return TRUE;
618 }
619
620 static void
621 write_network_to_xml (const gchar *id,
622                       EmpathyIrcNetwork *network,
623                       xmlNodePtr root)
624 {
625   xmlNodePtr network_node, servers_node;
626   GSList *servers, *l;
627   gchar *name, *charset;
628
629   if (!network->user_defined)
630     /* no need to write this network to the XML */
631     return;
632
633   network_node = xmlNewChild (root, NULL, "network", NULL);
634   xmlNewProp (network_node, "id", id);
635
636   if (network->dropped)
637     {
638       xmlNewProp (network_node, "dropped", "1");
639       return;
640     }
641
642   g_object_get (network,
643       "name", &name,
644       "charset", &charset,
645       NULL);
646   xmlNewProp (network_node, "name", name);
647   xmlNewProp (network_node, "network_charset", charset);
648   g_free (name);
649   g_free (charset);
650
651   servers = empathy_irc_network_get_servers (network);
652
653   servers_node = xmlNewChild (network_node, NULL, "servers", NULL);
654   for (l = servers; l != NULL; l = g_slist_next (l))
655     {
656       EmpathyIrcServer *server;
657       xmlNodePtr server_node;
658       gchar *address, *tmp;
659       guint port;
660       gboolean ssl;
661
662       server = l->data;
663
664       server_node = xmlNewChild (servers_node, NULL, "server", NULL);
665
666       g_object_get (server,
667           "address", &address,
668           "port", &port,
669           "ssl", &ssl,
670           NULL);
671
672       xmlNewProp (server_node, "address", address);
673
674       tmp = g_strdup_printf ("%u", port);
675       xmlNewProp (server_node, "port", tmp);
676       g_free (tmp);
677
678       xmlNewProp (server_node, "ssl", ssl ? "TRUE": "FALSE");
679
680       g_free (address);
681     }
682
683   /* free the list */
684   g_slist_foreach (servers, (GFunc) g_object_unref, NULL);
685   g_slist_free (servers);
686 }
687
688 static gboolean
689 irc_network_manager_file_save (EmpathyIrcNetworkManager *self)
690 {
691   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
692   xmlDocPtr doc;
693   xmlNodePtr root;
694
695   if (priv->user_file == NULL)
696     {
697       DEBUG ("can't save: no user file defined");
698       return FALSE;
699     }
700
701   DEBUG ("Saving IRC networks");
702
703   doc = xmlNewDoc ("1.0");
704   root = xmlNewNode (NULL, "networks");
705   xmlDocSetRootElement (doc, root);
706
707   g_hash_table_foreach (priv->networks, (GHFunc) write_network_to_xml, root);
708
709   /* Make sure the XML is indented properly */
710   xmlIndentTreeOutput = 1;
711
712   xmlSaveFormatFileEnc (priv->user_file, doc, "utf-8", 1);
713   xmlFreeDoc (doc);
714
715   xmlCleanupParser ();
716   xmlMemoryDump ();
717
718   priv->have_to_save = FALSE;
719
720   return TRUE;
721 }
722
723 static gboolean
724 find_network_by_address (const gchar *id,
725                          EmpathyIrcNetwork *network,
726                          const gchar *address)
727 {
728   GSList *servers, *l;
729   gboolean found = FALSE;
730
731   if (network->dropped)
732     return FALSE;
733
734   servers = empathy_irc_network_get_servers (network);
735
736   for (l = servers; l != NULL && !found; l = g_slist_next (l))
737     {
738       EmpathyIrcServer *server = l->data;
739       gchar *_address;
740
741       g_object_get (server, "address", &_address, NULL);
742       found = (_address != NULL && strcmp (address, _address) == 0);
743
744       g_free (_address);
745     }
746
747   g_slist_foreach (servers, (GFunc) g_object_unref, NULL);
748   g_slist_free (servers);
749
750   return found;
751 }
752
753 /**
754  * empathy_irc_network_manager_find_network_by_address:
755  * @manager: an #EmpathyIrcNetworkManager
756  * @address: the server address to look for
757  *
758  * Find the #EmpathyIrcNetwork which owns an #EmpathyIrcServer
759  * that has the given address.
760  *
761  * Returns: the found #EmpathyIrcNetwork, or %NULL if not found.
762  */
763 EmpathyIrcNetwork *
764 empathy_irc_network_manager_find_network_by_address (
765     EmpathyIrcNetworkManager *self,
766     const gchar *address)
767 {
768   EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
769   EmpathyIrcNetwork *network;
770
771   g_return_val_if_fail (address != NULL, NULL);
772
773   network = g_hash_table_find (priv->networks,
774       (GHRFunc) find_network_by_address, (gchar *) address);
775
776   return network;
777 }