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

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

#include "guiutils.h"
#include "cdialog.h"
#include "fb.h"
#include "progressdialog.h"

#include "edvtypes.h"
#include "cfg.h"
#include "edvconfirm.h"
#include "edvobj.h"
#include "edvarchobj.h"
#include "edvarchfio.h"
#include "edvarchop.h"
#include "archiveopts.h"
#include "archiveinfo.h"
#include "archiver.h"
#include "archivercb.h"
#include "archiveropcb.h"
#include "archivercontents.h"
#include "endeavour.h"
#include "edvcb.h"
#include "edvhelp.h"
#include "edvop.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"


void EDVArchiverMenuItemCB(GtkWidget *widget, gpointer data);
gint EDVArchiverMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
gint EDVArchiverMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);

void EDVArchiverOPCB(
	toolbar_item_struct *item, gint id, gpointer data  
);
void EDVArchiverOPEnterCB(
	toolbar_item_struct *item, gint id, gpointer data  
);
void EDVArchiverOPLeaveCB(
	toolbar_item_struct *item, gint id, gpointer data
);

void EDVArchiverFindBarStartCB(gpointer fb, gpointer data);
void EDVArchiverFindBarEndCB(
	gpointer fb, gint total_matches, gpointer data
);
gchar *EDVArchiverFindBarLocationCB(gpointer fb, gpointer data);
void EDVArchiverFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
);

void EDVArchiverBarStatusMessageCB(
	gpointer bar, const gchar *message, gpointer data
);
void EDVArchiverBarStatusProgressCB(
	gpointer bar, gfloat progress, gpointer data
);

void EDVArchiverOPSyncDisks(edv_archiver_struct *archiver);
void EDVArchiverOPWriteProtect(edv_archiver_struct *archiver);

void EDVArchiverOPNew(edv_archiver_struct *archiver);
void EDVArchiverOPOpen(edv_archiver_struct *archiver);

void EDVArchiverOPClose(edv_archiver_struct *archiver);
void EDVArchiverOPExit(edv_archiver_struct *archiver);

static edv_archive_object_struct **EDVArchiverGetSelectedObjects(
	edv_archiver_struct *archiver, gint *total
);
void EDVArchiverOPAdd(edv_archiver_struct *archiver);
void EDVArchiverOPExtract(edv_archiver_struct *archiver);
void EDVArchiverOPExtractAll(edv_archiver_struct *archiver);
void EDVArchiverOPDelete(edv_archiver_struct *archiver);
void EDVArchiverOPSelectAll(edv_archiver_struct *archiver);
void EDVArchiverOPUnselectAll(edv_archiver_struct *archiver);
void EDVArchiverOPInvertSelection(edv_archiver_struct *archiver);
void EDVArchiverOPProperties(edv_archiver_struct *archiver);

void EDVArchiverOPRefresh(edv_archiver_struct *archiver);
void EDVArchiverOPRefreshAll(edv_archiver_struct *archiver);
void EDVArchiverOPCommentAndStatistics(edv_archiver_struct *archiver);


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


/*
 *	Menu item activate callback.
 *
 *	The data must be a edv_archiver_opid_struct *.
 */
void EDVArchiverMenuItemCB(GtkWidget *widget, gpointer data)
{
	EDVArchiverOPCB(NULL, -1, data);
}

/*
 *	Menu item "enter_notify_event" signal callback.
 *
 *      The data must be a edv_archiver_opid_struct *.
 */
gint EDVArchiverMenuItemEnterCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVArchiverOPEnterCB(NULL, -1, data);
	return(TRUE);
}

/*
 *	Menu item "leave_notify_event" signal callback.
 *
 *      The given client data must be a edv_archiver_opid_struct *.
 */
gint EDVArchiverMenuItemLeaveCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	EDVArchiverOPLeaveCB(NULL, -1, data);
	return(TRUE);
}

/*
 *	Operation ID callback nexus.
 *
 *	The data must be a edv_archiver_opid_struct *.
 */
void EDVArchiverOPCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	edv_archiver_struct *archiver;
	edv_core_struct *core_ptr;
	edv_archiver_opid_struct *opid = EDV_ARCHIVER_OPID(data);
	if(opid == NULL)
	    return;

	archiver = EDV_ARCHIVER(opid->archiver);
	if(archiver == NULL)
	    return;

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

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

	archiver->freeze_count++;

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

	  case EDV_ARCHIVER_OP_NEW:
	    EDVArchiverOPNew(archiver);
	    break;

	  case EDV_ARCHIVER_OP_OPEN:
	    EDVArchiverOPOpen(archiver);
	    break;


	  case EDV_ARCHIVER_OP_CLOSE:
	    EDVArchiverOPClose(archiver);
	    break;

	  case EDV_ARCHIVER_OP_EXIT:
	    EDVArchiverOPExit(archiver);
	    break;


	  case EDV_ARCHIVER_OP_ADD:
	    EDVArchiverOPAdd(archiver);
	    break;

	  case EDV_ARCHIVER_OP_EXTRACT:
	    EDVArchiverOPExtract(archiver);
	    break;

	  case EDV_ARCHIVER_OP_EXTRACT_ALL:
	    EDVArchiverOPExtractAll(archiver);
	    break;

	  case EDV_ARCHIVER_OP_DELETE:
	    EDVArchiverOPDelete(archiver);
	    break;

	  case EDV_ARCHIVER_OP_SELECT_ALL:
	    EDVArchiverOPSelectAll(archiver);
	    break;

	  case EDV_ARCHIVER_OP_UNSELECT_ALL:
	    EDVArchiverOPUnselectAll(archiver);
	    break;

	  case EDV_ARCHIVER_OP_INVERT_SELECTION:
	    EDVArchiverOPInvertSelection(archiver);
	    break;

	  case EDV_ARCHIVER_OP_FIND:
	    EDVMapArchiverFindWin(core_ptr, archiver);
	    break;

	  case EDV_ARCHIVER_OP_PROPERTIES:
	    EDVArchiverOPProperties(archiver);
	    break;


	  case EDV_ARCHIVER_OP_HISTORY:
	    EDVMapHistoryListWin(core_ptr, archiver->toplevel);
	    break;

	  case EDV_ARCHIVER_OP_SYNC_DISKS:
	    EDVArchiverOPSyncDisks(archiver);
	    break;

	  case EDV_ARCHIVER_OP_WRITE_PROTECT:
	    EDVArchiverOPWriteProtect(archiver);
	    break;

	  case EDV_ARCHIVER_OP_RUN:
	    EDVMapRunDialogCommand(
		core_ptr,
		NULL,
		NULL,
		archiver->toplevel
	    );
	    break;

	  case EDV_ARCHIVER_OP_RUN_TERMINAL:
	    EDVRunTerminal(core_ptr, NULL, archiver->toplevel);
	    break;


	  case EDV_ARCHIVER_OP_REFRESH:
	    EDVArchiverOPRefresh(archiver);
	    break;

	  case EDV_ARCHIVER_OP_REFRESH_ALL:
	    EDVArchiverOPRefreshAll(archiver);
	    break;

	  case EDV_ARCHIVER_OP_COMMENT_AND_STATISTICS:
	    EDVArchiverOPCommentAndStatistics(archiver);
	    break;

	  case EDV_ARCHIVER_OP_SHOW_TOOL_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_TOOL_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_TOOL_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;

	  case EDV_ARCHIVER_OP_SHOW_LOCATION_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_LOCATION_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_LOCATION_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;

	  case EDV_ARCHIVER_OP_SHOW_FIND_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_FIND_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_FIND_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;

	  case EDV_ARCHIVER_OP_SHOW_STATUS_BAR:
	    if(core_ptr != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_STATUS_BAR
		);
		CFGItemListSetValueI(
		    core_ptr->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_STATUS_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core_ptr);
	    }
	    break;


	  case EDV_ARCHIVER_OP_MIME_TYPES:
	    EDVMapMIMETypesListWin(core_ptr, archiver->toplevel);
	    break;


	  case EDV_ARCHIVER_OP_NEW_BROWSER:
	    EDVNewBrowser(core_ptr);
	    break;

	  case EDV_ARCHIVER_OP_NEW_IMBR:
	    EDVNewImbr(core_ptr);
	    break;

	  case EDV_ARCHIVER_OP_NEW_ARCHIVER:
	    EDVNewArchiver(core_ptr);
	    break;

	  case EDV_ARCHIVER_OP_RECYCLE_BIN:
	    EDVMapRecBin(core_ptr);
	    break;


	  case EDV_ARCHIVER_OP_OPTIONS:
	    EDVMapOptionsWin(core_ptr, archiver->toplevel);
	    break;

	  case EDV_ARCHIVER_OP_CUSTOMIZE:
	    EDVMapCustomizeWin(core_ptr, archiver->toplevel);
	    break;


	  case EDV_ARCHIVER_OP_HELP_ABOUT:
	    EDVAbout(core_ptr, archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_CONTENTS:
	    EDVHelp(core_ptr, "Contents", archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_FILE_BROWSER:
	    EDVHelp(core_ptr, "File Browser", archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_IMAGE_BROWSER:
	    EDVHelp(core_ptr, "Image Browser", archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_ARCHIVER:
	    EDVHelp(core_ptr, "Archiver", archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_RECYCLE_BIN:
	    EDVHelp(core_ptr, "Recycle Bin", archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_KEYS_LIST:
	    EDVHelp(core_ptr, "Keys List", archiver->toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_COMMON_OPERATIONS:
	    EDVHelp(core_ptr, "Common Operations", archiver->toplevel);
	    break;
	}

	archiver->freeze_count--;
}

/*
 *	Operation ID enter notify callback nexus.
 *
 *	The data must be a edv_archiver_opid_struct *.
 */
void EDVArchiverOPEnterCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	const gchar *tooltip;
	edv_archiver_opid_struct *opid = EDV_ARCHIVER_OPID(data);
	edv_archiver_struct *archiver = EDV_ARCHIVER(
	    (opid != NULL) ? opid->archiver : NULL
	);
	if(archiver == NULL)
	    return;

	tooltip = opid->tooltip;
	if(!STRISEMPTY(tooltip))
	    EDVStatusBarMessage(archiver->status_bar, tooltip, FALSE);
}

/*
 *	Operation ID leave notify callback nexus.
 */
void EDVArchiverOPLeaveCB(
	toolbar_item_struct *item, gint id, gpointer data  
)
{
	edv_archiver_opid_struct *opid = EDV_ARCHIVER_OPID(data);
	edv_archiver_struct *archiver = EDV_ARCHIVER(            
	    (opid != NULL) ? opid->archiver : NULL
	);
	if(archiver == NULL)
	    return;

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


/*
 *	Find Bar get current location callback.
 */
gchar *EDVArchiverFindBarLocationCB(gpointer fb, gpointer data)
{
	return(EDVArchiverCurrentLocation(
	    EDV_ARCHIVER(data)
	));
}

/*
 *	Find Bar start find callback.
 */
void EDVArchiverFindBarStartCB(gpointer fb, gpointer data)
{
	GtkCList *clist;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	if(clist == NULL)
	    return;

	EDVArchiverSetBusy(archiver, TRUE);

	gtk_clist_freeze(clist);
	gtk_clist_unselect_all(clist);
	gtk_clist_thaw(clist);

	EDVArchiverUpdateMenus(archiver);
}

/*
 *	Find Bar end find callback.
 */
void EDVArchiverFindBarEndCB(
	gpointer fb, gint total_matches, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

	EDVArchiverSetBusy(archiver, FALSE);
}


/*
 *	Find Bar match callback.
 */
void EDVArchiverFindBarMatchCB(
	const gchar *path, const struct stat *lstat_buf,
	const gchar *excerpt, gint line_index,
	gpointer data
)
{
	gint row;
	GtkCList *clist;
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if((path == NULL) || (archiver == NULL))
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	if(clist == NULL)
	    return;

	row = EDVArchiverContentsFindRowByPath(archiver, path);
	if((row >= 0) && (row < clist->rows))
	    gtk_clist_select_row(clist, row, 0);
}


/*
 *	Status Bar set message callback.
 */
void EDVArchiverBarStatusMessageCB(
	gpointer bar, const gchar *message, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

	EDVStatusBarMessage(archiver->status_bar, message, FALSE);
}

/*
 *	Status Bar set progress callback.
 */
void EDVArchiverBarStatusProgressCB(
	gpointer bar, gfloat progress, gpointer data
)
{
	edv_archiver_struct *archiver = EDV_ARCHIVER(data);
	if(archiver == NULL)
	    return;

	EDVStatusBarProgress(archiver->status_bar, progress, FALSE);
}


/*
 *	Sync Disks.
 */
void EDVArchiverOPSyncDisks(edv_archiver_struct *archiver)
{
	if(archiver == NULL)
	    return;

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

	EDVArchiverSetBusy(archiver, TRUE);

	EDVSyncDisks(EDV_CORE(archiver->core_ptr));

	EDVArchiverSetBusy(archiver, FALSE);

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

/*
 *      Write Protect toggle.
 */
void EDVArchiverOPWriteProtect(edv_archiver_struct *archiver)
{
	gboolean write_protect;
	cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;


	if(archiver == NULL)
	    return;

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

	cfg_list = core_ptr->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_ptr, write_protect);
}


/*
 *	New Archive callback.
 */
void EDVArchiverOPNew(edv_archiver_struct *archiver)
{
	gboolean status, created_archive = FALSE;
	gint i;
	GtkWidget *toplevel;
	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;
	edv_core_struct *core_ptr;
	const gchar *ext_list[] = EDV_ARCHIVER_ARCHIVE_FILE_EXTENSIONS;

	if((archiver == NULL) || FileBrowserIsQuery())
	    return;

	toplevel = archiver->toplevel;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if(core_ptr == NULL)
	    return;

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

	/* Create file types list */
	for(i = 0; ext_list[i] != NULL; i += 2)
	    FileBrowserTypeListNew(
		&ftype, &total_ftypes,
		ext_list[i], ext_list[i + 1]
	    );

	/* Query user for new archive */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Create Archive",
	    "Create", "Cancel",
	    NULL,               /* Startup path */
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* Got user response? */
	if(status)
	{
	    gchar *new_path = STRDUP((total_path_rtns > 0) ?
		path_rtn[0] : NULL
	    );
	    if(!STRISEMPTY(new_path))
	    {
		gchar *name, *s;
		FILE *fp;
		struct stat stat_buf;

		/* Get pointer to the new archive name in new_path */
		name = strrchr(new_path, G_DIR_SEPARATOR);
		if(name != NULL)
		    name++;
		else
		    name = new_path;

		/* No extension? */
		if(strchr(name, '.') == NULL)
		{
		    /* Append extension and update new_path */
		    if((ftype_rtn != NULL) ? (ftype_rtn->ext != NULL) : FALSE)
		    {
			gchar *ext = STRDUP(ftype_rtn->ext);

			s = strpbrk(ext, " \t,");
			if(s != NULL)
			    *s = '\0';

			s = g_strdup_printf(
			    "%s%s%s",
			    new_path,
			    (*ext != '.') ? "." : "",
			    ext
			);
			g_free(new_path);
			new_path = s;

			g_free(ext);
		    }

		    /* Reget pointer to the new archive's name in
		     * new_path
		     */
		    name = strrchr(new_path, G_DIR_SEPARATOR);
		    if(name != NULL)
			name++;
		    else
			name = new_path;
		}

		/* Check extension to see if it is a valid archive */


		/* Make sure that the new archive to be created does
		 * not exist
		 */
		if(stat(new_path, &stat_buf))
		{
		    /* Does not exist, so `touch' the location to
		     * create an empty archive
		     */
		    fp = FOpen(new_path, "wb");
		    if(fp != NULL)
		    {
			/* Created new file, do not put anything in the
			 * file though, just close it immediatly
			 */
			fstat(fileno(fp), &stat_buf);
			FClose(fp);
			fp = NULL;

			created_archive = TRUE;

			EDVArchiverSetBusy(archiver, TRUE);
			GUIBlockInput(toplevel, TRUE);

			/* Clear the contents clist and load the listing
			 * of the new archive specified by new_path
			 */
			EDVArchiverSelectArchive(
			    archiver, new_path,
			    EDVArchiverCurrentPassword(archiver)
			);

			/* Report new object added */
			EDVObjectAddedEmit(
			    core_ptr, new_path, &stat_buf
			);

			GUIBlockInput(toplevel, FALSE);
			EDVArchiverSetBusy(archiver, FALSE);
		    }
		    else
		    {
			/* Unable to create new archive */
			gchar *buf = g_strdup_printf(
"Unable to create new archive:\n\
\n\
    %s\n",
			    new_path
			);
			EDVPlaySoundError(core_ptr);
			EDVMessageError(
			    "Create Archive Failed",
			    buf,
			    NULL,
			    toplevel
			);
			g_free(buf);
		    }
		}
		else
		{
		    /* An object already exists at the specified 
		     * location
		     */
		    EDVPlaySoundWarning(core_ptr);
		    EDVMessageWarning(
			"Create Archive Failed",
			"An object already exists at the specified location",
			NULL,
			toplevel
		    );
		}

		EDVArchiverUpdateMenus(archiver);
	    }

	    g_free(new_path);
	}

	/* Delete file types list */
	FileBrowserDeleteTypeList(ftype, total_ftypes);

	/* Reset file browser due to changes */
	FileBrowserReset();


	/* If a new archive was successfully created, then prompt to
	 * add objects
	 */
	if(created_archive)
	{
	    EDVArchiverOPAdd(archiver);
	}
}

/*
 *	Open archive callback.
 */
void EDVArchiverOPOpen(edv_archiver_struct *archiver)
{
	gboolean status;
	GtkWidget *toplevel;
	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;
	edv_core_struct *core_ptr;
	const gchar *ext_list[] = EDV_ARCHIVER_ARCHIVE_FILE_EXTENSIONS;

	if((archiver == NULL) || FileBrowserIsQuery())
	    return;

	toplevel = archiver->toplevel;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if(core_ptr == NULL)
	    return;

	/* Create file types list */
	if(ext_list != NULL)
	{
	    gint i;
	    gchar *s, *s2;

	    s = STRDUP("");
	    for(i = 0; ext_list[i] != NULL; i += 2)
	    {
		s2 = g_strconcat(s, ext_list[i], NULL);
		g_free(s);
		s = s2;

		if(ext_list[i + 2] != NULL)
		{
		    s2 = g_strconcat(s, " ", NULL);
		    g_free(s);
		    s = s2;
		}
	    }
	    FileBrowserTypeListNew(
		&ftype, &total_ftypes,
		s, "All archives"
	    );
	    g_free(s);

	    for(i = 0; ext_list[i] != NULL; i += 2)
		FileBrowserTypeListNew(
		    &ftype, &total_ftypes,
		    ext_list[i], ext_list[i + 1]
		);
	}
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);


	/* Query user for archive to open */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Open Archive",
	    "Open", "Cancel",
	    NULL,		/* Startup path */
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* Got user response? */
	if(status)
	{
	    const gchar *new_path = (total_path_rtns > 0) ?
		path_rtn[0] : NULL;
	    if(!STRISEMPTY(new_path))
	    {
		EDVArchiverSetBusy(archiver, TRUE);
		GUIBlockInput(toplevel, TRUE);

		/* Clear the contents clist and load the listing of the
		 * new archive specified by new_path
		 */
		EDVArchiverSelectArchive(
		    archiver, new_path,
		    EDVArchiverCurrentPassword(archiver)
		);

		GUIBlockInput(toplevel, FALSE);
		EDVArchiverSetBusy(archiver, FALSE);

		EDVArchiverUpdateMenus(archiver);
	    }
	}

	/* Delete file types list */
	FileBrowserDeleteTypeList(ftype, total_ftypes);
}


/*
 *      Close.
 */
void EDVArchiverOPClose(edv_archiver_struct *archiver)
{
	if(archiver == NULL)
	    return;

	EDVArchiverSyncConfiguration(archiver);
	EDVArchiverUnmap(archiver);
}

/*
 *	Close All Windows.
 */
void EDVArchiverOPExit(edv_archiver_struct *archiver)
{
	edv_core_struct *core_ptr;


	if(archiver == NULL)
	    return;

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

	EDVArchiverSyncConfiguration(archiver);
	EDVArchiverUnmap(archiver);

	/* Set need_close_all_windows on the core structure to TRUE,
	 * this will be checked during the management timeout, in
	 * which case all windows will be deleted
	 */
	core_ptr->need_close_all_windows = TRUE;
}


/*
 *	Returns a list of dynamically allocated archive object stat
 *	structures from the selected rows on the archiver's contents
 *	clist.
 *
 *	The calling function must deallocate the returned list and
 *	each structure.
 */
static edv_archive_object_struct **EDVArchiverGetSelectedObjects(
	edv_archiver_struct *archiver, gint *total
)
{
	gint i;
	GList *glist;
	GtkCList *clist;
	edv_archive_object_struct **list = NULL, *obj;


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

	if((archiver == NULL) || (total == NULL))
	    return(list);

	clist = (GtkCList *)archiver->contents_clist;
	if(clist == NULL)
	    return(list);

	/* Generate a list of archive object stat structures from the
	 * selected rows on the contents clist. Each structure must
	 * be coppied
	 */
	glist = clist->selection;
	while(glist != NULL)
	{
	    obj = (edv_archive_object_struct *)gtk_clist_get_row_data(
		clist, (gint)glist->data
	    );
	    if(obj != NULL)
	    {
		i = *total;
		*total = i + 1;
		list = (edv_archive_object_struct **)g_realloc(
		    list,
		    (*total) * sizeof(edv_archive_object_struct *)
		);
		if(list == NULL)
		    *total = 0;
		else
		    list[i] = EDVArchObjectCopy(obj);
	    }
	    glist = g_list_next(glist);
	}

	return(list);
}

/*
 *	Add Objects To Archive.
 */
void EDVArchiverOPAdd(edv_archiver_struct *archiver)
{
	gboolean yes_to_all = FALSE;
	gboolean recurse = TRUE;
	gint compression = 50;		/* 0 to 100 */
	gboolean dereference_links = FALSE;

	GtkWidget *toplevel;
	edv_core_struct *core_ptr;
	gchar *arch_obj = NULL, *add_dir = NULL;
	const gchar *error_mesg;
	gint i, status;

	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;

	gchar **tar_path = NULL;
	gint total_tar_paths = 0;
	gchar **new_aobj = NULL;
	gint total_new_aobjs = 0;


	if(archiver == NULL)
	    return;

	toplevel = archiver->toplevel;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if(core_ptr == NULL)
	    return;

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

#define DO_FREE_LOCALS	{		\
 for(i = 0; i < total_new_aobjs; i++)	\
  g_free(new_aobj[i]);			\
 g_free(new_aobj);			\
 new_aobj = NULL;			\
 total_new_aobjs = 0;			\
					\
 for(i = 0; i < total_tar_paths; i++)	\
  g_free(tar_path[i]);			\
 g_free(tar_path);			\
 tar_path = NULL;			\
 total_tar_paths = 0;			\
					\
 g_free(arch_obj);			\
 arch_obj = NULL;			\
 g_free(add_dir);			\
 add_dir = NULL;			\
					\
 /* Delete file types list */		\
 FileBrowserDeleteTypeList(ftype, total_ftypes); \
 ftype = NULL;				\
 total_ftypes = 0;			\
}

	/* Get current archive path */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Generate suggested add location based on parent directory
	 * of current archive
	 */
	add_dir = g_dirname(arch_obj);


	EDVArchiverSetBusy(archiver, TRUE);


	/* Begin querying user for add path */

	/* Create file types list */
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);

	/* Query user for extract destination */
	FileBrowserSetTransientFor(toplevel);
	status = (gint)FileBrowserGetResponse(
	    "Add Objects To Archive",
	    "Add", "Cancel",
	    add_dir,
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* User canceled? */
	if(!status)
	{
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}

	/* Get list of target objects to add */
	if(total_path_rtns > 0)
	{
	    const gchar *path;

	    total_tar_paths = total_path_rtns;
	    tar_path = (gchar **)g_realloc(
		tar_path, total_tar_paths * sizeof(gchar *)
	    );
	    for(i = 0; i < total_tar_paths; i++)
	    {
		path = path_rtn[i];
		if(STRISEMPTY(path))
		    continue;

		tar_path[i] = STRDUP(path);

		/* Path from file browser may have tailing
		 * deliminators, get rid of them
		 */
		StripPath(tar_path[i]);
	    }
	}

	/* Query user for add to archive options */
	if(!EDVArchAddOptsGetResponse(
	    core_ptr, toplevel, arch_obj,
	    &recurse, &compression, &dereference_links
	))
	{
	    /* User canceled */
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}


	status = 0;
	if(TRUE)
	{
	    /* Add the selected object(s) to the archive */
	    status = EDVArchOPAdd(
		core_ptr, arch_obj, tar_path, total_tar_paths,
		&new_aobj, &total_new_aobjs,
		toplevel, TRUE, TRUE, &yes_to_all,
		recurse, compression, dereference_links
	    );

	    /* Get error message (if any) that might have occured in the
	     * above operation
	     */
	    error_mesg = EDVArchOPGetError();
	    if(!STRISEMPTY(error_mesg))
	    {
		EDVPlaySoundError(core_ptr);
		EDVMessageError(
		    "Operation Error",
		    error_mesg,
		    NULL,
		    toplevel
		);
	    }

	    /* Report that the archive has been modified, this will
	     * cause the Archiver to reload the archive listing
	     */
	    if(!status)
	    {
		struct stat lstat_buf;
		if(!lstat(arch_obj, &lstat_buf))
		    EDVObjectModifiedEmit(
			core_ptr, arch_obj, arch_obj, &lstat_buf
		    );
	    }
	}

	/* Unmap progress dialog, it may have been mapped if any
	 * operations occured in the above loop
	 */
	ProgressDialogBreakQuery(TRUE);
	ProgressDialogSetTransientFor(NULL);

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

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update status bar */
	if(TRUE)
	{
	    gchar *buf;

	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Added %i object%s",
		    total_new_aobjs,
		    (total_new_aobjs == 1) ? "" : "s"
		);
		break;

	      case -4:  /* Cancel */
		buf = g_strdup_printf(
		    "Add operation canceled"
		);
		break;

	      default:  /* Error */
		buf = STRDUP("Unable to add object");
		break;
	    }
	    EDVStatusBarMessage(archiver->status_bar, buf, FALSE);
	    g_free(buf);
	}

	/* Reset due to possible file related change */
	FileBrowserReset();

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Extract Objects From Archive.
 */
void EDVArchiverOPExtract(edv_archiver_struct *archiver)
{
	gboolean yes_to_all = FALSE;
	gboolean	preserve_directories = TRUE,
			preserve_timestamps = TRUE;
	gint objects_extracted = 0;
	GtkWidget *toplevel;
	edv_archive_object_struct **list = NULL;
	edv_core_struct *core_ptr;
	gchar *arch_obj = NULL, *dest_path = NULL;
	gint status, total = 0;

	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;

	if(archiver == NULL)
	    return;

	toplevel = archiver->toplevel;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if(core_ptr == NULL)
	    return;

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

#define DO_FREE_LOCALS	{		\
 gint i;				\
					\
 for(i = 0; i < total; i++)		\
  EDVArchObjectDelete(list[i]);		\
 g_free(list);				\
 list = NULL;				\
 total = 0;				\
					\
 g_free(arch_obj);			\
 arch_obj = NULL;			\
 g_free(dest_path);			\
 dest_path = NULL;			\
					\
 /* Delete file types list */		\
 FileBrowserDeleteTypeList(ftype, total_ftypes); \
 ftype = NULL;				\
 total_ftypes = 0;			\
}

	/* Get current archive path */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Get list of selected archive objects */
	list = EDVArchiverGetSelectedObjects(archiver, &total);
	if((list == NULL) || (total <= 0))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Generate suggested extract location based on parent
	 * directory of the current archive
	 */
	dest_path = g_dirname(arch_obj);


	EDVArchiverSetBusy(archiver, TRUE);


	/* Begin querying user for extract destination */

	/* Generate file types list */
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);

	/* Query user for extract destination */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Select Extract Destination",
	    "Select", "Cancel",
	    dest_path,
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* User canceled? */
	if(!status)
	{
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}

	/* Get new destination */
	g_free(dest_path);
	dest_path = NULL;
	if(total_path_rtns > 0)
	{
	    const gchar *path = path_rtn[0];
	    if(!STRISEMPTY(path))
	    {
		g_free(dest_path);
		dest_path = STRDUP(path);

		/* Path from file browser may have tailing
		 * deliminators, so we need to get rid of them
		 */
		StripPath(dest_path);
	    }
	}
	/* No destination path selected or destination path is not
	 * a directory?
	 */
	if(dest_path == NULL)
	{
	    EDVPlaySoundWarning(core_ptr);
	    EDVMessageWarning(
		"Invalid Extract Destination",
		"The selected destination directory is invalid",
		NULL,
		toplevel
	    );
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}

	/* Query user for extract from archive options */
	if(!EDVArchExtractOptsGetResponse(
	    core_ptr, toplevel, arch_obj,
	    &preserve_directories,
	    &preserve_timestamps
	))
	{
	    /* User canceled */
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}


	status = 0;
	if(TRUE)
	{
	    const gchar *error_mesg;
	    gchar **new_path = NULL;
	    gint total_new_paths = 0;

	    /* Extract */
	    status = EDVArchOPExtract(
		core_ptr, arch_obj, list, total,
		dest_path, &new_path, &total_new_paths,
		toplevel, TRUE, TRUE, &yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );

	    /* Get error message (if any) that might have occured in the
	     * above operation
	     */
	    error_mesg = EDVArchOPGetError();
	    if(!STRISEMPTY(error_mesg))
	    {
		EDVPlaySoundError(core_ptr);
		EDVMessageError(
		    "Operation Error",
		    error_mesg,
		    NULL,
		    toplevel
		);
	    }

	    /* Report object(s) extracted? */
	    if((new_path != NULL) && !status)
	    {
		gint i;
		const gchar *s;
		struct stat lstat_buf;

		for(i = 0; i < total_new_paths; i++)
		{
		    s = new_path[i];
		    if(!STRISEMPTY(s) ? lstat(s, &lstat_buf) : TRUE)
			continue;

		    objects_extracted++;

		    EDVObjectAddedEmit(
			core_ptr, s, &lstat_buf
		    );
		}
	    }

	    /* Delete extracted objects list */
	    strlistfree(new_path, total_new_paths);
	}

	/* Unmap progress dialog, it may have been mapped if any
	 * operations occured in the above loop
	 */
	ProgressDialogBreakQuery(TRUE);
	ProgressDialogSetTransientFor(NULL);

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

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update status bar */
	if(TRUE)
	{
	    gchar *buf;

	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Extracted %i object%s",
		    objects_extracted,
		    (objects_extracted == 1) ? "" : "s"
		);
		break;

	      case -4:  /* Cancel */
		buf = g_strdup_printf(
		    "Extract operation canceled"
		);
		break;

	      default:  /* Error */
		buf = g_strdup_printf(
		    "Unable to extract object%s",
		    (total == 1) ? "" : "s"
		);
		break;
	    }
	    EDVStatusBarMessage(archiver->status_bar, buf, FALSE);
	    g_free(buf);
	}

	/* Reset due to possible file related change */
	FileBrowserReset();

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Extract All Objects From Archive.
 */
void EDVArchiverOPExtractAll(edv_archiver_struct *archiver)
{
	gboolean yes_to_all = FALSE;
	gboolean	preserve_directories = TRUE,
			preserve_timestamps = TRUE;
	gint objects_extracted = 0;
	GtkWidget *toplevel;
	GtkCList *clist;
	edv_archive_object_struct **list = NULL;
	edv_core_struct *core_ptr;
	gchar *arch_obj = NULL, *dest_path = NULL;
	gint status, total = 0;

	fb_type_struct **ftype = NULL, *ftype_rtn = NULL;
	gint total_ftypes = 0;
	gchar **path_rtn = NULL;
	gint total_path_rtns = 0;

	if(archiver == NULL)
	    return;

	toplevel = archiver->toplevel;
	clist = (GtkCList *)archiver->contents_clist;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if((clist == NULL) || (core_ptr == NULL))
	    return;

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

#define DO_FREE_LOCALS	{		\
 gint i;				\
					\
 for(i = 0; i < total; i++)		\
  EDVArchObjectDelete(list[i]);		\
 g_free(list);				\
 list = NULL;				\
 total = 0;				\
					\
 g_free(arch_obj);			\
 arch_obj = NULL;			\
 g_free(dest_path);			\
 dest_path = NULL;			\
					\
 /* Delete file types list */		\
 FileBrowserDeleteTypeList(ftype, total_ftypes);\
 ftype = NULL;				\
 total_ftypes = 0;			\
}

	/* Get current archive path */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Get list of all archive objects */
	total = clist->rows;
	if(total > 0)
	{
	    gint i;
	    edv_archive_object_struct *obj;

	    list = (edv_archive_object_struct **)g_malloc0(
		total * sizeof(edv_archive_object_struct *)
	    );
	    if(list == NULL)
		total = 0;
	    for(i = 0; i < total; i++)
	    {
		obj = EDV_ARCHIVE_OBJECT(
		    gtk_clist_get_row_data(clist, i)
		);
		if(obj != NULL)
		    list[i] = EDVArchObjectCopy(obj);
	    }
	}
	if((list == NULL) || (total <= 0))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Generate suggested extract location based on parent directory
	 * of current archive
	 */
	dest_path = g_dirname(arch_obj);


	EDVArchiverSetBusy(archiver, TRUE);


	/* Begin querying user for extract destination */

	/* Create file types list */
	FileBrowserTypeListNew(
	    &ftype, &total_ftypes,
	    "*.*", "All files"
	);

	/* Query user for extract destination */
	FileBrowserSetTransientFor(toplevel);
	status = (gint)FileBrowserGetResponse(
	    "Select Extract Destination",
	    "Select", "Cancel",
	    dest_path,
	    ftype, total_ftypes,
	    &path_rtn, &total_path_rtns,
	    &ftype_rtn
	);
	FileBrowserSetTransientFor(NULL);

	/* User canceled? */
	if(!status)
	{
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}

	/* Get new destination */
	g_free(dest_path);
	dest_path = NULL;
	if(total_path_rtns > 0)
	{
	    const gchar *path = path_rtn[0];
	    if(!STRISEMPTY(path))
	    {
		g_free(dest_path);
		dest_path = STRDUP(path);

		/* Path from file browser may have tailing
		 * deliminators, so we need to get rid of them
		 */
		StripPath(dest_path);
	    }
	}
	/* No destination path selected or destination path is not
	 * a directory?
	 */
	if(dest_path == NULL)
	{
	    EDVPlaySoundWarning(core_ptr);
	    EDVMessageWarning(
		"Invalid Extract Destination",
		"The selected destination directory is invalid",
		NULL,
		toplevel
	    );

	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}

	/* Query user for extract from archive options */
	if(!EDVArchExtractOptsGetResponse(
	    core_ptr, toplevel, arch_obj,
	    &preserve_directories,
	    &preserve_timestamps
	))
	{
	    /* User canceled */
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}


	status = 0;
	if(TRUE)
	{
	    const gchar *error_mesg;
	    gchar **new_path = NULL;
	    gint total_new_paths = 0;

	    /* Extract */
	    status = EDVArchOPExtract(
		core_ptr, arch_obj, list, total,
		dest_path, &new_path, &total_new_paths,
		toplevel, TRUE, TRUE, &yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );

	    /* Get error message (if any) that might have occured in the
	     * above operation
	     */
	    error_mesg = EDVArchOPGetError();
	    if(!STRISEMPTY(error_mesg))
	    {
		EDVPlaySoundError(core_ptr);
		EDVMessageError(
		    "Operation Error",
		    error_mesg,
		    NULL,
		    toplevel
		);
	    }

	    /* Report object(s) extracted? */
	    if((new_path != NULL) && !status)
	    {
		gint i;
		const gchar *s;
		struct stat lstat_buf;

		for(i = 0; i < total_new_paths; i++)
		{
		    s = new_path[i];
		    if(!STRISEMPTY(s) ? lstat(s, &lstat_buf) : TRUE)
			continue;

		    objects_extracted++;

		    EDVObjectAddedEmit(
			core_ptr, s, &lstat_buf
		    );
		}
	    }

	    /* Delete extracted objects list */ 
	    strlistfree(new_path, total_new_paths);
	}

	/* Unmap progress dialog, it may have been mapped if any
	 * operations occured in the above loop
	 */
	ProgressDialogBreakQuery(TRUE);
	ProgressDialogSetTransientFor(NULL);

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

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update status bar */
	if(TRUE)
	{
	    gchar *buf;

	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Extracted %i object%s",
		    objects_extracted,
		    (objects_extracted == 1) ? "" : "s"
		);
		break;

	      case -4:  /* Cancel */
		buf = g_strdup_printf(
		    "Extract operation canceled"
		);
		break;

	      default:  /* Error */
		buf = g_strdup_printf(
		    "Unable to extract object%s",
		    (total == 1) ? "" : "s"
		);
		break;
	    }
	    EDVStatusBarMessage(archiver->status_bar, buf, FALSE);
	    g_free(buf);
	}

	/* Reset due to possible file related change */
	FileBrowserReset();

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Delete Object From Archive.
 */
void EDVArchiverOPDelete(edv_archiver_struct *archiver)
{
	gboolean yes_to_all = FALSE;
	gint objects_deleted = 0;
	GtkWidget *toplevel;
	edv_archive_object_struct **list = NULL, *obj;
	edv_core_struct *core_ptr;
	gchar *arch_obj = NULL;
	const gchar *src_path, *error_mesg;
	gint i, status, total = 0;


	if(archiver == NULL)
	    return;

	toplevel = archiver->toplevel;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if(core_ptr == NULL)
	    return;

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

#define DO_FREE_LOCALS	{		\
 src_path = NULL;			\
					\
 for(i = 0; i < total; i++)		\
  EDVArchObjectDelete(list[i]);		\
 g_free(list);				\
 list = NULL;				\
 total = 0;				\
					\
 g_free(arch_obj);			\
 arch_obj = NULL;			\
}

	/* Get current archive path */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Get list of selected archive objects */
	list = EDVArchiverGetSelectedObjects(archiver, &total);
	if((list == NULL) || (total <= 0))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Get source path to first object */
	if(total == 1)
	    src_path = (list[0] != NULL) ? list[0]->full_path : NULL;
	else
	    src_path = NULL;


	EDVArchiverSetBusy(archiver, TRUE);


	/* Confirm delete */
	status = EDVConfirmArchiveDelete(
	    core_ptr, toplevel, src_path, total
	);
	switch(status)
	{
	  case CDIALOG_RESPONSE_YES_TO_ALL:
	    yes_to_all = TRUE;
	  case CDIALOG_RESPONSE_YES:
	    break;

	  default:
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	    break;
	}


	/* Iterate through selected objects */
	status = 0;
	for(i = 0; i < total; i++)
	{
	    obj = list[i];
	    if(obj == NULL)
		continue;

	    /* Delete archieve object */
	    status = EDVArchOPDelete(
		core_ptr, arch_obj, obj, toplevel,
		TRUE, TRUE, &yes_to_all
	    );

	    /* Get error message (if any) that might have occured in
	     * the above operation
	     */
	    error_mesg = EDVArchOPGetError();
	    if(!STRISEMPTY(error_mesg))
	    {
		EDVPlaySoundError(core_ptr);
		EDVMessageError(
		    "Operation Error",
		    error_mesg,
		    NULL,
		    toplevel
		);
	    }

	    /* Was the archive object successfully delected? */
	    if(!status)
	    {
		/* Report archive object removed */
		EDVArchiveObjectRemovedNotifyCB(
		    core_ptr, arch_obj, obj->full_path
		);

		/* Increment number of archive objects deleted */
		objects_deleted++;
	    }

	    /* Skip handling of the rest of the objects on error
	     * (status != 0) and that the error was not a user response
	     * of no (status != -5)
	     */
	    if(status && (status != -5))
		break;
	}

	/* Unmap progress dialog, it may have been mapped if any
	 * operations occured in the above loop
	 */
	ProgressDialogBreakQuery(TRUE);
	ProgressDialogSetTransientFor(NULL);

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

	EDVArchiverSetBusy(archiver, FALSE);
	
	/* Report that the archive has been modified, this will
	 * cause the archiver to reload the archive listing
	 */
	if(!status)
	{
	    struct stat lstat_buf;
	    if(!lstat(arch_obj, &lstat_buf))
		EDVObjectModifiedEmit(
		    core_ptr, arch_obj, arch_obj, &lstat_buf
		);
	}

	/* Update status bar */
	if(TRUE)
	{
	    gchar *buf;

	    switch(status)
	    {
	      case 0: case -5:
		buf = g_strdup_printf(
		    "Deleted %i object%s",
		    objects_deleted,
		    (objects_deleted == 1) ? "" : "s"
		);
		break;

	      case -4:  /* Cancel */
		buf = g_strdup_printf(
		    "Delete operation canceled"
		);
		break;

	      default:  /* Error */
		buf = g_strdup_printf(
		    "Unable to delete object%s",
		    (total == 1) ? "" : "s"
		);
		break;
	    }
	    EDVStatusBarMessage(archiver->status_bar, buf, FALSE);
	    g_free(buf);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Select All.
 */
void EDVArchiverOPSelectAll(edv_archiver_struct *archiver)
{
	edv_core_struct *core_ptr;
	GtkCList *clist;


	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if((clist == NULL) || (core_ptr == NULL))
	    return;

	EDVArchiverSetBusy(archiver, TRUE);

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

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

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

	EDVArchiverSetBusy(archiver, FALSE);
}

/*
 *	Unselect All.
 */
void EDVArchiverOPUnselectAll(edv_archiver_struct *archiver)
{
	edv_core_struct *core_ptr;
	GtkCList *clist;


	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if((clist == NULL) || (core_ptr == NULL))
	    return;

	EDVArchiverSetBusy(archiver, TRUE);

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

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

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

	EDVArchiverSetBusy(archiver, FALSE);
}

/*
 *      Invert Selection.
 */
void EDVArchiverOPInvertSelection(edv_archiver_struct *archiver)
{
	edv_core_struct *core_ptr;
	GtkCList *clist;
	GList *glist, *selection;
	gint row, total_rows;


	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	core_ptr = EDV_CORE(archiver->core_ptr);
	if((clist == NULL) || (core_ptr == NULL))
	    return;

	EDVArchiverSetBusy(archiver, 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(
	    archiver->status_bar, "Selection inverted", FALSE
	);
	EDVArchiverSetBusy(archiver, FALSE);
}

/*
 *	Archive Properties.
 */
void EDVArchiverOPProperties(edv_archiver_struct *archiver)
{
	gchar *arch_obj;
	struct stat lstat_buf;

	if(archiver == NULL)
	    return;

	/* Get current archive as the object */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	    return;

	/* Get object statistics */
	if(!lstat(arch_obj, &lstat_buf))
	{
	    edv_object_struct *obj = EDVObjectNew();
	    EDVObjectSetPath(obj, arch_obj);
	    EDVObjectSetStat(obj, &lstat_buf);

	    /* Create a new Properties Dialog */
	    EDVNewPropertiesDialog(
		EDV_CORE(archiver->core_ptr),
		obj,
		archiver->toplevel
	    );

	    EDVObjectDelete(obj);
	}

	g_free(arch_obj);
}


/*
 *	Refresh.
 */
void EDVArchiverOPRefresh(edv_archiver_struct *archiver)
{
	GtkWidget *w;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core_ptr;

	if(archiver == NULL)
	    return;

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

	cfg_list = core_ptr->cfg_list;

	EDVArchiverSetBusy(archiver, TRUE);
	GUIBlockInput(archiver->toplevel, TRUE);

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

	/* Update contents clist */
	clist = (GtkCList *)archiver->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);

	    /* Update list items */
	    EDVArchiverContentsDoUpdate(
		archiver,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );

	    gtk_clist_thaw(clist);

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

	EDVArchiverUpdateMenus(archiver);
	EDVStatusBarMessage(
	    archiver->status_bar, "Refreshed contents listing", FALSE
	);

	GUIBlockInput(archiver->toplevel, FALSE);
	EDVArchiverSetBusy(archiver, FALSE);
}

/*
 *      Refresh All.
 */
void EDVArchiverOPRefreshAll(edv_archiver_struct *archiver)
{
	if(archiver == NULL)
	    return;

	/* Refresh Archiver */
	EDVArchiverOPRefresh(archiver);
}

/*
 *	View Comment & Statistics.
 */
void EDVArchiverOPCommentAndStatistics(edv_archiver_struct *archiver)
{
	gchar *arch_obj;
	edv_archive_info_struct *d;

	if(archiver == NULL)
	    return;

	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	    return;

	/* Need to delete existing archive info dialog (if any) */
	d = archiver->arch_info;
	if(d != NULL)
	{
	    EDVArchiveInfoDelete(d);
	    archiver->arch_info = d = NULL;
	}

	EDVArchiverSetBusy(archiver, TRUE);

	/* Create new archive info dialog, load the current archive
	 * information, and map the archive info dialog
	 */
	archiver->arch_info = d = EDVArchiveInfoNew(
	    EDV_CORE(archiver->core_ptr),
	    arch_obj,
	    archiver->toplevel
	);
	if(d != NULL)
	{
	    EDVArchiveInfoMap(d);
	    EDVArchiveInfoResetHasChanges(d, FALSE);
	    EDVArchiveInfoUpdateMenus(d);
	}

	EDVArchiverSetBusy(archiver, FALSE);

	g_free(arch_obj);
}
