XMMS2

src/xmms/config.c

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