00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
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
00036
00037
00038
00039
00040
00041
00042
00043
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
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085 struct xmms_config_St {
00086 xmms_object_t obj;
00087
00088 const gchar *filename;
00089 GTree *properties;
00090
00091
00092 GMutex *mutex;
00093
00094
00095 gboolean is_parsing;
00096 GQueue *states;
00097 GQueue *sections;
00098 gchar *value_name;
00099 guint version;
00100 };
00101
00102
00103
00104
00105 struct xmms_config_property_St {
00106 xmms_object_t obj;
00107
00108
00109 const gchar *name;
00110
00111 gchar *value;
00112 };
00113
00114
00115
00116
00117
00118
00119
00120 static xmms_config_t *global_config;
00121
00122
00123
00124
00125 #define XMMS_CONFIG_VERSION 2
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
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
00166
00167
00168
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
00185
00186
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
00198
00199
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
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
00232
00233
00234 xmms_config_save ();
00235 }
00236
00237
00238
00239
00240
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
00251
00252
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
00266
00267
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
00281
00282
00283
00284
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
00303
00304
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
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
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
00366
00367
00368
00369
00370
00371
00372
00373
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
00399
00400
00401
00402
00403
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
00422
00423
00424
00425
00426
00427
00428
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
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
00491
00492
00493
00494
00495
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
00525
00526
00527
00528
00529
00530
00531
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
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
00568
00569
00570
00571
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
00592
00593
00594
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;
00603 }
00604
00605
00606
00607
00608
00609
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
00630
00631
00632
00633
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
00644
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
00673
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
00689
00690
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
00754
00755
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
00785
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
00804
00805
00806
00807
00808 if (data->prev_key) {
00809 gchar *c = current_key, *o = data->prev_key;
00810 gsize dots = 0;
00811
00812
00813
00814
00815 while (*c && *o && *c == *o) {
00816 c++;
00817 o++;
00818
00819 if (*c == '.')
00820 start = c + 1;
00821 };
00822
00823
00824
00825
00826
00827 while (*o) {
00828 if (*o == '.')
00829 dots++;
00830
00831 o++;
00832 };
00833
00834
00835
00836
00837 if (dots)
00838 data->prev_key = NULL;
00839
00840 while (dots--) {
00841
00842 data->indent[--data->indent_level] = '\0';
00843
00844 fprintf (data->fp, "%s</section>\n", data->indent);
00845 }
00846 }
00847
00848
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
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;
00875 }
00876
00877
00878
00879
00880
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
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
00914
00915
00916
00917 while (data.indent_level > 1) {
00918
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
00932
00933
00934
00935
00936
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
00944
00945
00946 g_free (prop->value);
00947 }
00948
00949
00950
00951
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
00966
00967
00968
00969
00970
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