/* * Copyright (C) 2007-2008 Guillaume Desmottes * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Authors: Guillaume Desmottes */ #include "config.h" #include "empathy-irc-network-manager.h" #include #include "empathy-utils.h" #define DEBUG_FLAG EMPATHY_DEBUG_IRC #include "empathy-debug.h" #define IRC_NETWORKS_DTD_RESOURCENAME "/org/gnome/Empathy/empathy-irc-networks.dtd" #define IRC_NETWORKS_FILENAME "irc-networks.xml" #define SAVE_TIMER 4 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIrcNetworkManager) typedef struct { GHashTable *networks; gchar *global_file; gchar *user_file; guint last_id; /* Do we have to save modifications to the user file ? */ gboolean have_to_save; /* Are we loading networks from XML files ? */ gboolean loading; /* source id of the autosave timer */ gint save_timer_id; } EmpathyIrcNetworkManagerPriv; /* properties */ enum { PROP_GLOBAL_FILE = 1, PROP_USER_FILE, LAST_PROPERTY }; G_DEFINE_TYPE (EmpathyIrcNetworkManager, empathy_irc_network_manager, G_TYPE_OBJECT); static void irc_network_manager_load_servers ( EmpathyIrcNetworkManager *manager); static gboolean irc_network_manager_file_parse ( EmpathyIrcNetworkManager *manager, const gchar *filename, gboolean user_defined); static gboolean irc_network_manager_file_save ( EmpathyIrcNetworkManager *manager); static void empathy_irc_network_manager_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object); EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); switch (property_id) { case PROP_GLOBAL_FILE: g_value_set_string (value, priv->global_file); break; case PROP_USER_FILE: g_value_set_string (value, priv->user_file); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void empathy_irc_network_manager_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object); EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); switch (property_id) { case PROP_GLOBAL_FILE: g_free (priv->global_file); priv->global_file = g_value_dup_string (value); break; case PROP_USER_FILE: g_free (priv->user_file); priv->user_file = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static GObject * empathy_irc_network_manager_constructor (GType type, guint n_props, GObjectConstructParam *props) { GObject *obj; EmpathyIrcNetworkManager *self; /* Parent constructor chain */ obj = G_OBJECT_CLASS (empathy_irc_network_manager_parent_class)-> constructor (type, n_props, props); self = EMPATHY_IRC_NETWORK_MANAGER (obj); irc_network_manager_load_servers (self); return obj; } static void empathy_irc_network_manager_finalize (GObject *object) { EmpathyIrcNetworkManager *self = EMPATHY_IRC_NETWORK_MANAGER (object); EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); if (priv->save_timer_id > 0) { g_source_remove (priv->save_timer_id); } if (priv->have_to_save) { irc_network_manager_file_save (self); } g_free (priv->global_file); g_free (priv->user_file); g_hash_table_unref (priv->networks); G_OBJECT_CLASS (empathy_irc_network_manager_parent_class)->finalize (object); } static void empathy_irc_network_manager_init (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_IRC_NETWORK_MANAGER, EmpathyIrcNetworkManagerPriv); self->priv = priv; priv->networks = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref); priv->last_id = 0; priv->have_to_save = FALSE; priv->loading = FALSE; priv->save_timer_id = 0; } static void empathy_irc_network_manager_class_init (EmpathyIrcNetworkManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *param_spec; object_class->constructor = empathy_irc_network_manager_constructor; object_class->get_property = empathy_irc_network_manager_get_property; object_class->set_property = empathy_irc_network_manager_set_property; g_type_class_add_private (object_class, sizeof (EmpathyIrcNetworkManagerPriv)); object_class->finalize = empathy_irc_network_manager_finalize; param_spec = g_param_spec_string ( "global-file", "path of the global networks file", "The path of the system-wide filename from which we have to load" " the networks list", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); g_object_class_install_property (object_class, PROP_GLOBAL_FILE, param_spec); param_spec = g_param_spec_string ( "user-file", "path of the user networks file", "The path of user's filename from which we have to load" " the networks list and to which we'll save his modifications", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB); g_object_class_install_property (object_class, PROP_USER_FILE, param_spec); } /** * empathy_irc_network_manager_new: * @global_file: the path of the global networks file, or %NULL * @user_file: the path of the user networks file, or %NULL * * Creates a new #EmpathyIrcNetworkManager * * Returns: a new #EmpathyIrcNetworkManager */ EmpathyIrcNetworkManager * empathy_irc_network_manager_new (const gchar *global_file, const gchar *user_file) { EmpathyIrcNetworkManager *manager; manager = g_object_new (EMPATHY_TYPE_IRC_NETWORK_MANAGER, "global-file", global_file, "user-file", user_file, NULL); return manager; } static gboolean save_timeout (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); priv->save_timer_id = 0; irc_network_manager_file_save (self); return FALSE; } static void reset_save_timeout (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); if (priv->save_timer_id > 0) { g_source_remove (priv->save_timer_id); } priv->save_timer_id = g_timeout_add_seconds (SAVE_TIMER, (GSourceFunc) save_timeout, self); } static void network_modified (EmpathyIrcNetwork *network, EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); network->user_defined = TRUE; if (!priv->loading) { priv->have_to_save = TRUE; reset_save_timeout (self); } } static void add_network (EmpathyIrcNetworkManager *self, EmpathyIrcNetwork *network, const gchar *id) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); g_hash_table_insert (priv->networks, g_strdup (id), g_object_ref (network)); g_signal_connect (network, "modified", G_CALLBACK (network_modified), self); } /** * empathy_irc_network_manager_add: * @manager: an #EmpathyIrcNetworkManager * @network: the #EmpathyIrcNetwork to add * * Add an #EmpathyIrcNetwork to the given #EmpathyIrcNetworkManager. * */ void empathy_irc_network_manager_add (EmpathyIrcNetworkManager *self, EmpathyIrcNetwork *network) { EmpathyIrcNetworkManagerPriv *priv; gchar *id = NULL; g_return_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self)); g_return_if_fail (EMPATHY_IS_IRC_NETWORK (network)); priv = GET_PRIV (self); /* generate an id for this network */ do { g_free (id); id = g_strdup_printf ("id%u", ++priv->last_id); } while (g_hash_table_lookup (priv->networks, id) != NULL && priv->last_id < G_MAXUINT); if (priv->last_id == G_MAXUINT) { DEBUG ("Can't add network: too many networks using a similar ID"); return; } DEBUG ("add server with \"%s\" as ID", id); network->user_defined = TRUE; add_network (self, network, id); priv->have_to_save = TRUE; reset_save_timeout (self); g_free (id); } /** * empathy_irc_network_manager_remove: * @manager: an #EmpathyIrcNetworkManager * @network: the #EmpathyIrcNetwork to remove * * Remove an #EmpathyIrcNetwork from the given #EmpathyIrcNetworkManager. * */ void empathy_irc_network_manager_remove (EmpathyIrcNetworkManager *self, EmpathyIrcNetwork *network) { EmpathyIrcNetworkManagerPriv *priv; g_return_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self)); g_return_if_fail (EMPATHY_IS_IRC_NETWORK (network)); priv = GET_PRIV (self); network->user_defined = TRUE; network->dropped = TRUE; priv->have_to_save = TRUE; reset_save_timeout (self); } static void append_active_networks_to_list (const gchar *id, EmpathyIrcNetwork *network, GSList **list) { if (network->dropped) return; *list = g_slist_prepend (*list, g_object_ref (network)); } static void append_dropped_networks_to_list (const gchar *id, EmpathyIrcNetwork *network, GSList **list) { if (!network->dropped) return; *list = g_slist_prepend (*list, g_object_ref (network)); } static GSList * get_network_list (EmpathyIrcNetworkManager *self, gboolean get_active) { EmpathyIrcNetworkManagerPriv *priv; GSList *irc_networks = NULL; g_return_val_if_fail (EMPATHY_IS_IRC_NETWORK_MANAGER (self), NULL); priv = GET_PRIV (self); if (get_active) { g_hash_table_foreach (priv->networks, (GHFunc) append_active_networks_to_list, &irc_networks); } else { g_hash_table_foreach (priv->networks, (GHFunc) append_dropped_networks_to_list, &irc_networks); } return irc_networks; } /** * empathy_irc_network_manager_get_networks: * @manager: an #EmpathyIrcNetworkManager * * Get the list of #EmpathyIrcNetwork associated with the given * manager. * * Returns: a new #GSList of refed #EmpathyIrcNetwork */ GSList * empathy_irc_network_manager_get_networks (EmpathyIrcNetworkManager *self) { return get_network_list (self, TRUE); } /** * empathy_irc_network_manager_get_dropped_networks: * @manager: an #EmpathyIrcNetworkManager * * Get the list of dropped #EmpathyIrcNetworks associated with the given * manager. * * Returns: a new #GSList of refed dropped #EmpathyIrcNetworks */ GSList * empathy_irc_network_manager_get_dropped_networks (EmpathyIrcNetworkManager *self) { return get_network_list (self, FALSE); } /* * API to save/load and parse the irc_networks file. */ static void load_global_file (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); if (priv->global_file == NULL) return; if (!g_file_test (priv->global_file, G_FILE_TEST_EXISTS)) { DEBUG ("Global networks file %s doesn't exist", priv->global_file); return; } irc_network_manager_file_parse (self, priv->global_file, FALSE); } static void load_user_file (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); if (priv->user_file == NULL) return; if (!g_file_test (priv->user_file, G_FILE_TEST_EXISTS)) { DEBUG ("User networks file %s doesn't exist", priv->global_file); return; } irc_network_manager_file_parse (self, priv->user_file, TRUE); } static void irc_network_manager_load_servers (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); priv->loading = TRUE; load_global_file (self); load_user_file (self); priv->loading = FALSE; priv->have_to_save = FALSE; } static void irc_network_manager_parse_irc_server (EmpathyIrcNetwork *network, xmlNodePtr node) { xmlNodePtr server_node; for (server_node = node->children; server_node; server_node = server_node->next) { gchar *address = NULL, *port = NULL, *ssl = NULL; if (strcmp ((const gchar *) server_node->name, "server") != 0) continue; address = (gchar *) xmlGetProp (server_node, (const xmlChar *) "address"); port = (gchar *) xmlGetProp (server_node, (const xmlChar *) "port"); ssl = (gchar *) xmlGetProp (server_node, (const xmlChar *) "ssl"); if (address != NULL) { gint port_nb = 0; gboolean have_ssl = FALSE; EmpathyIrcServer *server; if (port != NULL) port_nb = strtol (port, NULL, 10); if (port_nb <= 0 || port_nb > G_MAXUINT16) port_nb = 6667; if (ssl == NULL || strcmp (ssl, "TRUE") == 0) have_ssl = TRUE; DEBUG ("parsed server %s port %d ssl %d", address, port_nb, have_ssl); server = empathy_irc_server_new (address, port_nb, have_ssl); empathy_irc_network_append_server (network, server); } if (address) xmlFree (address); if (port) xmlFree (port); if (ssl) xmlFree (ssl); } } static void irc_network_manager_parse_irc_network (EmpathyIrcNetworkManager *self, xmlNodePtr node, gboolean user_defined) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); EmpathyIrcNetwork *network; xmlNodePtr child; gchar *str; gchar *id, *name; id = (gchar *) xmlGetProp (node, (const xmlChar *) "id"); if (xmlHasProp (node, (const xmlChar *) "dropped")) { if (!user_defined) { DEBUG ("the 'dropped' attribute shouldn't be used in the global file"); } network = g_hash_table_lookup (priv->networks, id); if (network != NULL) { network->dropped = TRUE; network->user_defined = TRUE; } xmlFree (id); return; } if (!xmlHasProp (node, (const xmlChar *) "name")) return; name = (gchar *) xmlGetProp (node, (const xmlChar *) "name"); network = empathy_irc_network_new (name); if (xmlHasProp (node, (const xmlChar *) "network_charset")) { gchar *charset; charset = (gchar *) xmlGetProp (node, (const xmlChar *) "network_charset"); g_object_set (network, "charset", charset, NULL); xmlFree (charset); } add_network (self, network, id); DEBUG ("add network %s (id %s)", name, id); for (child = node->children; child; child = child->next) { gchar *tag; tag = (gchar *) child->name; str = (gchar *) xmlNodeGetContent (child); if (!str) continue; if (strcmp (tag, "servers") == 0) { irc_network_manager_parse_irc_server (network, child); } xmlFree (str); } network->user_defined = user_defined; g_object_unref (network); xmlFree (name); xmlFree (id); } static gboolean irc_network_manager_file_parse (EmpathyIrcNetworkManager *self, const gchar *filename, gboolean user_defined) { xmlParserCtxtPtr ctxt; xmlDocPtr doc; xmlNodePtr networks; xmlNodePtr node; DEBUG ("Attempting to parse file:'%s'...", filename); ctxt = xmlNewParserCtxt (); /* Parse and validate the file. */ doc = xmlCtxtReadFile (ctxt, filename, NULL, 0); if (!doc) { g_warning ("Failed to parse file:'%s'", filename); xmlFreeParserCtxt (ctxt); return FALSE; } if (!empathy_xml_validate_from_resource (doc, IRC_NETWORKS_DTD_RESOURCENAME)) { g_warning ("Failed to validate file:'%s'", filename); xmlFreeDoc (doc); xmlFreeParserCtxt (ctxt); return FALSE; } /* The root node, networks. */ networks = xmlDocGetRootElement (doc); for (node = networks->children; node; node = node->next) { irc_network_manager_parse_irc_network (self, node, user_defined); } xmlFreeDoc (doc); xmlFreeParserCtxt (ctxt); return TRUE; } static void write_network_to_xml (const gchar *id, EmpathyIrcNetwork *network, xmlNodePtr root) { xmlNodePtr network_node, servers_node; GSList *servers, *l; gchar *name, *charset; if (!network->user_defined) /* no need to write this network to the XML */ return; network_node = xmlNewChild (root, NULL, (const xmlChar *) "network", NULL); xmlNewProp (network_node, (const xmlChar *) "id", (const xmlChar *) id); if (network->dropped) { xmlNewProp (network_node, (const xmlChar *) "dropped", (const xmlChar *) "1"); return; } g_object_get (network, "name", &name, "charset", &charset, NULL); xmlNewProp (network_node, (const xmlChar *) "name", (const xmlChar *) name); xmlNewProp (network_node, (const xmlChar *) "network_charset", (const xmlChar *) charset); g_free (name); g_free (charset); servers = empathy_irc_network_get_servers (network); servers_node = xmlNewChild (network_node, NULL, (const xmlChar *) "servers", NULL); for (l = servers; l != NULL; l = g_slist_next (l)) { EmpathyIrcServer *server; xmlNodePtr server_node; gchar *address, *tmp; guint port; gboolean ssl; server = l->data; server_node = xmlNewChild (servers_node, NULL, (const xmlChar *) "server", NULL); g_object_get (server, "address", &address, "port", &port, "ssl", &ssl, NULL); xmlNewProp (server_node, (const xmlChar *) "address", (const xmlChar *) address); tmp = g_strdup_printf ("%u", port); xmlNewProp (server_node, (const xmlChar *) "port", (const xmlChar *) tmp); g_free (tmp); xmlNewProp (server_node, (const xmlChar *) "ssl", ssl ? (const xmlChar *) "TRUE": (const xmlChar *) "FALSE"); g_free (address); } /* free the list */ g_slist_foreach (servers, (GFunc) g_object_unref, NULL); g_slist_free (servers); } static gboolean irc_network_manager_file_save (EmpathyIrcNetworkManager *self) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); xmlDocPtr doc; xmlNodePtr root; if (priv->user_file == NULL) { DEBUG ("can't save: no user file defined"); return FALSE; } DEBUG ("Saving IRC networks"); doc = xmlNewDoc ((const xmlChar *) "1.0"); root = xmlNewNode (NULL, (const xmlChar *) "networks"); xmlDocSetRootElement (doc, root); g_hash_table_foreach (priv->networks, (GHFunc) write_network_to_xml, root); /* Make sure the XML is indented properly */ xmlIndentTreeOutput = 1; xmlSaveFormatFileEnc (priv->user_file, doc, "utf-8", 1); xmlFreeDoc (doc); xmlMemoryDump (); priv->have_to_save = FALSE; return TRUE; } static gboolean find_network_by_address (const gchar *id, EmpathyIrcNetwork *network, const gchar *address) { GSList *servers, *l; gboolean found = FALSE; if (network->dropped) return FALSE; servers = empathy_irc_network_get_servers (network); for (l = servers; l != NULL && !found; l = g_slist_next (l)) { EmpathyIrcServer *server = l->data; gchar *_address; g_object_get (server, "address", &_address, NULL); found = (_address != NULL && strcmp (address, _address) == 0); g_free (_address); } g_slist_foreach (servers, (GFunc) g_object_unref, NULL); g_slist_free (servers); return found; } /** * empathy_irc_network_manager_find_network_by_address: * @manager: an #EmpathyIrcNetworkManager * @address: the server address to look for * * Find the #EmpathyIrcNetwork which owns an #EmpathyIrcServer * that has the given address. * * Returns: the found #EmpathyIrcNetwork, or %NULL if not found. */ EmpathyIrcNetwork * empathy_irc_network_manager_find_network_by_address ( EmpathyIrcNetworkManager *self, const gchar *address) { EmpathyIrcNetworkManagerPriv *priv = GET_PRIV (self); EmpathyIrcNetwork *network; g_return_val_if_fail (address != NULL, NULL); network = g_hash_table_find (priv->networks, (GHRFunc) find_network_by_address, (gchar *) address); return network; } EmpathyIrcNetworkManager * empathy_irc_network_manager_dup_default (void) { static EmpathyIrcNetworkManager *default_mgr = NULL; gchar *dir, *user_file_with_path, *global_file_with_path; if (default_mgr != NULL) return g_object_ref (default_mgr); dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL); g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR); user_file_with_path = g_build_filename (dir, IRC_NETWORKS_FILENAME, NULL); g_free (dir); global_file_with_path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "libempathy", IRC_NETWORKS_FILENAME, NULL); if (!g_file_test (global_file_with_path, G_FILE_TEST_EXISTS)) { g_free (global_file_with_path); global_file_with_path = g_build_filename (DATADIR, "empathy", IRC_NETWORKS_FILENAME, NULL); } default_mgr = empathy_irc_network_manager_new ( global_file_with_path, user_file_with_path); g_object_add_weak_pointer (G_OBJECT (default_mgr), (gpointer *) &default_mgr); g_free (global_file_with_path); g_free (user_file_with_path); return default_mgr; }