/*
 * libopensync - A synchronization framework
 * Copyright (C) 2004-2005  Armin Bauer <armin.bauer@opensync.org>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 * 
 */
 
#include "opensync.h"
#include "opensync_internals.h"

/**
 * @defgroup PrivateAPI Private APIs
 * @brief Available private APIs
 * 
 */

/**
 * @defgroup OSyncPrivate OpenSync Private API
 * @ingroup PrivateAPI
 * @brief The private API of opensync
 * 
 * This gives you an insight in the private API of opensync.
 * 
 */

/**
 * @defgroup OSyncEnvPrivate OpenSync Environment Internals
 * @ingroup OSyncPrivate
 * @brief The internals of the opensync environment
 * 
 */
/*@{*/

/* Get the value of a an OSyncEnv option
 *
 * Search order:
 * - options set using osync_env_set_option()
 * - OSYNC_* environment variables
 */
static const char *osync_env_query_option(OSyncEnv *env, const char *name)
{
	const char *value;
	value = g_hash_table_lookup(env->options, name);
	if (value)
		return value;

	gchar *env_name = g_strdup_printf("OSYNC_%s", name);
	value = getenv(env_name);
	g_free(env_name);

	if (value)
		return value;

	return NULL;
}

static osync_bool osync_env_query_option_bool(OSyncEnv *env, const char *name)
{
	const char *get_value;
	if (!(get_value = osync_env_query_option(env, name)))
		return FALSE;
	if (!strcmp(get_value, "TRUE"))
		return TRUE;
	return FALSE;
}

/** Export the list of loaded plugins through the OSYNC_LOADED_PLUGINS environment variable
 *
 */
void osync_env_export_loaded_modules(OSyncEnv *env)
{
	int num_modules = g_list_length(env->modules);

	/* build an array for g_strjoinv() */
	gchar **path_array = g_malloc0(sizeof(gchar*)*(num_modules + 1));
	int i;
	for (i = 0; i < num_modules; i++) {
		GModule *module = g_list_nth_data(env->modules, i);
		const gchar *path = g_module_name(module);
		/*XXX: casting to non-const, here. Ugly.
		 *
		 * We know the elements pointed by path_array won't
		 * be touched. But isn't g_strjoinv() supposed to get a
		 * 'const gchar **' instead of a 'gchar **'?
		 */
		path_array[i] = (gchar*)path;
	}

	/* Build a ':'-separated list */
	gchar *list_str = g_strjoinv(":", path_array);
	setenv("OSYNC_FORMAT_LIST", list_str, 1);
	g_free(list_str);
}

static void export_option_to_env(gpointer key, gpointer data, gpointer user_data)
{
	const char *name = (const char*)key;
	const char *value = (const char*)data;
	gchar *env_name = g_strdup_printf("OSYNC_%s", name);
	setenv(env_name, value, 1);
	g_free(env_name);
}

/** Export all options set through osync_env_set_option() to environment variables
 *
 */
void osync_env_export_all_options(OSyncEnv *env)
{
	g_hash_table_foreach(env->options, export_option_to_env, NULL);
}

static void free_hash(char *key, char *value, void *data)
{
	g_free(key);
	g_free(value);
}

/*! @brief Returns the next free number for a group in the environments configdir
 * 
 * Returns the next free number for a group in the environments configdir
 * 
 * @param env The osync environment
 * @returns The next free number
 * 
 */
long long int _osync_env_create_group_id(OSyncEnv *env)
{
	char *filename = NULL;
	long long int i = 0;
	do {
		i++;
		if (filename)
			g_free(filename);
		filename = g_strdup_printf("%s/group%lli", env->groupsdir, i);
	} while (g_file_test(filename, G_FILE_TEST_EXISTS));
	g_free(filename);
	return i;
}

/*@}*/

/**
 * @defgroup PublicAPI Public APIs
 * @brief Available public APIs
 * 
 */

/**
 * @defgroup OSyncPublic OpenSync Public API
 * @ingroup PublicAPI
 * @brief The public API of opensync
 * 
 * This gives you an insight in the public API of opensync.
 * 
 */

/**
 * @defgroup OSyncEnvAPI OpenSync Environment
 * @ingroup OSyncPublic
 * @brief The public API of the opensync environment
 * 
 */
/*@{*/


/*! @brief This will create a new opensync environment
 * 
 * The environment will hold all information about plugins, groups etc
 * 
 * @returns A pointer to a newly allocated environment. NULL on error.
 * 
 */
OSyncEnv *osync_env_new(void)
{
	OSyncEnv *env = g_malloc0(sizeof(OSyncEnv));
	env->is_initialized = FALSE;
	env->options = g_hash_table_new(g_str_hash, g_str_equal);
	
	//Set some defaults
	osync_env_set_option(env, "LOAD_GROUPS", "TRUE");
	osync_env_set_option(env, "LOAD_FORMATS", "TRUE");
	osync_env_set_option(env, "LOAD_PLUGINS", "TRUE");
	
	return env;
}

/*! @brief Frees a osync environment
 * 
 * Frees a osync environment and all resources.
 * 
 * @param env Pointer to the environment to free
 * 
 */
void osync_env_free(OSyncEnv *env)
{
	g_assert(env);
	g_hash_table_foreach(env->options, (GHFunc)free_hash, NULL);
	g_hash_table_destroy(env->options);
	g_free(env);
}

/*! @brief Sets a options on the environment
 * 
 * @param env Pointer to the environment
 * @param name Name of the option to set
 * @param value Value to set
 * 
 */
void osync_env_set_option(OSyncEnv *env, const char *name, const char *value)
{
	if (value)
		g_hash_table_insert(env->options, g_strdup(name), g_strdup(value));
	else
		g_hash_table_remove(env->options, name);
}

/*! @brief Initializes the environment (loads plugins)
 * 
 * This will load all available plugins from disk. You can configure the location to look
 * for plugins before calling this function
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param error Pointer to a error struct to return a error
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_env_initialize(OSyncEnv *env, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_env_initialize(%p, %p)", env, error);
	g_assert(env);
	
	if (env->is_initialized) {
		osync_error_set(error, OSYNC_ERROR_INITIALIZATION, "Cannot initialize the same environment twice");
		osync_trace(TRACE_EXIT_ERROR, "osync_env_initialize: %s", osync_error_print(error));
		return FALSE;
	}

	//Load the normal plugins
	if (osync_env_query_option_bool(env, "LOAD_PLUGINS")) {
		if (!osync_env_load_plugins(env, osync_env_query_option(env, "PLUGINS_DIRECTORY"), error)) {
			osync_trace(TRACE_EXIT_ERROR, "osync_env_initialize: %s", osync_error_print(error));
			return FALSE;
		}
	}

	//Load the format plugins
	if (osync_env_query_option_bool(env, "LOAD_FORMATS")) {
		if (!osync_env_load_formats(env, osync_env_query_option(env, "FORMATS_DIRECTORY"), error)) {
			osync_trace(TRACE_EXIT_ERROR, "osync_env_initialize: %s", osync_error_print(error));
			return FALSE;
		}
	}

	//Load groups
	if (osync_env_query_option_bool(env, "LOAD_GROUPS")) {
		if (!osync_env_load_groups(env, osync_env_query_option(env, "GROUPS_DIRECTORY"), error)) {
			osync_trace(TRACE_EXIT_ERROR, "osync_env_initialize: %s", osync_error_print(error));
			return FALSE;
		}
	}

	env->is_initialized = TRUE;
	osync_trace(TRACE_EXIT, "osync_env_initialize");
	return TRUE;
}

/*! @brief Finalizes the environment
 * 
 * This will finalize the environment and unload and free all loaded plugins
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param error Pointer to a error struct to return a error
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_env_finalize(OSyncEnv *env, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_env_finalize(%p, %p)", env, error);
	g_assert(env);
	
	if (!env->is_initialized) {
		osync_error_set(error, OSYNC_ERROR_INITIALIZATION, "Environment has to be initialized before");
		return FALSE;
	}
	
	while (osync_env_nth_group(env, 0))
		osync_group_free(osync_env_nth_group(env, 0));
	
	GList *plugins = g_list_copy(env->plugins);
	GList *p;
	for (p = plugins; p; p = p->next) {
		OSyncPlugin *plugin = p->data;
		osync_plugin_free(plugin);
	}
	g_list_free(plugins);
	
	//Unload all loaded modules
	GList *modules = g_list_copy(env->modules);
	for (p = modules; p; p = p->next) {
		GModule *module = p->data;
		osync_module_unload(env, module);
	}
	g_list_free(modules);

	osync_trace(TRACE_EXIT, "osync_env_finalize");
	return TRUE;
}

/*! @brief Loads all format and conversion plugins
 * 
 * This command will load all plugins for the conversion system.
 * If you dont change the path before it will load the plugins
 * from the default location
 * 
 * @param env The format environment
 * @param path The path to load from or NULL if to load from default path
 * @param error The location to return a error to
 * @returns TRUE if successfull, FALSE otherwise
 * 
 */
osync_bool osync_env_load_formats(OSyncEnv *env, const char *path, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, env, path, error);
	osync_bool must_exist = TRUE;
	
	if (!path) {
		path = OPENSYNC_FORMATSDIR;
		must_exist = FALSE;
	}
	
	if (!osync_module_load_dir(env, path, must_exist, error)) {
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}
	
	osync_trace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

/*! @brief Loads the sync modules from a given directory
 * 
 * Loads all sync modules from a directory into a osync environment
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param path The path where to look for plugins
 * @param error Pointer to a error struct to return a error
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_env_load_plugins(OSyncEnv *env, const char *path, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, env, path, error);
	osync_bool must_exist = TRUE;
	
	if (!path) {
		path = OPENSYNC_PLUGINDIR;
		must_exist = FALSE;
	}
	
	if (!osync_module_load_dir(env, path, must_exist, error)) {
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}
	
	osync_trace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

/*! @brief Finds the plugin with the given name
 * 
 * Finds the plugin with the given name
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param name The name to search for
 * @returns The plugin or NULL if not found
 * 
 */
OSyncPlugin *osync_env_find_plugin(OSyncEnv *env, const char *name)
{
	g_assert(env);
	OSyncPlugin *plugin;
	int i;
	for (i = 0; i < osync_env_num_plugins(env); i++) {
		plugin = osync_env_nth_plugin(env, i);
		if (g_ascii_strcasecmp(plugin->info.name, name) == 0) {
			return plugin;
		}
	}
	return NULL;
}

/*! @brief Returns the number of loaded plugins
 * 
 * Returns the number of loaded plugins. 0 if used before initialization
 * 
 * @param env Pointer to a OSyncEnv environment
 * @returns Number of plugins
 * 
 */
int osync_env_num_plugins(OSyncEnv *env)
{
	return g_list_length(env->plugins);
}

/*! @brief Returns pointer to nth plugin
 * 
 * Returns pointer to nth plugin
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param nth Which plugin to return
 * @returns Pointer to plugin
 * 
 */
OSyncPlugin *osync_env_nth_plugin(OSyncEnv *env, int nth)
{
	return (OSyncPlugin *)g_list_nth_data(env->plugins, nth);
}

/*! @brief Checks if a plugin is available and usable
 * 
 * @param env The environment in which the plugin should be loaded
 * @param pluginname The name of the plugin to check for
 * @param error If the return was FALSE, will contain the information why the plugin is not available
 * @returns TRUE if plugin was found and is usable, FALSE otherwise
 * 
 */
osync_bool osync_env_plugin_is_usable(OSyncEnv *env, const char *pluginname, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, env, pluginname, error);
	
	OSyncPlugin *plugin = osync_env_find_plugin(env, pluginname);
	if (!plugin) {
		osync_error_set(error, OSYNC_ERROR_PLUGIN_NOT_FOUND, "Unable to find plugin \"%s\". This can be caused by unresolved symbols", pluginname);
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}
	
	if (plugin->info.functions.is_available) {
		osync_bool ret = plugin->info.functions.is_available(error);
		osync_trace(ret ? TRACE_EXIT : TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return ret;
	}
	
	osync_trace(TRACE_EXIT, "%s: TRUE: No is_available function", __func__);
	return TRUE;
}

/*! @brief Loads the plugins from a given directory
 * 
 * Loads all plugins from a directory into a osync environment.
 * The directory must exist prior to opening.
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param path The path where to look for groups
 * @param error Pointer to a error struct to return a error
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_env_load_groups(OSyncEnv *env, const char *p, OSyncError **error)
{
	GDir *dir;
	GError *gerror = NULL;
	char *filename = NULL;
	char *real_path = NULL;
	char *path = g_strdup(p);
	
	if (!path) {
		OSyncUserInfo *user = osync_user_new(error);
		if (!user)
			return FALSE;
		path = g_strdup(osync_user_get_confdir(user));
		
		if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
			if (mkdir(path, 0700) == -1) {
				osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to create group directory at %s: %s", path, strerror(errno));
				g_free(path);
				return FALSE;
			}
			char *enginepath = g_strdup_printf("%s/engines", path);
			if (mkdir(enginepath, 0700) == -1) {
				osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to create engine group directory at %s: %s", enginepath, strerror(errno));
				g_free(path);
				g_free(enginepath);
				return FALSE;
			}
			g_free(enginepath);
			osync_debug("OSGRP", 3, "Created groups configdir %s\n", path);
		}
		osync_user_free(user);
	}
	
	if (!g_path_is_absolute(path)) {
		real_path = g_strdup_printf("%s/%s", g_get_current_dir(), path);
	} else {
		real_path = g_strdup(path);
	}
	
	if (!g_file_test(real_path, G_FILE_TEST_IS_DIR)) {
		osync_debug("OSGRP", 0, "%s exists, but is no dir", real_path);
		osync_error_set(error, OSYNC_ERROR_INITIALIZATION, "%s exists, but is no dir", real_path);
		g_free(real_path);
		g_free(path);
		return FALSE;
	}
	
	dir = g_dir_open(real_path, 0, &gerror);
	if (!dir) {
		osync_debug("OSGRP", 0, "Unable to open main configdir %s: %s", real_path, gerror->message);
		osync_error_set(error, OSYNC_ERROR_IO_ERROR, "Unable to open main configdir %s: %s", real_path, gerror->message);
		g_error_free (gerror);
		g_free(real_path);
		g_free(path);
		return FALSE;
	}
  
	const gchar *de = NULL;
	while ((de = g_dir_read_name(dir))) {
		filename = g_strdup_printf ("%s/%s", real_path, de);
		
		if (!g_file_test(filename, G_FILE_TEST_IS_DIR) || g_file_test(filename, G_FILE_TEST_IS_SYMLINK) || !g_pattern_match_simple("group*", de)) {
			g_free(filename);
			continue;
		}
		
		/* Try to open the confdir*/
		OSyncError *error = NULL;
		if (!osync_group_load(env, filename, &error)) {
			osync_debug("OSGRP", 0, "Unable to load group from %s: %s", filename, error->message);
			osync_error_free(&error);
		}
		
		g_free(filename);
	}
	g_free(real_path);
	g_dir_close(dir);
	
	env->groupsdir = path;
	return TRUE;
}

/*! @brief Finds the group with the given name
 * 
 * Finds the group with the given name
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param name Name of the group to search
 * @returns Pointer to group. NULL if not found
 * 
 */
OSyncGroup *osync_env_find_group(OSyncEnv *env, const char *name)
{
	OSyncGroup *group;
	int i;
	for (i = 0; i < osync_env_num_groups(env); i++) {
		group = osync_env_nth_group(env, i);
		if (g_ascii_strcasecmp(group->name, name) == 0) {
			return group;
		}
	}
	osync_debug("OSPLG", 0, "Couldnt find the group with the name %s", name);
	return NULL;
}

/*! @brief Adds the given group to the environment
 * 
 * Adds the given group to the environment
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param group The group to add
 * 
 */
void osync_env_append_group(OSyncEnv *env, OSyncGroup *group)
{
	env->groups = g_list_append(env->groups, group);
}

/*! @brief Removes the given group from the enviroment
 * 
 * Removes the given group from the environment
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param group The group to add
 * 
 */
void osync_env_remove_group(OSyncEnv *env, OSyncGroup *group)
{
	env->groups = g_list_remove(env->groups, group);
}

/*! @brief Counts the groups in the environment
 * 
 * Returns the number of groups
 * 
 * @param env Pointer to a OSyncEnv environment
 * @returns Number of groups
 * 
 */
int osync_env_num_groups(OSyncEnv *env)
{
	return g_list_length(env->groups);
}

/*! @brief Returns the nth group
 * 
 * Returns the nth groups from the environment
 * 
 * @param env Pointer to a OSyncEnv environment
 * @param nth Which group to return
 * @returns Pointer to the group
 * 
 */
OSyncGroup *osync_env_nth_group(OSyncEnv *env, int nth)
{
	return (OSyncGroup *)g_list_nth_data(env->groups, nth);;
}

/*@}*/

/**
 * @defgroup OSyncEnvAPIMisc OpenSync Misc
 * @ingroup OSyncPublic
 * @brief Some helper functions
 * 
 */
/*@{*/

/*! @brief Opens a xml document
 * 
 * Opens a xml document
 * 
 * @param doc Pointer to a xmldoc
 * @param cur The pointer to the first node
 * @param path The path of the document
 * @param topentry the name of the top node
 * @param error Pointer to a error struct
 * @returns TRUE if successfull, FALSE otherwise
 * 
 */
osync_bool _osync_open_xml_file(xmlDocPtr *doc, xmlNodePtr *cur, const char *path, const char *topentry, OSyncError **error)
{
	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
		osync_debug("OSXML", 1, "File %s does not exist", path);
		osync_error_set(error, OSYNC_ERROR_IO_ERROR, "File %s does not exist", path);
		return FALSE;
	}
	
	*doc = xmlParseFile(path);

	if (!*doc) {
		osync_debug("OSXML", 1, "Could not open: %s", path);
		osync_error_set(error, OSYNC_ERROR_IO_ERROR, "Could not open: %s", path);
		return FALSE;
	}

	*cur = xmlDocGetRootElement(*doc);

	if (!*cur) {
		osync_debug("OSXML", 0, "%s seems to be empty", path);
		osync_error_set(error, OSYNC_ERROR_IO_ERROR, "%s seems to be empty", path);
		xmlFreeDoc(*doc);
		return FALSE;
	}

	if (xmlStrcmp((*cur)->name, (const xmlChar *) topentry)) {
		osync_debug("OSXML", 0, "%s seems not to be a valid configfile.\n", path);
		osync_error_set(error, OSYNC_ERROR_IO_ERROR, "%s seems not to be a valid configfile.\n", path);
		xmlFreeDoc(*doc);
		return FALSE;
	}

	*cur = (*cur)->xmlChildrenNode;
	return TRUE;
}

/*! @brief Writes data to a file
 * 
 * Writes data to a file
 * 
 * @param filename Where to save the data
 * @param data Pointer to the data
 * @param size Size of the data
 * @param mode The mode to set on the file
 * @param oserror Pointer to a error struct
 * @returns TRUE if successfull, FALSE otherwise
 * 
 */
osync_bool osync_file_write(const char *filename, const char *data, int size, int mode, OSyncError **oserror)
{
	osync_bool ret = FALSE;
	GError *error = NULL;
	GIOChannel *chan = g_io_channel_new_file(filename, "w", &error);
	if (!chan) {
		osync_debug("OSYNC", 3, "Unable to open file %s for writing: %s", filename, error->message);
		osync_error_set(oserror, OSYNC_ERROR_IO_ERROR, "Unable to open file %s for writing: %s", filename, error->message);
		return FALSE;
	}
	if (mode) {
		int fd = g_io_channel_unix_get_fd(chan);
		if (fchmod(fd, mode)) {
			osync_debug("OSYNC", 3, "Unable to set file permissions %i for file %s", mode, filename);
			osync_error_set(oserror, OSYNC_ERROR_IO_ERROR, "Unable to set file permissions %i for file %s", mode, filename);
			return FALSE;
		}
	}
	gsize writen;
	g_io_channel_set_encoding(chan, NULL, NULL);
	if (g_io_channel_write_chars(chan, data, size, &writen, &error) != G_IO_STATUS_NORMAL) {
		osync_debug("OSYNC", 3, "Unable to write contents of file %s: %s", filename, error->message);
		osync_error_set(oserror, OSYNC_ERROR_IO_ERROR, "Unable to write contents of file %s: %s", filename, error->message);
	} else {
		g_io_channel_flush(chan, NULL);
		ret = TRUE;
	}
	g_io_channel_shutdown(chan, FALSE, NULL);
	g_io_channel_unref(chan);
	return ret;
}

/*! @brief Reads a file
 * 
 * Reads a file
 * 
 * @param filename Where to read the data from
 * @param data Pointer to the data
 * @param size Size of the data
 * @param oserror Pointer to a error struct
 * @returns TRUE if successfull, FALSE otherwise
 * 
 */
osync_bool osync_file_read(const char *filename, char **data, int *size, OSyncError **oserror)
{
	osync_bool ret = FALSE;
	GError *error = NULL;
	gsize sz = 0;
	
	if (!filename) {
		osync_debug("OSYNC", 3, "No file open specified");
		osync_error_set(oserror, OSYNC_ERROR_IO_ERROR, "No file to open specified");
		return FALSE;
	}
	GIOChannel *chan = g_io_channel_new_file(filename, "r", &error);
	if (!chan) {
		osync_debug("OSYNC", 3, "Unable to read file %s: %s", filename, error->message);
		osync_error_set(oserror, OSYNC_ERROR_IO_ERROR, "Unable to open file %s for reading: %s", filename, error->message);
		return FALSE;
	}
	g_io_channel_set_encoding(chan, NULL, NULL);
	if (g_io_channel_read_to_end(chan, data, &sz, &error) != G_IO_STATUS_NORMAL) {
		osync_debug("OSYNC", 3, "Unable to read contents of file %s: %s", filename, error->message);
		osync_error_set(oserror, OSYNC_ERROR_IO_ERROR, "Unable to read contents of file %s: %s", filename, error->message);
	} else {
		ret = TRUE;
		*size = (int)sz;
	}
	g_io_channel_shutdown(chan, FALSE, NULL);
	g_io_channel_unref(chan);
	return ret;
}

/*! @brief Returns the version of opensync
 * 
 * Returns a string identifying the major and minor version
 * of opensync (something like "0.11")
 * 
 * @returns String with version
 * 
 */
const char *osync_get_version(void)
{
	return VERSION;
}

/*! @brief Safely tries to malloc memory
 * 
 * Tries to malloc memory but returns an error in an OOM situation instead
 * of aborting
 * 
 * @param size The size in bytes to malloc
 * @param error The error which will hold the info in case of an error
 * @returns A pointer to the new memory or NULL in case of error
 * 
 */
void *osync_try_malloc0(unsigned int size, OSyncError **error)
{
	void *result = g_try_malloc(size);
	if (!result) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "No memory left");
		return NULL;
	}
	memset(result, 0, size);
	return result;
}

char *osync_strreplace(const char *input, const char *delimiter, const char *replacement)
{
	osync_return_val_if_fail(input != NULL, NULL);
	osync_return_val_if_fail(delimiter != NULL, NULL);
	osync_return_val_if_fail(replacement != NULL, NULL);

	gchar **array = g_strsplit(input, delimiter, 0);
	gchar *ret = g_strjoinv(replacement, array);
	g_strfreev(array);

	return ret;
}

/*@}*/

OSyncThread *osync_thread_new(GMainContext *context, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, context, error);
	
	OSyncThread *thread = osync_try_malloc0(sizeof(OSyncThread), error);
	if (!thread)
		goto error;

	if (!g_thread_supported ()) g_thread_init (NULL);
	
	thread->started_mutex = g_mutex_new();
	thread->started = g_cond_new();
	thread->context = context;
	if (thread->context)
		g_main_context_ref(thread->context);
	thread->loop = g_main_loop_new(thread->context, FALSE);
	
	osync_trace(TRACE_EXIT, "%s: %p", __func__, thread);
	return thread;
	
error:
	osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
	return NULL;
}

void osync_thread_free(OSyncThread *thread)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	osync_assert(thread);
	
	if (thread->started_mutex)
		g_mutex_free(thread->started_mutex);

	if (thread->started)
		g_cond_free(thread->started);
	
	if (thread->loop)
		g_main_loop_unref(thread->loop);
	
	if (thread->context)
		g_main_context_unref(thread->context);
		
	g_free(thread);
	osync_trace(TRACE_EXIT, "%s", __func__);
}

/*static gpointer osyncThreadStartCallback(gpointer data)
{
	OSyncThread *thread = data;
	
	g_mutex_lock(thread->started_mutex);
	g_cond_signal(thread->started);
	g_mutex_unlock(thread->started_mutex);
	
	g_main_loop_run(thread->loop);
	
	return NULL;
}*/

static gboolean osyncThreadStopCallback(gpointer data)
{
	OSyncThread *thread = data;
	
	g_main_loop_quit(thread->loop);
	
	return FALSE;
}

/*void osync_thread_start(OSyncThread *thread)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	osync_assert(thread);
	
	//Start the thread
	g_mutex_lock(thread->started_mutex);
	thread->thread = g_thread_create (osyncThreadStartCallback, thread, TRUE, NULL);
	g_cond_wait(thread->started, thread->started_mutex);
	g_mutex_unlock(thread->started_mutex);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
}*/

static gboolean osyncThreadStartCallback(gpointer data)
{
	OSyncThread *thread = data;
	
	g_mutex_lock(thread->started_mutex);
	g_cond_signal(thread->started);
	g_mutex_unlock(thread->started_mutex);
	return FALSE;
}

void osync_thread_start(OSyncThread *thread)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	
	g_mutex_lock(thread->started_mutex);
	GSource *idle = g_idle_source_new();
	g_source_set_callback(idle, osyncThreadStartCallback, thread, NULL);
	g_source_attach(idle, thread->context);
	thread->thread = g_thread_create ((GThreadFunc)g_main_loop_run, thread->loop, TRUE, NULL);
	g_cond_wait(thread->started, thread->started_mutex);
	g_mutex_unlock(thread->started_mutex);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
}	

void osync_thread_stop(OSyncThread *thread)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, thread);
	osync_assert(thread);
	
	GSource *source = g_idle_source_new();
	g_source_set_callback(source, osyncThreadStopCallback, thread, NULL);
	g_source_attach(source, thread->context);

	g_thread_join(thread->thread);
	thread->thread = NULL;
	
	g_source_unref(source);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
}

osync_bool osync_pattern_match(const char *pattern, const char *data, int size)
{
	GPatternSpec *spec = g_pattern_spec_new(pattern);
	osync_bool result = g_pattern_match(spec, size, data, NULL);
	g_pattern_spec_free(spec);
	return result;
}


syntax highlighted by Code2HTML, v. 0.9.1