XMMS2
src/xmms/config.c
Go to the documentation of this file.
00001 /*  XMMS2 - X Music Multiplexer System
00002  *  Copyright (C) 2003-2011 XMMS2 Team
00003  *
00004  *  PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Lesser General Public
00008  *  License as published by the Free Software Foundation; either
00009  *  version 2.1 of the License, or (at your option) any later version.
00010  *
00011  *  This library is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014  *  Lesser General Public License for more details.
00015  *
00016  */
00017 
00018 #include <glib.h>
00019 
00020 #include <stdlib.h>
00021 #include <unistd.h>
00022 #include <stdio.h>
00023 #include <string.h>
00024 #include <fcntl.h>
00025 #include <sys/types.h>
00026 #include <sys/stat.h>
00027 
00028 #include "xmmsc/xmmsc_idnumbers.h"
00029 #include "xmmspriv/xmms_config.h"
00030 #include "xmmspriv/xmms_utils.h"
00031 #include "xmms/xmms_ipc.h"
00032 #include "xmms/xmms_log.h"
00033 
00034 /*
00035 #include "xmms/util.h"
00036 #include "xmms/xmms.h"
00037 #include "xmms/object.h"
00038 #include "xmms/signal_xmms.h"
00039 #include "xmms/plugin.h"
00040 #include "xmms/ipc.h"
00041 */
00042 
00043 /** @internal */
00044 typedef enum {
00045     XMMS_CONFIG_STATE_INVALID,
00046     XMMS_CONFIG_STATE_START,
00047     XMMS_CONFIG_STATE_SECTION,
00048     XMMS_CONFIG_STATE_PROPERTY
00049 } xmms_configparser_state_t;
00050 
00051 typedef struct dump_tree_data_St {
00052     FILE *fp;
00053     xmms_configparser_state_t state;
00054 
00055     gchar indent[128];
00056     guint indent_level;
00057 
00058     gchar *prev_key;
00059 } dump_tree_data_t;
00060 
00061 static GTree *xmms_config_client_list_values (xmms_config_t *conf, xmms_error_t *err);
00062 static xmms_config_property_t *xmms_config_property_new (const gchar *name);
00063 static gchar *xmms_config_client_get_value (xmms_config_t *conf, const gchar *key, xmms_error_t *err);
00064 static gchar *xmms_config_client_register_value (xmms_config_t *config, const gchar *name, const gchar *def_value, xmms_error_t *error);
00065 static gint compare_key (gconstpointer a, gconstpointer b, gpointer user_data);
00066 static void xmms_config_client_set_value (xmms_config_t *conf, const gchar *key, const gchar *value, xmms_error_t *err);
00067 
00068 #include "config_ipc.c"
00069 
00070 /**
00071  * @defgroup Config Config
00072  * @brief Controls configuration for the server.
00073  *
00074  * The configuration is saved to, and loaded from an XML file. It's split into
00075  * plugin, client and core parts. This documents the configuration for parts
00076  * inside the server. For plugin config see each server object's documentation.
00077  *
00078  * @ingroup XMMSServer
00079  * @{
00080  */
00081 
00082 /**
00083  * Global parsed config
00084  */
00085 struct xmms_config_St {
00086     xmms_object_t obj;
00087 
00088     const gchar *filename;
00089     GTree *properties;
00090 
00091     /* Lock on globals are great! */
00092     GMutex *mutex;
00093 
00094     /* parsing */
00095     gboolean is_parsing;
00096     GQueue *states;
00097     GQueue *sections;
00098     gchar *value_name;
00099     guint version;
00100 };
00101 
00102 /**
00103  * A config property in the configuration file
00104  */
00105 struct xmms_config_property_St {
00106     xmms_object_t obj;
00107 
00108     /** Name of the config directive */
00109     const gchar *name;
00110     /** The data */
00111     gchar *value;
00112 };
00113 
00114 /**
00115  * Global config
00116  * Since there can only be one configuration per server
00117  * we can have the convenience of having it as a global variable.
00118  */
00119 
00120 static xmms_config_t *global_config;
00121 
00122 /**
00123  * Config file version
00124  */
00125 #define XMMS_CONFIG_VERSION 2
00126 
00127 /**
00128  * @}
00129  * @addtogroup Config
00130  * @{
00131  */
00132 
00133 /**
00134  * Config functions
00135  */
00136 
00137 /**
00138  * Lookup config key and return its associated value as a string.
00139  * This is a convenient function to make it easier to get a configuration value
00140  * rather than having to call #xmms_config_property_get_string separately.
00141  *
00142  * @param conf Global config
00143  * @param key Configuration property to lookup
00144  * @param err if error occurs this will be filled in
00145  *
00146  * @return A string with the value. If the value is an int it will return NULL
00147  */
00148 const gchar *
00149 xmms_config_property_lookup_get_string (xmms_config_t *conf, const gchar *key,
00150                                         xmms_error_t *err)
00151 {
00152     xmms_config_property_t *prop;
00153 
00154     prop = xmms_config_lookup (key);
00155     if (!prop) {
00156         xmms_error_set (err, XMMS_ERROR_NOENT,
00157                         "Trying to get non-existent property");
00158         return NULL;
00159     }
00160 
00161     return xmms_config_property_get_string (prop);
00162 }
00163 
00164 /**
00165  * Look up a config key from the global config
00166  * @param path A configuration path. Could be core.myconfig or
00167  * effect.foo.myconfig
00168  * @return An #xmms_config_property_t
00169  */
00170 xmms_config_property_t *
00171 xmms_config_lookup (const gchar *path)
00172 {
00173     xmms_config_property_t *prop;
00174     g_return_val_if_fail (global_config, NULL);
00175 
00176     g_mutex_lock (global_config->mutex);
00177     prop = g_tree_lookup (global_config->properties, path);
00178     g_mutex_unlock (global_config->mutex);
00179 
00180     return prop;
00181 }
00182 
00183 /**
00184  * Get the name of a config property.
00185  * @param prop The config property
00186  * @return Name of config property
00187  */
00188 const gchar *
00189 xmms_config_property_get_name (const xmms_config_property_t *prop)
00190 {
00191     g_return_val_if_fail (prop, NULL);
00192 
00193     return prop->name;
00194 }
00195 
00196 /**
00197  * Set the data of the config property to a new value
00198  * @param prop The config property
00199  * @param data The value to set
00200  */
00201 void
00202 xmms_config_property_set_data (xmms_config_property_t *prop, const gchar *data)
00203 {
00204     GTree *dict;
00205 
00206     g_return_if_fail (prop);
00207     g_return_if_fail (data);
00208 
00209     /* check whether the value changed at all */
00210     if (prop->value && !strcmp (prop->value, data))
00211         return;
00212 
00213     g_free (prop->value);
00214     prop->value = g_strdup (data);
00215     xmms_object_emit (XMMS_OBJECT (prop),
00216                       XMMS_IPC_SIGNAL_CONFIGVALUE_CHANGED,
00217                       (gpointer) data);
00218 
00219     dict = g_tree_new_full (compare_key, NULL,
00220                             NULL, (GDestroyNotify) xmmsv_unref);
00221     g_tree_insert (dict, (gchar *) prop->name,
00222                    xmmsv_new_string (prop->value));
00223 
00224     xmms_object_emit_f (XMMS_OBJECT (global_config),
00225                         XMMS_IPC_SIGNAL_CONFIGVALUE_CHANGED,
00226                         XMMSV_TYPE_DICT,
00227                         dict);
00228 
00229     g_tree_destroy (dict);
00230 
00231     /* save the database to disk, so we don't lose any data
00232      * if the daemon crashes
00233      */
00234     xmms_config_save ();
00235 }
00236 
00237 /**
00238  * Return the value of a config property as a string
00239  * @param prop The config property
00240  * @return value as string
00241  */
00242 const gchar *
00243 xmms_config_property_get_string (const xmms_config_property_t *prop)
00244 {
00245     g_return_val_if_fail (prop, NULL);
00246     return prop->value;
00247 }
00248 
00249 /**
00250  * Return the value of a config property as an int
00251  * @param prop The config property
00252  * @return value as int
00253  */
00254 gint
00255 xmms_config_property_get_int (const xmms_config_property_t *prop)
00256 {
00257     g_return_val_if_fail (prop, 0);
00258     if (prop->value)
00259         return atoi (prop->value);
00260 
00261     return 0;
00262 }
00263 
00264 /**
00265  * Return the value of a config property as a float
00266  * @param prop The config property
00267  * @return value as float
00268  */
00269 gfloat
00270 xmms_config_property_get_float (const xmms_config_property_t *prop)
00271 {
00272     g_return_val_if_fail (prop, 0.0);
00273     if (prop->value)
00274         return atof (prop->value);
00275 
00276     return 0.0;
00277 }
00278 
00279 /**
00280  * Set a callback function for a config property.
00281  * This will be called each time the property's value changes.
00282  * @param prop The config property
00283  * @param cb The callback to set
00284  * @param userdata Data to pass on to the callback
00285  */
00286 void
00287 xmms_config_property_callback_set (xmms_config_property_t *prop,
00288                                    xmms_object_handler_t cb,
00289                                    gpointer userdata)
00290 {
00291     g_return_if_fail (prop);
00292 
00293     if (!cb)
00294         return;
00295 
00296     xmms_object_connect (XMMS_OBJECT (prop),
00297                          XMMS_IPC_SIGNAL_CONFIGVALUE_CHANGED,
00298                          (xmms_object_handler_t) cb, userdata);
00299 }
00300 
00301 /**
00302  * Remove a callback from a config property
00303  * @param prop The config property
00304  * @param cb The callback to remove
00305  */
00306 void
00307 xmms_config_property_callback_remove (xmms_config_property_t *prop,
00308                                       xmms_object_handler_t cb,
00309                                       gpointer userdata)
00310 {
00311     g_return_if_fail (prop);
00312 
00313     if (!cb)
00314         return;
00315 
00316     xmms_object_disconnect (XMMS_OBJECT (prop),
00317                             XMMS_IPC_SIGNAL_CONFIGVALUE_CHANGED, cb, userdata);
00318 }
00319 
00320 /**
00321  * Register a new config property. This should be called from the init code
00322  * as XMMS2 won't allow set/get on properties that haven't been registered.
00323  *
00324  * @param path The path in the config tree.
00325  * @param default_value If the value was not found in the configfile, what
00326  * should we use?
00327  * @param cb A callback function that will be called if the value is changed by
00328  * the client. Can be set to NULL.
00329  * @param userdata Data to pass to the callback function.
00330  * @return A newly allocated #xmms_config_property_t for the registered
00331  * property.
00332  */
00333 xmms_config_property_t *
00334 xmms_config_property_register (const gchar *path,
00335                                const gchar *default_value,
00336                                xmms_object_handler_t cb,
00337                                gpointer userdata)
00338 {
00339 
00340     xmms_config_property_t *prop;
00341 
00342     g_mutex_lock (global_config->mutex);
00343 
00344     prop = g_tree_lookup (global_config->properties, path);
00345     if (!prop) {
00346         prop = xmms_config_property_new (g_strdup (path));
00347 
00348         xmms_config_property_set_data (prop, (gchar *) default_value);
00349         g_tree_replace (global_config->properties,
00350                        (gchar *) prop->name, prop);
00351     }
00352 
00353     if (cb) {
00354         xmms_config_property_callback_set (prop, cb, userdata);
00355     }
00356 
00357     g_mutex_unlock (global_config->mutex);
00358 
00359     return prop;
00360 }
00361 
00362 /**
00363  * @}
00364  *
00365  * @if internal
00366  * @addtogroup Config
00367  * @{
00368  */
00369 
00370 /**
00371  * @internal Get the current parser state for the given element name
00372  * @param[in] name Element name to match to a state
00373  * @return Parser state matching element name
00374  */
00375 static xmms_configparser_state_t
00376 get_current_state (const gchar *name)
00377 {
00378     static struct {
00379         const gchar *name;
00380         xmms_configparser_state_t state;
00381     } *ptr, lookup[] = {
00382         {"xmms", XMMS_CONFIG_STATE_START},
00383         {"section", XMMS_CONFIG_STATE_SECTION},
00384         {"property", XMMS_CONFIG_STATE_PROPERTY},
00385         {NULL, XMMS_CONFIG_STATE_INVALID}
00386     };
00387 
00388     for (ptr = lookup; ptr && ptr->name; ptr++) {
00389         if (!strcmp (ptr->name, name)) {
00390             return ptr->state;
00391         }
00392     }
00393 
00394     return XMMS_CONFIG_STATE_INVALID;
00395 }
00396 
00397 /**
00398  * @internal Look for the value associated with an attribute name, given lists
00399  * of attribute names and attribute values.
00400  * @param[in] names List of attribute names
00401  * @param[in] values List of attribute values matching up to names
00402  * @param[in] needle Attribute name to look for
00403  * @return The attribute value, or NULL if not found
00404  */
00405 static const gchar *
00406 lookup_attribute (const gchar **names, const gchar **values,
00407                   const gchar *needle)
00408 {
00409     const gchar **n, **v;
00410 
00411     for (n = names, v = values; *n && *v; n++, v++) {
00412         if (!strcmp ((gchar *) *n, needle)) {
00413             return *v;
00414         }
00415     }
00416 
00417     return NULL;
00418 }
00419 
00420 /**
00421  * @internal Parse start tag in config file. This function is called whenever
00422  * a start tag is encountered by the GMarkupParser from #xmms_config_init
00423  * @param ctx The parser context.
00424  * @param name The name of the element encountered
00425  * @param attr_name List of attribute names in tag
00426  * @param attr_data List of attribute data in tag
00427  * @param userdata User data - In this case, the global config
00428  * @param error GError to be filled in if an error is encountered
00429  */
00430 static void
00431 xmms_config_parse_start (GMarkupParseContext *ctx,
00432                          const gchar *name,
00433                          const gchar **attr_name,
00434                          const gchar **attr_data,
00435                          gpointer userdata,
00436                          GError **error)
00437 {
00438     xmms_config_t *config = userdata;
00439     xmms_configparser_state_t state;
00440     const gchar *attr;
00441 
00442     state = get_current_state (name);
00443     g_queue_push_head (config->states, GINT_TO_POINTER (state));
00444 
00445     switch (state) {
00446         case XMMS_CONFIG_STATE_INVALID:
00447             *error = g_error_new (G_MARKUP_ERROR,
00448                                   G_MARKUP_ERROR_UNKNOWN_ELEMENT,
00449                                   "Unknown element '%s'", name);
00450             return;
00451         case XMMS_CONFIG_STATE_START:
00452             /* check config version here */
00453             attr = lookup_attribute (attr_name, attr_data, "version");
00454             if (attr) {
00455                 if (strcmp (attr, "0.02") == 0) {
00456                     config->version = 2;
00457                 } else {
00458                     config->version = atoi (attr);
00459                 }
00460             }
00461             return;
00462         default:
00463             break;
00464     }
00465 
00466     attr = lookup_attribute (attr_name, attr_data, "name");
00467     if (!attr) {
00468         *error = g_error_new (G_MARKUP_ERROR,
00469                               G_MARKUP_ERROR_INVALID_CONTENT,
00470                               "Attribute 'name' missing");
00471         return;
00472     }
00473 
00474     switch (state) {
00475         case XMMS_CONFIG_STATE_SECTION:
00476             g_queue_push_head (config->sections, g_strdup (attr));
00477 
00478             break;
00479         case XMMS_CONFIG_STATE_PROPERTY:
00480             g_free (config->value_name);
00481             config->value_name = g_strdup (attr);
00482 
00483             break;
00484         default:
00485             break;
00486     }
00487 }
00488 
00489 /**
00490  * @internal Parse end tag in config file. This function is called whenever
00491  * an end tag is encountered by the GMarkupParser from #xmms_config_init
00492  * @param ctx The parser context.
00493  * @param name The name of the element encountered
00494  * @param userdata User data - In this case, the global config
00495  * @param error GError to be filled in if an error is encountered
00496  */
00497 static void
00498 xmms_config_parse_end (GMarkupParseContext *ctx,
00499                        const gchar *name,
00500                        gpointer userdata,
00501                        GError **error)
00502 {
00503     xmms_config_t *config = userdata;
00504     xmms_configparser_state_t state;
00505 
00506     state = GPOINTER_TO_INT (g_queue_pop_head (config->states));
00507 
00508     switch (state) {
00509         case XMMS_CONFIG_STATE_SECTION:
00510             g_free (g_queue_pop_head (config->sections));
00511 
00512             break;
00513         case XMMS_CONFIG_STATE_PROPERTY:
00514             g_free (config->value_name);
00515             config->value_name = NULL;
00516 
00517             break;
00518         default:
00519             break;
00520     }
00521 }
00522 
00523 /**
00524  * @internal Parse text in config file. This function is called whenever
00525  * text (anything between start and end tags) is encountered by the
00526  * GMarkupParser from #xmms_config_init
00527  * @param ctx The parser context.
00528  * @param text The text
00529  * @param text_len Length of the text
00530  * @param userdata User data - In this case, the global config
00531  * @param error GError to be filled in if an error is encountered
00532  */
00533 static void
00534 xmms_config_parse_text (GMarkupParseContext *ctx,
00535                         const gchar *text,
00536                         gsize text_len,
00537                         gpointer userdata,
00538                         GError **error)
00539 {
00540     xmms_config_t *config = userdata;
00541     xmms_configparser_state_t state;
00542     xmms_config_property_t *prop;
00543     GList *l;
00544     gchar key[256] = "";
00545     gsize siz = sizeof (key);
00546 
00547     state = GPOINTER_TO_INT (g_queue_peek_head (config->states));
00548 
00549     if (state != XMMS_CONFIG_STATE_PROPERTY)
00550         return;
00551 
00552     /* assemble the config key, based on the traversed sections */
00553     for (l = config->sections->tail; l; l = l->prev) {
00554         g_strlcat (key, l->data, siz);
00555         g_strlcat (key, ".", siz);
00556     }
00557 
00558     g_strlcat (key, config->value_name, siz);
00559 
00560     prop = xmms_config_property_new (g_strdup (key));
00561     xmms_config_property_set_data (prop, (gchar *) text);
00562 
00563     g_tree_replace (config->properties, (gchar *) prop->name, prop);
00564 }
00565 
00566 /**
00567  * @internal Set a key to a new value
00568  * @param conf The config
00569  * @param key The key to look for
00570  * @param value The value to set the key to
00571  * @param err To be filled in if an error occurs
00572  */
00573 static void
00574 xmms_config_client_set_value (xmms_config_t *conf,
00575                               const gchar *key, const gchar *value,
00576                               xmms_error_t *err)
00577 {
00578     xmms_config_property_t *prop;
00579 
00580     prop = xmms_config_lookup (key);
00581     if (prop) {
00582         xmms_config_property_set_data (prop, value);
00583     } else {
00584         xmms_error_set (err, XMMS_ERROR_NOENT,
00585                         "Trying to set non-existent config property");
00586     }
00587 
00588 }
00589 
00590 /**
00591  * @internal Convert global config properties dict to a normal dict
00592  * @param key The dict key
00593  * @param property An xmms_config_property_t
00594  * @param udata The dict to store configvals
00595  */
00596 static gboolean
00597 xmms_config_foreach_dict (gpointer key, xmms_config_property_t *prop,
00598                           GTree *dict)
00599 {
00600     g_tree_insert (dict, g_strdup (key), xmmsv_new_string (prop->value));
00601 
00602     return FALSE; /* keep going */
00603 }
00604 
00605 /**
00606  * @internal List all keys and values in the config.
00607  * @param conf The config
00608  * @param err To be filled in if an error occurs
00609  * @return a dict with config properties and values
00610  */
00611 static GTree *
00612 xmms_config_client_list_values (xmms_config_t *conf, xmms_error_t *err)
00613 {
00614     GTree *ret;
00615 
00616     ret = g_tree_new_full (compare_key, NULL,
00617                            g_free, (GDestroyNotify)xmmsv_unref);
00618 
00619     g_mutex_lock (conf->mutex);
00620     g_tree_foreach (conf->properties,
00621                     (GTraverseFunc) xmms_config_foreach_dict,
00622                     (gpointer) ret);
00623     g_mutex_unlock (conf->mutex);
00624 
00625     return ret;
00626 }
00627 
00628 /**
00629  * @internal Look for a key in the config and return its value as a string
00630  * @param conf The config
00631  * @param key The key to look for
00632  * @param err To be filled in if an error occurs
00633  * @return The value of the key, or NULL if not found
00634  */
00635 static gchar *
00636 xmms_config_client_get_value (xmms_config_t *conf, const gchar *key,
00637                               xmms_error_t *err)
00638 {
00639     return g_strdup (xmms_config_property_lookup_get_string (conf, key, err));
00640 }
00641 
00642 /**
00643  * @internal Destroy a config object
00644  * @param object The object to destroy
00645  */
00646 static void
00647 xmms_config_destroy (xmms_object_t *object)
00648 {
00649     xmms_config_t *config = (xmms_config_t *)object;
00650 
00651     g_mutex_free (config->mutex);
00652 
00653     g_tree_destroy (config->properties);
00654 
00655     xmms_config_unregister_ipc_commands ();
00656 }
00657 
00658 static gint
00659 compare_key (gconstpointer a, gconstpointer b, gpointer user_data)
00660 {
00661     return strcmp ((gchar *) a, (gchar *) b);
00662 }
00663 
00664 static GTree *
00665 create_tree (void)
00666 {
00667     return g_tree_new_full (compare_key, NULL, g_free,
00668                             (GDestroyNotify) __int_xmms_object_unref);
00669 }
00670 
00671 /**
00672  * @internal Clear data in a config object
00673  * @param config The config object to clear
00674  */
00675 static void
00676 clear_config (xmms_config_t *config)
00677 {
00678     g_tree_destroy (config->properties);
00679     config->properties = create_tree ();
00680 
00681     config->version = XMMS_CONFIG_VERSION;
00682 
00683     g_free (config->value_name);
00684     config->value_name = NULL;
00685 }
00686 
00687 /**
00688  * @internal Initialize and parse the config file. Resets to default config
00689  * on parse error.
00690  * @param[in] filename The absolute path to a config file as a string.
00691  */
00692 void
00693 xmms_config_init (const gchar *filename)
00694 {
00695     GMarkupParser pars;
00696     GMarkupParseContext *ctx;
00697     xmms_config_t *config;
00698     int ret, fd = -1;
00699     gboolean parserr = FALSE, eof = FALSE;
00700 
00701     config = xmms_object_new (xmms_config_t, xmms_config_destroy);
00702     config->mutex = g_mutex_new ();
00703     config->filename = filename;
00704 
00705     config->properties = create_tree ();
00706 
00707     config->version = 0;
00708     global_config = config;
00709 
00710     xmms_config_register_ipc_commands (XMMS_OBJECT (config));
00711 
00712     memset (&pars, 0, sizeof (pars));
00713 
00714     pars.start_element = xmms_config_parse_start;
00715     pars.end_element = xmms_config_parse_end;
00716     pars.text = xmms_config_parse_text;
00717 
00718     if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
00719         fd = open (filename, O_RDONLY);
00720     }
00721 
00722     if (fd > -1) {
00723         config->is_parsing = TRUE;
00724         config->states = g_queue_new ();
00725         config->sections = g_queue_new ();
00726         ctx = g_markup_parse_context_new (&pars, 0, config, NULL);
00727 
00728         while ((!eof) && (!parserr)) {
00729             GError *error = NULL;
00730             gchar buffer[1024];
00731 
00732             ret = read (fd, buffer, 1024);
00733             if (ret < 1) {
00734                 g_markup_parse_context_end_parse (ctx, &error);
00735                 if (error) {
00736                     xmms_log_error ("Cannot parse config file: %s",
00737                                     error->message);
00738                     g_error_free (error);
00739                     error = NULL;
00740                     parserr = TRUE;
00741                 }
00742                 eof = TRUE;
00743             }
00744 
00745             g_markup_parse_context_parse (ctx, buffer, ret, &error);
00746             if (error) {
00747                 xmms_log_error ("Cannot parse config file: %s",
00748                                 error->message);
00749                 g_error_free (error);
00750                 error = NULL;
00751                 parserr = TRUE;
00752             }
00753             /* check config file version, assumes that g_markup_context_parse
00754              * above managed to parse the <xmms> element during the first
00755              * iteration of this loop */
00756             if (XMMS_CONFIG_VERSION > config->version) {
00757                 clear_config (config);
00758                 break;
00759             }
00760         }
00761 
00762         close (fd);
00763         g_markup_parse_context_free (ctx);
00764 
00765         while (!g_queue_is_empty (config->sections)) {
00766             g_free (g_queue_pop_head (config->sections));
00767         }
00768 
00769         g_queue_free (config->states);
00770         g_queue_free (config->sections);
00771 
00772         config->is_parsing = FALSE;
00773     } else {
00774         xmms_log_info ("No configfile specified, using default values.");
00775     }
00776 
00777     if (parserr) {
00778         xmms_log_info ("The config file could not be parsed, reverting to default configuration..");
00779         clear_config (config);
00780     }
00781 }
00782 
00783 /**
00784  * @internal Shut down the config layer - free memory from the global
00785  * configuration.
00786  */
00787 void
00788 xmms_config_shutdown ()
00789 {
00790     xmms_object_unref (global_config);
00791 
00792 }
00793 
00794 static gboolean
00795 dump_tree (gchar *current_key, xmms_config_property_t *prop,
00796            dump_tree_data_t *data)
00797 {
00798     gchar *prop_name, section[256];
00799     gchar *dot = NULL, *current_last_dot, *start = current_key;
00800 
00801     prop_name = strrchr (current_key, '.');
00802 
00803     /* check whether we need to open a new section.
00804      * this is always the case if data->prev_key == NULL.
00805      * but if the sections of the last key and the current key differ,
00806      * we also need to do that.
00807      */
00808     if (data->prev_key) {
00809         gchar *c = current_key, *o = data->prev_key;
00810         gsize dots = 0;
00811 
00812         /* position c and o at the respective ends of the common
00813          * prefixes of the previous and the current key.
00814          */
00815         while (*c && *o && *c == *o) {
00816             c++;
00817             o++;
00818 
00819             if (*c == '.')
00820                 start = c + 1;
00821         };
00822 
00823         /* from this position on, count the number of dots in the
00824          * previous key (= number of dots that are present in the
00825          * previous key, but no the current key).
00826          */
00827         while (*o) {
00828             if (*o == '.')
00829                 dots++;
00830 
00831             o++;
00832         };
00833 
00834         /* we'll close the previous key's sections now, so we don't
00835          * have to worry about it next time this function is called.
00836          */
00837         if (dots)
00838             data->prev_key = NULL;
00839 
00840         while (dots--) {
00841             /* decrease indent level */
00842             data->indent[--data->indent_level] = '\0';
00843 
00844             fprintf (data->fp, "%s</section>\n", data->indent);
00845         }
00846     }
00847 
00848     /* open section tags */
00849     dot = strchr (start, '.');
00850     current_last_dot = start - 1;
00851 
00852     while (dot) {
00853         strncpy (section, current_last_dot + 1, dot - current_last_dot + 1);
00854         section[dot - current_last_dot - 1] = 0;
00855 
00856         fprintf (data->fp, "%s<section name=\"%s\">\n",
00857                  data->indent, section);
00858 
00859         /* increase indent level */
00860         g_assert (data->indent_level < 127);
00861         data->indent[data->indent_level] = '\t';
00862         data->indent[++data->indent_level] = '\0';
00863 
00864         current_last_dot = dot;
00865         dot = strchr (dot + 1, '.');
00866     };
00867 
00868     data->prev_key = current_key;
00869 
00870     fprintf (data->fp, "%s<property name=\"%s\">%s</property>\n",
00871              data->indent, prop_name + 1,
00872              xmms_config_property_get_string (prop));
00873 
00874     return FALSE; /* keep going */
00875 }
00876 
00877 /**
00878  * @internal Save the global configuration to disk.
00879  * @param file Absolute path to configfile. This will be overwritten.
00880  * @return TRUE on success.
00881  */
00882 gboolean
00883 xmms_config_save (void)
00884 {
00885     FILE *fp = NULL;
00886     dump_tree_data_t data;
00887 
00888     g_return_val_if_fail (global_config, FALSE);
00889 
00890     /* don't try to save config while it's being read */
00891     if (global_config->is_parsing)
00892         return FALSE;
00893 
00894     if (!(fp = fopen (global_config->filename, "w"))) {
00895         xmms_log_error ("Couldn't open %s for writing.",
00896                         global_config->filename);
00897         return FALSE;
00898     }
00899 
00900     fprintf (fp, "<?xml version=\"1.0\"?>\n<xmms version=\"%i\">\n",
00901              XMMS_CONFIG_VERSION);
00902 
00903     data.fp = fp;
00904     data.state = XMMS_CONFIG_STATE_START;
00905     data.prev_key = NULL;
00906 
00907     strcpy (data.indent, "\t");
00908     data.indent_level = 1;
00909 
00910     g_tree_foreach (global_config->properties,
00911                     (GTraverseFunc) dump_tree, &data);
00912 
00913     /* close the remaining section tags. the final indent level
00914      * was started with the opening xmms tag, so the loop condition
00915      * is '> 1' here rather than '> 0'.
00916      */
00917     while (data.indent_level > 1) {
00918         /* decrease indent level */
00919         data.indent[--data.indent_level] = '\0';
00920 
00921         fprintf (fp, "%s</section>\n", data.indent);
00922     }
00923 
00924     fprintf (fp, "</xmms>\n");
00925     fclose (fp);
00926 
00927     return TRUE;
00928 }
00929 
00930 /*
00931  * Value manipulation
00932  */
00933 
00934 /**
00935  * @internal Destroy a config value
00936  * @param object The object to destroy
00937  */
00938 static void
00939 xmms_config_property_destroy (xmms_object_t *object)
00940 {
00941     xmms_config_property_t *prop = (xmms_config_property_t *) object;
00942 
00943     /* don't free val->name here, it's taken care of in
00944      * xmms_config_destroy()
00945      */
00946     g_free (prop->value);
00947 }
00948 
00949 /**
00950  * @internal Create a new config value
00951  * @param name The name of the new config value
00952  */
00953 static xmms_config_property_t *
00954 xmms_config_property_new (const gchar *name)
00955 {
00956     xmms_config_property_t *ret;
00957 
00958     ret = xmms_object_new (xmms_config_property_t, xmms_config_property_destroy);
00959     ret->name = name;
00960 
00961     return ret;
00962 }
00963 
00964 /**
00965  * @internal Register a client config value
00966  * @param config The config
00967  * @param name The name of the config value
00968  * @param def_value The default value to use
00969  * @param error To be filled in if an error occurs
00970  * @return The full path to the config value registered
00971  */
00972 static gchar *
00973 xmms_config_client_register_value (xmms_config_t *config,
00974                                    const gchar *name,
00975                                    const gchar *def_value,
00976                                    xmms_error_t *error)
00977 {
00978     gchar *tmp;
00979     tmp = g_strdup_printf ("clients.%s", name);
00980     xmms_config_property_register (tmp, def_value, NULL, NULL);
00981     return tmp;
00982 }
00983 
00984 /** @} */