XMMS2
|
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 /** @} */