#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fnmatch.h>
#include <glib.h>

#include "../include/string.h"
#include "../include/fio.h"
#include "../include/disk.h"

#include "edvtypes.h"
#include "edvrecbin.h"
#include "edvrecbinfio.h"
#include "edvarchobj.h"
#include "edvarchfio.h"
#include "edvfind.h"


static gchar *EDVFindGrepExcerptFile(
	const gchar *path, const gchar *expression,
	gboolean case_sensitive, gint *line_index
);

/* Object In Archive Finding */
static void EDVFindArchiveObjectByNameIterate(
	edv_core_struct *core_ptr,
	const gchar *arch_path, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer),
	gint *total_matches, gboolean *stop_find
);
gint EDVFindArchiveObjectByName(
	edv_core_struct *core_ptr,
	const gchar *arch_path,	const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer)
);

/* Recycled Object Finding */
static void EDVFindRecycledObjectByContentIterate(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	),
	gint *total_matches, gboolean *stop_find
);
gint EDVFindRecycledObjectByContent(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	)
);

static void EDVFindRecycledObjectByNameIterate(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer),
	gint *total_matches, gboolean *stop_find
);
gint EDVFindRecycledObjectByName(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer)
);


/* Object Finding */
static void EDVFindObjectByContentIterate(
	const gchar *path, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	),
	gint *total_matches, gboolean *stop_find
);
gint EDVFindObjectByContent(
	const gchar *start_dir, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	)
);

static void EDVFindObjectByNameIterate(
	const gchar *path, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer),
	gint *total_matches, gboolean *stop_find
);
gint EDVFindObjectByName(
	const gchar *start_dir, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer)
);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)

#define ISCR(c)		(((c) == '\n') || ((c) == '\r'))
#define ISBLANK(c)	(((c) == ' ') || ((c) == '\t'))


/*
 *	Opens the file specified by path and searches for a string
 *	within it that matches the given expression.
 *
 *	If a match is made then a dynamically allocated string will be
 *	returned containing the excerpt of the matched line.
 */
static gchar *EDVFindGrepExcerptFile(
	const gchar *path, const gchar *expression,
	gboolean case_sensitive, gint *line_index
)
{
	gulong cur_line_start_pos = 0l;
	const gchar *expre_ptr;
	gchar *excerpt = NULL;
	gint c;
	FILE *fp;

	if(line_index != NULL)
	    *line_index = 0;

	if(STRISEMPTY(path) || STRISEMPTY(expression))
	    return(NULL);

	/* Open file for reading */
	fp = FOpen(path, "rb");
	if(fp == NULL)
	    return(NULL);

	/* Begin searching for the given expression in the file */
	expre_ptr = expression;
	while(TRUE)
	{
	    /* Get next character */
	    c = (gint)fgetc(fp);
	    if((int)c == EOF)
		break;

	    /* If not performing case sensitive search then make the
	     * character that was just read uppercase
	     */
	    if(!case_sensitive)
		c = (gint)toupper((int)c);

	    /* If this character marks the end of the line then record
	     * the last line starting position as one character ahead
	     * of the current position
	     */
	    if(ISCR(c))
	    {
		if(line_index != NULL)
		    *line_index = (*line_index) + 1;

		cur_line_start_pos = (gulong)ftell(fp);
	    }

	    /* Does this character matches the current character of
	     * expression?
	     *
	     * If so then increment the expression pointer
	     *
	     * Otherwise reset the expression pointer back to the start
	     * of the expression
	     */
	    if(((gchar)c == *expre_ptr) && (c != '\0'))
		expre_ptr++;
	    else
		expre_ptr = expression;

	    /* Matched entire expression?
	     *
	     * Test if the expression pointer has been incremented all
	     * the way to the end which suggests that the entire
	     * expression has been matched
	     */
	    if(*expre_ptr == '\0')
	    {
		gchar *s;

		/* Seek to start of the current line */
		fseek(fp, (long)cur_line_start_pos, SEEK_SET);

		/* Get current line as a string */
		s = (gchar *)FGetStringLiteral(fp);
		if(s != NULL)
		{
		    const gint	left_pad_max = 40,
				exp_len_max = 80;
		    gchar *s_exp = (case_sensitive) ?
			(gchar *)strstr((char *)s, (const char *)expression) :
			(gchar *)strcasestr((char *)s, (const char *)expression);
		    gchar *s2;

		    /* Sanitize and limit length of string */
		    for(s2 = s; *s2 != '\0'; s2++)
		    {
			/* Non-ACSII character? */
			if(!isascii((int)(*s2)))
			    *s2 = ' ';		/* Replace with space */

			/* Is s2 past the start of the expression? */
			if((s_exp != NULL) ? (s2 > s_exp) : FALSE)
			{
			    if((gint)(s2 - s_exp) >= exp_len_max)
			    {
				*s2 = '\0';
				break;
			    }
			}
			else
			{
			    if((gint)(s2 - s) >= exp_len_max)
			    {
				*s2 = '\0';
				break;
			    }
			}
		    }

		    /* Copy sanitized line as the excerpt */
		    g_free(excerpt);
		    if(s_exp != NULL)
			excerpt = STRDUP(
			    ((s_exp - s) > left_pad_max) ?
				(s_exp - left_pad_max) : s
			);
		    else
			excerpt = STRDUP(s);

		    g_free(s);
		}

		break;	/* Break after first match */

	    }	/* Matched entire expression? */
	}

	/* Close file */
	FClose(fp);

	return(excerpt);
}


/*
 *      Called by EDVFindArchiveObjectByName() to perform one find iteration
 *      on the given path.
 */
static void EDVFindArchiveObjectByNameIterate(
	edv_core_struct *core_ptr,
	const gchar *arch_path, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer),
	gint *total_matches, gboolean *stop_find
)
{
	gint i, total_aobjs = 0;
	edv_archive_object_struct **aobj = NULL, *aobj_ptr;

	if(*stop_find)
	    return;

	/* Get listing of all archive objects found in the given
	 * archive
	 */
	aobj = EDVArchFIOGetListing(
	    core_ptr, arch_path, &total_aobjs,
	    NULL, 0,	/* No filter, get all objects in archive */
	    NULL	/* No password */
	);
	/* Iterate through archive objects */
	for(i = 0; i < total_aobjs; i++)
	{
	    aobj_ptr = aobj[i];
	    if(aobj_ptr == NULL)
		continue;

	    /* User requested that the search be stopped? */
	    if(*stop_find)
		break;

	    /* Report progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(aobj_ptr->name, -1.0f, client_data))
		{
		    *stop_find = TRUE;
		    continue;
		}
	    }

	    /* Does the object in archive has a name? */
	    if(!STRISEMPTY(aobj_ptr->name))
	    {
		/* Do match on object's name */
		gchar *s = STRDUP(aobj_ptr->name);

		/* If not performing case sensitive search then make
		 * the name uppercase
		 */
		if(!case_sensitive)
		    strtoupper((char *)s);

		/* Check if the object's name matches the expression */
		if(!fnmatch((const char *)expression, (char *)s, 0))
		{
		    /* Got match, increment number of matches and call
		     * match callback
		     */
		    *total_matches = (*total_matches) + 1;
		    if(match_cb != NULL)
			match_cb(
			    aobj_ptr->full_path,	/* Path */
			    NULL,			/* No stats */
			    client_data			/* Data */
			);
		}

		g_free(s);	/* Delete copy of object's name */
	    }
	}

	/* Delete all archive object stats */
	for(i = 0; i < total_aobjs; i++)
	    EDVArchObjectDelete(aobj[i]);
	g_free(aobj);
}

/*
 *      Searches for a object who's name matches the given expression
 *      in the archive.
 *
 *      If progress_cb is not NULL then it will be called frequently to
 *      update progress. If progress_cb returns non-zero then the
 *      operation will end prematurly.
 *
 *      If match_cb is not NULL then each matched object will be reported
 *      to it.
 *
 *      Returns the number of occurances found.
 */
gint EDVFindArchiveObjectByName(
	edv_core_struct *core_ptr,
	const gchar *arch_path, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer)
)
{
	gint i;
	gchar *lexpression;
	gboolean *stop_find;
	gint *total_matches;


	if((core_ptr == NULL) || STRISEMPTY(arch_path) ||
	   STRISEMPTY(expression)
	)
	    return(0);

	if(!strcmp((const char *)expression, "*"))
	    return(0);

	/* Convert expression to upper case if not performing case
	 * sensitive matching
	 */
	lexpression = STRDUP(expression);
	if(!case_sensitive)
	    strtoupper((char *)lexpression);

	/* Allocate total number of matches variable for passing to
	 * iteration calls
	 */
	stop_find = (gboolean *)g_malloc(sizeof(gboolean));
	*stop_find = FALSE;

	total_matches = (gint *)g_malloc(sizeof(gint));
	*total_matches = 0;

	/* Begin search */
	EDVFindArchiveObjectByNameIterate(
	    core_ptr,
	    arch_path, lexpression,
	    case_sensitive,
	    client_data,
	    progress_cb, match_cb,
	    total_matches, stop_find
	);

	/* Record total matches */
	i = *total_matches;

	g_free(lexpression);
	g_free(stop_find);
	g_free(total_matches);

	return(i);
}


/*
 *	Called by EDVFindRecycledObjectByContent() to perform one find
 *	iteration on the given path.
 */
static void EDVFindRecycledObjectByContentIterate(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	),
	gint *total_matches, gboolean *stop_find
)
{
	gchar *recycled_dir;
	edv_recbin_index_struct *rbi_ptr;
	edv_recbin_object_struct *obj;

	if(*stop_find)
	    return;

	/* Get recycled objects directory from the recycled objects
	 * index file path
	 */
	recycled_dir = g_dirname(recycled_index_file);

	/* Get recycled objects directory index pointer used to iterate
	 * through all recycled objects
	 */
	rbi_ptr = EDVRecBinIndexOpen(recycled_index_file);
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    /* Get recycled object */
	    obj = rbi_ptr->obj;
	    if(obj == NULL)
		continue;

	    if(*stop_find)
		break;

	    /* Report progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(obj->name, -1.0f, client_data))
		{
		    *stop_find = TRUE;
		    continue;
		}
	    }

	    /* Do match */
	    if(TRUE)
	    {
		gint line_index;
		gchar	*full_path = g_strdup_printf(
		    "%s%c%i",
		    recycled_dir, G_DIR_SEPARATOR, obj->index
		),
			*excerpt = EDVFindGrepExcerptFile(
		    full_path, expression, case_sensitive,
		    &line_index
		);
		if(excerpt != NULL)
		{
		    gchar index_str[80];
		    struct stat lstat_buf;

		    /* Got match, increment match count and call match
		     * callback. The path passed should be the object's
		     * name which is a string specifying a recycled object
		     * index number
		     */
		    *total_matches = (*total_matches) + 1;
		    g_snprintf(
			index_str, sizeof(index_str),
			"%i",
			obj->index
		    );
		    if((match_cb != NULL) &&
		       !lstat((char *)full_path, &lstat_buf)
		    )
			match_cb(
			    index_str,		/* Path as index string */
			    &lstat_buf,		/* Local Stats */
			    excerpt,		/* Excerpt */
			    line_index,		/* Line Index */
			    client_data		/* Data */
			); 

		    g_free(excerpt);	/* Delete copy of excerpt */
		}

		g_free(full_path);	/* Delete full path */
	    }
	}

	EDVRecBinIndexClose(rbi_ptr);

	g_free(recycled_dir);
}

/*
 *      Searches for recycled object who contains a string that matches
 *	the given expression in the recycled objects directory obtained
 *	from the given recycle objects index file.
 *
 *      If progress_cb is not NULL then it will be called frequently to
 *      update progress. If progress_cb returns non-zero then the
 *      operation will end prematurly.
 *
 *      If match_cb is not NULL then each matched object will be reported
 *      to it.
 *
 *      Returns the number of occurances found.
 */
gint EDVFindRecycledObjectByContent(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	)
)
{
	gint i;
	gchar *lexpression;
	gboolean *stop_find;
	gint *total_matches;

	if(STRISEMPTY(recycled_index_file) || STRISEMPTY(expression))
	    return(0);

	/* Convert expression to upper case if not performing case
	 * sensitive matching
	 */
	lexpression = STRDUP(expression);
	if(!case_sensitive)
	    strtoupper((char *)lexpression);

	/* Allocate total number of matches variable for passing to
	 * iteration calls
	 */
	stop_find = (gboolean *)g_malloc(sizeof(gboolean));
	*stop_find = FALSE;

	total_matches = (gint *)g_malloc(sizeof(gint));
	*total_matches = 0;

	/* Begin search */
	EDVFindRecycledObjectByContentIterate(
	    recycled_index_file, lexpression,
	    case_sensitive,
	    client_data,
	    progress_cb, match_cb,
	    total_matches, stop_find
	);

	/* Record total matches */
	i = *total_matches;

	g_free(lexpression);
	g_free(stop_find);
	g_free(total_matches);

	return(i);
}


/*
 *	Called by EDVFindRecycledObjectByName() to perform one find iteration
 *	on the given path.
 */
static void EDVFindRecycledObjectByNameIterate(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer),
	gint *total_matches, gboolean *stop_find
)
{
	gchar *recycled_dir;
	edv_recbin_index_struct *rbi_ptr;
	const edv_recbin_object_struct *obj;

	if(*stop_find)
	    return;

	/* Get recycled objects directory from the recycled objects
	 * index file path
	 */
	recycled_dir = g_dirname(recycled_index_file);

	/* Get recycled objects directory index pointer used to iterate
	 * through all recycled objects
	 */
	rbi_ptr = EDVRecBinIndexOpen(recycled_index_file);
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    /* Get recycled object */
	    obj = rbi_ptr->obj;
	    if(obj == NULL)
		continue;

	    if(*stop_find)
		break;

	    /* Report progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(obj->name, -1.0f, client_data))
		{
		    *stop_find = TRUE;
		    continue;
		}
	    }

	    /* Does the object have a name? */
	    if(!STRISEMPTY(obj->name))
	    {
		gchar *s = STRDUP(obj->name);

		if(!case_sensitive)
		    strtoupper((char *)s);

		if(!fnmatch((const char *)expression, (char *)s, 0))
		{
		    gchar *full_path, index_str[80];

		    /* Got match, increment match count and call match
		     * callback
		     *
		     * The path passed should be the object's name
		     * which is a string specifying a recycled object
		     * index number
		     */
		    *total_matches = (*total_matches) + 1;

		    g_snprintf(
			index_str, sizeof(index_str),
			"%i",
			obj->index
		    );
		    full_path = g_strdup_printf(
			"%s%c%i",
			recycled_dir, G_DIR_SEPARATOR, obj->index
		    );
		    if((full_path != NULL) && (match_cb != NULL))
		    {
			struct stat lstat_buf;
			if(!lstat((char *)full_path, &lstat_buf))
			    match_cb(
				index_str,
				&lstat_buf,
				client_data
			    );
		    }

		    g_free(full_path);
		}

		g_free(s);
	    }
	}

	EDVRecBinIndexClose(rbi_ptr);

	g_free(recycled_dir);
}

/*
 *	Searches for a recycled object who's name matches the given
 *	expression in the recycled objects directory obtained from the
 *	given recycled objects index file.
 *
 *      If progress_cb is not NULL then it will be called frequently to
 *      update progress. If progress_cb returns non-zero then the
 *      operation will end prematurly.
 *
 *      If match_cb is not NULL then each matched object will be reported
 *      to it.
 *
 *      Returns the number of occurances found.
 */
gint EDVFindRecycledObjectByName(
	const gchar *recycled_index_file, const gchar *expression,
	gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer)
)
{
	gint i;
	gchar *lexpression;
	gboolean *stop_find;
	gint *total_matches;

	if(STRISEMPTY(recycled_index_file) || STRISEMPTY(expression))
	    return(0);
	if(!strcmp(expression, "*"))
	    return(0);

	/* Convert expression to upper case if not matching case
	 * sensitive
	 */
	lexpression = STRDUP(expression);
	if(!case_sensitive)
	    strtoupper((char *)lexpression);

	/* Allocate total number of matches variable for passing to
	 * iteration calls
	 */
	stop_find = (gboolean *)g_malloc(sizeof(gboolean));
	*stop_find = FALSE;

	total_matches = (gint *)g_malloc(sizeof(gint));
	*total_matches = 0;

	/* Begin search */
	EDVFindRecycledObjectByNameIterate(
	    recycled_index_file, lexpression,
	    case_sensitive,
	    client_data,
	    progress_cb, match_cb,
	    total_matches, stop_find
	);

	/* Record total matches */
	i = *total_matches;

	g_free(lexpression);
	g_free(stop_find);
	g_free(total_matches);

	return(i);
}


/*
 *	Called by EDVFindObjectByContent() to perform one find iteration
 *	on the given path and its child objects if path is a directory and
 *	recurisive is TRUE.
 */
static void EDVFindObjectByContentIterate(
	const gchar *path, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	),
	gint *total_matches, gboolean *stop_find
)
{
	gint strc;
	gchar **strv;

	if(*stop_find)
	    return;

	/* Get listing of child objects if the given path is a directory */
	strv = (gchar **)GetDirEntNames2((const char *)path, (int *)&strc);
	if(strv != NULL)
	{
	    gint i;
	    const gchar *name;
	    gchar *child_path = NULL;
	    struct stat stat_buf, lstat_buf;

	    strv = (gchar **)StringQSort((char **)strv, (int)strc);
	    if(strv == NULL)
		return;

	    for(i = 0; i < strc; i++)
	    {
#define DO_FREE_CONTINUE	{	\
 g_free(child_path);			\
 child_path = NULL;			\
					\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
					\
 continue;				\
}
		name = strv[i];
		if(STRISEMPTY(name))
		    DO_FREE_CONTINUE

		/* User requested stop? */
		if(*stop_find)
		    DO_FREE_CONTINUE

		/* Skip special directory notations */
		if(!strcmp((const char *)name, "..") ||
		   !strcmp((const char *)name, ".")
		)
		    DO_FREE_CONTINUE

		/* Get full path to child object as child_path */
		child_path = STRDUP(PrefixPaths(
		    (const char *)path, (const char *)name
		));
		if(child_path == NULL)
		    DO_FREE_CONTINUE

		/* Report progress */
		if(progress_cb != NULL)
		{
		    if(progress_cb(child_path, -1.0f, client_data))
		    {
			*stop_find = TRUE;
			DO_FREE_CONTINUE
		    }
		}

		/* Do match */
		if(TRUE)
		{
		    gint line_index;
		    gchar *excerpt = EDVFindGrepExcerptFile(
			child_path, expression,
			case_sensitive, &line_index
		    );
		    if(excerpt != NULL)
		    {
			/* Got match, increment number of matches and
			 * call the match callback
			 */
			*total_matches = (*total_matches) + 1;
			if((match_cb != NULL) &&
			   !lstat((char *)child_path, &lstat_buf)
			)
			    match_cb(
				child_path,	/* Path */
				&lstat_buf,	/* Local Stats */
				excerpt,	/* Excerpt */
				line_index,	/* Line Index */
				client_data	/* Data */
			    );

			g_free(excerpt);
		    }
		}


		/* Get destination stats */
		if(stat((char *)child_path, &stat_buf))
		    DO_FREE_CONTINUE

		/* Is it a directory and should we recurse into it? */
		if(recursive && S_ISDIR(stat_buf.st_mode))
		{
		    EDVFindObjectByContentIterate(
			child_path, expression,
			recursive, case_sensitive,
			client_data,
			progress_cb, match_cb,
			total_matches, stop_find
		    );
		}

		DO_FREE_CONTINUE
#undef DO_FREE_CONTINUE
	    }

	    g_free(strv);
	}
}

/*
 *	Searches for an object who contains a string that matches the
 *	given expression starting from the directory specified by
 *	start_dir.
 *
 *	If progress_cb is not NULL then it will be called frequently to
 *	update progress. If progress_cb returns non-zero then the
 *	operation will end prematurly.
 *
 *	If match_cb is not NULL then each matched object will be reported
 *	to it.
 *
 *	Returns the number of occurances found.
 */
gint EDVFindObjectByContent(
	const gchar *start_dir, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(
		const gchar *, const struct stat *,
		const gchar *, gint,
		gpointer
	)
)
{
	gint i;
	gchar *lexpression;
	gboolean *stop_find;
	gint *total_matches;

	if(STRISEMPTY(start_dir) || STRISEMPTY(expression))
	    return(0);

	/* Convert expression to upper case if not matching case
	 * sensitive
	 */
	lexpression = STRDUP(expression);
	if(!case_sensitive)
	    strtoupper((char *)lexpression);

	/* Allocate total number of matches variable for passing to
	 * iteration calls
	 */
	stop_find = (gboolean *)g_malloc(sizeof(gboolean));
	*stop_find = FALSE;

	total_matches = (gint *)g_malloc(sizeof(gint));
	*total_matches = 0;

	/* Begin search */
	EDVFindObjectByContentIterate(
	    start_dir, lexpression,
	    recursive, case_sensitive,
	    client_data,
	    progress_cb, match_cb,
	    total_matches, stop_find
	);

	/* Record total matches */
	i = *total_matches;

	g_free(lexpression);
	g_free(stop_find);
	g_free(total_matches);

	return(i);
}


/*
 *	Called by EDVFindObjectByName() to perform one find iteration on
 *	the given path and its child objects if path is a directory and
 *	recurisive is TRUE.
 */
static void EDVFindObjectByNameIterate(
	const gchar *path, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer),
	gint *total_matches, gboolean *stop_find
)
{
	gint strc;
	gchar **strv;

	if(*stop_find)
	    return;

	/* Get listing of child objects if the given path is a directory */
	strv = (gchar **)GetDirEntNames2((const char *)path, (int *)&strc);
	if(strv != NULL)
	{
	    gint i;
	    const gchar *name;
	    gchar *child_path = NULL;
	    struct stat stat_buf, lstat_buf;

	    strv = (gchar **)StringQSort((char **)strv, (int)strc);
	    if(strv == NULL)
		return;

	    for(i = 0; i < strc; i++)
	    {
#define DO_FREE_CONTINUE	{	\
 g_free(child_path);			\
 child_path = NULL;			\
					\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
					\
 continue;				\
}
		name = strv[i];
		if(STRISEMPTY(name))
		    DO_FREE_CONTINUE

		/* User requested stop? */
		if(*stop_find)
		    DO_FREE_CONTINUE

		/* Skip special directory notations */
		if(!strcmp((const char *)name, "..") ||
		   !strcmp((const char *)name, ".")
		)
		    DO_FREE_CONTINUE

		/* Get full path to child object as child_path */
		child_path = STRDUP(PrefixPaths(
		    (const char *)path, (const char *)name
		));
		if(child_path == NULL)
		    DO_FREE_CONTINUE

		/* Report progress */
		if(progress_cb != NULL)
		{
		    if(progress_cb(child_path, -1.0f, client_data))
		    {
			*stop_find = TRUE;
			DO_FREE_CONTINUE
		    }
		}

		/* Do match */
		if(TRUE)
		{
		    gchar *s = STRDUP(name);

		    if(!case_sensitive)
			strtoupper((char *)s);

		    if(!fnmatch((const char *)expression, (char *)s, 0))
		    {
			/* Got match, increment number of matches and
			 * call the match callback
			 */
			*total_matches = (*total_matches) + 1;
			if((match_cb != NULL) &&
			   !lstat((char *)child_path, &lstat_buf)
			)
			    match_cb(
				child_path,	/* Path */
				&lstat_buf,	/* Stats */
				client_data	/* Data */
			    );
		    }

		    g_free(s);
		}

		/* Get destination stats */
		if(stat((char *)child_path, &stat_buf))
		    DO_FREE_CONTINUE

		/* Is it a directory and should we recurse into it? */
		if(recursive && S_ISDIR(stat_buf.st_mode))
		{
		    EDVFindObjectByNameIterate(
			child_path, expression,
			recursive, case_sensitive,
			client_data,
			progress_cb, match_cb,
			total_matches, stop_find
		    );
		}

		DO_FREE_CONTINUE
#undef DO_FREE_CONTINUE
	    }

	    g_free(strv);
	}
}

/*
 *	Searches for an object who's name matches the given expression
 *	starting from the directory specified by start_dir.
 *
 *	If progress_cb is not NULL then it will be called frequently to
 *	update progress. If progress_cb returns non-zero then the
 *	operation will end prematurly.
 *
 *	If match_cb is not NULL then each matched object will be reported
 *	to it.
 *
 *	Returns the number of occurances found.
 */
gint EDVFindObjectByName(
	const gchar *start_dir, const gchar *expression,
	gboolean recursive, gboolean case_sensitive,
	gpointer client_data,
	gint (*progress_cb)(const gchar *, gfloat, gpointer),
	void (*match_cb)(const gchar *, const struct stat *, gpointer)
)
{
	gint i;
	gchar *lexpression;
	gboolean *stop_find;
	gint *total_matches;


	if(STRISEMPTY(start_dir) || STRISEMPTY(expression))
	    return(0);
	if(!strcmp((const char *)expression, "*"))
	    return(0);

	/* Convert expression to upper case if not matching case
	 * sensitive
	 */
	lexpression = STRDUP(expression);
	if(!case_sensitive)
	    strtoupper((char *)lexpression);

	/* Allocate total number of matches variable for passing to
	 * iteration calls
	 */
	stop_find = (gboolean *)g_malloc(sizeof(gboolean));
	*stop_find = FALSE;

	total_matches = (gint *)g_malloc(sizeof(gint));
	*total_matches = 0;

	/* Begin search */
	EDVFindObjectByNameIterate(
	    start_dir, lexpression,
	    recursive, case_sensitive,
	    client_data,
	    progress_cb, match_cb,
	    total_matches, stop_find
	);

	/* Record total matches */
	i = *total_matches;

	g_free(lexpression);
	g_free(stop_find);
	g_free(total_matches);

	return(i);
}
