/* $Id: e2_option.c 641 2007-09-05 03:14:54Z tpgww $

Copyright (C) 2003-2007 tooar <tooar@gmx.net>

This file is part of emelFM2.
emelFM2 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

emelFM2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file src/config/e2_option.c
@brief general option handling

This file contains general functions to register, unregister and set
options.
*/
/**
\page options the configuration system

ToDo - description of how options work
*/

#include "emelfm2.h"
#include <string.h>
#include "e2_fs.h"
#include "e2_option.h"
#include "e2_option_tree.h"
#include "e2_action.h"
#include "e2_task.h"
#include "e2_filetype.h"
#include "e2_plugins.h"
#include "e2_cl_option.h"
#include "e2_filelist.h"

static void _e2_option_clean1 (E2_OptionSet *set);

  /******************/
 /***** public *****/
/******************/

/**
@brief Initialize the config directory pointer
It's intended that config dirs be native, not virtual
The config directory pointer (e2_cl_options.config_dir) is set
from command line or default dir. If the dir doesn't exist,
create it if possible.
Any problem results in an appropriate error message.

@return TRUE if dir exists or is successfully created, else FALSE
*/
gboolean e2_option_set_config_dir (void)
{
	//check if the config dir is really there
	gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.config_dir);
#ifdef E2_VFS
	VPATH ddata = { local, NULL };	//only local config data
	if (!e2_fs_is_dir3 (&ddata E2_ERR_NONE()))
#else
	if (!e2_fs_is_dir3 (local E2_ERR_NONE()))
#endif
	{	//if not, try to create it
		printd (INFO, "creating config directory '%s'", e2_cl_options.config_dir);
#ifdef E2_VFS
		if (e2_fs_recurse_mkdir (&ddata, 0755 E2_ERR_NONE()))
#else
		if (e2_fs_recurse_mkdir (local, 0755 E2_ERR_NONE()))
#endif
		{
			printd (WARN, "could not create config directory '%s'",
				e2_cl_options.config_dir);
			F_FREE (local);
			return FALSE;
		}
	}
	//it's there, but can we use it ?
#ifdef E2_VFS
	else if (e2_fs_access (&ddata, R_OK | W_OK | X_OK E2_ERR_NONE()))
#else
	else if (e2_fs_access (local, R_OK | W_OK | X_OK E2_ERR_NONE()))
#endif
	{	//nope
		printd (WARN, "cannot access config directory '%s'",
			e2_cl_options.config_dir);
		F_FREE (local);
		return FALSE;
	}
	F_FREE (local);
	return TRUE;
}
/**
@brief Initialize the default trash directory and a pointer to it
It is intended that this default be native, not virtual
Uses command-line option if that was provided.
Otherwise, tries to use XDG standard, or if that is not
available, tries the e2 config dir.
Creates dir if doesn't exist, or print error message.
Sets e2_cl_options.trash_dir to the path (a trailing '/'),
or it stays NULL if dir not available.
Needs to be called after the config dir is checked/created
Expects BGL off/open
@return
*/
void e2_option_set_trash_dir (void)
{
/*	gchar *local;
	if (e2_cl_options.trash_dir == NULL || *e2_cl_options.trash_dir == '\0')
	{	//trash dir was not determined by a command line option
		if (e2_cl_options.trash_dir != NULL)
		{
			g_free (e2_cl_options.trash_dir);
			e2_cl_options.trash_dir = NULL;
		}
		//look for the defaults
		gboolean freeme = FALSE;
		gchar *dhome = (gchar *) g_getenv ("XDG_DATA_HOME");
		if (dhome == NULL || *dhome == '\0')
		{
			const gchar *home = g_get_home_dir ();
			if (home != NULL)
			{
				dhome = g_build_filename (home, ".local", "share", NULL);
				freeme = TRUE;
			}
			else if (e2_cl_options.config_dir != NULL)
				dhome = e2_cl_options.config_dir;
			else
			{
				gdk_threads_enter ();
				e2_output_print_error (
				_("Cannot identify where to put trash. Please set the XDG_DATA_HOME or HOME environment variable."),
				FALSE);
				gdk_threads_leave ();
				return;
			}
		}
		e2_cl_options.trash_dir = g_build_filename (dhome, "Trash", "files", NULL);
		if (freeme)
			g_free (dhome);
	}
	else  //config dir was specified in the startup command
	{
		//if the command line dir-path is not absolute,
		//correct this to prevent problems later on
		if (!g_path_is_absolute (e2_cl_options.trash_dir))
		{
			gchar *current_dir = g_get_current_dir ();
			gchar *tr_dir = g_build_filename (current_dir, e2_cl_options.trash_dir, NULL);
			g_free (current_dir);
			g_free (e2_cl_options.trash_dir);
			e2_cl_options.trash_dir = tr_dir;
		}
	}
*/
	//make the trash dir if it doesn't exist
	gchar *local = F_FILENAME_TO_LOCALE (e2_cl_options.trash_dir);
#ifdef E2_VFS
	VPATH ddata = { local, NULL };	//only local config data
	if (!e2_fs_is_dir3 (&ddata E2_ERR_NONE()))
#else
	if (!e2_fs_is_dir3 (local E2_ERR_NONE()))
#endif
	{
		printd (INFO, "creating trash directory '%s'", e2_cl_options.trash_dir);
#ifdef E2_VFS
		if (e2_fs_recurse_mkdir (&ddata, 0755 E2_ERR_NONE()))
#else
		if (e2_fs_recurse_mkdir (local, 0755 E2_ERR_NONE()))
#endif
		{
			gchar *message = g_strdup_printf (_("Cannot create trash directory %s"),
				e2_cl_options.trash_dir);
			gdk_threads_enter ();
			e2_output_print_error (message, TRUE);
			gdk_threads_leave ();
			g_free (e2_cl_options.trash_dir);
			e2_cl_options.trash_dir = NULL;
		}
	}
	//it's there, but can we use it ?
#ifdef E2_VFS
	else if (e2_fs_access (&ddata, R_OK | X_OK E2_ERR_NONE()))
#else
	else if (e2_fs_access (local, R_OK | X_OK E2_ERR_NONE()))
#endif
	{	//nope
		printd (WARN, "cannot access trash directory '%s'",
			e2_cl_options.trash_dir);
		F_FREE (local);
		g_free (e2_cl_options.trash_dir);
		e2_cl_options.trash_dir = NULL;
		return;
	}
	F_FREE (local);
	//cleanup, add trailer
	if (e2_cl_options.trash_dir != NULL)
		e2_cl_options.trash_dir = e2_utils_path_clean (e2_cl_options.trash_dir);
}
/**
@brief dump all current configuration data and recreate it, with window content updates as appropriate
This may be called from within a timer function, or manually
It must not be run when there is an open config dialog (closing that causes crash)
@param reload TRUE to reload the config file before recreating config data
@param recreate TRUE to recreate main window in accord with updated data

@return
*/
void e2_option_refresh (gboolean reload, gboolean recreate)
{
	app.rebuild_enabled = FALSE;	//block recursion
	//this is the one option that we want to keep ...
	gboolean advanced = e2_option_bool_get ("advanced-config");
	e2_keybinding_clean ();
	g_hash_table_destroy (app.filetypes);
	e2_plugins_unload_all (FALSE);	//do this before data are cleared
	//clear relevant current information
	e2_option_clear_data ();
	//now re-create things, in the same order as at session-start
	e2_option_default_register ();

	if (reload)
	{
		printd (INFO, "reloading config due to external change");
		e2_option_file_read ();
	}

	e2_option_tree_install_defaults ();

	e2_plugins_load_all (); // load plugins (if any)

	//re-initialise things that are not done in normal 'recreate' functions
	e2_pane_create_option_data (&app.pane1);
	e2_pane_create_option_data (&app.pane2);

	e2_toolbar_initialise (E2_BAR_PANE1);
	e2_toolbar_initialise (E2_BAR_PANE2);
	e2_toolbar_initialise (E2_BAR_TASK);
	e2_toolbar_initialise (E2_BAR_COMMAND);

	if (recreate)
		e2_window_recreate (&app.window); //this also recreates key bindings
	else
		e2_keybinding_register_all ();

	e2_filetype_add_all ();

	e2_option_bool_set ("advanced-config", advanced);

	app.rebuild_enabled = TRUE;	//unblock
}
/**
@brief disable checking for changed config file, if that is in effect

@return
*/
void e2_option_disable_config_checks (void)
{
	if (e2_option_bool_get ("auto-refresh-config") && app.timers[CONFIG_T] != 0)
	{
#ifdef E2_FAM
		if (app.monitor_type != E2_MONITOR_DEFAULT)
			e2_fs_FAM_cancel_monitor_config ();	//don't trigger a config file reload due to this write
#endif
		g_source_remove (app.timers[CONFIG_T]);
		app.timers[CONFIG_T] = 0;
	}
}
/**
@brief enable checking for changed config file, if that is in effect

@return
*/
void e2_option_enable_config_checks (void)
{
	if (e2_option_bool_get ("auto-refresh-config") && app.timers[CONFIG_T] == 0)
	{
#ifdef E2_FAM
		if (app.monitor_type != E2_MONITOR_DEFAULT)
			e2_fs_FAM_monitor_config ();
#endif
		app.timers[CONFIG_T] =
#ifdef USE_GLIB2_14
			g_timeout_add_seconds (E2_CONFIGCHECK_INTERVAL_S,
#else
			g_timeout_add (E2_CONFIGCHECK_INTERVAL,
#endif
			(GSourceFunc) e2_option_check_config_files, NULL);
	}
}

gboolean cfgdirty;

/**
@brief check for and respond to changed config file

This is the fn called by the check-config-files timer
With FAM use, update is normally detected and flagged as part
of the filepane monitoring, as all change-data arrives via the
same stream
Otherwise, here we check config file mtim.tvsec against a stored
value.
If update is indicated, recreate window after re-reading config data
@param user_data UNUSED pointer specified when the timer was initiated
@return TRUE, always, so timer continues
*/
gboolean e2_option_check_config_files (gpointer User_data)
{
#ifdef E2_FAM
	if (app.monitor_type == E2_MONITOR_DEFAULT)
	{
#endif
		struct stat stat_buf;
		gchar *filename = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL);
		gchar *local = F_FILENAME_TO_LOCALE (filename);
#ifdef E2_VFS
		VPATH ddata = { local, NULL }; //config data always local
		cfgdirty = (!e2_fs_stat (&ddata, &stat_buf E2_ERR_NONE())
#else
		cfgdirty = (!e2_fs_stat (local, &stat_buf E2_ERR_NONE())
#endif
				&& stat_buf.st_mtime != app.config_mtime);
		if (cfgdirty && app.rebuild_enabled)
			app.config_mtime = stat_buf.st_mtime;
		F_FREE (local);
		g_free (filename);
#ifdef E2_FAM
	}
#endif
	if (cfgdirty && app.rebuild_enabled)
	{
		//we're in a timer fn, so gtk's callback lock doesn't apply
		//explicit lock - or else the filelists won't rebuild FIXME why is that
		gdk_threads_enter ();
		e2_option_refresh (TRUE, TRUE);
		gdk_threads_leave ();
		e2_output_print (&app.tab, _("Configuration data re-loaded"), NULL, TRUE, NULL);
		cfgdirty = FALSE;
	}
	return TRUE;
}
/**
@brief register a config option

@param type flag for the type of set that it is
@param name name of the option, a constant string
@param group group the option belongs to, used in config dialog, a r-t string  FREEME
@param desc textual description of the option used in config dialog, a r-t _() string FREEME ?
@param tip tooltip used when displaying the config dialog, a _() string, or NULL
@param depends name of another option this one depends on, or NULL
@param flags bitflags determining how the option data is handled

@return the option data struct
*/
E2_OptionSet *e2_option_register (E2_OptionType type, gchar *name, gchar *group,
	gchar *desc, gchar *tip, gchar *depends, E2_OptionFlags flags)
{
	E2_OptionSet *set = e2_option_get_simple (name);
	if (set == NULL)
	{
#ifdef USE_GLIB2_10
		set = (E2_OptionSet *) g_slice_alloc (sizeof (E2_OptionSet));
//		set = ALLOCATE (E2_OptionSet);
#else
		set = ALLOCATE (E2_OptionSet);
#endif
		CHECKALLOCATEDWARN (set, )
		if (set != NULL)
		{
			g_ptr_array_add (options_array, set);
			g_hash_table_insert (options_hash, name, set);	//replace not valid
			//non-freeable parameters set only once
			set->type = type;
			set->flags = flags;
			set->hook_freezed = FALSE;
			set->widget = NULL;
		}
	}
	//CHECKME handle re-registration of a different type ?
	//string parameters set each registration, so clear any old versions elsewhere
	if (set != NULL)
	{
		set->name = name;
		set->group = group;
		set->desc = desc;
		set->tip = tip;
		set->depends = depends;
		g_hook_list_init (&set->hook_value_changed, sizeof (GHook));
	}
	else
	{
		//FIXME advise user and cleanup
		exit (1);
	}
	return set;
}
/**
@brief unregister option named @a name
This is for plugin options, essentially
@param name option name string

@return TRUE if the option was registered
*/
gboolean e2_option_unregister (gchar *name)
{
	E2_OptionSet *set = g_hash_table_lookup (options_hash, name);
	if (set == NULL)
		return FALSE;

	g_ptr_array_remove (options_array, set);
	g_hash_table_remove (options_hash, name);
	return TRUE;
}
/**
@brief get E2_OptionSet with warning

@param option name of the option

@return option or NULL if it cannot be found
*/
E2_OptionSet *e2_option_get (gchar *option)
{
	E2_OptionSet *set = (E2_OptionSet *) g_hash_table_lookup (options_hash, option);
	if (set == NULL)
		printd (WARN, "trying to get option '%s' which doesn't exist", option);
	return set;
}
/**
@brief get E2_OptionSet without warning

@param option name of the option

@return option or NULL if the option cannot be found
*/
E2_OptionSet *e2_option_get_simple (gchar *option)
{
	return (E2_OptionSet *) g_hash_table_lookup (options_hash, option);
}

/* UNUSED
void *e2_option_void_get (E2_OptionSet *set, gchar *option)
{
	switch (set->type)
	{
		case E2_OPTION_TYPE_BOOL:
			if (g_str_equal (option, "true")) return (void *) TRUE;
			else return (void *) FALSE;
			break;
		case E2_OPTION_TYPE_STR:
		case E2_OPTION_TYPE_FONT:
		case E2_OPTION_TYPE_ICON:
		case E2_OPTION_TYPE_SEL:
			return (void *) option;
		default:
			return NULL;
			break;
	}
	return NULL;
}
*/

E2_OptionSet *e2_option_attach_value_changed (gchar *option, GtkWidget *widget,
	gpointer func, gpointer data)
{
	E2_OptionSet *set = e2_option_get (option);
	if (set != NULL)
		e2_option_attach_value_changed_simple (set, widget, func, data);
	return set;
}

void e2_option_attach_value_changed_simple (E2_OptionSet *set, GtkWidget *widget,
	gpointer func, gpointer data)
{
	GHook *hook = e2_hook_register (&set->hook_value_changed, func, data);
	E2_Duo *duo = MALLOCATE (E2_Duo);	//too small for slice
	duo->a = hook;
	duo->b = &set->hook_value_changed;
	g_object_set_data_full (G_OBJECT (widget), "e2-option-attach-value-changed",
		duo, (GDestroyNotify) e2_hook_unattach_cb);
}

void e2_option_connect (GtkWidget *controller, gboolean active)
{
	g_object_set_data (G_OBJECT (controller), "e2-controller-blocked",
		GINT_TO_POINTER (!active));
}
/**
@brief clear all relevant option data prior to a refresh of all options
@return
*/
void e2_option_clear_data (void)
{
/*FIXME if we only clear relevant data, when we re-walk the array later,
 it has some items with crap content
 more-or-less same code as _e2_option_clean1()
	guint i;
	gpointer *walker;
	for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++)
	{
		E2_OptionSet *set = *walker;
		E2_OptionFlags flags = set->flags;
		//several strings have to be re-installed when re-registering a set,
		//so get rid of existing ones
 		if (flags & E2_OPTION_FLAG_FREENAME)
			g_free (set->name);
		if (flags & E2_OPTION_FLAG_FREEGROUP)
			g_free (set->group);
		if (flags & E2_OPTION_FLAG_FREEDESC)
			g_free (set->desc);
		if (flags & E2_OPTION_FLAG_FREETIP)
			g_free (set->tip);
		if (flags & E2_OPTION_FLAG_FREEDEPENDS)
			g_free (set->depends);

		E2_OptionType type = set->type;
		if (type &
			( E2_OPTION_TYPE_INT | E2_OPTION_TYPE_STR
			| E2_OPTION_TYPE_FONT | E2_OPTION_TYPE_COLOR ))
				g_free (set->sval);
		else if (type & E2_OPTION_TYPE_SEL)
			g_strfreev (set->ex.sel.def);
		else if (type & E2_OPTION_TYPE_TREE)
		{
			if (set->ex.tree.def != NULL)
			{
				g_strfreev (set->ex.tree.def);
				set->ex.tree.def = NULL;
			}
			e2_list_free_with_data (&set->ex.tree.columns);
			set->ex.tree.columns_num = 0;	//insurance, not really needed
			g_object_unref (G_OBJECT (set->ex.tree.model));
		}
		//set-related hooks are un-registered when config dialog is destroyed
		//anyway, we may want to run the hook after this change
	}
*/
	g_ptr_array_free (options_array, TRUE);
	options_array = g_ptr_array_new ();

	g_hash_table_destroy (options_hash);
	options_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
		(GDestroyNotify) _e2_option_clean1);
}

/*void e2_option_tree_stores_clear (void)
{
	guint i;
	gpointer *walker;
	E2_OptionSet *set;
	for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++)
	{
		set = *walker;
		if (set->type == E2_OPTION_TYPE_TREE)
			gtk_tree_store_clear (set->ex.tree.model);
	}
} */
/* this was used only at session end, so don't bother to cleanup
void destroy_config ()
{
	g_ptr_array_free (options_array, FALSE);
	g_hash_table_destroy (options_hash);
	g_hash_table_destroy (options_queue);
} */

gboolean write_error = FALSE;
static void _e2_option_file_write_unknowns (gpointer key, gpointer value, E2_FILE *f)
{
	if (e2_fs_file_write (f, "%s\n", (gchar *)value) == 0)
		write_error = TRUE;
}
/**
@brief write current configuration to native file @a utfpath
The current configuration will be written to the default config file 'config'.
Expects BGL to be on/closed
@param utfpath absolute path of file to write (utf-8 string), or NULL to use default

@return
*/
void e2_option_file_write (const gchar *utfpath)
{
#ifdef E2_VFS
	VPATH ddata;
	VPATH tdata;
#endif
	gboolean freepath = (utfpath == NULL);
	gchar *usepath = (freepath) ?
		g_build_filename (e2_cl_options.config_dir, default_config_file, NULL):
		(gchar *)utfpath;
	gchar *local = F_FILENAME_TO_LOCALE (usepath);
	gchar *tempname = e2_utils_get_tempname (local);

	E2_FILE *f = e2_fs_open_writestream (tempname E2_ERR_NONE());
	if (f != NULL)
	{
		printd (DEBUG, "write config file: %s", usepath);
		if (e2_fs_file_write (f,
			//the 1st line is language-independent, for version verification
			"# "PROGNAME" (v "VERSION RELEASE")\n\n" ) == 0)
				goto error_handler;
		if (e2_fs_file_write (f,
		 //FIXME = remind translators not to remove # from line starts
		  _("# This is the %s configuration data file.\n"
			"# It will be overwritten each time the program is run!\n\n"
			"# If you're inclined to edit the file between program sessions, note this:\n"
			"# for tree options, you have to use \\| to escape | and you have to use \\< to escape <,\n"
			"#  if that is the first non-space character on a line.\n\n"),
			PROGNAME
		) == 0)
				goto error_handler;

		guint i;
		gpointer *walker;
		E2_OptionSet *set;
		for (i = 0, walker = options_array->pdata; i < options_array->len; i++, walker++)
		{
			set = *walker;
			switch (set->type)
			{
				case E2_OPTION_TYPE_INT:
					if (e2_fs_file_write (f, "# %s\n%s=%d\n",
						set->desc, set->name, set->ival) == 0)
							goto error_handler;
					break;
				case E2_OPTION_TYPE_BOOL:
//					if (e2_fs_file_write (f, "# %s\n%s=%s\n",
//						set->desc, set->name, set->sval) == 0)
//							goto error_handler;
//					break;
				case E2_OPTION_TYPE_STR:
				case E2_OPTION_TYPE_FONT:
				case E2_OPTION_TYPE_COLOR:
//					if (e2_fs_file_write (f, "# %s\n%s=%s\n",
//						set->desc, set->name, set->sval) == 0)
//							goto error_handler;
//					break;
				case E2_OPTION_TYPE_SEL:
					if (e2_fs_file_write (f, "# %s\n%s=%s\n",
						set->desc, set->name, set->sval) == 0)
							goto error_handler;
					break;
				case E2_OPTION_TYPE_TREE:
					if (e2_fs_file_write (f, "# %s\n%s=<\n",
						set->name, set->name) == 0)
							goto error_handler;
					GtkTreeIter iter;
					if (gtk_tree_model_get_iter_first (set->ex.tree.model, &iter))
						e2_option_tree_write_to_file (f, set, &iter, 0);	//error handling ?
					if (e2_fs_file_write (f, ">\n") == 0)
						goto error_handler;
					break;
				default:
					printd (WARN, "don't know how to write option '%s' to config file",
						set->name);
					break;
			}
		}
		//now the unknown options, if any
		//need an error flag
		write_error = FALSE;
		g_hash_table_foreach (options_queue,
			(GHFunc) _e2_option_file_write_unknowns, f);
		if (write_error)
			goto error_handler;

		e2_fs_close_stream (f);
#ifdef E2_VFS
		tdata.localpath = tempname;
		tdata.spacedata = NULL;
		ddata.localpath = local;
		ddata.spacedata = NULL;
#endif
		gdk_threads_leave ();	//downstream errors invoke local mutex locking
#ifdef E2_VFS
		e2_task_backend_rename (&tdata, &ddata);
		e2_fs_chmod (&ddata, 0600 E2_ERR_NONE());
#else
		e2_task_backend_rename (tempname, local);
		e2_fs_chmod (local, 0600 E2_ERR_NONE());
#endif
		gdk_threads_enter ();
		goto cleanup;
	}
error_handler:
	if (f != NULL)
	{
		e2_fs_close_stream (f);
		gdk_threads_leave ();	//downstream errors invoke local mutex locking
#ifdef E2_VFS
		e2_task_backend_delete (&tdata);
#else
		e2_task_backend_delete (tempname);
#endif
		gdk_threads_enter ();
	}
	gchar *msg = g_strdup_printf (_("Cannot write config file %s - %s"),
		usepath, g_strerror (errno));	//ok for native-only config file
	e2_output_print_error (msg, TRUE);
	sleep (1);
cleanup:
	if (freepath)
		g_free (usepath);
	F_FREE (local);
	g_free (tempname);
}
/**
@brief Set value of "single-valued" option named @a option

This is called from the config file line parser.
If the set data exists already, its value is set according to @a str.
If the set data does not exist already, the set is logged in the
'unknown' queue

@param option name of option to be set
@param str string with value to be stored for option named @a option

@return TRUE if @a option was set successfully, even if in the unknown options queue
*/
gboolean e2_option_set_from_string (gchar *option, gchar *str)
{
	E2_OptionSet *set = e2_option_get_simple (option);
	if (set == NULL)
	{
		//reinstate the form of the config line
		gchar *saveme = g_strconcat (option, "=", str, NULL);
		//and log the freeable strings
		e2_option_unknown_record (g_strdup (option), saveme);
		return FALSE;
	}
	return e2_option_set_value_from_string (set, str);
}
/**
@brief Setup the default value for @a set in accord with parameter @a str

This sets the appropriate set->sval, depending on the type of option
There is no check for existence of @a set

@param set pointer to data struct for the option being processed
@param str string form of value to be stored for @a set

@return TRUE if the value was valid and stored
*/
gboolean e2_option_set_value_from_string (E2_OptionSet *set, gchar *str)
{
	gboolean retval = TRUE;
	switch (set->type)
	{
		case E2_OPTION_TYPE_BOOL:
			set->hook_freezed = TRUE;
			e2_option_bool_set_direct (set, g_str_equal (str, "true") ? TRUE : FALSE);
			set->hook_freezed = FALSE;
			break;
		case E2_OPTION_TYPE_INT:
		{
			//FIXME: check for conversion success
			gint i = (gint) g_ascii_strtoull (str, NULL, 10);
			set->hook_freezed = TRUE;
			e2_option_int_set_direct (set, i);
			set->hook_freezed = FALSE;
			break;
		}
		case E2_OPTION_TYPE_STR:
		case E2_OPTION_TYPE_FONT:
			e2_option_str_set_direct (set, str);
			break;
		case E2_OPTION_TYPE_COLOR:
			return e2_option_color_set_str_direct (set, str);
			break;
		case E2_OPTION_TYPE_SEL:
		{
			const gchar *val;
			gint i = 0;
			set->ival = -1;
			while ((val = set->ex.sel.def[i]) != NULL)
			{
				if (g_str_equal (str, val))
				{
					set->ival = i;
					set->sval = (gchar *) val;
					break;
				}
				i++;
			}
			if (set->ival == -1)
			{
				printd (WARN, "bad value for sel option '%s'", set->name);
				retval = FALSE;
				set->ival = 0;
				set->sval = (gchar *) set->ex.sel.def[0];
			}
//			if (!set->hook_freezed)
//				e2_hook_list_run (&set->hook_value_changed, GINT_TO_POINTER (set->ival));
		}
			break;
		default:
			break;
	}
	return retval;
}
/**
@brief Read config options from @a f and parse them

Critical characters in @a f are assumed to be ascii
This function is more-or-less replicated in the config plugin
@param f NULL-terminated array of strings in config file format

@return
*/
void e2_option_read_array (gchar *f[])
{
	gint i = -1;	//array index
	gchar *line; //pointer to the current line
	gchar **split;

	while ((line = f[++i]) != NULL)
	{
		g_strchomp (line);
		//ignore empty lines and comments
		if (*line == '\0') continue;
		if (line[0] == '#') continue;

		split = g_strsplit (line, "=", 2);
		if (split[1] != NULL)
		{
			if (!g_str_equal (split[1], "<")) //not a tree set
			{
			 	if (!e2_option_set_from_string (split[0], split[1]))
					printd (WARN, "could not set option '%s'", split[0]);
			}
			else //a tree set
				if (!e2_option_tree_set_from_array (split[0], f, &i, NULL))
					printd (WARN, "could not set tree option '%s'", split[0]);
		}
		g_strfreev (split);
	}
}
/**
@brief read config file into memory at @a contents
It's intended that a config file be native, not virtual.
If the file is read successfully, log the post-read config dir timestamp
@param contents pointer to place to store the loaded file content

@return TRUE if the file was read
*/
static gboolean _e2_option_config_file_read (gpointer *contents)
{
	//find absolute path to config file
	gchar *filepath = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL);
	gchar *local = F_FILENAME_TO_LOCALE (filepath);
#ifdef E2_VFS
	VPATH ddata = { local, NULL };	//only local config data
	if (e2_fs_access (&ddata, R_OK E2_ERR_NONE()))
#else
	if (e2_fs_access (local, R_OK E2_ERR_NONE()))
#endif
	{	//try for the old-style name
		g_free (filepath);
		F_FREE (local);
		filepath = g_build_filename (e2_cl_options.config_dir, "config", NULL);
		local = F_FILENAME_TO_LOCALE (filepath);
#ifdef E2_VFS
		ddata.localpath = local;
		if (e2_fs_access (&ddata, R_OK E2_ERR_NONE()))
#else
		if (e2_fs_access (local, R_OK E2_ERR_NONE()))
#endif
		{
			g_free (filepath);
			F_FREE (local);
			return FALSE;
		}
	}

	gboolean retval;
	//get file
#ifdef E2_VFS
	if (e2_fs_get_file_contents (&ddata, contents, NULL, TRUE E2_ERR_NONE()))
#else
	if (e2_fs_get_file_contents (local, contents, NULL, TRUE E2_ERR_NONE()))
#endif
	{
//#ifndef E2_FAM do this anyway ...
		//log the current timestamp
		struct stat stat_buf;
#ifdef E2_VFS
		e2_fs_stat (&ddata, &stat_buf E2_ERR_NONE());
#else
		e2_fs_stat (local, &stat_buf E2_ERR_NONE());
#endif
		app.config_mtime = stat_buf.st_mtime;
//#endif
		printd (DEBUG, "config file '%s' read", filepath);
		retval = TRUE;
	}
	else
	{
		printd (WARN, "could not read config file '%s'", filepath);
		retval = FALSE;
	}
	g_free (filepath);
	F_FREE (local);
	return retval;
}
/**
@brief read config file

First, the configuration is read, then its content is parsed with
e2_option_read_array(). If tree options have no configuration,
they are initialized with a default one.
(All other options have been intialized when they were registered.)
@return TRUE if the config file was read
*/
gboolean e2_option_file_read (void)
{
	gpointer contents;
	if (_e2_option_config_file_read (&contents))
	{
		gboolean retval;
		//everything goes into an array
		gchar **split = g_strsplit ((gchar *)contents, "\n", -1);
		if (split[0] != NULL)
		{	//there is something to work with, so parse it
			//get the config file version (always ascii) from 1st line of file
			//(done here to avoid problems when line(s) not from the file are processed
			if (strstr (split[0], "(v") != NULL )
			{
				gchar *sp = g_strrstr (split[0], ")");
				if (sp != NULL)
					*sp = '\0';
				sp = g_strstrip (strstr (split[0], "(v") + 2);
				g_strlcpy (app.cfgfile_version, sp, sizeof(app.cfgfile_version));
			}
			e2_option_read_array (split);
			retval = TRUE;
		}
		else
			retval = FALSE;

		g_strfreev (split);
		g_free (contents);	//need free() if file buffer allocated by malloc()
		return retval;
	}
	else
		return FALSE;
}
/**
@brief cleanup data for an item in the options hash

@set pointer to data struct to be processed

@return
*/
static void _e2_option_clean1 (E2_OptionSet *set)
{
//	printd (DEBUG, "destroying set %s", set->name);
	E2_OptionFlags flags = set->flags;
	if (flags & E2_OPTION_FLAG_FREENAME)
		g_free (set->name);
	if (flags & E2_OPTION_FLAG_FREEGROUP)
		g_free (set->group);
	if (flags & E2_OPTION_FLAG_FREEDESC)
		g_free (set->desc);
	if (flags & E2_OPTION_FLAG_FREETIP)
		g_free (set->tip);
	if (flags & E2_OPTION_FLAG_FREEDEPENDS)
		g_free (set->depends);

	E2_OptionType type = set->type;
	if (type &
		( E2_OPTION_TYPE_INT | E2_OPTION_TYPE_STR
		| E2_OPTION_TYPE_FONT | E2_OPTION_TYPE_COLOR ))
			g_free (set->sval);
	else if (type & E2_OPTION_TYPE_SEL)
		g_strfreev (set->ex.sel.def);
	else if (type & E2_OPTION_TYPE_TREE)
	{
		if (set->ex.tree.def != NULL)
			g_strfreev (set->ex.tree.def);
		e2_list_free_with_data (&set->ex.tree.columns);
		g_object_unref (G_OBJECT (set->ex.tree.model));
	}
	//CHECKME set-related hooks are un-registered when config dialog is destroyed
	//so this may be redundant, or at least, needs to happen after that destruction
	//or we may want to run the hooks after this change
	if (set->hook_value_changed.is_setup)
		g_hook_list_clear (&set->hook_value_changed);
#ifdef USE_GLIB2_10
	g_slice_free1 (sizeof (E2_OptionSet), set);
//	DEALLOCATE (E2_OptionSet, set);
#else
	DEALLOCATE (E2_OptionSet, set);
#endif
}
/**
@brief initialize option system

internal data structures of the option system will be initialized.
this includes creation of a hash table for fast option lookup.
this function is called on startup, by the main function

@return
*/
void e2_option_init (void)
{
/* "core" options are registered before a config file (if any) is read.
For those that are non-tree, a default value is registered during the
registration process. For core tree options, a pointer to the default
initialisation function is stored, to reduce needless parsing.
When a config file is read, option values from that are installed,
replacing defaults or installing trees. Finally, missing tree values
are installed.

"Transient" (con-core) options are not registered at session-start, so
any such that's found in a config file is simply listed as is. Any later
registration of a transient option should be followed by a check of
that list, and installation of the config file values if found there,
and then, for a tree option, installation of default values if nothing
was queued. The list has strings in the same format as was in the config
file. Hence tree-options are multi-lined. Installation requires parsing.

Writing a config file finishes with the then-current contents of the
unknown-list. The listed strings do not need to be reformatted before
writing */
	const gchar *lang = g_getenv ("LC_MESSAGES");
	if (lang == NULL)
		lang = g_getenv ("LANG");
	if (lang == NULL)
		lang = g_getenv ("LC_ALL");
	if (lang == NULL)
		lang = "C";
	default_config_file = g_strconcat ("config-", lang, NULL);  //DO NOT TRANSLATE
	options_array = g_ptr_array_new ();
	options_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
		(GDestroyNotify) _e2_option_clean1);
	options_queue = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
		g_free);
	//before config loaded, setup all option types, default values, tips etc
	//tree option default values are not set yet
	e2_option_default_register ();
#ifndef E2_FAM
	e2_fs_FAM_config_stamp ();	//set baseline time, for config refreshing
#endif
	app.rebuild_enabled = TRUE;	//set signal which allows config refreshes

#ifdef E2_IMAGECACHE
	e2_cache_icons_init ();
#endif
}
/**
@brief setup array of i18n config dialog labels

Some of these are the same as action labels
Array size defined in e2_config_dialog.h

@return
*/
void e2_option_setup_labels (void)
{
	config_labels[0]=_("aliases");
	config_labels[1]=_("bookmarks");
	config_labels[2]=_("colors");
	config_labels[3]=_("columns");
	config_labels[4]=_("command bar");
	config_labels[5]=_("command line");
	config_labels[6]=_("commands");
	config_labels[7]=_("confirmation");
	config_labels[8]=_("context menu");
	config_labels[9]=_("custom menus");
	config_labels[10]=_("delays");
	config_labels[11]=_("dialogs");
	config_labels[12]=_("directory lines");
	config_labels[13]=_("extensions");
	config_labels[14]=_("file actions");
	config_labels[15]=_("filetypes");
	config_labels[16]=_("fonts");
	config_labels[17]=_("general");
	config_labels[18]=_("history");
	config_labels[19]=_("icons");
	config_labels[20]=_("interface");
	config_labels[21]=_("item types");
	config_labels[22]=_("key bindings");
	config_labels[23]=_("make directory");
	config_labels[24]=_("menus");
	config_labels[25]=_("miscellaneous");
	config_labels[26]=_("options");
	config_labels[27]=_("output");
	config_labels[28]=_("pane 1");
	config_labels[29]=_("pane1 bar");
	config_labels[30]=_("pane 2");
	config_labels[31]=_("pane2 bar");
	config_labels[32]=_("panes");
	config_labels[33]=_("plugins");
	config_labels[34]=_("position");
	config_labels[35]=_("search");
	config_labels[36]=_("startup");
	config_labels[37]=_("style");
	config_labels[38]=_("tab completion");
	config_labels[39] =_("task bar");
	config_labels[40]=_("view");
//maybe more space here, see array definition in header file
}
