2 * Copyright (C) 2007-2008 Guillaume Desmottes
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.
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.
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
18 * Authors: Guillaume Desmottes <gdesmott@gnome.org>
23 #include <sys/types.h>
25 #include <libxml/parser.h>
26 #include <libxml/tree.h>
28 #include "empathy-utils.h"
29 #include "empathy-irc-network-manager.h"
31 #define DEBUG_FLAG EMPATHY_DEBUG_IRC
32 #include "empathy-debug.h"
34 #define IRC_NETWORKS_DTD_FILENAME "empathy-irc-networks.dtd"
35 #define IRC_NETWORKS_FILENAME "irc-networks.xml"
38 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIrcNetworkManager)
46 /* Do we have to save modifications to the user file ? */
47 gboolean have_to_save;
48 /* Are we loading networks from XML files ? */
50 /* source id of the autosave timer */
52 } EmpathyIrcNetworkManagerPriv;
62 G_DEFINE_TYPE (EmpathyIrcNetworkManager, empathy_irc_network_manager,
65 static void irc_network_manager_load_servers (
66 EmpathyIrcNetworkManager *manager);
67 static gboolean irc_network_manager_file_parse (
68 EmpathyIrcNetworkManager *manager, const gchar *filename,
69 gboolean user_defined);
70 static gboolean irc_network_manager_file_save (
71 EmpathyIrcNetworkManager *manager);
74 empathy_irc_network_manager_get_property (GObject *object,
79 EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object);
80 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
84 case PROP_GLOBAL_FILE:
85 g_value_set_string (value, priv->global_file);
88 g_value_set_string (value, priv->user_file);
91 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
97 empathy_irc_network_manager_set_property (GObject *object,
102 EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object);
103 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
107 case PROP_GLOBAL_FILE:
108 g_free (priv->global_file);
109 priv->global_file = g_value_dup_string (value);
112 g_free (priv->user_file);
113 priv->user_file = g_value_dup_string (value);
116 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
122 empathy_irc_network_manager_constructor (GType type,
124 GObjectConstructParam *props)
127 EmpathyIrcNetworkManager *self;
129 /* Parent constructor chain */
130 obj = G_OBJECT_CLASS (empathy_irc_network_manager_parent_class)->
131 constructor (type, n_props, props);
133 self = EMPATHY_IRC_NETWORK_MANAGER (obj);
134 irc_network_manager_load_servers (self);
140 empathy_irc_network_manager_finalize (GObject *object)
142 EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object);
143 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
145 if (priv->save_timer_id > 0)
147 g_source_remove (priv->save_timer_id);
150 if (priv->have_to_save)
152 irc_network_manager_file_save (self);
155 g_free (priv->global_file);
156 g_free (priv->user_file);
158 g_hash_table_destroy (priv->networks);
160 G_OBJECT_CLASS (empathy_irc_network_manager_parent_class)->finalize (object);
164 empathy_irc_network_manager_init (EmpathyIrcNetworkManager *self)
166 EmpathyIrcNetworkManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
167 EMPATHY_TYPE_IRC_NETWORK_MANAGER, EmpathyIrcNetworkManagerPriv);
171 priv->networks = g_hash_table_new_full (g_str_hash, g_str_equal,
172 (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref);
176 priv->have_to_save = FALSE;
177 priv->loading = FALSE;
178 priv->save_timer_id = 0;
182 empathy_irc_network_manager_class_init (EmpathyIrcNetworkManagerClass *klass)
184 GObjectClass *object_class = G_OBJECT_CLASS (klass);
185 GParamSpec *param_spec;
187 object_class->constructor = empathy_irc_network_manager_constructor;
188 object_class->get_property = empathy_irc_network_manager_get_property;
189 object_class->set_property = empathy_irc_network_manager_set_property;
191 g_type_class_add_private (object_class, sizeof (EmpathyIrcNetworkManagerPriv));
193 object_class->finalize = empathy_irc_network_manager_finalize;
195 param_spec = g_param_spec_string (
197 "path of the global networks file",
198 "The path of the system-wide filename from which we have to load"
199 " the networks list",
201 G_PARAM_CONSTRUCT_ONLY |
203 G_PARAM_STATIC_NAME |
204 G_PARAM_STATIC_NICK |
205 G_PARAM_STATIC_BLURB);
206 g_object_class_install_property (object_class, PROP_GLOBAL_FILE, param_spec);
208 param_spec = g_param_spec_string (
210 "path of the user networks file",
211 "The path of user's filename from which we have to load"
212 " the networks list and to which we'll save his modifications",
214 G_PARAM_CONSTRUCT_ONLY |
216 G_PARAM_STATIC_NAME |
217 G_PARAM_STATIC_NICK |
218 G_PARAM_STATIC_BLURB);
219 g_object_class_install_property (object_class, PROP_USER_FILE, param_spec);
223 * empathy_irc_network_manager_new:
224 * @global_file: the path of the global networks file, or %NULL
225 * @user_file: the path of the user networks file, or %NULL
227 * Creates a new #EmpathyIrcNetworkManager
229 * Returns: a new #EmpathyIrcNetworkManager
231 EmpathyIrcNetworkManager *
232 empathy_irc_network_manager_new (const gchar *global_file,
233 const gchar *user_file)
235 EmpathyIrcNetworkManager *manager;
237 manager = g_object_new (EMPATHY_TYPE_IRC_NETWORK_MANAGER,
238 "global-file", global_file,
239 "user-file", user_file,
246 save_timeout (EmpathyIrcNetworkManager *self)
248 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
250 priv->save_timer_id = 0;
251 irc_network_manager_file_save (self);
257 reset_save_timeout (EmpathyIrcNetworkManager *self)
259 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
261 if (priv->save_timer_id > 0)
263 g_source_remove (priv->save_timer_id);
266 priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER,
267 (GSourceFunc) save_timeout, self);
271 network_modified (EmpathyIrcNetwork *network,
272 EmpathyIrcNetworkManager *self)
274 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
276 network->user_defined = TRUE;
280 priv->have_to_save = TRUE;
281 reset_save_timeout (self);
286 add_network (EmpathyIrcNetworkManager *self,
287 EmpathyIrcNetwork *network,
290 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
292 g_hash_table_insert (priv->networks, g_strdup (id), g_object_ref (network));
294 g_signal_connect (network, "modified", G_CALLBACK (network_modified), self);
298 * empathy_irc_network_manager_add:
299 * @manager: an #EmpathyIrcNetworkManager
300 * @network: the #EmpathyIrcNetwork to add
302 * Add an #EmpathyIrcNetwork to the given #EmpathyIrcNetworkManager.
306 empathy_irc_network_manager_add (EmpathyIrcNetworkManager *self,
307 EmpathyIrcNetwork *network)
309 EmpathyIrcNetworkManagerPriv *priv;
312 g_return_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self));
313 g_return_if_fail (EMPATHY_IS_IRC_NETWORK (network));
315 priv = GET_PRIV (self);
317 /* generate an id for this network */
321 id = g_strdup_printf ("id%u", ++priv->last_id);
322 } while (g_hash_table_lookup (priv->networks, id) != NULL &&
323 priv->last_id < G_MAXUINT);
325 if (priv->last_id == G_MAXUINT)
327 DEBUG ("Can't add network: too many networks using a similar ID");
331 DEBUG ("add server with \"%s\" as ID", id);
333 network->user_defined = TRUE;
334 add_network (self, network, id);
336 priv->have_to_save = TRUE;
337 reset_save_timeout (self);
343 * empathy_irc_network_manager_remove:
344 * @manager: an #EmpathyIrcNetworkManager
345 * @network: the #EmpathyIrcNetwork to remove
347 * Remove an #EmpathyIrcNetwork from the given #EmpathyIrcNetworkManager.
351 empathy_irc_network_manager_remove (EmpathyIrcNetworkManager *self,
352 EmpathyIrcNetwork *network)
354 EmpathyIrcNetworkManagerPriv *priv;
356 g_return_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self));
357 g_return_if_fail (EMPATHY_IS_IRC_NETWORK (network));
359 priv = GET_PRIV (self);
361 network->user_defined = TRUE;
362 network->dropped = TRUE;
364 priv->have_to_save = TRUE;
365 reset_save_timeout (self);
369 append_network_to_list (const gchar *id,
370 EmpathyIrcNetwork *network,
373 if (network->dropped)
376 *list = g_slist_prepend (*list, g_object_ref (network));
380 * empathy_irc_network_manager_get_networks:
381 * @manager: an #EmpathyIrcNetworkManager
383 * Get the list of #EmpathyIrcNetwork associated with the given
386 * Returns: a new #GSList of refed #EmpathyIrcNetwork
389 empathy_irc_network_manager_get_networks (EmpathyIrcNetworkManager *self)
391 EmpathyIrcNetworkManagerPriv *priv;
392 GSList *irc_networks = NULL;
394 g_return_val_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self), NULL);
396 priv = GET_PRIV (self);
398 g_hash_table_foreach (priv->networks, (GHFunc) append_network_to_list,
405 * API to save/load and parse the irc_networks file.
409 load_global_file (EmpathyIrcNetworkManager *self)
411 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
413 if (priv->global_file == NULL)
416 if (!g_file_test (priv->global_file, G_FILE_TEST_EXISTS))
418 DEBUG ("Global networks file %s doesn't exist", priv->global_file);
422 irc_network_manager_file_parse (self, priv->global_file, FALSE);
426 load_user_file (EmpathyIrcNetworkManager *self)
428 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
430 if (priv->user_file == NULL)
433 if (!g_file_test (priv->user_file, G_FILE_TEST_EXISTS))
435 DEBUG ("User networks file %s doesn't exist", priv->global_file);
439 irc_network_manager_file_parse (self, priv->user_file, TRUE);
443 irc_network_manager_load_servers (EmpathyIrcNetworkManager *self)
445 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
447 priv->loading = TRUE;
449 load_global_file (self);
450 load_user_file (self);
452 priv->loading = FALSE;
453 priv->have_to_save = FALSE;
457 irc_network_manager_parse_irc_server (EmpathyIrcNetwork *network,
460 xmlNodePtr server_node;
462 for (server_node = node->children; server_node;
463 server_node = server_node->next)
465 gchar *address = NULL, *port = NULL, *ssl = NULL;
467 if (strcmp ((const gchar *) server_node->name, "server") != 0)
470 address = (gchar *) xmlGetProp (server_node, (const xmlChar *) "address");
471 port = (gchar *) xmlGetProp (server_node, (const xmlChar *) "port");
472 ssl = (gchar *) xmlGetProp (server_node, (const xmlChar *) "ssl");
477 gboolean have_ssl = FALSE;
478 EmpathyIrcServer *server;
481 port_nb = strtol (port, NULL, 10);
483 if (port_nb <= 0 || port_nb > G_MAXUINT16)
486 if (ssl == NULL || strcmp (ssl, "TRUE") == 0)
489 DEBUG ("parsed server %s port %d ssl %d", address, port_nb, have_ssl);
491 server = empathy_irc_server_new (address, port_nb, have_ssl);
492 empathy_irc_network_append_server (network, server);
505 irc_network_manager_parse_irc_network (EmpathyIrcNetworkManager *self,
507 gboolean user_defined)
509 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
510 EmpathyIrcNetwork *network;
515 id = (gchar *) xmlGetProp (node, (const xmlChar *) "id");
516 if (xmlHasProp (node, (const xmlChar *) "dropped"))
520 DEBUG ("the 'dropped' attribute shouldn't be used in the global file");
523 network = g_hash_table_lookup (priv->networks, id);
526 network->dropped = TRUE;
527 network->user_defined = TRUE;
533 if (!xmlHasProp (node, (const xmlChar *) "name"))
536 name = (gchar *) xmlGetProp (node, (const xmlChar *) "name");
537 network = empathy_irc_network_new (name);
539 if (xmlHasProp (node, (const xmlChar *) "network_charset"))
542 charset = (gchar *) xmlGetProp (node, (const xmlChar *) "network_charset");
543 g_object_set (network, "charset", charset, NULL);
547 add_network (self, network, id);
548 DEBUG ("add network %s (id %s)", name, id);
550 for (child = node->children; child; child = child->next)
554 tag = (gchar *) child->name;
555 str = (gchar *) xmlNodeGetContent (child);
560 if (strcmp (tag, "servers") == 0)
562 irc_network_manager_parse_irc_server (network, child);
568 network->user_defined = user_defined;
569 g_object_unref (network);
575 irc_network_manager_file_parse (EmpathyIrcNetworkManager *self,
576 const gchar *filename,
577 gboolean user_defined)
579 EmpathyIrcNetworkManagerPriv *priv;
580 xmlParserCtxtPtr ctxt;
585 priv = GET_PRIV (self);
587 DEBUG ("Attempting to parse file:'%s'...", filename);
589 ctxt = xmlNewParserCtxt ();
591 /* Parse and validate the file. */
592 doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
595 g_warning ("Failed to parse file:'%s'", filename);
596 xmlFreeParserCtxt (ctxt);
600 if (!empathy_xml_validate (doc, IRC_NETWORKS_DTD_FILENAME)) {
601 g_warning ("Failed to validate file:'%s'", filename);
603 xmlFreeParserCtxt (ctxt);
607 /* The root node, networks. */
608 networks = xmlDocGetRootElement (doc);
610 for (node = networks->children; node; node = node->next)
612 irc_network_manager_parse_irc_network (self, node, user_defined);
616 xmlFreeParserCtxt (ctxt);
622 write_network_to_xml (const gchar *id,
623 EmpathyIrcNetwork *network,
626 xmlNodePtr network_node, servers_node;
628 gchar *name, *charset;
630 if (!network->user_defined)
631 /* no need to write this network to the XML */
634 network_node = xmlNewChild (root, NULL, (const xmlChar *) "network", NULL);
635 xmlNewProp (network_node, (const xmlChar *) "id", (const xmlChar *) id);
637 if (network->dropped)
639 xmlNewProp (network_node, (const xmlChar *) "dropped",
640 (const xmlChar *) "1");
644 g_object_get (network,
648 xmlNewProp (network_node, (const xmlChar *) "name", (const xmlChar *) name);
649 xmlNewProp (network_node, (const xmlChar *) "network_charset",
650 (const xmlChar *) charset);
654 servers = empathy_irc_network_get_servers (network);
656 servers_node = xmlNewChild (network_node, NULL, (const xmlChar *) "servers",
658 for (l = servers; l != NULL; l = g_slist_next (l))
660 EmpathyIrcServer *server;
661 xmlNodePtr server_node;
662 gchar *address, *tmp;
668 server_node = xmlNewChild (servers_node, NULL, (const xmlChar *) "server",
671 g_object_get (server,
677 xmlNewProp (server_node, (const xmlChar *) "address",
678 (const xmlChar *) address);
680 tmp = g_strdup_printf ("%u", port);
681 xmlNewProp (server_node, (const xmlChar *) "port",
682 (const xmlChar *) tmp);
685 xmlNewProp (server_node, (const xmlChar *) "ssl",
686 ssl ? (const xmlChar *) "TRUE": (const xmlChar *) "FALSE");
692 g_slist_foreach (servers, (GFunc) g_object_unref, NULL);
693 g_slist_free (servers);
697 irc_network_manager_file_save (EmpathyIrcNetworkManager *self)
699 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
703 if (priv->user_file == NULL)
705 DEBUG ("can't save: no user file defined");
709 DEBUG ("Saving IRC networks");
711 doc = xmlNewDoc ((const xmlChar *) "1.0");
712 root = xmlNewNode (NULL, (const xmlChar *) "networks");
713 xmlDocSetRootElement (doc, root);
715 g_hash_table_foreach (priv->networks, (GHFunc) write_network_to_xml, root);
717 /* Make sure the XML is indented properly */
718 xmlIndentTreeOutput = 1;
720 xmlSaveFormatFileEnc (priv->user_file, doc, "utf-8", 1);
725 priv->have_to_save = FALSE;
731 find_network_by_address (const gchar *id,
732 EmpathyIrcNetwork *network,
733 const gchar *address)
736 gboolean found = FALSE;
738 if (network->dropped)
741 servers = empathy_irc_network_get_servers (network);
743 for (l = servers; l != NULL && !found; l = g_slist_next (l))
745 EmpathyIrcServer *server = l->data;
748 g_object_get (server, "address", &_address, NULL);
749 found = (_address != NULL && strcmp (address, _address) == 0);
754 g_slist_foreach (servers, (GFunc) g_object_unref, NULL);
755 g_slist_free (servers);
761 * empathy_irc_network_manager_find_network_by_address:
762 * @manager: an #EmpathyIrcNetworkManager
763 * @address: the server address to look for
765 * Find the #EmpathyIrcNetwork which owns an #EmpathyIrcServer
766 * that has the given address.
768 * Returns: the found #EmpathyIrcNetwork, or %NULL if not found.
771 empathy_irc_network_manager_find_network_by_address (
772 EmpathyIrcNetworkManager *self,
773 const gchar *address)
775 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
776 EmpathyIrcNetwork *network;
778 g_return_val_if_fail (address != NULL, NULL);
780 network = g_hash_table_find (priv->networks,
781 (GHRFunc) find_network_by_address, (gchar *) address);
786 EmpathyIrcNetworkManager *
787 empathy_irc_network_manager_dup_default (void)
789 static EmpathyIrcNetworkManager *default_mgr = NULL;
790 gchar *dir, *user_file_with_path, *global_file_with_path;
792 if (default_mgr != NULL)
793 return g_object_ref (default_mgr);
795 dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
796 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
797 user_file_with_path = g_build_filename (dir, IRC_NETWORKS_FILENAME, NULL);
800 global_file_with_path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"),
801 "libempathy", IRC_NETWORKS_FILENAME, NULL);
802 if (!g_file_test (global_file_with_path, G_FILE_TEST_EXISTS))
804 g_free (global_file_with_path);
805 global_file_with_path = g_build_filename (DATADIR, "empathy",
806 IRC_NETWORKS_FILENAME, NULL);
809 default_mgr = empathy_irc_network_manager_new (
810 global_file_with_path, user_file_with_path);
812 g_object_add_weak_pointer (G_OBJECT (default_mgr), (gpointer *) &default_mgr);
814 g_free (global_file_with_path);
815 g_free (user_file_with_path);