2 settings.c : Irssi settings
4 Copyright (C) 1999 Timo Sirainen
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #include "lib-config/iconfig.h"
30 #include "default-config.h"
34 #define SETTINGS_AUTOSAVE_TIMEOUT (1000*60*60) /* 1 hour */
36 CONFIG_REC *mainconfig;
38 static GString *last_errors;
39 static GSList *last_invalid_modules;
40 static int fe_initialized;
41 static int config_changed; /* FIXME: remove after .98 (unless needed again) */
43 static GHashTable *settings;
44 static int timeout_tag;
46 static int config_last_modifycounter;
47 static time_t config_last_mtime;
48 static long config_last_size;
49 static unsigned int config_last_checksum;
51 static SETTINGS_REC *settings_find(const char *key)
55 g_return_val_if_fail(key != NULL, NULL);
57 rec = g_hash_table_lookup(settings, key);
59 g_warning("settings_get_default_str(%s) : "
60 "unknown setting", key);
67 static SETTINGS_REC *settings_get(const char *key, SettingType type)
71 g_return_val_if_fail(key != NULL, NULL);
73 rec = settings_find(key);
75 g_warning("settings_get(%s) : not found", key);
78 if (type != -1 && rec->type != type) {
79 g_warning("settings_get(%s) : invalid type", key);
87 settings_get_str_type(const char *key, SettingType type)
92 rec = settings_get(key, type);
93 if (rec == NULL) return NULL;
95 node = iconfig_node_traverse("settings", FALSE);
96 node = node == NULL ? NULL : config_node_section(node, rec->module, -1);
98 return node == NULL ? rec->default_value.v_string :
99 config_node_get_str(node, key, rec->default_value.v_string);
102 const char *settings_get_str(const char *key)
104 return settings_get_str_type(key, -1);
107 int settings_get_int(const char *key)
112 rec = settings_get(key, SETTING_TYPE_INT);
113 if (rec == NULL) return 0;
115 node = iconfig_node_traverse("settings", FALSE);
116 node = node == NULL ? NULL : config_node_section(node, rec->module, -1);
118 return node == NULL ? rec->default_value.v_int :
119 config_node_get_int(node, key, rec->default_value.v_int);
122 int settings_get_bool(const char *key)
127 rec = settings_get(key, SETTING_TYPE_BOOLEAN);
128 if (rec == NULL) return FALSE;
130 node = iconfig_node_traverse("settings", FALSE);
131 node = node == NULL ? NULL : config_node_section(node, rec->module, -1);
133 return node == NULL ? rec->default_value.v_bool :
134 config_node_get_bool(node, key, rec->default_value.v_bool);
137 int settings_get_time(const char *key)
142 str = settings_get_str_type(key, SETTING_TYPE_TIME);
143 if (str != NULL && !parse_time_interval(str, &msecs))
144 g_warning("settings_get_time(%s) : Invalid time '%s'", key, str);
145 return str == NULL ? 0 : msecs;
148 int settings_get_level(const char *key)
152 str = settings_get_str_type(key, SETTING_TYPE_LEVEL);
153 return str == NULL ? 0 : level2bits(str);
156 int settings_get_size(const char *key)
161 str = settings_get_str_type(key, SETTING_TYPE_SIZE);
162 if (str != NULL && !parse_size(str, &bytes))
163 g_warning("settings_get_size(%s) : Invalid size '%s'", key, str);
164 return str == NULL ? 0 : bytes;
167 static void settings_add(const char *module, const char *section,
168 const char *key, SettingType type,
169 const SettingValue *default_value)
173 g_return_if_fail(key != NULL);
174 g_return_if_fail(section != NULL);
176 rec = g_hash_table_lookup(settings, key);
178 /* Already exists, make sure it's correct type */
179 if (rec->type != type) {
180 g_warning("Trying to add already existing "
181 "setting '%s' with different type.", key);
185 rec = g_new(SETTINGS_REC, 1);
186 rec->module = g_strdup(module);
187 rec->key = g_strdup(key);
188 rec->section = g_strdup(section);
191 rec->default_value = *default_value;
192 g_hash_table_insert(settings, rec->key, rec);
198 void settings_add_str_module(const char *module, const char *section,
199 const char *key, const char *def)
201 SettingValue default_value;
203 memset(&default_value, 0, sizeof(default_value));
204 default_value.v_string = g_strdup(def);
205 settings_add(module, section, key, SETTING_TYPE_STRING, &default_value);
208 void settings_add_int_module(const char *module, const char *section,
209 const char *key, int def)
211 SettingValue default_value;
213 memset(&default_value, 0, sizeof(default_value));
214 default_value.v_int = def;
215 settings_add(module, section, key, SETTING_TYPE_INT, &default_value);
218 void settings_add_bool_module(const char *module, const char *section,
219 const char *key, int def)
221 SettingValue default_value;
223 memset(&default_value, 0, sizeof(default_value));
224 default_value.v_bool = def;
225 settings_add(module, section, key, SETTING_TYPE_BOOLEAN,
229 void settings_add_time_module(const char *module, const char *section,
230 const char *key, const char *def)
232 SettingValue default_value;
234 memset(&default_value, 0, sizeof(default_value));
235 default_value.v_string = g_strdup(def);
236 settings_add(module, section, key, SETTING_TYPE_TIME, &default_value);
239 void settings_add_level_module(const char *module, const char *section,
240 const char *key, const char *def)
242 SettingValue default_value;
244 memset(&default_value, 0, sizeof(default_value));
245 default_value.v_string = g_strdup(def);
246 settings_add(module, section, key, SETTING_TYPE_LEVEL, &default_value);
249 void settings_add_size_module(const char *module, const char *section,
250 const char *key, const char *def)
252 SettingValue default_value;
254 memset(&default_value, 0, sizeof(default_value));
255 default_value.v_string = g_strdup(def);
256 settings_add(module, section, key, SETTING_TYPE_SIZE, &default_value);
259 static void settings_destroy(SETTINGS_REC *rec)
261 if (rec->type != SETTING_TYPE_INT &&
262 rec->type != SETTING_TYPE_BOOLEAN)
263 g_free(rec->default_value.v_string);
265 g_free(rec->section);
270 static void settings_unref(SETTINGS_REC *rec, int remove_hash)
272 if (--rec->refcount == 0) {
274 g_hash_table_remove(settings, rec->key);
275 settings_destroy(rec);
279 void settings_remove(const char *key)
283 g_return_if_fail(key != NULL);
285 rec = g_hash_table_lookup(settings, key);
287 settings_unref(rec, TRUE);
290 static int settings_remove_hash(const char *key, SETTINGS_REC *rec,
293 if (strcmp(rec->module, module) == 0) {
294 settings_unref(rec, FALSE);
301 void settings_remove_module(const char *module)
303 g_hash_table_foreach_remove(settings,
304 (GHRFunc) settings_remove_hash,
308 static CONFIG_NODE *settings_get_node(const char *key)
313 g_return_val_if_fail(key != NULL, NULL);
315 rec = g_hash_table_lookup(settings, key);
317 g_warning("Changing unknown setting '%s'", key);
321 node = iconfig_node_traverse("settings", TRUE);
322 return config_node_section(node, rec->module, NODE_TYPE_BLOCK);
325 void settings_set_str(const char *key, const char *value)
327 iconfig_node_set_str(settings_get_node(key), key, value);
330 void settings_set_int(const char *key, int value)
332 iconfig_node_set_int(settings_get_node(key), key, value);
335 void settings_set_bool(const char *key, int value)
337 iconfig_node_set_bool(settings_get_node(key), key, value);
340 int settings_set_time(const char *key, const char *value)
344 if (!parse_time_interval(value, &msecs))
347 iconfig_node_set_str(settings_get_node(key), key, value);
351 int settings_set_level(const char *key, const char *value)
353 iconfig_node_set_str(settings_get_node(key), key, value);
357 int settings_set_size(const char *key, const char *value)
361 if (!parse_size(value, &size))
364 iconfig_node_set_str(settings_get_node(key), key, value);
368 SettingType settings_get_type(const char *key)
372 g_return_val_if_fail(key != NULL, -1);
374 rec = g_hash_table_lookup(settings, key);
375 return rec == NULL ? -1 : rec->type;
378 /* Get the record of the setting */
379 SETTINGS_REC *settings_get_record(const char *key)
381 g_return_val_if_fail(key != NULL, NULL);
383 return g_hash_table_lookup(settings, key);
386 static void sig_init_finished(void)
388 fe_initialized = TRUE;
389 if (last_errors != NULL) {
390 signal_emit("settings errors", 1, last_errors->str);
391 g_string_free(last_errors, TRUE);
394 if (config_changed) {
395 /* some backwards compatibility changes were made to
396 config file, reload it */
397 g_warning("Some settings were automatically "
398 "updated, please /SAVE");
399 signal_emit("setup changed", 0);
403 static void settings_clean_invalid_module(const char *module)
409 node = iconfig_node_traverse("settings", FALSE);
410 if (node == NULL) return;
412 node = config_node_section(node, module, -1);
413 if (node == NULL) return;
415 for (tmp = config_node_first(node->value); tmp != NULL; tmp = next) {
416 CONFIG_NODE *subnode = tmp->data;
417 next = config_node_next(tmp);
419 set = g_hash_table_lookup(settings, subnode->key);
420 if (set == NULL || strcmp(set->module, module) != 0)
421 iconfig_node_remove(node, subnode);
425 /* remove all invalid settings from config file. works only with the
426 modules that have already called settings_check() */
427 void settings_clean_invalid(void)
429 while (last_invalid_modules != NULL) {
430 char *module = last_invalid_modules->data;
432 settings_clean_invalid_module(module);
434 last_invalid_modules =
435 g_slist_remove(last_invalid_modules, module);
440 static int backwards_compatibility(const char *module, CONFIG_NODE *node,
443 const char *new_key, *new_module;
444 CONFIG_NODE *new_node;
448 new_value = NULL; new_key = NULL; new_module = NULL;
450 /* fe-text term_type -> fe-common/core term_charset - for 0.8.10-> */
451 if (strcmp(module, "fe-text") == 0) {
452 if (strcasecmp(node->key, "term_type") == 0 ||
453 /* kludge for cvs-version where term_charset was in fe-text */
454 strcasecmp(node->key, "term_charset") == 0) {
455 new_module = "fe-common/core";
456 new_key = "term_charset";
457 new_value = !is_valid_charset(node->value) ? NULL :
458 g_strdup(node->value);
459 new_node = iconfig_node_traverse("settings", FALSE);
460 new_node = new_node == NULL ? NULL :
461 config_node_section(new_node, new_module, -1);
463 config_node_set_str(mainconfig, new_node,
466 config_node_set_str(mainconfig, parent,
469 config_changed = TRUE;
470 return new_key != NULL;
473 new_value = NULL, new_key = NULL;
474 /* FIXME: remove later - for 0.8.6 -> */
475 if (node->value == NULL || !is_numeric(node->value, '\0'))
478 old_value = atoi(node->value);
480 if (strcmp(module, "fe-text") == 0) {
481 if (strcasecmp(node->key, "lag_min_show") == 0)
482 new_value = g_strdup_printf("%dms", old_value*10);
483 else if (strcasecmp(node->key, "scrollback_hours") == 0) {
484 new_value = g_strdup_printf("%dh", old_value);
485 new_key = "scrollback_time";
487 } else if (strcmp(module, "irc/core") == 0) {
488 if (strcasecmp(node->key, "cmd_queue_speed") == 0)
489 new_value = g_strdup_printf("%dms", old_value);
490 } else if (strcmp(module, "irc/dcc") == 0) {
491 if (strcasecmp(node->key, "dcc_autoget_max_size") == 0)
492 new_value = g_strdup_printf("%dk", old_value);
493 } else if (strcmp(module, "irc/notify") == 0) {
494 if (strcasecmp(node->key, "notify_idle_time") == 0)
495 new_value = g_strdup_printf("%dmin", old_value);
496 } else if (strcmp(module, "core") == 0) {
497 if (strcasecmp(node->key, "write_buffer_mins") == 0) {
498 new_value = g_strdup_printf("%dmin", old_value);
499 new_key = "write_buffer_timeout";
500 } else if (strcasecmp(node->key, "write_buffer_kb") == 0) {
501 new_value = g_strdup_printf("%dk", old_value);
502 new_key = "write_buffer_size";
506 if (new_key != NULL || new_value != NULL) {
507 config_node_set_str(mainconfig, parent,
508 new_key != NULL ? new_key : node->key,
510 new_value : node->value);
511 if (new_key != NULL) {
513 config_node_set_str(mainconfig, parent,
516 config_changed = TRUE;
519 return new_key != NULL;
522 /* verify that all settings in config file for `module' are actually found
524 void settings_check_module(const char *module)
527 CONFIG_NODE *node, *parent;
532 g_return_if_fail(module != NULL);
534 node = iconfig_node_traverse("settings", FALSE);
535 node = node == NULL ? NULL : config_node_section(node, module, -1);
536 if (node == NULL) return;
538 errors = g_string_new(NULL);
539 g_string_sprintf(errors, "Unknown settings in configuration "
540 "file for module %s:", module);
544 tmp = config_node_first(node->value);
545 for (; tmp != NULL; tmp = next) {
547 next = config_node_next(tmp);
549 set = g_hash_table_lookup(settings, node->key);
550 if (backwards_compatibility(module, node, parent))
553 if (set == NULL || strcmp(set->module, module) != 0) {
554 g_string_sprintfa(errors, " %s", node->key);
559 if (gslist_find_icase_string(last_invalid_modules,
561 /* mark this module having invalid settings */
562 last_invalid_modules =
563 g_slist_append(last_invalid_modules,
567 signal_emit("settings errors", 1, errors->str);
569 if (last_errors == NULL)
570 last_errors = g_string_new(NULL);
572 g_string_append_c(last_errors, '\n');
573 g_string_append(last_errors, errors->str);
576 g_string_free(errors, TRUE);
579 static int settings_compare(SETTINGS_REC *v1, SETTINGS_REC *v2)
581 return strcmp(v1->section, v2->section);
584 static void settings_hash_get(const char *key, SETTINGS_REC *rec,
587 *list = g_slist_insert_sorted(*list, rec,
588 (GCompareFunc) settings_compare);
591 GSList *settings_get_sorted(void)
596 g_hash_table_foreach(settings, (GHFunc) settings_hash_get, &list);
602 /* if we get SIGTERM after this, just die instead of coming back here. */
603 signal(SIGTERM, SIG_DFL);
605 /* quit from all servers too.. */
606 signal_emit("command quit", 1, "");
612 /* Yes, this is my own stupid checksum generator, some "real" algorithm
613 would be nice but would just take more space without much real benefit */
614 static unsigned int file_checksum(const char *fname)
618 unsigned int checksum = 0;
620 f = open(fname, O_RDONLY);
621 if (f == -1) return 0;
624 while ((ret = read(f, buf, sizeof(buf))) > 0) {
626 checksum += buf[ret] << ((n++ & 3)*8);
632 static void irssi_config_save_state(const char *fname)
636 g_return_if_fail(fname != NULL);
638 if (stat(fname, &statbuf) != 0)
641 /* save modify time, file size and checksum */
642 config_last_mtime = statbuf.st_mtime;
643 config_last_size = statbuf.st_size;
644 config_last_checksum = file_checksum(fname);
647 int irssi_config_is_changed(const char *fname)
652 fname = mainconfig->fname;
654 if (stat(fname, &statbuf) != 0)
657 return config_last_mtime != statbuf.st_mtime &&
658 (config_last_size != statbuf.st_size ||
659 config_last_checksum != file_checksum(fname));
662 static CONFIG_REC *parse_configfile(const char *fname)
670 fname = get_irssi_config();
672 if (stat(fname, &statbuf) == 0)
675 /* user configuration file not found, use the default one
677 path = SYSCONFDIR"/"IRSSI_GLOBAL_CONFIG;
678 if (stat(path, &statbuf) != 0) {
679 /* no configuration file in sysconfdir ..
680 use the build-in configuration */
685 config = config_open(path, -1);
686 if (config == NULL) {
687 str = g_strdup_printf("Error opening configuration file %s: %s",
688 path, g_strerror(errno));
689 signal_emit("gui dialog", 2, "error", str);
692 config = config_open(NULL, -1);
695 if (config->fname != NULL)
696 config_parse(config);
698 config_parse_data(config, default_config, "internal");
700 config_change_file_name(config, fname, 0660);
701 irssi_config_save_state(fname);
705 static void init_configfile(void)
710 if (stat(get_irssi_dir(), &statbuf) != 0) {
711 /* ~/.irssi not found, create it. */
712 if (mkpath(get_irssi_dir(), 0700) != 0) {
713 g_error("Couldn't create %s directory", get_irssi_dir());
715 } else if (!S_ISDIR(statbuf.st_mode)) {
716 g_error("%s is not a directory.\n"
717 "You should remove it with command: rm %s",
718 get_irssi_dir(), get_irssi_dir());
721 mainconfig = parse_configfile(NULL);
722 config_last_modifycounter = mainconfig->modifycounter;
725 if (config_last_error(mainconfig) != NULL) {
726 str = g_strdup_printf("Ignored errors in configuration file:\n%s",
727 config_last_error(mainconfig));
728 signal_emit("gui dialog", 2, "error", str);
732 signal(SIGTERM, sig_term);
735 int settings_reread(const char *fname)
737 CONFIG_REC *tempconfig;
740 str = fname == NULL ? NULL : convert_home(fname);
741 tempconfig = parse_configfile(str);
742 g_free_not_null(str);
744 if (tempconfig == NULL) {
745 signal_emit("gui dialog", 2, "error", g_strerror(errno));
749 if (config_last_error(tempconfig) != NULL) {
750 str = g_strdup_printf("Errors in configuration file:\n%s",
751 config_last_error(tempconfig));
752 signal_emit("gui dialog", 2, "error", str);
755 config_close(tempconfig);
759 config_close(mainconfig);
760 mainconfig = tempconfig;
761 config_last_modifycounter = mainconfig->modifycounter;
763 signal_emit("setup changed", 0);
764 signal_emit("setup reread", 1, mainconfig->fname);
768 int settings_save(const char *fname, int autosave)
774 fname = mainconfig->fname;
776 error = config_write(mainconfig, fname, 0660) != 0;
777 irssi_config_save_state(fname);
778 config_last_modifycounter = mainconfig->modifycounter;
780 str = g_strdup_printf("Couldn't save configuration file: %s",
781 config_last_error(mainconfig));
782 signal_emit("gui dialog", 2, "error", str);
785 signal_emit("setup saved", 2, fname, GINT_TO_POINTER(autosave));
789 static int sig_autosave(void)
793 if (!settings_get_bool("settings_autosave") ||
794 config_last_modifycounter == mainconfig->modifycounter)
797 if (!irssi_config_is_changed(NULL))
798 settings_save(NULL, TRUE);
800 fname = g_strconcat(mainconfig->fname, ".autosave", NULL);
801 str = g_strdup_printf("Configuration file was modified "
802 "while irssi was running. Saving "
803 "configuration to file '%s' instead. "
804 "Use /SAVE or /RELOAD to get rid of "
805 "this message.", fname);
806 signal_emit("gui dialog", 2, "warning", str);
809 settings_save(fname, TRUE);
816 void settings_init(void)
818 settings = g_hash_table_new((GHashFunc) g_istr_hash,
819 (GCompareFunc) g_istr_equal);
822 last_invalid_modules = NULL;
823 fe_initialized = FALSE;
824 config_changed = FALSE;
826 config_last_mtime = 0;
827 config_last_modifycounter = 0;
830 settings_add_bool("misc", "settings_autosave", TRUE);
831 timeout_tag = g_timeout_add(SETTINGS_AUTOSAVE_TIMEOUT,
832 (GSourceFunc) sig_autosave, NULL);
833 signal_add("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
834 signal_add("gui exit", (SIGNAL_FUNC) sig_autosave);
837 static void settings_hash_free(const char *key, SETTINGS_REC *rec)
839 settings_destroy(rec);
842 void settings_deinit(void)
844 g_source_remove(timeout_tag);
845 signal_remove("irssi init finished", (SIGNAL_FUNC) sig_init_finished);
846 signal_remove("gui exit", (SIGNAL_FUNC) sig_autosave);
848 g_slist_foreach(last_invalid_modules, (GFunc) g_free, NULL);
849 g_slist_free(last_invalid_modules);
851 g_hash_table_foreach(settings, (GHFunc) settings_hash_free, NULL);
852 g_hash_table_destroy(settings);
854 if (mainconfig != NULL) config_close(mainconfig);