#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <gtk/gtk.h>

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

#include "guiutils.h"
#include "cdialog.h"
#include "pdialog.h"
#include "progressdialog.h"
#include "toolbar.h"

#include "cfg.h"
#include "edv_types.h"
#include "edv_obj.h"
#include "edv_recycled_obj.h"
#include "edv_recbin_index.h"
#include "edv_recbin_purge.h"
#include "edv_recbin_recover.h"
#include "edv_find_bar.h"
#include "edv_status_bar.h"
#include "edv_confirm.h"
#include "edv_utils.h"
#include "endeavour2.h"
#include "edv_recbin_sync.h"
#include "recbin.h"
#include "recbin_cb.h"
#include "recbin_op_cb.h"
#include "recbin_contents_list.h"
#include "recbin_desktop_icon.h"
#include "edv_cb.h"
#include "edv_help.h"
#include "edv_op.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#include "images/icon_wildcards_32x32.xpm"


static gint EDVRecBinRMkDir(
	edv_core_struct *core, const gchar *path
);

void EDVRecBinOPCB(
	toolbar_item_struct *item, gint id, gpointer data
);
void EDVRecBinOPEnterCB(
	toolbar_item_struct *item, gint id, gpointer data
);
void EDVRecBinOPLeaveCB(
	toolbar_item_struct *item, gint id, gpointer data
);

void EDVRecBinOPSyncRecycleBin(edv_recbin_struct *recbin);

void EDVRecBinOPSyncDisks(edv_recbin_struct *recbin);
void EDVRecBinOPWriteProtect(edv_recbin_struct *recbin);
void EDVRecBinOPDeleteMethodRecycle(edv_recbin_struct *recbin);
void EDVRecBinOPDeleteMethodPurge(edv_recbin_struct *recbin);

void EDVRecBinOPClose(edv_recbin_struct *recbin);
void EDVRecBinOPExit(edv_recbin_struct *recbin);

void EDVRecBinOPRecover(edv_recbin_struct *recbin);
void EDVRecBinOPPurge(edv_recbin_struct *recbin);
void EDVRecBinOPPurgeAll(edv_recbin_struct *recbin);
void EDVRecBinOPSelectAll(edv_recbin_struct *recbin);
void EDVRecBinOPUnselectAll(edv_recbin_struct *recbin);
void EDVRecBinOPInvertSelection(edv_recbin_struct *recbin);

void EDVRecBinOPRefresh(edv_recbin_struct *recbin);
void EDVRecBinOPRefreshAll(edv_recbin_struct *recbin);

void EDVRecBinContentsFilter(edv_recbin_struct *recbin);

void EDVRecBinMIMETypes(edv_recbin_struct *recbin);


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


/*
 *	Creates the directory and all parent directories as needed and
 *	then reports them.
 *
 *	The path specifies the directory, if it does not exist then
 *	it and all its parent directories will be created as needed.
 */
static gint EDVRecBinRMkDir(
	edv_core_struct *core, const gchar *path
)
{
	struct stat stat_buf;
	gchar *dpath;
	GList *paths_list = NULL;

	if(STRISEMPTY(path))
	    return(-2);

	dpath = STRDUP(path);
	if(dpath == NULL)
	    return(-3);

	/* Path does not exist? */
	if(stat(dpath, &stat_buf))
	{ 
	    const guint m = EDVGetUMask();
	    const gchar *parent;
	    GList *glist;

	    /* Seek to first deliminator */
	    gchar *s = strchr(
		dpath + STRLEN("/"),
		G_DIR_SEPARATOR
	    );

	    /* Iterate through each directory compoent in the
	     * path, by tempory setting the deliminator to '\0'
	     * and checking for its existance
	     */
	    while(s != NULL)
	    {
		/* Tempory set deliminator to '\0' */
		*s = '\0';

		/* Does this compoent not exist? */
		if(stat(dpath, &stat_buf))
		    paths_list = g_list_append(
			paths_list, STRDUP(dpath)
		    );

		/* Restore deliminator */
		*s = G_DIR_SEPARATOR;

		/* Seek to next deliminator (if any) */
		s = strchr(s + 1, G_DIR_SEPARATOR);
	    }

	    /* Last directory compoent does not exist (from the
	     * very first check), so add it to the list of new
	     * objects
	     */
	    paths_list = g_list_append(
		paths_list, STRDUP(dpath)
	    );

	    /* Create the directories */
	    if(rmkdir(
		dpath,
		(~m) &
		    (S_IRUSR | S_IWUSR | S_IXUSR |
		     S_IRGRP | S_IWGRP | S_IXGRP |
		     S_IROTH | S_IWOTH | S_IXOTH)
	    ))
	    {
		g_list_foreach(paths_list, (GFunc)g_free, NULL);
		g_list_free(paths_list);
		g_free(dpath);
		return(-1);
	    }

	    /* Report the added directories */
	    for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	    {
		parent = (const gchar *)glist->data;
		if(parent == NULL)
		    continue;

		if(!lstat((const char *)parent, &stat_buf))
		    EDVObjectAddedEmit(
			core, parent, &stat_buf
		    );
	    }
	}

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

	return(0);
}


/*
 *	Operation id callback nexus.
 *
 *	The given client data must be a edv_recbin_opid_struct *.
 */
void EDVRecBinOPCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	GtkWidget *toplevel;
	edv_recbin_struct *recbin;
	edv_core_struct *core;
	edv_recbin_opid_struct *opid = EDV_RECBIN_OPID(data);
	if(opid == NULL)
	    return;

	recbin = opid->recbin;
	if(recbin == NULL)
	    return;

	if(recbin->processing || (recbin->freeze_count > 0))
	    return;

	toplevel = recbin->toplevel;
	core = recbin->core;
	if(core == NULL)
	    return;

	recbin->freeze_count++;

	/* Handle by operation id code */
	switch(opid->op)
	{
	  case EDV_RECBIN_OP_NONE:
	  case EDV_RECBIN_OP_SEPARATOR:
	    break;

	  case EDV_RECBIN_OP_CLOSE:
	    EDVRecBinOPClose(recbin);
	    break;

	  case EDV_RECBIN_OP_EXIT:
	    EDVRecBinOPExit(recbin);
	    break;


	  case EDV_RECBIN_OP_RECOVER:
	    EDVRecBinOPRecover(recbin);
	    break;

	  case EDV_RECBIN_OP_PURGE:
	    EDVRecBinOPPurge(recbin);
	    break;

	  case EDV_RECBIN_OP_PURGE_ALL:
	    EDVRecBinOPPurgeAll(recbin);
	    break;

	  case EDV_RECBIN_OP_SELECT_ALL:
	    EDVRecBinOPSelectAll(recbin);
	    break;

	  case EDV_RECBIN_OP_UNSELECT_ALL:
	    EDVRecBinOPUnselectAll(recbin);
	    break;

	  case EDV_RECBIN_OP_INVERT_SELECTION:
	    EDVRecBinOPInvertSelection(recbin);
	    break;

	  case EDV_RECBIN_OP_FIND:
	    EDVMapRecBinFindWin(core, recbin);
	    break;

	  case EDV_RECBIN_OP_HISTORY:
	    EDVMapHistoryListWin(core, toplevel);
	    break;

	  case EDV_RECBIN_OP_SYNC_RECYCLE_BIN:
	    EDVRecBinOPSyncRecycleBin(recbin);
	    break;

	  case EDV_RECBIN_OP_SYNC_DISKS:
	    EDVRecBinOPSyncDisks(recbin);
	    break;

	  case EDV_RECBIN_OP_WRITE_PROTECT:
	    EDVRecBinOPWriteProtect(recbin);
	    break;

	  case EDV_RECBIN_OP_DELETE_METHOD_RECYCLE:
	    EDVRecBinOPDeleteMethodRecycle(recbin);
	    break;

	  case EDV_RECBIN_OP_DELETE_METHOD_PURGE:
	    EDVRecBinOPDeleteMethodPurge(recbin);
	    break;

	  case EDV_RECBIN_OP_RUN:
	    EDVMapRunDialogCommand(
		core,
		NULL,
		NULL,
		toplevel
	    );
	    break;

	  case EDV_RECBIN_OP_RUN_TERMINAL:
	    EDVRunTerminal(core, NULL, NULL, toplevel);
	    break;


	  case EDV_RECBIN_OP_REFRESH:
	    EDVRecBinOPRefresh(recbin);
	    break;

	  case EDV_RECBIN_OP_REFRESH_ALL:
	    EDVRecBinOPRefreshAll(recbin);
	    break;

	  case EDV_RECBIN_OP_SHOW_TOOL_BAR:
	    if(core != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_TOOL_BAR
		);
		CFGItemListSetValueI(
		    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_TOOL_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core);
	    }
	    break;

	  case EDV_RECBIN_OP_SHOW_FIND_BAR:
	    if(core != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_FIND_BAR
		);
		CFGItemListSetValueI(
		    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_FIND_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core);
	    }
	    break;

	  case EDV_RECBIN_OP_SHOW_STATUS_BAR:
	    if(core != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_STATUS_BAR
		);
		CFGItemListSetValueI(
		    core->cfg_list, EDV_CFG_PARM_RECBIN_SHOW_STATUS_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core);
	    }
	    break;


	  case EDV_RECBIN_OP_CONTENTS_LIST_FILTER:
	    EDVRecBinContentsFilter(recbin);
	    break;


	  case EDV_RECBIN_OP_MIME_TYPES:
	    EDVRecBinMIMETypes(recbin);
	    break;


	  case EDV_RECBIN_OP_NEW_BROWSER:
	    EDVNewBrowser(core, NULL);
	    break;

	  case EDV_RECBIN_OP_NEW_IMBR:
	    EDVNewImbr(core, NULL);
	    break;

	  case EDV_RECBIN_OP_NEW_ARCHIVER:
	    EDVNewArchiver(core, NULL, NULL);
	    break;


	  case EDV_RECBIN_OP_OPTIONS:
	    EDVMapOptionsWin(core, toplevel);
	    break;

	  case EDV_RECBIN_OP_CUSTOMIZE:
	    EDVMapCustomizeWin(core, toplevel);
	    break;


	  case EDV_RECBIN_OP_HELP_ABOUT:
	    EDVAbout(core, toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_CONTENTS:
	    EDVHelp(core, "Contents", toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_FILE_BROWSER:
	    EDVHelp(core, "File Browser", toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_IMAGE_BROWSER:
	    EDVHelp(core, "Image Browser", toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_ARCHIVER:
	    EDVHelp(core, "Archiver", toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_RECYCLE_BIN:
	    EDVHelp(core, "Recycle Bin", toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_KEYS_LIST:
	    EDVHelp(core, "Keys List", toplevel);
	    break;
	  case EDV_RECBIN_OP_HELP_COMMON_OPERATIONS:
	    EDVHelp(core, "Common Operations", toplevel);
	    break;
	}

	recbin->freeze_count--;
}

/*
 *	Operation id enter notify callback nexus.
 *
 *	The given client data must be a edv_recbin_opid_struct *.
 */
void EDVRecBinOPEnterCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	const gchar *tooltip;
	edv_recbin_opid_struct *opid = EDV_RECBIN_OPID(data);
	edv_recbin_struct *recbin = (opid != NULL) ? opid->recbin : NULL;
	if(recbin == NULL)
	    return;

	tooltip = opid->tooltip;
	if(tooltip != NULL)
	    EDVStatusBarMessage(recbin->status_bar, tooltip, FALSE);
}

/*
 *	Operation id leave notify callback nexus.
 */
void EDVRecBinOPLeaveCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	edv_recbin_opid_struct *opid = EDV_RECBIN_OPID(data);
	edv_recbin_struct *recbin = (opid != NULL) ? opid->recbin : NULL;
	if(recbin == NULL)
	    return;

	EDVStatusBarMessage(recbin->status_bar, NULL, FALSE);
}


/*
 *	Sync Recycle Bin.
 *
 *	Compacts recycled objects and fix any errors.
 */
void EDVRecBinOPSyncRecycleBin(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	gint status;
	GtkWidget *toplevel;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	if(recbin->processing)
	    return;

	toplevel = recbin->toplevel;
	core = recbin->core;
	if(core == NULL)
	    return;

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

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Syncing recycle bin...",
	    TRUE
	);

	EDVRecBinSetBusy(recbin, TRUE);

	/* Sync the recycle bin */
	status = EDVRecBinSync(
	    core,
	    toplevel,
	    TRUE,
	    TRUE,
	    &yes_to_all,
	    EDVRecBinStatusMessageCB,
	    EDVRecBinStatusProgressCB,
	    recbin
	);

	/* Refresh listing of recycled objects */
	EDVRecBinOPRefresh(recbin);

	EDVRecBinSetBusy(recbin, FALSE);

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Recycle bin sync done",
	    FALSE
	);
	EDVStatusBarProgress(recbin->status_bar, 0.0f, FALSE);
}


/*
 *	Sync Disks.
 */
void EDVRecBinOPSyncDisks(edv_recbin_struct *recbin)
{
	if(recbin == NULL)
	    return;

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Syncing disks...",
	    TRUE
	);

	EDVRecBinSetBusy(recbin, TRUE);

	EDVSyncDisks(recbin->core);

	EDVRecBinSetBusy(recbin, FALSE);

	EDVStatusBarMessage(
	    recbin->status_bar,
	    "Disk sync done",
	    FALSE
	);
	EDVStatusBarProgress(recbin->status_bar, 0.0f, FALSE);
}

/*
 *	Write Protect Toggle.
 */
void EDVRecBinOPWriteProtect(edv_recbin_struct *recbin)
{
	gboolean write_protect;
	cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	/* Get current write protect state */
	write_protect = (gboolean)CFGItemListGetValueI(
	    cfg_list, EDV_CFG_PARM_WRITE_PROTECT
	);

	/* Toggle write protect */
	write_protect = !write_protect;

	/* Set new write protect state */
	CFGItemListSetValueI(
	    cfg_list, EDV_CFG_PARM_WRITE_PROTECT,
	    write_protect, FALSE
	);

	/* Emit write protect changed signal */
	EDVWriteProtectChangedEmit(core, write_protect);
}

/*
 *	Delete Method: Recycle.
 */
void EDVRecBinOPDeleteMethodRecycle(edv_recbin_struct *recbin)
{
	cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	EDV_SET_I(
	    EDV_CFG_PARM_DELETE_METHOD,
	    EDV_DELETE_METHOD_RECYCLE
	);
	EDVReconfiguredEmit(core);
}

/*
 *	Delete Method: Purge.
 */
void EDVRecBinOPDeleteMethodPurge(edv_recbin_struct *recbin)
{
	cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	EDV_SET_I(
	    EDV_CFG_PARM_DELETE_METHOD,
	    EDV_DELETE_METHOD_PURGE
	);
	EDVReconfiguredEmit(core);
}


/*
 *	Close.
 */
void EDVRecBinOPClose(edv_recbin_struct *recbin)
{
	if(recbin == NULL)
	    return;

	/* Set current properties of the recycle bin to the configuration
	 * list.
	 */
	EDVRecBinSyncConfiguration(recbin);

	/* Unmap the recbin */
	EDVRecBinUnmap(recbin);
}

/*
 *	Close all windows.
 */
void EDVRecBinOPExit(edv_recbin_struct *recbin)
{
	edv_core_struct *core;


	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	/* Set current properties of the recycle bin to the configuration
	 * list.
	 */
	EDVRecBinSyncConfiguration(recbin);

	/* Unmap this recycle bin */
	EDVRecBinUnmap(recbin);

	/* Schedual a new pending operation on the core to close all
	 * the windows
	 */
	core->pending_flags |= EDV_CORE_PENDING_CLOSE_ALL_WINDOWS;
}


/*
 *	Recover callback.
 */
void EDVRecBinOPRecover(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	struct stat stat_buf;
	gint row, status, nobjs, nobjs_recovered = 0;
	guint index;
	gchar *new_path;
	const gchar *error_msg;
	GList *glist, *obj_list = NULL;
	GtkWidget *toplevel;
	GtkCList *clist;
	edv_recycled_object_struct *obj = NULL;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	clist = (GtkCList *)recbin->contents_clist;
	core = recbin->core;
	if((clist == NULL) || (core == NULL))
	    return;

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

#define DO_FREE_LOCALS	{				\
 if(obj_list != NULL) {					\
  g_list_foreach(					\
   obj_list, (GFunc)EDVRecycledObjectDelete, NULL	\
  );							\
  g_list_free(obj_list);				\
 }							\
}

	EDVRecBinSetBusy(recbin, TRUE);

	/* Get the list of selected recycled objects */
	for(glist = clist->selection;
            glist != NULL;
            glist = g_list_next(glist)
	)
	{
	    row = (gint)glist->data;
	    obj = EDV_RECYCLED_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
		obj_list = g_list_append(
		    obj_list,
		    EDVRecycledObjectCopy(obj)
		);
	}

	/* No recycled objects to recover? */
	if(obj_list == NULL)
	{
	    DO_FREE_LOCALS
	    EDVRecBinSetBusy(recbin, FALSE);
	    return;
	}

	nobjs = g_list_length(obj_list);

	/* Confirm recover */
	status = EDVConfirmRecover(
	    core, toplevel,
	    (obj != NULL) ? obj->name : NULL,
	    nobjs,
	    ((obj != NULL) && (nobjs == 1)) ? obj->original_path : NULL
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES:
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    break;

	  default:
	    DO_FREE_LOCALS
	    EDVRecBinSetBusy(recbin, FALSE);
	    return;
	    break;
	}

	/* Iterate through list of recycled objects and recover each
	 * one
	 */
	status = 0;
	for(glist = obj_list;
            glist != NULL;
            glist = g_list_next(glist)
	)
	{
	    obj = EDV_RECYCLED_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    index = obj->index;

	    /* Check if this object's original path no longer exists */
	    if((obj->original_path != NULL) ?
		stat((const char *)obj->original_path, &stat_buf) : FALSE
	    )
	    {
		gint response;
		const gchar *parent = obj->original_path;
		gchar *s = g_strdup_printf(
"The original location:\n\
\n\
    %s\n\
\n\
Of:\n\
\n\
    %s\n\
\n\
No longer exists, create it?",
		    parent,
		    obj->name
		);
		EDVPlaySoundQuestion(core);
		CDialogSetTransientFor(toplevel);
		response = CDialogGetResponse(
		    "Create Directory",
		    s,
		    NULL,
		    CDIALOG_ICON_QUESTION,
		    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
		    CDIALOG_BTNFLAG_YES
		);
		CDialogSetTransientFor(NULL);
		g_free(s);

		/* Create directory? */
		if(response == CDIALOG_RESPONSE_YES)
		{
		    if(EDVRecBinRMkDir(core, parent))
		    {
			s = g_strdup_printf(
"Unable to create directory:\n\
\n\
    %s",
			    parent
			);
			EDVPlaySoundError(core);
			EDVMessageError(
			    "Create Directory Error",
			    s,
			    NULL,
			    toplevel
			);
			g_free(s);
		    }
		}
		/* User aborted? */
		else if(response == CDIALOG_RESPONSE_CANCEL)
		{
		    status = -4;
		    break;
		}
	    }

	    /* Recover this recycled object */
	    new_path = NULL;
	    status = EDVRecBinRecover(
		core,
		index,		/* Recycled object to recover */
		NULL,		/* Use the original location as the
				 * recovery path */
		&new_path,
		toplevel,
		TRUE, TRUE, &yes_to_all
	    );

	    /* Get error message (if any) describing the error that
	     * might have occured in the above operation
	     */
	    error_msg = EDVRecBinRecoverGetError();
	    if(!STRISEMPTY(error_msg))
	    {
		EDVPlaySoundError(core);
		EDVMessageObjectOPError(
		    "Recover Recycled Object Error",
		    error_msg,
		    obj->name,
		    toplevel
		);
	    }

	    /* Object recovered? */
	    if(new_path != NULL)
	    {
		struct stat lstat_buf;

		nobjs_recovered++;

		/* Report this recycled object removed */
		EDVRecycledObjectRemovedEmit(core, index);

		/* Report the recovered object */
		if(!lstat(new_path, &lstat_buf))
		    EDVObjectAddedEmit(
			core,
			new_path,
			&lstat_buf
		    );

		g_free(new_path);
	    }

	    /* User aborted? */
	    if(status == -4)
		break;
	}

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

	/* Play completed sound on success */
	if(status == 0)
	    EDVPlaySoundCompleted(core);

	/* Update the status bar message */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Recover operation canceled"
		);
	    else if(nobjs_recovered > 0)
		s = g_strdup_printf(
		    "Recovered %i %s",
		    nobjs_recovered,
		    (nobjs_recovered == 1) ? "recycled object" : "recycled objects" 		);
	    else
		s = g_strdup_printf(
		    "Unable to recover %s",
		    (nobjs == 1) ? "recycled object" : "recycled objects"
		);
	    EDVStatusBarMessage(recbin->status_bar, s, FALSE);
	    g_free(s);
	}

	DO_FREE_LOCALS

	EDVRecBinSetBusy(recbin, FALSE);

#undef DO_FREE_LOCALS
}

/*
 *	Purge callback.
 */
void EDVRecBinOPPurge(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	gint row, status, nobjs, nobjs_purged = 0;
	guint index;
	const gchar *error_msg;
	GtkWidget *toplevel;
	GList *glist, *obj_list = NULL;
	GtkCList *clist;
	edv_recycled_object_struct *obj = NULL;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	clist = (GtkCList *)recbin->contents_clist;
	core = recbin->core;
	if((clist == NULL) || (core == NULL))
	    return;

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

#define DO_FREE_LOCALS	{				\
 g_list_foreach(					\
  obj_list, (GFunc)EDVRecycledObjectDelete, NULL		\
 );							\
 g_list_free(obj_list);					\
 obj_list = NULL;					\
}

	/* Get a list of selected recycled objects indices */
	glist = clist->selection;
	while(glist != NULL)
	{
	    row = (gint)glist->data;
	    obj = EDV_RECYCLED_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    );
	    if(obj != NULL)
		obj_list = g_list_append(
		    obj_list,
		    EDVRecycledObjectCopy(obj)
		);

	    glist = g_list_next(glist);
	}

	/* No recycled objects to purge? */
	if(obj_list == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	nobjs = g_list_length(obj_list);

	/* Confirm purge */
	status = EDVConfirmPurge(
	    core, toplevel,
	    (obj != NULL) ? obj->name : NULL,
	    nobjs
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES:
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    break;

	  default:
	    DO_FREE_LOCALS
	    return;
	    break;
	}

	/* Do not reference the last selected recycled object after
	 * this point
	 */
	obj = NULL;

	EDVRecBinSetBusy(recbin, TRUE);

	/* Iterate through list of recycled objects and purge each
	 * one
	 */
	status = 0;
	for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_RECYCLED_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    index = obj->index;

	    /* Purge this recycled object */
	    status = EDVRecBinPurge(
		core,
		index,
		toplevel,
		(nobjs > 0) ?
		    ((gfloat)nobjs_purged / (gfloat)nobjs) : -1.0f,
		TRUE, TRUE, &yes_to_all
	    );

	    /* Get the error message describing the error that might
	     * have occured in the above operation
	     */
	    error_msg = EDVRecBinPurgeGetError();
	    if(!STRISEMPTY(error_msg))
	    {
		EDVPlaySoundError(core);
		EDVMessageObjectOPError(
		    "Purge Recycled Object Error",
		    error_msg,
		    obj->name,
		    toplevel
		);
	    }

	    /* Was the recycled object purged successfully? */
	    if(status == 0)
	    {
		nobjs_purged++;
		EDVRecycledObjectRemovedEmit(core, index);
	    }

	    /* User aborted? */
	    if(status == -4)
		break;
	}

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

	/* Play completed sound on success */
	if(status == 0) 
	    EDVPlaySoundCompleted(core);

	EDVRecBinSetBusy(recbin, FALSE);

	/* Update the status bar message */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Purge operation canceled"
		);
	    else if(nobjs_purged > 0)
		s = g_strdup_printf(
		    "Purged %i %s",
		    nobjs_purged,
		    (nobjs_purged == 1) ? "recycled object" : "recycled objects"
		);
	    else
		s = g_strdup_printf(
		    "Unable to purge %s",
		    (nobjs == 1) ? "recycled object" : "recycled objects"
		);
	    EDVStatusBarMessage(recbin->status_bar, s, FALSE);
	    g_free(s);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Purge all callback.
 */
void EDVRecBinOPPurgeAll(edv_recbin_struct *recbin)
{
	gboolean yes_to_all = FALSE;
	gint status, nobjs, nobjs_purged = 0;
	guint index;
	const gchar *error_msg, *recycled_index_file;
	GList *glist, *index_list = NULL;
	GtkWidget *toplevel;
	edv_recbin_index_struct *rbi_ptr;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	core = recbin->core;
	if(core == NULL)
	    return;

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

	/* Get path to recycled objects index file */
	recycled_index_file = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX
	);
	if(recycled_index_file == NULL)
	    return;

#define DO_FREE_LOCALS	{	\
 g_list_free(index_list);	\
}

	/* Get a list of all recycled object indices */
	rbi_ptr = EDVRecBinIndexOpen(recycled_index_file);
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    if(rbi_ptr->index != 0)
		index_list = g_list_append(
		    index_list,
		    (gpointer)rbi_ptr->index
		);
	}
	EDVRecBinIndexClose(rbi_ptr);
	rbi_ptr = NULL;

	/* No recycled objects to purge? */
	if(index_list == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	nobjs = g_list_length(index_list);

	/* Confirm purge */
	status = EDVConfirmPurge(
	    core, toplevel,
	    NULL,
	    nobjs
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES:
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    break;

	  default:
	    DO_FREE_LOCALS
	    return;
	    break;
	}


	EDVRecBinSetBusy(recbin, TRUE);

#ifdef EDV_RECBIN_USE_PURGE_ALL
/* Do not define this, this has issues about progress updating,
 * read subsequent comments in this block
 */
	/* Purge all contents from the recycled objects directory
	 * including the recycled objects index file
	 */
	status = EDVRecBinPurgeAll(
	    core, toplevel,
	    TRUE, TRUE, &yes_to_all
	);

	/* Get the error message describing the error that might have
	 * occured in the above operation
	 */
	error_msg = EDVRecBinPurgeGetError();
	if(!STRISEMPTY(error_msg))
	{
	    EDVPlaySoundError(core);
	    EDVMessageObjectOPError(
		"Purge Recycled Object Error",
		error_msg,
		NULL,
		toplevel
	    );
	}

	/* All recycled objects purged successfully? */
	if(status == 0)
	{
	    /* Send recycled object purged signal to all of Endeavour's
	     * resources for each recycled object listed in our recorded
	     * recycled objects index array
	     */
	    for(glist = index_list; glist != NULL; glist = g_list_next(glist))
	    {
		index = (guint)glist->data;

		/* Update the progress bar for notifying of resources
		 * as well
		 */
/* This might appear a bit confusing since it is not very neat */
		if(ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			(gfloat)nobjs_purged / (gfloat)nobjs,
			EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			/* If user wants to stop, then end updating of
			 * Endeavour's resources prematurly but do not
			 * change the value of status.
			 */
			break;
		    }
		}
		EDVRecycledObjectRemovedEmit(core, index);
		nobjs_purged++;
	    }
	}
#else
	/* Iterate through list of recycled objects and purge each
	 * one
	 */
	status = 0;
	for(glist = index_list; glist != NULL; glist = g_list_next(glist))
	{
	    index = (guint)glist->data;

	    /* Purge this recycled object */
	    status = EDVRecBinPurge(
		core,
		index,			/* Recycled object to purge */
		toplevel,
		(nobjs > 0) ?
		    ((gfloat)nobjs_purged / (gfloat)nobjs) : -1.0f,
		TRUE, TRUE, &yes_to_all
	    );

	    /* Get the error message describing the error that might
	     * have occured in the above operation
	     */
	    error_msg = EDVRecBinPurgeGetError();
	    if(!STRISEMPTY(error_msg))
	    {
		EDVPlaySoundError(core);
		EDVMessageObjectOPError(
		    "Purge Recycled Object Error",
		    error_msg,
		    NULL,
		    toplevel
		);
	    }

	    /* Was the recycled object purged successfully? */
	    if(status == 0)
	    {
		nobjs_purged++;
		EDVRecycledObjectRemovedEmit(core, index);
	    }

	    /* User aborted? */
	    if(status == -4)
		break;
	}

	/* If no errors or aborting occured then do one more through
	 * purge all but do not show progress for it. This will also
	 * remove any stray recycled object files and the recycled objects
	 * index file
	 */
	if(status == 0)
	    EDVRecBinPurgeAll(
		core, toplevel,
		FALSE, TRUE, &yes_to_all
	    );
#endif

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

	/* Play completed sound on success */
	if(status == 0)
	    EDVPlaySoundCompleted(core);

	EDVRecBinSetBusy(recbin, FALSE);

	/* Update the status bar message */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Purge operation canceled"
		);
	    else if(nobjs_purged > 0)
		s = g_strdup_printf(
		    "Purged %i %s",
		    nobjs_purged,
		    (nobjs_purged == 1) ? "recycled object" : "recycled objects"
		);
	    else
		s = g_strdup_printf(
		    "Unable to purge %s",
		    (nobjs == 1) ? "recycled object" : "recycled objects"
		);
	    EDVStatusBarMessage(recbin->status_bar, s, FALSE);
	    g_free(s);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Select all.
 */
void EDVRecBinOPSelectAll(edv_recbin_struct *recbin)
{
	edv_core_struct *core;
	GtkCList *clist;


	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	/* Get contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist == NULL)
	    return;

	EDVRecBinSetBusy(recbin, TRUE);

	/* Select all rows on clist */
	gtk_clist_freeze(clist);
	gtk_clist_select_all(clist);
	gtk_clist_thaw(clist);

	/* Assume highest row index as the last selected row */
	recbin->contents_clist_selected_row = clist->rows - 1;

	EDVStatusBarMessage(
	    recbin->status_bar, "All objects selected", FALSE
	);

	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *	Unselect all.
 */
void EDVRecBinOPUnselectAll(edv_recbin_struct *recbin)
{
	edv_core_struct *core;
	GtkCList *clist;


	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	/* Get contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist == NULL)
	    return;

	EDVRecBinSetBusy(recbin, TRUE);

	/* Unselect all rows on clist */
	gtk_clist_freeze(clist);
	gtk_clist_unselect_all(clist);
	gtk_clist_thaw(clist);

	/* Mark contents clist's row as unselected */
	recbin->contents_clist_selected_row = -1;

	EDVStatusBarMessage(
	    recbin->status_bar, "All objects unselected", FALSE
	);

	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *	Invert Selection.
 */
void EDVRecBinOPInvertSelection(edv_recbin_struct *recbin)
{
	edv_core_struct *core;
	GtkCList *clist;
	GList *glist, *selection;
	gint row, total_rows;


	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	/* Get contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist == NULL)
	    return;

	EDVRecBinSetBusy(recbin, TRUE);
	gtk_clist_freeze(clist);

	/* Get copy of selected rows list from clist */
	selection = (clist->selection != NULL) ?
	    g_list_copy(clist->selection) : NULL;

	for(row = 0, total_rows = clist->rows;
	    row < total_rows;
	    row++
	)
	{
	    for(glist = selection;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		if(row == (gint)glist->data)
		{
		    gtk_clist_unselect_row(clist, row, 0);
		    break;
		}
	    }
	    /* Row not selected? */
	    if(glist == NULL)
		gtk_clist_select_row(clist, row, 0);
	}

	g_list_free(selection);

	gtk_clist_thaw(clist);
	EDVStatusBarMessage(
	    recbin->status_bar, "Selection inverted", FALSE
	);
	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *	Refresh.
 */
void EDVRecBinOPRefresh(edv_recbin_struct *recbin)
{
	GtkWidget *w, *toplevel;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	core = recbin->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	EDVRecBinSetBusy(recbin, TRUE);
	GUIBlockInput(toplevel, TRUE);

	/* Refresh toplevel */
	w = recbin->toplevel;
	if(w != NULL)
	    gtk_widget_queue_resize(w);

	/* Update contents clist */
	clist = (GtkCList *)recbin->contents_clist;
	if(clist != NULL)
	{
	    /* Record last scroll position */
	    gfloat last_x = GTK_ADJUSTMENT_GET_VALUE(clist->hadjustment),
		   last_y = GTK_ADJUSTMENT_GET_VALUE(clist->vadjustment);

	    gtk_clist_freeze(clist);

	    /* Refresh list items */
	    EDVRecBinContentsGetListing(
		recbin,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );

	    gtk_clist_thaw(clist);

	    /* Scroll back to original position */
	    EDVScrollCListToPosition(clist, last_x, last_y);
	}

	EDVRecBinUpdateMenus(recbin);
	EDVStatusBarMessage(
	    recbin->status_bar, "Refreshed contents listing", FALSE
	);

	GUIBlockInput(toplevel, FALSE);
	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *	Refresh All.
 */
void EDVRecBinOPRefreshAll(edv_recbin_struct *recbin)
{
	const gchar *recycled_index_file;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(recbin == NULL)
	    return;

	core = recbin->core;
	if(core == NULL)
	    return;

	cfg_list = core->cfg_list;

	/* Get path of recycled object index file */
	recycled_index_file = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX
	);

	/* Update number of recycled objects on the core structure */
	core->last_recbin_items = EDVRecBinIndexGetTotal(
	    recycled_index_file
	);

	/* Refresh Recycle Bin Desktop Icon */
	EDVRecBinDeskIconUpdate(core->recbin_deskicon);


	/* Refresh Recycle Bin */
	EDVRecBinOPRefresh(recbin);
}


/*
 *	Sets the contents list filter.
 */
void EDVRecBinContentsFilter(edv_recbin_struct *recbin)
{
	gchar **strv;
	gint strc;
	GtkWidget *toplevel;

	if((recbin == NULL) || PDialogIsQuery())
	    return;

	toplevel = recbin->toplevel;
	EDVRecBinSetBusy(recbin, TRUE);

	PDialogDeleteAllPrompts();
	PDialogSetTransientFor(toplevel);
	PDialogAddPrompt(NULL, "Filter:", recbin->contents_list_filter);
	PDialogSetSize(320, -1);
	strv = PDialogGetResponseIconData(
	    "Set Filter",
	    NULL, NULL,
	    (guint8 **)icon_wildcards_32x32_xpm,
	    "Set", "Cancel",
	    PDIALOG_BTNFLAG_SUBMIT | PDIALOG_BTNFLAG_CANCEL,
	    PDIALOG_BTNFLAG_SUBMIT,
	    &strc
	);
	PDialogSetTransientFor(NULL);
	if((strv != NULL) && (strc > 0))  
	{                                 
	    if(strc > 0)
	    {
		g_free(recbin->contents_list_filter);
		recbin->contents_list_filter = STRDUP(strv[0]);
	    }

	    EDVRecBinOPRefresh(recbin);
	}

	PDialogDeleteAllPrompts();

	EDVRecBinSetBusy(recbin, FALSE);
}

/*
 *      MIME Types.
 */
void EDVRecBinMIMETypes(edv_recbin_struct *recbin)
{
	gchar *type_str = NULL;
	gint i;
	GtkWidget *toplevel;
	GtkCList *clist;
	edv_core_struct *core;
	if(recbin == NULL)
	    return;

	toplevel = recbin->toplevel;
	clist = (GtkCList *)recbin->contents_clist;
	core = recbin->core;
	if((clist == NULL) || (core == NULL))
	    return;

	i = EDVCListGetSelectedLast(clist, NULL);
	if(i > -1)
	{
	    edv_recycled_object_struct *obj = EDV_RECYCLED_OBJECT(
		gtk_clist_get_row_data(clist, i)
	    );
	    if(obj != NULL)
		EDVMatchObjectTypeString(
		    core->mimetype, core->total_mimetypes,
		    obj->type,
		    obj->permissions,
		    obj->name,
		    &type_str
	    );
	}

	EDVMapMIMETypesListWin(core, type_str, toplevel);
}
