#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 "edvtypes.h"
#include "cfg.h"
#include "edvobj.h"
#include "edvfop.h"
#include "browser.h"
#include "browsercb.h"
#include "browserdirtree.h"
#include "endeavour.h"
#include "edvcb.h"
#include "edvopen.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


static void EDVBrowserDirTreeFPromptCancelCB(gpointer data);

static void EDVBrowserDirTreeSetNode(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCTree *ctree, GtkCTreeNode *node, edv_object_struct *obj
);

GtkCTreeNode *EDVBrowserDirTreeGetToplevel(edv_browser_struct *browser);

GtkCTreeNode *EDVBrowserDirTreeInsertNode(
	edv_browser_struct *browser,
	GtkCTreeNode *parent, GtkCTreeNode *sibling,
	edv_object_struct *obj
);

void EDVBrowserDirTreeCreateToplevels(edv_browser_struct *browser);

void EDVBrowserDirTreeSetOriginPath(
	edv_browser_struct *browser, const gchar *path
);


static void EDVBrowserDirTreeDoGetChildrenListIterate(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCTree *ctree, GtkCTreeNode *parent,
	gboolean update_progress, gboolean recurse,
	gboolean hide_object_hidden, gboolean hide_object_noaccess
);
static void EDVBrowserDirTreeDoGetChildrenList(
	edv_browser_struct *browser, GtkCTreeNode *parent,
	gboolean update_progress, gboolean recurse
);

void EDVBrowserDirTreeDoRefresh(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
);
void EDVBrowserDirTreeDoExpand(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
);

static void EDVBrowserDirTreeDoSyncIterate(
	edv_browser_struct *browser, GtkCTree *ctree,
	GtkCTreeNode *node,
	gboolean parent_is_expanded
);
void EDVBrowserDirTreeDoSync(
	edv_browser_struct *browser, GtkCTreeNode *node
);

static void EDVBrowserDirTreeDoSelectPathIterate(
	GtkCTree *ctree, GtkCTreeNode *node, const gchar *path,
	GtkCTreeNode **matched_node_ptr
);
void EDVBrowserDirTreeDoSelectPath(
	edv_browser_struct *browser, const gchar *path
);

static void EDVBrowserDirTreeFindNodeByPathIterate(
	GtkCTree *ctree, GtkCTreeNode *node,
	const gchar *path, GtkCTreeNode **matched_node_ptr
);
GtkCTreeNode *EDVBrowserDirTreeFindNodeByPath(
	edv_browser_struct *browser, const gchar *path
);

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

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

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_name,
	const struct stat *lstat_buf
);
void EDVBrowserDirTreeObjectRemovedNotify(
	edv_browser_struct *browser, const gchar *path
);

void EDVBrowserDirTreeMountNotify(
	edv_browser_struct *browser, edv_device_struct *dev_ptr,
	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)


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

	g_free(cb_data);
}


/*
 *	Sets the node's icon, text, and style.
 */
static void EDVBrowserDirTreeSetNode(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCTree *ctree, GtkCTreeNode *node, edv_object_struct *obj
)
{
	const gchar *text = obj->name;
	GtkStyle *style = NULL;
	GtkCTreeRow *row_ptr;
	GdkPixmap	*pixmap,
			*pixmap_exp,
			*pixmap_ext,
			*pixmap_hid;
	GdkBitmap	*mask,
			*mask_exp,
			*mask_ext,
			*mask_hid;

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

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

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

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

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

	/* Get pointer ctree row from the given node */
	row_ptr = GTK_CTREE_ROW(node);
	if(row_ptr == NULL)
	    return;

	/* Update text? */
	if(text != NULL)
	    gtk_ctree_set_node_info(
		ctree, node, text,
		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 File Browser's Directory Tree to the
 *	specified parent node after the specified sibling node.
 *
 *	If parent is NULL then the new node will be a toplevel node.
 *
 *	If sibling is NULL then the new node will be appended to the
 *	parent's list of child nodes.
 *
 *	The specified object will be transfered and should not be
 *	referenced after this call.
 */
GtkCTreeNode *EDVBrowserDirTreeInsertNode(
	edv_browser_struct *browser,
	GtkCTreeNode *parent, GtkCTreeNode *sibling,
	edv_object_struct *obj
)
{
	gint i, columns;
	gchar **strv;
	GtkCTreeNode *node;
	GtkCList *clist;
	GtkCTree *ctree;
	edv_core_struct *core_ptr;
	GtkWidget *w = (browser != NULL) ? browser->directory_ctree : NULL;
	if((w == NULL) || (obj == NULL))
	{
	    EDVObjectDelete(obj);
	    return(NULL);
	}

	clist = GTK_CLIST(w);
	ctree = GTK_CTREE(w);
	core_ptr = EDV_CORE(browser->core_ptr);
	if(core_ptr == NULL)
	{
	    EDVObjectDelete(obj);
	    return(NULL);
	}

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

	/* Allocate node name values */
	strv = (gchar **)g_malloc(columns * sizeof(gchar *));
	for(i = 0; i < columns; i++)
	    strv[i] = "";

	/* Insert 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 node name values */
	g_free(strv);

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

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

	/* Set the new node's row data as the specified Object, which
	 * should not be referenced again after this call
	 */
	gtk_ctree_node_set_row_data_full(
	    ctree, node,
	    obj, EDVBrowserDirTreeItemDestroyCB
	);

	return(node);
}


/*
 *	Creates the File Browser Directory Tree's toplevel nodes.
 *
 *	Any existing nodes will be deleted.
 */
void EDVBrowserDirTreeCreateToplevels(edv_browser_struct *browser)
{
	const gchar *path;
	edv_object_struct *obj;
	GtkCTreeNode *node;
	struct stat lstat_buf;
	GtkCTree *ctree = (browser != NULL) ?
	    (GtkCTree *)browser->directory_ctree : NULL;
	if(ctree == NULL)
	    return;

	/* Mark as busy but do not mark as processing since callbacks
	 * need to see things as not processing when nodes are expanded
	 * in EDVBrowserDirTreeDoRefresh()
	 */
	EDVBrowserSetBusy(browser, TRUE);

	/* Remove existing toplevel nodes (if any) */
	node = EDVBrowserDirTreeGetToplevel(browser);
	if(node != NULL)                             
	    gtk_ctree_remove_node(ctree, node);


	/* Get toplevel origin path */
	path = browser->directory_ctree_origin_path;
	if(STRISEMPTY(path))
	    path = "/";

	/* Begin creating toplevel nodes */

	/* Create object for toplevel node */
	obj = EDVObjectNew();
	if(!lstat(path, &lstat_buf))
	{
	    EDVObjectSetPath(obj, path);
	    EDVObjectSetStat(obj, &lstat_buf);
	    obj->link_valid = TRUE;		/* Assume valid */
	}
	node = EDVBrowserDirTreeInsertNode(
	    browser, NULL, NULL, obj
	);
	obj = NULL;

	/* Created toplevel node? */
	if(node != NULL)
	{
	    /* Get one level of nodes from this toplevel node */
	    EDVBrowserDirTreeDoGetChildrenList(
		browser, node, TRUE, FALSE
	    );
	}

	EDVBrowserSetBusy(browser, FALSE);
}

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

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

	ctree = GTK_CTREE(browser->directory_ctree);
	clist = GTK_CLIST(ctree);

	/* No change? */
	if(path == browser->directory_ctree_origin_path)
	    return;
	if((browser->directory_ctree_origin_path != NULL) ?
	    !strcmp(browser->directory_ctree_origin_path, path) : FALSE
	)
	    return;

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

	/* Delete all existing nodes and recreate toplevels on the
	 * Directory Tree
	 */
	gtk_clist_freeze(clist);
	EDVBrowserDirTreeCreateToplevels(browser);
	gtk_clist_thaw(clist);

	/* Select and expand toplevel node (do not freeze) */
	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.
 */
static void EDVBrowserDirTreeDoGetChildrenListIterate(
	edv_core_struct *core_ptr, edv_browser_struct *browser,
	GtkCTree *ctree, GtkCTreeNode *parent,
	gboolean update_progress, gboolean recurse,
	gboolean hide_object_hidden, 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 = EDVGetPathName(parent_path);
	    gchar *buf = g_strdup_printf(
		"Scanning directory: %s",
		parent_name
	    );
	    EDVStatusBarMessage(
		browser->status_bar, buf, FALSE
	    );
	    g_free(buf);
	}


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

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

	    /* Iterate through each string */
	    child_path = NULL;
	    for(i = 0; i < strc; i++)
	    {
#define FREE_AND_CONTINUE	{	\
 g_free(child_path);			\
 child_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 */
		child_path = STRDUP(PrefixPaths(parent_path, s));
		if(child_path == NULL)
		    FREE_AND_CONTINUE

		/* Get local and destination stats of child object */
		if(lstat(child_path, &lstat_buf))
		    FREE_AND_CONTINUE
		if(stat(child_path, &stat_buf))
		    FREE_AND_CONTINUE


		/* Destination child object is a directory? */
		if(S_ISDIR(stat_buf.st_mode))
		{
		    GtkCTreeNode *node;

		    /* Create new object */
		    obj = EDVObjectNew();
		    if(obj != NULL)
		    {
			EDVObjectSetPath(obj, child_path);
			EDVObjectSetStat(obj, &lstat_buf);
			obj->link_valid = TRUE;

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


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

		    /* Recurse into sub directories? */
		    if(recurse && (node != NULL))
		    {
			EDVBrowserDirTreeDoGetChildrenListIterate(
			    core_ptr, 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 for the specified parent node, any
 *	existing child nodes in the specified parent node will be
 *	deleted.
 *
 *	This will recurse no more than two levels from the specified
 *	parent node or not recurse at all of recurse is FALSE.
 *
 *	Inputs assumed valid.
 */
static void EDVBrowserDirTreeDoGetChildrenList(
	edv_browser_struct *browser, GtkCTreeNode *parent,
	gboolean update_progress, gboolean recurse
)
{
	gboolean hide_object_hidden, hide_object_noaccess;
	const cfg_item_struct *cfg_list;
	GtkCTreeNode *node, *sibling_node;
	GtkCTree *ctree = GTK_CTREE(browser->directory_ctree);
	edv_core_struct *core_ptr = EDV_CORE(browser->core_ptr);
	if((parent == NULL) || (core_ptr == NULL))
	    return;

	cfg_list = core_ptr->cfg_list;

	/* Get 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 initial progress */
	if(update_progress)
	{
	    EDVStatusBarMessage(
		browser->status_bar,
		"Scanning directories...",
		FALSE
	    );
	    EDVStatusBarProgress(browser->status_bar, -1.0, TRUE);
	}

	/* First 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 child node for the specified parent node */
	EDVBrowserDirTreeDoGetChildrenListIterate(
	    core_ptr, browser, ctree, parent,
	    update_progress, recurse,
	    hide_object_hidden, hide_object_noaccess
	);

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


/*
 *	Refreshes the File Browser's Directory Tree by refetching the
 *	list of child nodes from the specified node and recursing no
 *	more than 2 levels.
 *
 *	All nodes will have their referenced object checked to see if
 *	it exists, if the object does not exist then the node is
 *	removed.
 *
 *	The specified node may be removed by this call so it should not
 *	be referenced again.
 */
void EDVBrowserDirTreeDoRefresh(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
)
{
	GtkCTree *ctree = (browser != NULL) ?
	    (GtkCTree *)browser->directory_ctree : NULL;
	if(ctree == NULL)
	    return;

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

	/* Remove all child nodes of the specified node and then
	 * refetch all child nodes, recursing to at most 2 levels
	 */
	if(node != NULL)
	    EDVBrowserDirTreeDoGetChildrenList(
		browser, node, update_progress, TRUE
	    );

	/* Check all nodes (including the given node) to see if the
	 * referenced object
	 *
	 * Any node that references an object that does not exist will
	 * be removed, so the given node should not be referenced
	 * after this call
	 *
	 * All child nodes will be updated on the directory ctree
	 */
	EDVBrowserDirTreeDoSync(browser, NULL);
}

/*
 *	Procedure to create child nodes parented to the given node, each
 *	child node will be a child directory or a link that points to a
 *	directory in the directory specified by the given node.
 */
void EDVBrowserDirTreeDoExpand(
	edv_browser_struct *browser, GtkCTreeNode *node,
	gboolean update_progress
)
{
	if((browser == NULL) || (node == NULL))
	    return;

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


/*
 *	Called by EDVBrowserDirTreeDoSync() to update nodes and
 *	remove non-existant nodes.
 */
static void EDVBrowserDirTreeDoSyncIterate(
	edv_browser_struct *browser, GtkCTree *ctree,
	GtkCTreeNode *node,
	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 */
		EDVBrowserDirTreeDoSyncIterate(
		    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(
			browser, node, FALSE, FALSE
		    );
	    }

	    /* Get this node's object */
	    obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );

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

	    /* Got Object and its full path is specified? */
	    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? */
		    else if(!S_ISDIR(stat_buf.st_mode))
		    {
			/* 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);
			obj->link_valid = TRUE;
			EDVBrowserDirTreeSetNode(
			    EDV_CORE(browser->core_ptr),
			    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.
 */
void EDVBrowserDirTreeDoSync(
	edv_browser_struct *browser, GtkCTreeNode *node
)
{
	GtkCTreeRow *row_ptr;
	edv_core_struct *core_ptr;
	GtkCTree *ctree = (browser != NULL) ?
	    (GtkCTree *)browser->directory_ctree : NULL;
	if(ctree == NULL)
	    return;

	core_ptr = EDV_CORE(browser->core_ptr);
	if(core_ptr == NULL)
	    return;

	/* 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);

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


/*
 *	Called by EDVBrowserDirTreeDoSelectPath() 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 EDVBrowserDirTreeDoSelectPathIterate(
	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 */
		    EDVBrowserDirTreeDoSelectPathIterate(
			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 EDVBrowserDirTreeDoSelectPath(
	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)
	 */
	EDVBrowserDirTreeDoSelectPathIterate(
	    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);
}

/*
 *	Called by EDVBrowserDirTreeFindNodeByPath() or itself to check
 *	child nodes of the given node, recursing if a path prefix match
 *	is made
 *
 *	If a perfect match is made *matched_node_ptr will be set to TRUE
 */
static void EDVBrowserDirTreeFindNodeByPathIterate(
	GtkCTree *ctree, GtkCTreeNode *node,
	const gchar *path, GtkCTreeNode **matched_node_ptr
)
{
	const gchar *cur_path;
	edv_object_struct *obj;

	/* Already matched node? */
	if((matched_node_ptr != NULL) ? (*matched_node_ptr != NULL) : TRUE)
	    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 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))
	    {
		/* Node's path and specified path match completely, so
		 * mark that we found the node
		 */
		if(matched_node_ptr != NULL)
		    *matched_node_ptr = node;
		return;
	    }
	    else
	    {
		/* Not a complete match but the node's path is a prefix
		 * of the specified path, so this means we are on the
		 * right track
		 *
	         * Recurse into this node's children and continue the 
		 * search
		 */

		/* Get child node of this node and continue search */
		node = EDVNodeGetChild(node);
		while(node != NULL)
		{
		    /* Check this node, recurse if it is a match */
		    EDVBrowserDirTreeFindNodeByPathIterate(
			ctree, node, path, matched_node_ptr
		    );
		    /* Got match? If so then we should not continue further */
		    if((matched_node_ptr != NULL) ?
			(*matched_node_ptr != NULL) : TRUE
		    )
		        return;

		    /* Get this child node's sibling */
		    node = EDVNodeGetSibling(node);
		}
	    }
	}
}

/*
 *	Searches for a node on the browser's directory ctree that matches
 *	the full path specified by path.
 *
 *	Will not expand or modify any nodes.
 *
 *	Can return NULL on failed match.
 */
GtkCTreeNode *EDVBrowserDirTreeFindNodeByPath(
	edv_browser_struct *browser, const gchar *path
)
{
	gchar *full_path;
	GtkCTreeNode *node, **matched_node_ptr, *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);

	/* 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 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
	 */
	EDVBrowserDirTreeFindNodeByPathIterate(
	    ctree, node, full_path, matched_node_ptr
	);

	/* Get matched node (if any) */
	matched_node = *matched_node_ptr;

	g_free(matched_node_ptr);
	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
)
{
	GtkWidget *w;
	GtkCTree *ctree;
	edv_object_struct *object;
	const gchar *name;
	gchar *full_path;
	struct stat stat_buf;


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

	w = browser->directory_ctree;
	if(w != NULL)
	    ctree = GTK_CTREE(w);
	else
	    return;

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

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

	    EDVOpenWithObjectPath(
		EDV_CORE(browser->core_ptr), full_path,
		NULL,		/* Command name */
		browser->toplevel, TRUE,
		&stdout_path_rtn, &stderr_path_rtn
	    );

	    g_free(stdout_path_rtn);
	    g_free(stderr_path_rtn);
	}

	/* Delete copy of full path */
	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_ptr = EDV_CORE(browser->core_ptr);
	    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;
		gint status;
		const gchar *error_mesg;
		gchar	*old_full_path = STRDUP(obj->full_path),
			*new_obj = NULL;

		/* Perform rename */
		status = EDVFOPRename(
		    core_ptr, old_full_path, value,
		    &new_obj, toplevel,
		    FALSE, TRUE,
		    &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_mesg = EDVFOPGetError();
		if(!STRISEMPTY(error_mesg))
		{
		    EDVPlaySoundError(core_ptr);
		    EDVMessageError(
			"Operation Error",
			error_mesg,
			NULL,
			toplevel
		    );
		}

		/* Got new object full path name (implying success)? */
		if((new_obj != NULL) && (old_full_path != NULL))
		{
		    struct stat lstat_buf;
		    const gchar	*new_child = EDVGetPathName(new_obj),
				*old_child = EDVGetPathName(old_full_path);

		    /* Get new local statistics for the renamed object */
		    if(!lstat(new_obj, &lstat_buf))
		    {
			gchar *buf = g_strdup_printf(
			    "Object \"%s\" renamed to \"%s\"",
			    old_child, new_child
			);
			EDVStatusBarMessage(
			    browser->status_bar, buf, FALSE
			);
			g_free(buf);

			/* Emit object modified signal to all windows */
			EDVObjectModifiedEmit(
			    core_ptr, old_full_path,
			    new_obj, &lstat_buf
			);
		    }
		}
		else
		{
		    /* Did not get new object path new_obj, implying failed */
		    EDVStatusBarMessage(
			browser->status_bar, "Rename object failed", FALSE
		    );
		}

#if 0
		/* The disk object structure may now be invalid if the
		 * object modified signal was emitted
		 */
		object = NULL;
#endif

		/* Delete coppies of paths */
		g_free(new_obj);
		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
)
{
	GtkWidget *w;
	GtkCList *clist;
	GtkCTree *ctree;
	edv_object_struct *object;
	gint row, column;
	gint cx, cy, px, py, pwidth, pheight;

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

	/* Check and warn if write protect is enabled */
	if(EDVCheckWriteProtect(
	    EDV_CORE(browser->core_ptr), TRUE, browser->toplevel
	))
	    return;

	w = browser->directory_ctree;
	if(w != NULL)
	    ctree = GTK_CTREE(w);
	else
	    return;

	clist = GTK_CLIST(ctree);

	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 disk object structure from the directory ctree node */
	object = EDV_OBJECT(
	    gtk_ctree_node_get_row_data(ctree, node)
	);
	if(object == NULL)
	    return;

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

	if(TRUE)
	{
	    gchar *value = STRDUP(object->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
)
{
	gchar *parent_path;
	GtkCTreeNode *node, *parent_node;
	struct stat stat_buf;
	GtkCTree *ctree = (browser != NULL) ?
	    (GtkCTree *)browser->directory_ctree : NULL;
	if((ctree == NULL) || STRISEMPTY(path) || (lstat_buf == NULL))
	    return;

	/* Check if the path goes to a directory */
	if(stat(path, &stat_buf))
	    return;
	if(!S_ISDIR(stat_buf.st_mode))
	    return;

	/* Get parent of the specified path, we need to use the parent
	 * because that is the only way we know where to create the
	 * new node in
	 */
	parent_path = g_dirname(path);
	if(parent_path == NULL)
	    return;

	/* Look for the parent node that matches the parent of the
	 * new object
	 */
	parent_node = EDVBrowserDirTreeFindNodeByPath(browser, parent_path);
	if(parent_node == NULL)
	{
	    /* Unable to find parent node, this implies we have no way
	     * to create the node for the new object
	     */
	    g_free(parent_path);
	    return;
	}

	/* Check if the node specified by path already exists */
	node = EDVBrowserDirTreeFindNodeByPath(browser, path);
	if(node != NULL)
	{
	    /* Node already exists for the given path, so just update
	     * the node by regetting child nodes and recursing at most
	     * 2 levels
	     */
	    gtk_clist_freeze(GTK_CLIST(ctree));
	    EDVBrowserDirTreeDoGetChildrenList(
		browser, node, TRUE, TRUE
	    );
	    gtk_clist_thaw(GTK_CLIST(ctree));
	}
	else
	{
	    /* Add new node for the new object */
	    edv_object_struct *obj = EDVObjectNew();
	    EDVObjectSetPath(obj, path);
	    EDVObjectSetStat(obj, lstat_buf);
	    obj->link_valid = TRUE;

	    gtk_clist_freeze(GTK_CLIST(ctree));

	    /* Add object to the direcotry tree at the parent_node
	     *
	     * The object should not be referenced after this call
	     */
	    node = EDVBrowserDirTreeInsertNode(
		browser, parent_node, NULL, obj
	    );
	    if(node != NULL)
	    {
		/* Get new listing of child objects for the new
		 * node, recurse no more than 2 levels
		 */
		EDVBrowserDirTreeDoGetChildrenList(
		    browser, node, TRUE, TRUE
		);
	    }

	    gtk_clist_thaw(GTK_CLIST(ctree));
	}

	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
)
{
	gint stat_result;
	GtkCTreeNode *node;
	struct stat stat_buf;
	GtkCTree *ctree = (browser != NULL) ? 
	    (GtkCTree *)browser->directory_ctree : NULL;
	if((ctree == NULL) || STRISEMPTY(path) || (lstat_buf == NULL))
	    return;

	/* If the new path is not given then assume it is the same as
	 * the original path
	 */
	if(new_path == NULL)
	    new_path = path;

	/* Get destination stats of the modified object */
	stat_result = stat(new_path, &stat_buf);

	/* Destination invalid or does not go to a directory? */
	if((!stat_result) ? !S_ISDIR(stat_buf.st_mode) : TRUE)
	{
	    /* Destination does not go to a directory, so we need to
	     * check if there is an existing node that matches the
	     * original path
	     *
	     * If it exists then it should no longer be listed and must
	     * be removed
	     */
	    node = EDVBrowserDirTreeFindNodeByPath(browser, path);
	    if(node != NULL)
	    {
		/* Found a node that reffers to the old path of the
		 * object who's destination is no longer a directory,
		 * remove it
		 */
		gtk_ctree_remove_node(ctree, node);
	    }
	    return;
	}

	/* Look for a node that matches the old path of the object */
	node = EDVBrowserDirTreeFindNodeByPath(browser, path);
	if(node == NULL)
	{
	    /* No existing node matches the old path, so check if an
	     * existing node matches the parent of the new path
	     */
	    gchar *new_parent_path = g_dirname(new_path);

	    /* Find node that matches the full path of the parent of
	     * the new path
	     */
	    GtkCTreeNode *parent_node = EDVBrowserDirTreeFindNodeByPath(
		browser, new_parent_path
	    );
	    if(parent_node != NULL)
	    {
		/* Modified node does not exist but its parent does,
		 * which implies we need to create a new child node for
		 * the matched parent_node
		 */
		edv_object_struct *obj = EDVObjectNew();
		EDVObjectSetPath(obj, new_path);
		EDVObjectSetStat(obj, lstat_buf);
		obj->link_valid = TRUE;

		gtk_clist_freeze(GTK_CLIST(ctree));

		/* Add node to the parent_node, this will transfer
		 * the object to the new node
		 *
		 * The given object should not be referenced again
		 */
		node = EDVBrowserDirTreeInsertNode(
		    browser, 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(
			browser, node, TRUE, TRUE
		    );
		}

		gtk_clist_thaw(GTK_CLIST(ctree));
	    }

	    g_free(new_parent_path);
	}
	else
	{
	    /* Got existing node that matches the original path, so
	     * update the node and its 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);
		obj->link_valid = TRUE;

		gtk_clist_freeze(GTK_CLIST(ctree));

		/* Update node's text, pixmap, and style with respect
		 * to the newly modified disk object
		 */
		EDVBrowserDirTreeSetNode(
		    EDV_CORE(browser->core_ptr), browser,
		    ctree, node, obj
		);

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

		gtk_clist_thaw(GTK_CLIST(ctree));
	    }
	}
}

/*
 *	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
)
{
	GtkCTreeNode *node;
	GtkCTree *ctree = (browser != NULL) ? 
	    (GtkCTree *)browser->directory_ctree : NULL;
	if((ctree == NULL) || STRISEMPTY(path))
	    return;

	/* Find node that matches the specified path */
	node = EDVBrowserDirTreeFindNodeByPath(browser, path);
	if(node == NULL)
	    return;

	/* Remove matched node and all of its child nodes */
	gtk_ctree_remove_node(ctree, node);
}


/*
 *      This should be called whenever a object has been mounted or
 *	unmounted.
 */
void EDVBrowserDirTreeMountNotify(
	edv_browser_struct *browser, edv_device_struct *dev_ptr,
	gboolean is_mounted
)
{
	gchar *mount_path;
	GtkCTreeNode *node;
	struct stat lstat_buf;
	GtkCTree *ctree = (browser != NULL) ? 
	    (GtkCTree *)browser->directory_ctree : NULL;
	if((ctree == NULL) || (dev_ptr == NULL))
	    return;

	/* Get copy of mount path from device */
	mount_path = STRDUP(dev_ptr->mount_path);
	if(mount_path == NULL)
	    return;

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


	/* Find node that matches the mount path */
	node = EDVBrowserDirTreeFindNodeByPath(browser, mount_path);
	if(node != NULL)
	{
	    /* Found existing node that matches the mount path, so
	     * update the node and its object
	     */
	    edv_object_struct *obj = EDV_OBJECT(
		gtk_ctree_node_get_row_data(ctree, node)
	    );
	    if((obj != NULL) && !lstat(mount_path, &lstat_buf))
	    {
		EDVObjectSetPath(obj, mount_path);
		EDVObjectSetStat(obj, &lstat_buf);
		obj->link_valid = TRUE;

		gtk_clist_freeze(GTK_CLIST(ctree));

		/* Update node's text, pixmap, and style with respect to
		 * the newly modified disk object
		 */
		EDVBrowserDirTreeSetNode(
		    EDV_CORE(browser->core_ptr), browser,
		    ctree, node, obj
		);

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

		gtk_clist_thaw(GTK_CLIST(ctree));
	    }
	}

	g_free(mount_path);
}
