#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <gtk/gtk.h>

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

#include "guiutils.h"
#include "progressdialog.h"
#include "fprompt.h"

#include "edv_types.h"
#include "cfg.h"
#include "edv_obj.h"
#include "edv_obj_op.h"
#include "browser.h"
#include "browser_cb.h"
#include "browser_dir_tree.h"
#include "endeavour2.h"
#include "edv_cb.h"
#include "edv_open.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"


#if 0
static edv_object_struct *EDVBrowserNewErrorObject(
	const gchar *path
);
#endif

/* Callbacks */
static void EDVBrowserDirTreeFPromptCancelCB(gpointer data);

/* Node Setting */
static void EDVBrowserDirTreeSetNode(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *node,
	edv_object_struct *obj
);

/* Getting */
GtkCTreeNode *EDVBrowserDirTreeGetToplevel(edv_browser_struct *browser);

static GtkCTreeNode *EDVBrowserDirTreeInsertNode(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *parent, GtkCTreeNode *sibling,
	edv_object_struct *obj
);

/* Set Origin Path */
void EDVBrowserDirTreeSetOriginPath(
	edv_browser_struct *browser, const gchar *path
);

/* Get Listing */
static void EDVBrowserDirTreeDoGetChildrenListIterate(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *parent,
	const gboolean update_progress, const gboolean recurse,
	const gboolean hide_object_hidden,
	const gboolean hide_object_noaccess
);
static void EDVBrowserDirTreeDoGetChildrenList(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *parent,
	const gboolean update_progress, const gboolean recurse
);
void EDVBrowserDirTreeCreateToplevels(edv_browser_struct *browser);
void EDVBrowserDirTreeGetListing(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
);
void EDVBrowserDirTreeClear(edv_browser_struct *browser);

/* Expanding */
void EDVBrowserDirTreeExpand(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
);

/* Realize Listing */
static void EDVBrowserDirTreeRealizeListingIterate(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *node,
	const gboolean parent_is_expanded
);
void EDVBrowserDirTreeRealizeListing(
	edv_browser_struct *browser, GtkCTreeNode *node
);

/* Selecting */
static void EDVBrowserDirTreeSelectPathIterate(
	GtkCTree *ctree, GtkCTreeNode *node, const gchar *path,
	GtkCTreeNode **matched_node_ptr
);
void EDVBrowserDirTreeSelectPath(
	edv_browser_struct *browser, const gchar *path
);

/* Finding */
static void EDVBrowserDirTreeFindNodeByIndexIterate(
	GtkCTree *ctree,
	GtkCTreeNode *node,
	const gulong device_index, const gulong index,
	GtkCTreeNode **matched_node
);
GtkCTreeNode *EDVBrowserDirTreeFindNodeByIndex(
	edv_browser_struct *browser,
	const gulong device_index, const gulong index
);
static void EDVBrowserDirTreeFindNodeByPathIterate(
	GtkCTree *ctree,
	GtkCTreeNode *node,
	const gchar *path,
	GtkCTreeNode **matched_node
);
GtkCTreeNode *EDVBrowserDirTreeFindNodeByPath(
	edv_browser_struct *browser, const gchar *path
);

/* Opening */
void EDVBrowserDirTreeDoOpenWithObject(
	edv_browser_struct *browser, GtkCTreeNode *node
);

/* Renaming */
static void EDVBrowserDirTreeFPromptRenameApplyCB(
	gpointer data, const gchar *value
);
void EDVBrowserDirTreeDoFPromptRename(
	edv_browser_struct *browser, GtkCTreeNode *node
);

/* Object Callbacks */
void EDVBrowserDirTreeObjectAddedNotify(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
);
void EDVBrowserDirTreeObjectModifiedNotify(
	edv_browser_struct *browser,
	const gchar *path, const gchar *new_path,
	const struct stat *lstat_buf
);
void EDVBrowserDirTreeObjectRemovedNotify(
	edv_browser_struct *browser, const gchar *path
);

/* Mount Callbacks */
void EDVBrowserDirTreeMountNotify(
	edv_browser_struct *browser, edv_device_struct *dev,
	gboolean is_mounted
);


#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)


#if 0
/*
 *	Creates a new error object.
 *
 *	The path specifies the full path to the error object.
 */
static edv_object_struct *EDVBrowserNewErrorObject(
	const gchar *path
)
{
	edv_object_struct *obj = EDVObjectNew();
	if(obj == NULL)
	    return(NULL);

	obj->type = EDV_OBJECT_TYPE_ERROR;
	EDVObjectSetPath(obj, path);
	EDVObjectUpdateLinkFlags(obj);

	return(obj);
}
#endif


/*
 *	FPrompt cancel callback.
 */
static void EDVBrowserDirTreeFPromptCancelCB(gpointer data)
{
	gpointer *cb_data = (gpointer *)data;
	if(cb_data == NULL)
	    return;

	g_free(cb_data);
}


/*
 *	Sets the Directory Tree node's icon, text, and style.
 *
 *	The ctree specifies the Directory Tree.
 *
 *	The node specifies the node.
 *
 *	The obj specifies the object who's values will be used to
 *	set the node's icon, text, and style.
 *
 *	All inputs assumed valid.
 */
static void EDVBrowserDirTreeSetNode(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *node,
	edv_object_struct *obj
)
{
	const gchar *text = obj->name;
	GdkPixmap	*pixmap,
			*pixmap_exp,
			*pixmap_ext,
			*pixmap_hid;
	GdkBitmap	*mask,
			*mask_exp,
			*mask_ext,
			*mask_hid;
	GtkStyle	**cell_styles = browser->cell_style,
			*style = NULL;
	GtkCTreeRow *row_ptr;

	/* Get icon */
	EDVMatchObjectIcon(
	    core->device, core->total_devices,
	    core->mimetype, core->total_mimetypes,
	    obj->type,
	    obj->full_path,
	    EDV_OBJECT_IS_LINK_VALID(obj),
	    obj->permissions,
	    0,			/* Small icons */
	    &pixmap, &mask,
	    &pixmap_exp, &mask_exp,
	    &pixmap_ext, &mask_ext,
	    &pixmap_hid, &mask_hid
	);

	/* Hidden? */
	if(EDVIsObjectHidden(obj))
	{
	    if(pixmap_hid != NULL)
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	}

#if 0
	/* Home directory? */
	if((!STRISEMPTY(obj->full_path) && 
	    !STRISEMPTY(core->home_dir)) ? 
	    !strcmp(obj->full_path, core->home_dir) : FALSE
	)
	{
	    if(browser->folder_home_pixmap != NULL)
	    {
		pixmap = browser->folder_home_pixmap;
		mask = browser->folder_home_mask;
	    }
	}
#endif

	/* Directory not accessable? */
	if(!EDVIsObjectAccessable(core, obj))
	{
	    if(browser->folder_noaccess_pixmap != NULL)
	    {
		pixmap = browser->folder_noaccess_pixmap;
		mask = browser->folder_noaccess_mask;
	    }
	    if(style == NULL)
		style = cell_styles[EDV_BROWSER_CELL_STYLE_NO_ACCESS];
	}

	/* Link destination is a grand parent? */
	if(EDV_OBJECT_IS_LINK_TAR_GRAND_PARENT(obj))
	{
	    if(style == NULL)
		style = cell_styles[EDV_BROWSER_CELL_STYLE_RECURSIVE_LINK];
	}

	/* If the expanded icon is not available then use the
	 * standard icon as the expanded icon
	 */
	if(pixmap_exp == NULL)
	{
	    pixmap_exp = pixmap;
	    mask_exp = mask;
	}

	/* Get the GtkCTreeRow in order to obtain current values */
	row_ptr = GTK_CTREE_ROW(node);
	if(row_ptr == NULL)
	    return;

	/* Update node */
	gtk_ctree_set_node_info(
	    ctree, node,
	    (text != NULL) ? text : "(null)",
	    EDV_LIST_PIXMAP_TEXT_SPACING,
	    (pixmap != NULL) ? 
		pixmap : row_ptr->pixmap_closed,
	    (mask != NULL) ?
		mask : row_ptr->mask_closed,
	    (pixmap_exp != NULL) ?
		pixmap_exp : row_ptr->pixmap_opened,
	    (mask_exp != NULL) ?
		mask_exp : row_ptr->mask_opened,
	    row_ptr->is_leaf,
	    row_ptr->expanded
	);

	/* Set node style */
	gtk_ctree_node_set_row_style(ctree, node, style);
}


/*
 *	Returns the File Browser Directory Tree's toplevel node.
 */
GtkCTreeNode *EDVBrowserDirTreeGetToplevel(edv_browser_struct *browser)
{
	GtkCTree *ctree = (browser != NULL) ?
	    (GtkCTree *)browser->directory_ctree : NULL;
	if(ctree == NULL)
	    return(NULL);

	/* For now just get the first toplevel, later on we may need
	 * to return a node other than the first toplevel if multiple
	 * vfs are to be supported
	 */
	return(EDVNodeGetToplevel(ctree));
}


/*
 *	Inserts a node to the Directory Tree.
 *
 *	The ctree specifies the Directory Tree.
 *
 *	The parent specifies the parent node that the new node will
 *	be inserted to. If parent is NULL then the new noed will be
 *	a new toplevel node.
 *
 *	The sibling specifies the sibling node that the new node will
 *	be inserted after. If sibling is NULL then the new node will
 *	be appended to the parent's list of child nodes.
 *
 *	The obj specifies the object that will be set as the new
 *	node's data. The specified obj should not be referenced again
 *	after this call.
 *
 *	Returns the new node or NULL on error.
 *
 *	All inputs assumed valid.
 */
static GtkCTreeNode *EDVBrowserDirTreeInsertNode(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *parent, GtkCTreeNode *sibling,
	edv_object_struct *obj
)
{
	gint i, columns;
	gchar **strv;
	GtkCTreeNode *node;
	GtkCList *clist = GTK_CLIST(ctree);

	/* Get the number of columns */
	columns = MAX(clist->columns, 1);

	/* Allocate the new node's cell values */
	strv = (gchar **)g_malloc(columns * sizeof(gchar *));
	for(i = 0; i < columns; i++)
	    strv[i] = "";

	/* Insert the new node */
	node = gtk_ctree_insert_node(
	    ctree, parent, sibling, strv,
	    EDV_LIST_PIXMAP_TEXT_SPACING,
	    NULL, NULL, NULL, NULL,
	    FALSE,			/* Is Leaf? */
	    FALSE			/* Expanded? */
	);

	/* Delete the cell values */
	g_free(strv);

	/* Failed to insert a new node? */
	if(node == NULL)
	{
	    EDVObjectDelete(obj);
	    return(NULL);
	}

	/* Set the text, pixmaps, and style for the new node */
	EDVBrowserDirTreeSetNode(
	    core, browser, ctree,
	    node,
	    obj
	);

	/* Set the new node's row data as the specified object */
	gtk_ctree_node_set_row_data_full(
	    ctree, node,
	    obj, EDVBrowserDirTreeItemDestroyCB
	);

	return(node);
}


/*
 *	Sets the origin path and recreates the toplevel nodes.
 */
void EDVBrowserDirTreeSetOriginPath(
	edv_browser_struct *browser, const gchar *path
)
{
	const gchar *origin_path;
	GtkCList *clist;
	GtkCTreeNode *node;
	GtkCTree *ctree;

	if((browser == NULL) || STRISEMPTY(path))
	    return;

	origin_path = browser->directory_ctree_origin_path;
	ctree = (GtkCTree *)browser->directory_ctree;
	if(ctree == NULL)
	    return;

	clist = GTK_CLIST(ctree);

	/* No change? */
	if(path == origin_path)
	    return;
	if((origin_path != NULL) ? !strcmp(origin_path, path) : FALSE)
	    return;

	/* Set the new origin path */
	g_free(browser->directory_ctree_origin_path);
	browser->directory_ctree_origin_path = STRDUP(path);

	/* Delete all existing nodes and recreate toplevels */
	EDVBrowserDirTreeCreateToplevels(browser);

	/* Select and expand the toplevel node */
	node = EDVBrowserDirTreeGetToplevel(browser);
	if(node != NULL)
	{
	    gtk_ctree_expand(ctree, node);
	    gtk_ctree_select(ctree, node);
	}
}


/*
 *	Called by EDVBrowserDirTreeDoGetChildrenList() to get a list
 *	of child nodes.
 *
 *	All inputs assumed valid.
 */
static void EDVBrowserDirTreeDoGetChildrenListIterate(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *parent,
	const gboolean update_progress, const gboolean recurse,
	const gboolean hide_object_hidden,
	const gboolean hide_object_noaccess
)
{
	gchar *parent_path;
	gint i, strc;
	gchar **strv;
	const gchar *s;
	struct stat lstat_buf, stat_buf;

	/* Get object from the specified parent node */
	edv_object_struct * obj = EDV_OBJECT(
	    gtk_ctree_node_get_row_data(ctree, parent)
	);
	if(obj == NULL)
	    return;

	/* Get copy of the object's full path */
	parent_path = STRDUP(obj->full_path);
	if(parent_path == NULL)
	    return;

	/* Do not reference the object past this point */
	obj = NULL;


	/* Update status bar indicating which directory we're scanning? */
	if(update_progress)
	{
	    const gchar *parent_name = g_basename(parent_path);
	    gchar *s = g_strdup_printf(
		"Scanning directory: %s",
		parent_name
	    );
	    EDVStatusBarMessage(
		browser->status_bar, s, FALSE
	    );
	    g_free(s);
	}


	/* Get listing of contents in the directory specified by
	 * parent_path
	 */
	strv = GetDirEntNames2(parent_path, &strc);
	if(strv != NULL)
	{
	    gchar *full_path;

	    /* Sort strings */
	    strv = StringQSort(strv, strc);
	    if(strv == NULL)
	    {
		g_free(parent_path);
		return;
	    }

	    /* Iterate through each string */
	    full_path = NULL;
	    for(i = 0; i < strc; i++)
	    {
#define FREE_AND_CONTINUE	{	\
 g_free(full_path);			\
 full_path = NULL;			\
					\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
					\
 continue;				\
}
		s = strv[i];
		if(s == NULL)
		    FREE_AND_CONTINUE

		/* Skip special dir notations */
		if(!strcmp(s, "..") || !strcmp(s, "."))
		    FREE_AND_CONTINUE

		/* Get full path to child object */
		full_path = STRDUP(PrefixPaths(parent_path, s));
		if(full_path == NULL)
		    FREE_AND_CONTINUE

		/* Get the local and destination stats of child
		 * object
		 */
		if(lstat(full_path, &lstat_buf))
		{
#if 0
/* Do not add error objects on the tree */
		    obj = EDVBrowserNewErrorObject(full_path);
		    node = EDVBrowserDirTreeInsertNode(
			core, browser, ctree,
			parent, NULL,
			obj
		    );
#endif
		    FREE_AND_CONTINUE
		}
		if(stat(full_path, &stat_buf))
		{
#if 0
/* Do not add error objects on the tree */
		    obj = EDVBrowserNewErrorObject(full_path);
		    node = EDVBrowserDirTreeInsertNode(
			core, browser, ctree,
			parent, NULL,
			obj
		    );
#endif
		    FREE_AND_CONTINUE
		}

		/* Destination child object is a directory? */
#ifdef S_ISDIR
		if(S_ISDIR(stat_buf.st_mode))
#else
		if(FALSE)
#endif
		{
		    GtkCTreeNode *node = NULL;
		    edv_object_struct *obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, full_path);
			EDVObjectSetStat(obj, &lstat_buf);
			EDVObjectUpdateLinkFlags(obj);

			/* Begin filter checks */
			if((hide_object_hidden ?
			    EDVIsObjectHidden(obj) : FALSE) ||
			   (hide_object_noaccess ?
			    !EDVIsObjectAccessable(core, obj) : FALSE)
			)
			{
			    EDVObjectDelete(obj);
			}
			else
			{
			    /* Add/transfer the object to the tree */
			    node = EDVBrowserDirTreeInsertNode(
				core, browser, ctree,
				parent, NULL,
				obj
			    );
			}
		    }

		    /* Report progress */
		    if(update_progress)
			EDVStatusBarProgress(browser->status_bar, -1.0, TRUE);

		    /* Recurse into sub directories? */
		    if(recurse && (node != NULL))
		    {
			EDVBrowserDirTreeDoGetChildrenListIterate(
			    core, browser, ctree,
			    node,
			    update_progress,
			    FALSE,	/* Do not recurse in this iteration */
			    hide_object_hidden, hide_object_noaccess
			);
		    }
		}

		FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
	    }

	    /* Delete pointer array (each string has already been
	     * deleted
	     */
	    g_free(strv);
	}

	/* Delete copy of parent path */
	g_free(parent_path);
}

/*
 *	Gets a list of child nodes from the parent node, any existing
 *	child nodes in the specified parent node will be deleted.
 *
 *	This will recurse no more than 2 levels from the specified
 *	parent node or not recurse at all of recurse is FALSE.
 *
 *	The ctree specifies the directory tree.
 *
 *	The parent specifies the parent node.
 *
 *	Inputs assumed valid.
 */
static void EDVBrowserDirTreeDoGetChildrenList(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *parent,
	const gboolean update_progress, const gboolean recurse
)
{
	gboolean hide_object_hidden, hide_object_noaccess;
	GtkCTreeNode *node, *sibling_node;
	const cfg_item_struct *cfg_list = core->cfg_list;

	/* Get the display options */
	hide_object_hidden = !EDV_GET_B(
	    EDV_CFG_PARM_BROWSER_SHOW_OBJECT_HIDDEN
	);
	hide_object_noaccess = !EDV_GET_B(
	    EDV_CFG_PARM_BROWSER_SHOW_OBJECT_NOACCESS
	);

	/* Report the initial progress? */
	if(update_progress)
	{
	    EDVStatusBarMessage(
		browser->status_bar,
		"Scanning directories...",
		FALSE
	    );
	    EDVStatusBarProgress(browser->status_bar, -1.0f, TRUE);
	}

	/* Delete all child nodes from the specified parent node */
	node = EDVNodeGetChild(parent);
	while(node != NULL)
	{
	    sibling_node = EDVNodeGetSibling(node);
	    gtk_ctree_remove_node(ctree, node);
	    node = sibling_node;
	}

	/* Get new listing of child nodes for the specified parent
	 * node
	 */
	EDVBrowserDirTreeDoGetChildrenListIterate(
	    core, browser, ctree, parent,
	    update_progress, recurse,
	    hide_object_hidden, hide_object_noaccess
	);

	/* Report the final progress? */
	if(update_progress)
	{
	    EDVStatusBarMessage(browser->status_bar, NULL, FALSE);
	    EDVStatusBarProgress(browser->status_bar, 0.0f, FALSE);
	}
}

/*
 *	Creates the toplevel nodes.
 *
 *	Any existing nodes will be deleted.
 */
void EDVBrowserDirTreeCreateToplevels(edv_browser_struct *browser)
{
	struct stat lstat_buf;
	const gchar *path;
	GtkCList *clist;
	GtkCTreeNode *node;
	GtkCTree *ctree;
	edv_object_struct *obj;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	gtk_clist_freeze(clist);

	/* Delete all existing nodes */
	EDVBrowserDirTreeClear(browser);

	/* Get the origin path as the path for the toplevel node */
	path = browser->directory_ctree_origin_path;
	if(STRISEMPTY(path))
#if defined(_WIN32)
	    path = "C:\\";
#else
	    path = "/";
#endif

	/* Create object for toplevel node */
	obj = EDVObjectNew();
	if(obj == NULL)
	{
	    gtk_clist_thaw(clist);
	    return;
	}

	if(!lstat(path, &lstat_buf))
	{
	    EDVObjectSetPath(obj, path);
	    EDVObjectSetStat(obj, &lstat_buf);
	    EDVObjectUpdateLinkFlags(obj);
	}

	/* Add/transfer the object to the tree */
	node = EDVBrowserDirTreeInsertNode(
	    core, browser, ctree,
	    NULL, NULL,
	    obj
	);

	/* Successfully created the toplevel node? */
	if(node != NULL)
	{
	    /* Get one level of nodes from this toplevel node */
	    EDVBrowserDirTreeDoGetChildrenList(
		core, browser, ctree,
		node,				/* Parent node */
		TRUE, FALSE
	    );
	}

	gtk_clist_thaw(clist);
}

/*
 *	Gets a listing of child nodes from the specified node, recursing
 *	no more than 2 levels.
 *
 *	If the node is NULL then the toplevel node will be used.
 *
 *	Any existing child nodes of the specified node will be deleted
 *	first before getting the new list of child nodes.
 */
void EDVBrowserDirTreeGetListing(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
)
{
	GtkCList *clist;
	GtkCTree *ctree;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	gtk_clist_freeze(clist);

	/* Use toplevel node if the specified node is NULL */
	if(node == NULL)
	    node = EDVBrowserDirTreeGetToplevel(browser);

	/* Delete any existing child nodes from the specified node
	 * and then get a new list of child nodes
	 */
	if(node != NULL)
	    EDVBrowserDirTreeDoGetChildrenList(
		core, browser, ctree,
		node,				/* Parent node */
		update_progress, TRUE
	    );

	/* Realize listing */
	EDVBrowserDirTreeRealizeListing(browser, NULL);

	gtk_clist_thaw(clist);
}

/*
 *	Deletes all nodes on the Directory Tree.
 */
void EDVBrowserDirTreeClear(edv_browser_struct *browser)
{
	GtkCList *clist;
	GtkCTreeNode *node;
	GtkCTree *ctree;

	if(browser == NULL)
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	if(ctree == NULL)
	    return;

	clist = GTK_CLIST(ctree);

	node = EDVBrowserDirTreeGetToplevel(browser);
	if(node != NULL)
	{
	    gtk_clist_freeze(clist);
	    gtk_ctree_remove_node(ctree, node);
	    gtk_clist_thaw(clist);
	}
}


/*
 *	Expands the specified node and gets a listing of child nodes
 *	of the specified node.
 */
void EDVBrowserDirTreeExpand(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
)
{
	GtkCList *clist;
	GtkCTree *ctree;
	edv_core_struct *core;

	if((browser == NULL) || (node == NULL))
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	gtk_clist_freeze(clist);

	/* Get child directories as child nodes of the given expanded
	 * node, recursing at most 2 levels
	 */
	EDVBrowserDirTreeDoGetChildrenList(
	    core, browser, ctree,
	    node,				/* Parent node */
	    update_progress, TRUE
	);

	gtk_clist_thaw(clist);
}


/*
 *	Called by EDVBrowserDirTreeRealizeListing() to update existing
 *	nodes and to remove all non-existant nodes, then recurse into
 *	any child nodes.
 *
 *	All inputs assumed valid.
 */
static void EDVBrowserDirTreeRealizeListingIterate(
	edv_core_struct *core, edv_browser_struct *browser,
	GtkCTree *ctree,
	GtkCTreeNode *node,
	const gboolean parent_is_expanded
)
{
	GtkCTreeRow *row_ptr;
	GtkCTreeNode *child_node, *next_node;
	edv_object_struct *obj;

	/* Iterate through the specified node and its siblings */
	while(node != NULL)
	{
	    row_ptr = GTK_CTREE_ROW(node);
	    if(row_ptr == NULL)
		break;

	    /* Does this node have children? */
	    child_node = row_ptr->children;
	    if(child_node != NULL)
	    {
		/* Recurse into child nodes */
		EDVBrowserDirTreeRealizeListingIterate(
		    core, browser, ctree,
		    child_node,
		    row_ptr->expanded
		);
	    }
	    else
	    {
		/* The node did not have child nodes, check if the
		 * node has child nodes now
		 *
		 * Do this only if this node's parent is expanded to
		 * prevent recursions caused by multiple calls to sync
		 *
		 * Recurse one level
		 */
		if(parent_is_expanded)
		    EDVBrowserDirTreeDoGetChildrenList(
			core, browser, ctree,
			node,			/* Parent node */
			FALSE, FALSE
		    );
	    }

	    /* Get the next sibling node (which may be NULL) */
	    next_node = row_ptr->sibling;

	    /* Get this node's object */
	    obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );
	    if((obj != NULL) ? !STRISEMPTY(obj->full_path) : FALSE)
	    {
		struct stat lstat_buf;

		/* Check if the Object no longer exists locally */
		if(lstat(obj->full_path, &lstat_buf))
		{
		    /* No longer exists locally, remove the node */
		    gtk_ctree_remove_node(ctree, node);
		    node = NULL;
		    row_ptr = NULL;
		    obj = NULL;
		}
		else
		{
		    struct stat stat_buf;

		    /* Exists locally, now get the destination stats */
		    if(stat(obj->full_path, &stat_buf))
		    {
			/* Destination no longer exists, remove the node */
			gtk_ctree_remove_node(ctree, node);
			node = NULL;
			row_ptr = NULL;
			obj = NULL;
		    }
		    /* Destination does not goes to a directory? */
#ifdef S_ISDIR
		    else if(!S_ISDIR(stat_buf.st_mode))
#else
		    else if(TRUE)
#endif
		    {
			/* Destination not a directory, remove the node */
			gtk_ctree_remove_node(ctree, node);
			node = NULL;
			row_ptr = NULL;
			obj = NULL;
		    }
		    else
		    {
			/* All checks passed, update the object and
			 * the node
			 */
			EDVObjectSetStat(obj, &lstat_buf);
			EDVObjectUpdateLinkFlags(obj);
			EDVBrowserDirTreeSetNode(
			    core, browser, ctree,
			    node,
			    obj
			);
		    }
		}
	    }

	    /* The current node, row_ptr, and obj should now be
	     * considered invalid
	     */

	    /* Set current node to the next sibling */
	    node = next_node;
	}

}

/*
 *	Updates the existing nodes and removes any non-existant nodes
 *	starting from the specified node.
 *
 *	If the node is NULL then the toplevel node will be used.
 */
void EDVBrowserDirTreeRealizeListing(
	edv_browser_struct *browser, GtkCTreeNode *node
)
{
	GtkCList *clist;
	GtkCTreeRow *row_ptr;
	GtkCTree *ctree;
	edv_core_struct *core;

	if(browser == NULL)
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	/* Use toplevel node if the specified node is NULL */
	if(node == NULL)
	    node = EDVBrowserDirTreeGetToplevel(browser);
	if(node == NULL)
	    return;

	row_ptr = GTK_CTREE_ROW(node);

	gtk_clist_freeze(clist);

	/* Begin updating nodes and removing non-existant nodes */
	EDVBrowserDirTreeRealizeListingIterate(
	    core, browser, ctree,
	    node,
	    (row_ptr != NULL) ? row_ptr->expanded : FALSE
	);

	gtk_clist_thaw(clist);
}


/*
 *	Called by EDVBrowserDirTreeSelectPath() to seek through all
 *	child directories for a prefix match to the given full path and
 *	recurse into child directories.
 *
 *	The given path is the full path that is intended to be selected
 *	if found.
 */
static void EDVBrowserDirTreeSelectPathIterate(
	GtkCTree *ctree, GtkCTreeNode *node, const gchar *path,
	GtkCTreeNode **matched_node_ptr
)
{
	const gchar *cur_path;
	edv_object_struct *obj;

	/* Got match? If so then we should not continue the search */
	if(*matched_node_ptr != NULL)
	    return;

	if(node == NULL)
	    return;

	/* Get the node's Object */
	obj = EDV_OBJECT(
	    gtk_ctree_node_get_row_data(ctree, node)
	);

	/* Get the Object's full path */
	cur_path = (obj != NULL) ? obj->full_path : NULL;
	if(STRISEMPTY(cur_path))
	    return;

	/* Is cur_path a parent of the search path? */
	if(EDVIsParentPath(cur_path, path))
	{
	    GtkCTreeRow *ctree_row_ptr;

	    /* Check if the search path and the current path are a
	     * complete match
	     */
	    if(!strcmp(path, cur_path))
	    {
		/* Got complete match, set matched node and return */
		*matched_node_ptr = node;
		return;
	    }
	    else
	    {
		/* Not a complete match, but cur_path is a parent of
		 * the search path so we are on the right track
		 *
		 * Get child node and iterate through all its siblings
		 * and children
		 */

		/* Expand current node as needed
		 *
		 * Note that this may call the expand callback and that
		 * it will load more child nodes
		 */
		ctree_row_ptr = GTK_CTREE_ROW(node);
		if(!ctree_row_ptr->expanded)
		    gtk_ctree_expand(ctree, node);

		/* Get child node and iterate through children */
		node = EDVNodeGetChild(node);
		while(node != NULL)
		{
		    /* Check this node, recurse if it is a match */
		    EDVBrowserDirTreeSelectPathIterate(
			ctree, node, path, matched_node_ptr
		    );
		    /* If we got a match, then do not continue search */
		    if((matched_node_ptr != NULL) ? (*matched_node_ptr != NULL) : TRUE)
			return;

		    /* Get sibling */
		    node = EDVNodeGetSibling(node);
		}
	    }
	}
}

/*
 *	Procedure to select (if possible or as much of) the given
 *	full path to a directory.
 *
 *	Nodes will be expanded and updated as needed.
 */
void EDVBrowserDirTreeSelectPath(
	edv_browser_struct *browser, const gchar *path
)
{
	GtkCList *clist;
	GtkCTree *ctree;
	gchar *full_path;
	GtkCTreeNode *node, **matched_node_ptr;
	GtkWidget *w = (browser != NULL) ? browser->directory_ctree : NULL;
	if((w == NULL) || STRISEMPTY(path))
	    return;

	clist = GTK_CLIST(w);
	ctree = GTK_CTREE(w);

	/* Path must be absolute */
	if(!g_path_is_absolute(path))
	    return;

	/* Make a copy of the given search path */
	full_path = STRDUP(path);
	if(full_path == NULL)
	    return;

	/* Strip tailing deliminators */
	StripPath(full_path);

	/* Allocate matched node marker to pass around so we can tell
	 * when we got a matched node
	 */
	matched_node_ptr = (GtkCTreeNode **)g_malloc(sizeof(GtkCTreeNode *));
	*matched_node_ptr = NULL;

	/* Get the toplevel node to start the search from */
	node = EDVBrowserDirTreeGetToplevel(browser);

	/* Search for the path, expanding (thus updating nodes) and
	 * select the matched node (if found)
	 */
	EDVBrowserDirTreeSelectPathIterate(
	    ctree, node, full_path, matched_node_ptr
	);

	/* Got match in the above search? */
	if(*matched_node_ptr != NULL)
	{
	    /* Select the matched node */
	    gtk_ctree_select(ctree, *matched_node_ptr);
	}

	g_free(matched_node_ptr);
	g_free(full_path);
}


/*
 *	Performs one iteration of finding the Directory Tree node by
 *	location index.
 */
static void EDVBrowserDirTreeFindNodeByIndexIterate(
	GtkCTree *ctree, 
	GtkCTreeNode *node,
	const gulong device_index, const gulong index,
	GtkCTreeNode **matched_node
)
{
	edv_object_struct *obj;

	if((*matched_node != NULL) || (node == NULL))
	    return;

	obj = EDV_OBJECT(
	    gtk_ctree_node_get_row_data(ctree, node)
	);
	if(obj == NULL)
	    return;

	/* Does this object's index location match the specified index
	 * location?
	 */
	if((obj->device_index == device_index) &&
	   (obj->index == index)
	)
	{
	    *matched_node = node;
	    return;
	}
	else
	{
	    /* No match, now check if any of this node's child nodes
	     * match
	     */
	    GtkCTreeRow *row_ptr = GTK_CTREE_ROW(node);
	    if(row_ptr == NULL)
		return;

	    node = row_ptr->children;
	    while(node != NULL)
	    {
		EDVBrowserDirTreeFindNodeByIndexIterate(
		    ctree,
		    node,
		    device_index, index,
		    matched_node
		);
		if(*matched_node != NULL)
		    return;

		/* Get the sibling */
		row_ptr = GTK_CTREE_ROW(node);
		if(row_ptr == NULL)
		    break;

		node = row_ptr->sibling;
	    }
	}
}

/*
 *	Matches the Directory Tree node who's object matches the
 *	location index.
 *
 *	The device_index specifies the index of the device that the
 *	object reside son.
 *
 *	The index specifies the index on the device that the object
 *	resides at.
 *
 *	Returns the matched node or NULL on error.
 */
GtkCTreeNode *EDVBrowserDirTreeFindNodeByIndex(
	edv_browser_struct *browser,                  
	const gulong device_index, const gulong index
)
{
	GtkCTreeNode *node, *matched_node;
	GtkCTree *ctree;

	if(browser == NULL)
	    return(NULL);

	ctree = (GtkCTree *)browser->directory_ctree;
	if(ctree == NULL)
	    return(NULL);

	/* Get the toplevel node to start the search at */
	node = EDVBrowserDirTreeGetToplevel(browser);

	/* Begin the search */
	matched_node = NULL;
	EDVBrowserDirTreeFindNodeByIndexIterate(
	    ctree,
	    node,
	    device_index, index,
	    &matched_node
	);

	return(matched_node);
}

/*
 *	Performs one iteration of finding the Directory Tree node by
 *	path.
 */
static void EDVBrowserDirTreeFindNodeByPathIterate(
	GtkCTree *ctree,
	GtkCTreeNode *node,
	const gchar *path,
	GtkCTreeNode **matched_node
)
{
	const gchar *cur_path;
	edv_object_struct *obj;

	if((*matched_node != NULL) || (node == NULL))
	    return;

	obj = EDV_OBJECT(
	    gtk_ctree_node_get_row_data(ctree, node)
	);

	/* Get the Object's full path */
	cur_path = (obj != NULL) ? obj->full_path : NULL;
	if(STRISEMPTY(cur_path))
	    return;

	/* Is the specified node's path a prefix of the specified path? */
	if(EDVIsParentPath(cur_path, path))
	{
	    /* Yes it is, now check if it is a complete match */
	    if(!strcmp(path, cur_path))
	    {
		*matched_node = node;
		return;
	    }
	    else
	    {
		/* Not a complete match, check the child nodes */
		node = EDVNodeGetChild(node);
		while(node != NULL)
		{
		    EDVBrowserDirTreeFindNodeByPathIterate(
			ctree,
			node,
			path,
			matched_node
		    );
		    if(*matched_node != NULL)
		        return;

		    /* Get the sibling */
		    node = EDVNodeGetSibling(node);
		}
	    }
	}
}

/*
 *	Matches the Directory Tree node who's object matches the
 *	path.
 *
 *	The path specifies the full path of the object.
 *
 *	Returns the matched node or NULL on error.
 */
GtkCTreeNode *EDVBrowserDirTreeFindNodeByPath(
	edv_browser_struct *browser, const gchar *path
)
{
	gchar *full_path;
	GtkCTreeNode *node, *matched_node;
	GtkCTree *ctree;
	GtkWidget *w = (browser != NULL) ? browser->directory_ctree : NULL;
	if((w == NULL) || STRISEMPTY(path))
	    return(NULL);

	ctree = GTK_CTREE(w);

	/* Path must be absolute */
	if(!g_path_is_absolute(path))
	    return(NULL);

	/* Make a copy of the given path as the full path */
	full_path = STRDUP(path);
	if(full_path == NULL)
	    return(NULL);

	/* Strip tailing deliminators */
	StripPath(full_path);

	/* Get the toplevel node to start search from */
	node = EDVBrowserDirTreeGetToplevel(browser);

	/* Begin searching for a node who's object's fuil path matches
	 * the specified path
	 *
	 * matched_node_ptr will point to the matched node if a match
	 * was made
	 */
	matched_node = NULL;
	EDVBrowserDirTreeFindNodeByPathIterate(
	    ctree,
	    node,
	    full_path,
	    &matched_node
	);

	g_free(full_path);

	return(matched_node);
}


/*
 *	Procedure to `open with' the object listed on the given
 *	browser's directory ctree specified by node.
 */
void EDVBrowserDirTreeDoOpenWithObject(
	edv_browser_struct *browser, GtkCTreeNode *node
)
{
	const gchar *name;
	gchar *full_path;
	struct stat stat_buf;
	GtkWidget *w, *toplevel;
	GtkCTree *ctree;
	edv_object_struct *obj;
	edv_core_struct *core;

	if((browser == NULL) || (node == NULL))
	    return;

	toplevel = browser->toplevel;
	w = browser->directory_ctree;
	core = browser->core;
	if((w == NULL) || (core == NULL))
	    return;

	ctree = GTK_CTREE(w);

	obj = EDV_OBJECT(gtk_ctree_node_get_row_data(ctree, node));
	if(obj == NULL)
	    return;

	/* Check name of object for special notations */
	name = obj->name;
	if(name != NULL)
	{
	    if(!strcmp(name, "."))
	    {
		full_path = NULL;
	    }
	    else if(!strcmp(name, ".."))
	    {
		full_path = g_dirname(
		    EDVBrowserCurrentLocation(browser)
		);
	    }
	    else
	    {
		full_path = STRDUP(obj->full_path);
	    }
	}
	else
	{
	    /* No name available, get copy of full path */
	    full_path = STRDUP(obj->full_path);
	}
	if(full_path == NULL)
	    return;

	/* Get stats of destination object, the object structure may
	 * have that information already but we want to ensure we have
	 * the most up to date information
	 */
	if(stat(full_path, &stat_buf))
	{
	    g_free(full_path);
	    return;
	}

	if(TRUE)
	{
	    gchar       *stdout_path_rtn = NULL,
			*stderr_path_rtn = NULL;
	    GList *paths_list = NULL;

	    paths_list = g_list_append(
		paths_list,
		STRDUP(full_path)
	    );

	    EDVOpenWith(
		core,
		paths_list,		/* Paths List */
		NULL,			/* Command Name */
		toplevel,		/* Toplevel */
		TRUE,			/* Verbose */
		&stdout_path_rtn,
		&stderr_path_rtn
	    );

	    g_list_foreach(paths_list, (GFunc)g_free, NULL);
	    g_list_free(paths_list);

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}

	g_free(full_path);
}


/*
 *	FPrompt rename callback.
 */
static void EDVBrowserDirTreeFPromptRenameApplyCB(
	gpointer data, const gchar *value
)
{
	gpointer *cb_data = (gpointer *)data;
	edv_browser_struct *browser;
	GtkCTree *ctree;
	GtkCTreeNode *node;
	if(cb_data == NULL)
	    return;

	/* Get callback data */
	browser = EDV_BROWSER(cb_data[0]);
	ctree = (GtkCTree *)cb_data[1];
	node = (GtkCTreeNode *)cb_data[2];

	/* Inputs valid? */
	if((browser != NULL) && (ctree != NULL) && (node != NULL) &&
	   (value != NULL)
	)
	{
	    GtkWidget *toplevel = browser->toplevel;
	    edv_core_struct *core = browser->core;
	    edv_object_struct *obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );
	    if((obj != NULL) ? (obj->full_path != NULL) : FALSE)
	    {
		gboolean yes_to_all = FALSE;
		const gchar *error_msg;
		gchar *old_full_path = STRDUP(obj->full_path);
		GList *modified_paths_list;

		/* Rename */
		EDVObjectOPRename(
		    core,
		    old_full_path, value,
		    &modified_paths_list,
		    toplevel,
		    FALSE,			/* Do not show progress */
		    TRUE,			/* Interactive */
		    &yes_to_all
		);

		/* Unmap progress dialog, it may have been mapped in the
		 * above operation
		 */
		ProgressDialogBreakQuery(FALSE);
		ProgressDialogSetTransientFor(NULL);

		/* Get error message if any that might have occured in the
		 * above operation
		 */
		error_msg = EDVObjectOPGetError();
		if(!STRISEMPTY(error_msg))
		{
		    EDVPlaySoundError(core);
		    EDVMessageObjectOPError(
			"Rename Object Error",
			error_msg,
			old_full_path,
			toplevel
		    );
		}

		/* Report the modified objects */
		if(modified_paths_list != NULL)
		{
		    struct stat lstat_buf;
		    const gchar *modified_path;
		    GList *glist;

		    for(glist = modified_paths_list;
			glist != NULL;
			glist = g_list_next(glist)
		    )
		    {
			modified_path = (const gchar *)glist->data;
			if(modified_path == NULL)
			    continue;

			if(!lstat(modified_path, &lstat_buf))
			{
			    gchar *s = g_strdup_printf(
				"Object \"%s\" renamed to \"%s\"",
				g_basename(old_full_path),
				g_basename(modified_path)
			    );
			    EDVStatusBarMessage(browser->status_bar, s, FALSE);
			    g_free(s);

			    EDVObjectModifiedEmit(
				core,
				old_full_path,
				modified_path,
				&lstat_buf
			    );
			}
		    }

		    g_list_foreach(modified_paths_list, (GFunc)g_free, NULL);
		    g_list_free(modified_paths_list);
		}
		else
		{
		    /* Did not get the modified object path so this
		     * implies failure
		     */
		    EDVStatusBarMessage(
			browser->status_bar,
			"Rename object failed",
			FALSE
		    );
		}

		g_free(old_full_path);
	    }
	}

	g_free(cb_data);
}

/*
 *	Maps the FPrompt over the File Browser directory tree item
 *	to rename.
 */
void EDVBrowserDirTreeDoFPromptRename(
	edv_browser_struct *browser, GtkCTreeNode *node
)
{
	gint row, column;
	gint cx, cy, px, py, pwidth, pheight;
	GtkWidget *w, *toplevel;
	GtkCList *clist;
	GtkCTree *ctree;
	edv_object_struct *obj;
	edv_core_struct *core;

	if((browser == NULL) || (node == NULL) || FPromptIsQuery())
	    return;

	toplevel = browser->toplevel;
	w = browser->directory_ctree;
	core = browser->core;
	if((w == NULL) || (core == NULL))
	    return;

	ctree = GTK_CTREE(w);
	clist = GTK_CLIST(ctree);

	/* Check and warn if write protect is enabled */
	if(EDVCheckWriteProtect(
	    core, TRUE, toplevel
	))
	    return;

	EDVBrowserSyncData(browser);

	/* Get row and column of the given node */
	if(!EDVNodeGetIndex(ctree, node, &row, &column))
	    return;

	/* Get clist cell geometry */
	if(!gtk_clist_get_cell_geometry(
	    clist, column, row,
	    &cx, &cy, &pwidth, &pheight
	))
	    return;

	/* Get root window relative coordinates */
	px = 0;
	py = 0;
	gdk_window_get_deskrelative_origin(
	    clist->clist_window, &px, &py
	);
/*	px += cx + 0; */
	py += cy - 2;	/* Move up a little */

	/* Modify intended prompt width to match width of ctree */
	pwidth = w->allocation.width;


	/* Get object from the directory ctree node */
	obj = EDV_OBJECT(
	    gtk_ctree_node_get_row_data(ctree, node)
	);
	if(obj == NULL)
	    return;

	/* Check if object name is a special notation that should not
	 * be allowed to be renamed
	 */
	if(obj->name != NULL)
	{
	    const gchar *name = obj->name;
	    if(!strcmp(name, ".") || !strcmp(name, "..") ||
	       !strcmp(name, "/")
	    )
		return;
	}

	if(TRUE)
	{
	    gchar *value = STRDUP(obj->name);

	    /* Create callback data */
	    gpointer *cb_data = (gpointer *)g_malloc0(
		3 * sizeof(gpointer)
	    );
	    cb_data[0] = browser;
	    cb_data[1] = ctree;
	    cb_data[2] = node;

	    /* Map floating prompt to change values */
	    FPromptSetTransientFor(browser->toplevel);
	    FPromptSetPosition(px, py);
	    FPromptMapQuery(
		NULL,			/* No label */
		value,
		NULL,			/* No tooltip message */
		FPROMPT_MAP_NO_MOVE,	/* Map code */
		pwidth, -1,		/* Width and height */
		0,			/* No buttons */
		cb_data,		/* Callback data */
		NULL,			/* No browse callback */
		EDVBrowserDirTreeFPromptRenameApplyCB,
		EDVBrowserDirTreeFPromptCancelCB
	    );

	    g_free(value);
	}
}


/*
 *	This should be called whenever a new object has been added, it
 *	will add a new tree node as needed to represent the new object.
 *
 *	The given path must be an absolute path to the object.
 */
void EDVBrowserDirTreeObjectAddedNotify(
	edv_browser_struct *browser, const gchar *path,
	const struct stat *lstat_buf
)
{
	struct stat stat_buf, parent_stat_buf;
	gchar *parent_path;
	GtkCList *clist;
	GtkCTreeNode *node, *parent_node;
	GtkCTree *ctree;
	edv_core_struct *core;

	if((browser == NULL) || STRISEMPTY(path) || (lstat_buf == NULL))
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	/* Get the added object's destination statistics */
	if(stat(path, &stat_buf))
	    return;

	/* Skip if the added object's destination is not a directory */
#ifdef S_ISDIR
	if(!S_ISDIR(stat_buf.st_mode))
#else
	if(TRUE)
#endif
	    return;

	/* Check if the added object is already listed */
	node = EDVBrowserDirTreeFindNodeByIndex(
	    browser,
	    (gulong)lstat_buf->st_dev,
	    (gulong)lstat_buf->st_ino
	);
	if(node != NULL)
	{
	    /* A node for the added object already exists, update
	     * this node and its object
	     */
	    edv_object_struct *obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );
	    if(obj != NULL)
	    {
		EDVObjectSetPath(obj, path);
		EDVObjectSetStat(obj, lstat_buf);
		EDVObjectUpdateLinkFlags(obj);

		gtk_clist_freeze(clist);

		/* Update the node */
		EDVBrowserDirTreeSetNode(
		    core, browser, ctree,
		    node,
		    obj
		);

		/* Get new listing of child objects for the updated
		 * node, recurse no more than 2 levels
		 */
		EDVBrowserDirTreeDoGetChildrenList(
		    core, browser, ctree,
		    node,			/* Parent node */
		    TRUE, TRUE
		);

		gtk_clist_thaw(clist);
	    }
	    return;
	}

	/* Get the added object's parent */
	parent_path = g_dirname(path);
	if(parent_path == NULL)
	    return;

	/* Get the parent's local statistics */
	if(lstat(parent_path, &parent_stat_buf))
	{
	    g_free(parent_path);
	    return;
	}

	/* Get the parent node of the added object */
	parent_node = EDVBrowserDirTreeFindNodeByIndex(
	    browser,
	    (gulong)parent_stat_buf.st_dev,
	    (gulong)parent_stat_buf.st_ino
	);
	if(parent_node != NULL)
	{
	    /* Create the new node for the added object */
	    edv_object_struct *obj = EDVObjectNew();
	    if(obj != NULL)
	    {
		EDVObjectSetPath(obj, path);
		EDVObjectSetStat(obj, lstat_buf);
		EDVObjectUpdateLinkFlags(obj);

		gtk_clist_freeze(clist);

		/* Add/transfer the object to the tree */
		node = EDVBrowserDirTreeInsertNode(
		    core, browser, ctree,
		    parent_node, NULL,
		    obj
		);
		if(node != NULL)
		{
		    /* Get new listing of child objects for the new
		     * node, recursing no more than 2 levels
		     */
		    EDVBrowserDirTreeDoGetChildrenList(
			core, browser, ctree,
			node,			/* Parent node */
			TRUE, TRUE
		    );
		}

		gtk_clist_thaw(clist);
	    }
	}

	g_free(parent_path);
}

/*
 *	This should be called whenever a object has been modified, it will
 *	search for the object and then reupdate the matching node.
 *
 *	The given path must be an absolute path to the object and must
 *	be the path of the object's original name. The new_path must be
 *	an absolute path to the object at its new name, the new_path may
 *	be NULL if there was no name change.
 */
void EDVBrowserDirTreeObjectModifiedNotify(
	edv_browser_struct *browser,
	const gchar *path, const gchar *new_path,
	const struct stat *lstat_buf
)
{
	struct stat stat_buf;
	gint stat_result;
	GtkCList *clist;
	GtkCTreeNode *node;
	GtkCTree *ctree;
	edv_core_struct *core;
	if((browser == NULL) || STRISEMPTY(path) || (lstat_buf == NULL))
	    return;

	if(new_path == NULL)
	    new_path = path;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	/* Get the modified object's destination statistics */
	stat_result = (gint)stat((const char *)new_path, &stat_buf);

	/* Find the node who's object matches the modified object */
	node = EDVBrowserDirTreeFindNodeByIndex(
	    browser,
	    (gulong)lstat_buf->st_dev,
	    (gulong)lstat_buf->st_ino
	);

#ifdef S_ISDIR
	/* Is the destination invalid or it does not lead to a
	 * directory?
	 */
	if((stat_result == 0) ? !S_ISDIR(stat_buf.st_mode) : TRUE)
	{
	    /* The destination either no longer exists or is not
	     * a directory, remove the node that refer to this
	     * object (if any)
	     */
	    if(node != NULL)
	    {
		gtk_clist_freeze(clist);
		gtk_ctree_remove_node(ctree, node);
		gtk_clist_thaw(clist);
	    }
	    return;
	}
#endif	/* S_ISDIR */

	/* Is there a node that refers to the modified object? */
	if(node != NULL)
	{
	    /* Update the object */
	    edv_object_struct *obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );
	    if(obj != NULL)
	    {
		EDVObjectSetPath(obj, new_path);
		EDVObjectSetStat(obj, lstat_buf);
		EDVObjectUpdateLinkFlags(obj);

		gtk_clist_freeze(clist);

		/* Update the node */
		EDVBrowserDirTreeSetNode(
		    core, browser, ctree,
		    node,
		    obj
		);

		/* Get new listing of child objects for the updated
		 * node, recurse no more than 2 levels
		 */
		EDVBrowserDirTreeDoGetChildrenList(
		    core, browser, ctree,
		    node,
		    TRUE, TRUE
		);

		gtk_clist_thaw(clist);
	    }
	}
	else
	{
	    /* No existing node matches the old path so check if an
	     * existing node matches the parent of the new path
	     */
	    struct stat parent_stat_buf;
	    gchar *new_parent_path = g_dirname(new_path);
	    if((new_parent_path != NULL) ?
		!stat(new_parent_path, &parent_stat_buf) : FALSE
	    )
	    {
		GtkCTreeNode *parent_node = EDVBrowserDirTreeFindNodeByIndex(
		    browser,
		    (gulong)parent_stat_buf.st_dev,
		    (gulong)parent_stat_buf.st_ino
		);
		if(parent_node != NULL)
		{
		    /* Create a node for the modified object */
		    edv_object_struct *obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, new_path);
			EDVObjectSetStat(obj, lstat_buf);
			EDVObjectUpdateLinkFlags(obj);

			gtk_clist_freeze(clist);

			/* Add/transfer the object to the tree */
			node = EDVBrowserDirTreeInsertNode(
			    core, browser, ctree,
			    parent_node, NULL,
			    obj
			);
			if(node != NULL)
			{
			    /* Get new listing of child disk objects
			     * for the new node, recurse no more than 2
			     * levels
			     */
			    EDVBrowserDirTreeDoGetChildrenList(
				core, browser, ctree,
				node,		/* Parent node */
				TRUE, TRUE
			    );
			}

			gtk_clist_thaw(clist);
		    }
		}
	    }
	    g_free(new_parent_path);
	}
}

/*
 *	This should be called whenever a object has been removed, it will
 *	search for the object and then remove the matching node.
 *
 *	The given path must be an absolute path to the object.
 */
void EDVBrowserDirTreeObjectRemovedNotify(
	edv_browser_struct *browser, const gchar *path
)
{
	GtkCList *clist;
	GtkCTreeNode *node;
	GtkCTree *ctree;
	edv_core_struct *core;

	if((browser == NULL) || STRISEMPTY(path))
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);
 
	/* Remove any node that matches the removed path */
	node = EDVBrowserDirTreeFindNodeByPath(browser, path);
	if(node != NULL)
	{
	    gtk_clist_freeze(clist);
	    gtk_ctree_remove_node(ctree, node);
	    gtk_clist_thaw(clist);
	}
}


/*
 *	This should be called whenever a object has been mounted or
 *	unmounted.
 */
void EDVBrowserDirTreeMountNotify(
	edv_browser_struct *browser, edv_device_struct *dev,
	gboolean is_mounted
)
{
	gchar *mount_path;
	GtkCList *clist;
	GtkCTreeNode *node;
	GtkCTree *ctree;
	edv_core_struct *core;

	if((browser == NULL) || (dev == NULL))
	    return;

	ctree = (GtkCTree *)browser->directory_ctree;
	core = browser->core;
	if((ctree == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(ctree);

	/* Get the mount path */
	mount_path = STRDUP(dev->mount_path);
	if(mount_path == NULL)
	    return;

	/* Simplify mount path */
	EDVSimplifyPath(mount_path);

	/* Find the node that matches this mount path */
	node = EDVBrowserDirTreeFindNodeByPath(browser, mount_path);
	if(node != NULL)
	{
	    /* Found existing node that matches this mount path */
	    struct stat lstat_buf;
	    edv_object_struct *obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );
	    if((obj != NULL) && !lstat(mount_path, &lstat_buf))
	    {
		/* Update the object */
		EDVObjectSetPath(obj, mount_path);
		EDVObjectSetStat(obj, &lstat_buf);
		EDVObjectUpdateLinkFlags(obj);

		gtk_clist_freeze(clist);

		/* Update the node */
		EDVBrowserDirTreeSetNode(
		    core, browser, ctree,
		    node,
		    obj
		);

		/* Get new listing of child objects for the updated
		 * node, recurse no more than 2 levels
		 */
		EDVBrowserDirTreeDoGetChildrenList(
		    core, browser, ctree,
		    node,			/* Parent node */
		    TRUE, TRUE
		);

		gtk_clist_thaw(clist);
	    }
	}

	/* Unmounted? */
	if(!is_mounted)
	{
	    /* Is the current location within the mount path? */
	    gchar *cur_loc = STRDUP(EDVBrowserCurrentLocation(browser));
	    if((cur_loc != NULL) ? strpfx(cur_loc, mount_path) : FALSE)
	    {
		/* Select the unmounted device's node as needed */
		GtkCTreeNode *cur_node = EDVBrowserDirTreeFindNodeByPath(
		    browser, cur_loc
		);
		if((node != NULL) && (node != cur_node))
		{
		    gtk_clist_freeze(clist);
		    gtk_ctree_select(ctree, node);
		    gtk_clist_thaw(clist);
		}
	    }
	    g_free(cur_loc);
	}

	g_free(mount_path);
}
