diff options
Diffstat (limited to 'tp-account-widgets/tpaw-irc-network-manager.c')
-rw-r--r-- | tp-account-widgets/tpaw-irc-network-manager.c | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/tp-account-widgets/tpaw-irc-network-manager.c b/tp-account-widgets/tpaw-irc-network-manager.c new file mode 100644 index 000000000..e6fdebacc --- /dev/null +++ b/tp-account-widgets/tpaw-irc-network-manager.c @@ -0,0 +1,852 @@ +/* + * 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 <gdesmott@gnome.org> + */ + +#include "config.h" +#include "tpaw-irc-network-manager.h" + +#include <sys/stat.h> + +#include "empathy-utils.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_IRC +#include "empathy-debug.h" + +#define IRC_NETWORKS_DTD_RESOURCENAME "/org/gnome/AccountWidgets/tpaw-irc-networks.dtd" +#define IRC_NETWORKS_FILENAME "irc-networks.xml" +#define SAVE_TIMER 4 + +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, TpawIrcNetworkManager) +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; +} TpawIrcNetworkManagerPriv; + +/* properties */ +enum +{ + PROP_GLOBAL_FILE = 1, + PROP_USER_FILE, + LAST_PROPERTY +}; + +G_DEFINE_TYPE (TpawIrcNetworkManager, tpaw_irc_network_manager, + G_TYPE_OBJECT); + +static void irc_network_manager_load_servers ( + TpawIrcNetworkManager *manager); +static gboolean irc_network_manager_file_parse ( + TpawIrcNetworkManager *manager, const gchar *filename, + gboolean user_defined); +static gboolean irc_network_manager_file_save ( + TpawIrcNetworkManager *manager); + +static void +tpaw_irc_network_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TpawIrcNetworkManager *self = TPAW_IRC_NETWORK_MANAGER (object); + TpawIrcNetworkManagerPriv *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 +tpaw_irc_network_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TpawIrcNetworkManager *self = TPAW_IRC_NETWORK_MANAGER (object); + TpawIrcNetworkManagerPriv *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 * +tpaw_irc_network_manager_constructor (GType type, + guint n_props, + GObjectConstructParam *props) +{ + GObject *obj; + TpawIrcNetworkManager *self; + + /* Parent constructor chain */ + obj = G_OBJECT_CLASS (tpaw_irc_network_manager_parent_class)-> + constructor (type, n_props, props); + + self = TPAW_IRC_NETWORK_MANAGER (obj); + irc_network_manager_load_servers (self); + + return obj; +} + +static void +tpaw_irc_network_manager_finalize (GObject *object) +{ + TpawIrcNetworkManager *self = TPAW_IRC_NETWORK_MANAGER (object); + TpawIrcNetworkManagerPriv *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 (tpaw_irc_network_manager_parent_class)->finalize (object); +} + +static void +tpaw_irc_network_manager_init (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TPAW_TYPE_IRC_NETWORK_MANAGER, TpawIrcNetworkManagerPriv); + + 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 +tpaw_irc_network_manager_class_init (TpawIrcNetworkManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GParamSpec *param_spec; + + object_class->constructor = tpaw_irc_network_manager_constructor; + object_class->get_property = tpaw_irc_network_manager_get_property; + object_class->set_property = tpaw_irc_network_manager_set_property; + + g_type_class_add_private (object_class, sizeof (TpawIrcNetworkManagerPriv)); + + object_class->finalize = tpaw_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); +} + +/** + * tpaw_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 #TpawIrcNetworkManager + * + * Returns: a new #TpawIrcNetworkManager + */ +TpawIrcNetworkManager * +tpaw_irc_network_manager_new (const gchar *global_file, + const gchar *user_file) +{ + TpawIrcNetworkManager *manager; + + manager = g_object_new (TPAW_TYPE_IRC_NETWORK_MANAGER, + "global-file", global_file, + "user-file", user_file, + NULL); + + return manager; +} + +static gboolean +save_timeout (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *priv = GET_PRIV (self); + + priv->save_timer_id = 0; + irc_network_manager_file_save (self); + + return FALSE; +} + +static void +reset_save_timeout (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *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 (TpawIrcNetwork *network, + TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *priv = GET_PRIV (self); + + network->user_defined = TRUE; + + if (!priv->loading) + { + priv->have_to_save = TRUE; + reset_save_timeout (self); + } +} + +static void +add_network (TpawIrcNetworkManager *self, + TpawIrcNetwork *network, + const gchar *id) +{ + TpawIrcNetworkManagerPriv *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); +} + +/** + * tpaw_irc_network_manager_add: + * @manager: an #TpawIrcNetworkManager + * @network: the #TpawIrcNetwork to add + * + * Add an #TpawIrcNetwork to the given #TpawIrcNetworkManager. + * + */ +void +tpaw_irc_network_manager_add (TpawIrcNetworkManager *self, + TpawIrcNetwork *network) +{ + TpawIrcNetworkManagerPriv *priv; + gchar *id = NULL; + + g_return_if_fail (TPAW_IS_IRC_NETWORK_MANAGER (self)); + g_return_if_fail (TPAW_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); +} + +/** + * tpaw_irc_network_manager_remove: + * @manager: an #TpawIrcNetworkManager + * @network: the #TpawIrcNetwork to remove + * + * Remove an #TpawIrcNetwork from the given #TpawIrcNetworkManager. + * + */ +void +tpaw_irc_network_manager_remove (TpawIrcNetworkManager *self, + TpawIrcNetwork *network) +{ + TpawIrcNetworkManagerPriv *priv; + + g_return_if_fail (TPAW_IS_IRC_NETWORK_MANAGER (self)); + g_return_if_fail (TPAW_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, + TpawIrcNetwork *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, + TpawIrcNetwork *network, + GSList **list) +{ + if (!network->dropped) + return; + + *list = g_slist_prepend (*list, g_object_ref (network)); +} + +static GSList * +get_network_list (TpawIrcNetworkManager *self, + gboolean get_active) +{ + TpawIrcNetworkManagerPriv *priv; + GSList *irc_networks = NULL; + + g_return_val_if_fail (TPAW_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; +} + +/** + * tpaw_irc_network_manager_get_networks: + * @manager: an #TpawIrcNetworkManager + * + * Get the list of #TpawIrcNetwork associated with the given + * manager. + * + * Returns: a new #GSList of refed #TpawIrcNetwork + */ +GSList * +tpaw_irc_network_manager_get_networks (TpawIrcNetworkManager *self) +{ + return get_network_list (self, TRUE); +} + +/** + * tpaw_irc_network_manager_get_dropped_networks: + * @manager: an #TpawIrcNetworkManager + * + * Get the list of dropped #TpawIrcNetworks associated with the given + * manager. + * + * Returns: a new #GSList of refed dropped #TpawIrcNetworks + */ +GSList * +tpaw_irc_network_manager_get_dropped_networks (TpawIrcNetworkManager *self) +{ + return get_network_list (self, FALSE); +} + +/* + * API to save/load and parse the irc_networks file. + */ + +static void +load_global_file (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *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 (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *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 (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *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 (TpawIrcNetwork *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; + TpawIrcServer *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 = tpaw_irc_server_new (address, port_nb, have_ssl); + tpaw_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 (TpawIrcNetworkManager *self, + xmlNodePtr node, + gboolean user_defined) +{ + TpawIrcNetworkManagerPriv *priv = GET_PRIV (self); + TpawIrcNetwork *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 = tpaw_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 (TpawIrcNetworkManager *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, + TpawIrcNetwork *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 = tpaw_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)) + { + TpawIrcServer *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 (TpawIrcNetworkManager *self) +{ + TpawIrcNetworkManagerPriv *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, + TpawIrcNetwork *network, + const gchar *address) +{ + GSList *servers, *l; + gboolean found = FALSE; + + if (network->dropped) + return FALSE; + + servers = tpaw_irc_network_get_servers (network); + + for (l = servers; l != NULL && !found; l = g_slist_next (l)) + { + TpawIrcServer *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; +} + +/** + * tpaw_irc_network_manager_find_network_by_address: + * @manager: an #TpawIrcNetworkManager + * @address: the server address to look for + * + * Find the #TpawIrcNetwork which owns an #TpawIrcServer + * that has the given address. + * + * Returns: the found #TpawIrcNetwork, or %NULL if not found. + */ +TpawIrcNetwork * +tpaw_irc_network_manager_find_network_by_address ( + TpawIrcNetworkManager *self, + const gchar *address) +{ + TpawIrcNetworkManagerPriv *priv = GET_PRIV (self); + TpawIrcNetwork *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; +} + +TpawIrcNetworkManager * +tpaw_irc_network_manager_dup_default (void) +{ + static TpawIrcNetworkManager *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"), + "tp-account-widgets", 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 = tpaw_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; +} |