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_unref (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_active_networks_to_list (const gchar *id,
370 EmpathyIrcNetwork *network,
373 if (network->dropped)
376 *list = g_slist_prepend (*list, g_object_ref (network));
380 append_dropped_networks_to_list (const gchar *id,
381 EmpathyIrcNetwork *network,
384 if (!network->dropped)
387 *list = g_slist_prepend (*list, g_object_ref (network));
391 get_network_list (EmpathyIrcNetworkManager *self,
394 EmpathyIrcNetworkManagerPriv *priv;
395 GSList *irc_networks = NULL;
397 g_return_val_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self), NULL);
399 priv = GET_PRIV (self);
403 g_hash_table_foreach (priv->networks,
404 (GHFunc) append_active_networks_to_list, &irc_networks);
408 g_hash_table_foreach (priv->networks,
409 (GHFunc) append_dropped_networks_to_list, &irc_networks);
416 * empathy_irc_network_manager_get_networks:
417 * @manager: an #EmpathyIrcNetworkManager
419 * Get the list of #EmpathyIrcNetwork associated with the given
422 * Returns: a new #GSList of refed #EmpathyIrcNetwork
425 empathy_irc_network_manager_get_networks (EmpathyIrcNetworkManager *self)
427 return get_network_list (self, TRUE);
431 * empathy_irc_network_manager_get_dropped_networks:
432 * @manager: an #EmpathyIrcNetworkManager
434 * Get the list of dropped #EmpathyIrcNetworks associated with the given
437 * Returns: a new #GSList of refed dropped #EmpathyIrcNetworks
440 empathy_irc_network_manager_get_dropped_networks (EmpathyIrcNetworkManager *self)
442 return get_network_list (self, FALSE);
446 * API to save/load and parse the irc_networks file.
450 load_global_file (EmpathyIrcNetworkManager *self)
452 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
454 if (priv->global_file == NULL)
457 if (!g_file_test (priv->global_file, G_FILE_TEST_EXISTS))
459 DEBUG ("Global networks file %s doesn't exist", priv->global_file);
463 irc_network_manager_file_parse (self, priv->global_file, FALSE);
467 load_user_file (EmpathyIrcNetworkManager *self)
469 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
471 if (priv->user_file == NULL)
474 if (!g_file_test (priv->user_file, G_FILE_TEST_EXISTS))
476 DEBUG ("User networks file %s doesn't exist", priv->global_file);
480 irc_network_manager_file_parse (self, priv->user_file, TRUE);
484 irc_network_manager_load_servers (EmpathyIrcNetworkManager *self)
486 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
488 priv->loading = TRUE;
490 load_global_file (self);
491 load_user_file (self);
493 priv->loading = FALSE;
494 priv->have_to_save = FALSE;
498 irc_network_manager_parse_irc_server (EmpathyIrcNetwork *network,
501 xmlNodePtr server_node;
503 for (server_node = node->children; server_node;
504 server_node = server_node->next)
506 gchar *address = NULL, *port = NULL, *ssl = NULL;
508 if (strcmp ((const gchar *) server_node->name, "server") != 0)
511 address = (gchar *) xmlGetProp (server_node, (const xmlChar *) "address");
512 port = (gchar *) xmlGetProp (server_node, (const xmlChar *) "port");
513 ssl = (gchar *) xmlGetProp (server_node, (const xmlChar *) "ssl");
518 gboolean have_ssl = FALSE;
519 EmpathyIrcServer *server;
522 port_nb = strtol (port, NULL, 10);
524 if (port_nb <= 0 || port_nb > G_MAXUINT16)
527 if (ssl == NULL || strcmp (ssl, "TRUE") == 0)
530 DEBUG ("parsed server %s port %d ssl %d", address, port_nb, have_ssl);
532 server = empathy_irc_server_new (address, port_nb, have_ssl);
533 empathy_irc_network_append_server (network, server);
546 irc_network_manager_parse_irc_network (EmpathyIrcNetworkManager *self,
548 gboolean user_defined)
550 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
551 EmpathyIrcNetwork *network;
556 id = (gchar *) xmlGetProp (node, (const xmlChar *) "id");
557 if (xmlHasProp (node, (const xmlChar *) "dropped"))
561 DEBUG ("the 'dropped' attribute shouldn't be used in the global file");
564 network = g_hash_table_lookup (priv->networks, id);
567 network->dropped = TRUE;
568 network->user_defined = TRUE;
574 if (!xmlHasProp (node, (const xmlChar *) "name"))
577 name = (gchar *) xmlGetProp (node, (const xmlChar *) "name");
578 network = empathy_irc_network_new (name);
580 if (xmlHasProp (node, (const xmlChar *) "network_charset"))
583 charset = (gchar *) xmlGetProp (node, (const xmlChar *) "network_charset");
584 g_object_set (network, "charset", charset, NULL);
588 add_network (self, network, id);
589 DEBUG ("add network %s (id %s)", name, id);
591 for (child = node->children; child; child = child->next)
595 tag = (gchar *) child->name;
596 str = (gchar *) xmlNodeGetContent (child);
601 if (strcmp (tag, "servers") == 0)
603 irc_network_manager_parse_irc_server (network, child);
609 network->user_defined = user_defined;
610 g_object_unref (network);
616 irc_network_manager_file_parse (EmpathyIrcNetworkManager *self,
617 const gchar *filename,
618 gboolean user_defined)
620 xmlParserCtxtPtr ctxt;
625 DEBUG ("Attempting to parse file:'%s'...", filename);
627 ctxt = xmlNewParserCtxt ();
629 /* Parse and validate the file. */
630 doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
633 g_warning ("Failed to parse file:'%s'", filename);
634 xmlFreeParserCtxt (ctxt);
638 if (!empathy_xml_validate (doc, IRC_NETWORKS_DTD_FILENAME)) {
639 g_warning ("Failed to validate file:'%s'", filename);
641 xmlFreeParserCtxt (ctxt);
645 /* The root node, networks. */
646 networks = xmlDocGetRootElement (doc);
648 for (node = networks->children; node; node = node->next)
650 irc_network_manager_parse_irc_network (self, node, user_defined);
654 xmlFreeParserCtxt (ctxt);
660 write_network_to_xml (const gchar *id,
661 EmpathyIrcNetwork *network,
664 xmlNodePtr network_node, servers_node;
666 gchar *name, *charset;
668 if (!network->user_defined)
669 /* no need to write this network to the XML */
672 network_node = xmlNewChild (root, NULL, (const xmlChar *) "network", NULL);
673 xmlNewProp (network_node, (const xmlChar *) "id", (const xmlChar *) id);
675 if (network->dropped)
677 xmlNewProp (network_node, (const xmlChar *) "dropped",
678 (const xmlChar *) "1");
682 g_object_get (network,
686 xmlNewProp (network_node, (const xmlChar *) "name", (const xmlChar *) name);
687 xmlNewProp (network_node, (const xmlChar *) "network_charset",
688 (const xmlChar *) charset);
692 servers = empathy_irc_network_get_servers (network);
694 servers_node = xmlNewChild (network_node, NULL, (const xmlChar *) "servers",
696 for (l = servers; l != NULL; l = g_slist_next (l))
698 EmpathyIrcServer *server;
699 xmlNodePtr server_node;
700 gchar *address, *tmp;
706 server_node = xmlNewChild (servers_node, NULL, (const xmlChar *) "server",
709 g_object_get (server,
715 xmlNewProp (server_node, (const xmlChar *) "address",
716 (const xmlChar *) address);
718 tmp = g_strdup_printf ("%u", port);
719 xmlNewProp (server_node, (const xmlChar *) "port",
720 (const xmlChar *) tmp);
723 xmlNewProp (server_node, (const xmlChar *) "ssl",
724 ssl ? (const xmlChar *) "TRUE": (const xmlChar *) "FALSE");
730 g_slist_foreach (servers, (GFunc) g_object_unref, NULL);
731 g_slist_free (servers);
735 irc_network_manager_file_save (EmpathyIrcNetworkManager *self)
737 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
741 if (priv->user_file == NULL)
743 DEBUG ("can't save: no user file defined");
747 DEBUG ("Saving IRC networks");
749 doc = xmlNewDoc ((const xmlChar *) "1.0");
750 root = xmlNewNode (NULL, (const xmlChar *) "networks");
751 xmlDocSetRootElement (doc, root);
753 g_hash_table_foreach (priv->networks, (GHFunc) write_network_to_xml, root);
755 /* Make sure the XML is indented properly */
756 xmlIndentTreeOutput = 1;
758 xmlSaveFormatFileEnc (priv->user_file, doc, "utf-8", 1);
763 priv->have_to_save = FALSE;
769 find_network_by_address (const gchar *id,
770 EmpathyIrcNetwork *network,
771 const gchar *address)
774 gboolean found = FALSE;
776 if (network->dropped)
779 servers = empathy_irc_network_get_servers (network);
781 for (l = servers; l != NULL && !found; l = g_slist_next (l))
783 EmpathyIrcServer *server = l->data;
786 g_object_get (server, "address", &_address, NULL);
787 found = (_address != NULL && strcmp (address, _address) == 0);
792 g_slist_foreach (servers, (GFunc) g_object_unref, NULL);
793 g_slist_free (servers);
799 * empathy_irc_network_manager_find_network_by_address:
800 * @manager: an #EmpathyIrcNetworkManager
801 * @address: the server address to look for
803 * Find the #EmpathyIrcNetwork which owns an #EmpathyIrcServer
804 * that has the given address.
806 * Returns: the found #EmpathyIrcNetwork, or %NULL if not found.
809 empathy_irc_network_manager_find_network_by_address (
810 EmpathyIrcNetworkManager *self,
811 const gchar *address)
813 EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self);
814 EmpathyIrcNetwork *network;
816 g_return_val_if_fail (address != NULL, NULL);
818 network = g_hash_table_find (priv->networks,
819 (GHRFunc) find_network_by_address, (gchar *) address);
824 EmpathyIrcNetworkManager *
825 empathy_irc_network_manager_dup_default (void)
827 static EmpathyIrcNetworkManager *default_mgr = NULL;
828 gchar *dir, *user_file_with_path, *global_file_with_path;
830 if (default_mgr != NULL)
831 return g_object_ref (default_mgr);
833 dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
834 g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
835 user_file_with_path = g_build_filename (dir, IRC_NETWORKS_FILENAME, NULL);
838 global_file_with_path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"),
839 "libempathy", IRC_NETWORKS_FILENAME, NULL);
840 if (!g_file_test (global_file_with_path, G_FILE_TEST_EXISTS))
842 g_free (global_file_with_path);
843 global_file_with_path = g_build_filename (DATADIR, "empathy",
844 IRC_NETWORKS_FILENAME, NULL);
847 default_mgr = empathy_irc_network_manager_new (
848 global_file_with_path, user_file_with_path);
850 g_object_add_weak_pointer (G_OBJECT (default_mgr), (gpointer *) &default_mgr);
852 g_free (global_file_with_path);
853 g_free (user_file_with_path);