#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.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 "pdialog.h"
#include "fb.h"
#include "progressdialog.h"
#include "toolbar.h"

#include "cfg.h"
#include "edv_types.h"
#include "edv_confirm.h"
#include "edv_obj.h"
#include "edv_archive_obj.h"
#include "edv_archive_stat.h"
#include "edv_archive_add.h"
#include "edv_archive_extract.h"
#include "edv_archive_delete.h"
#include "edv_archive_chk.h"
#include "edv_archive_fix.h"
#include "edv_find_bar.h"
#include "edv_status_bar.h"
#include "archive_options_dlg.h"
#include "archive_info_dlg.h"
#include "archiver.h"
#include "archiver_cb.h"
#include "archiver_op_cb.h"
#include "archiver_contents_list.h"
#include "endeavour2.h"
#include "edv_cb.h"
#include "edv_help.h"
#include "edv_op.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#include "images/icon_wildcards_32x32.xpm"


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 EDVArchiverOPSyncDisks(edv_archiver_struct *archiver);
void EDVArchiverOPWriteProtect(edv_archiver_struct *archiver);
void EDVArchiverOPDeleteMethodRecycle(edv_archiver_struct *archiver);
void EDVArchiverOPDeleteMethodPurge(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 GList *EDVArchiverGetSelectedObjects(
	edv_archiver_struct *archiver
);
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 EDVArchiverOPCheck(edv_archiver_struct *archiver);
void EDVArchiverOPFix(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);

void EDVArchiverContentsFilter(edv_archiver_struct *archiver);
void EDVArchiverMIMETypes(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)


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

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

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

	toplevel = archiver->toplevel;
	core = archiver->core;
	if(core == 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_CHECK:
	    EDVArchiverOPCheck(archiver);
	    break;

	  case EDV_ARCHIVER_OP_FIX:
	    EDVArchiverOPFix(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, archiver);
	    break;

	  case EDV_ARCHIVER_OP_PROPERTIES:
	    EDVArchiverOPProperties(archiver);
	    break;


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

	  case EDV_ARCHIVER_OP_SYNC_DISKS:
	    EDVArchiverOPSyncDisks(archiver);
	    break;

	  case EDV_ARCHIVER_OP_WRITE_PROTECT:
	    EDVArchiverOPWriteProtect(archiver);
	    break;

	  case EDV_ARCHIVER_OP_DELETE_METHOD_RECYCLE:
	    EDVArchiverOPDeleteMethodRecycle(archiver);
	    break;

	  case EDV_ARCHIVER_OP_DELETE_METHOD_PURGE:
	    EDVArchiverOPDeleteMethodPurge(archiver);
	    break;

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

	  case EDV_ARCHIVER_OP_RUN_TERMINAL:
	    if(core != NULL)
	    {
		gchar	*location = STRDUP(EDVArchiverCurrentLocation(archiver)),
			*wd = g_dirname(location);
		EDVRunTerminal(core, NULL, wd, toplevel);
		g_free(wd);
		g_free(location);
	    }
	    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 != NULL)
	    {
		gboolean state = !CFGItemListGetValueI(
		    core->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_TOOL_BAR
		);
		CFGItemListSetValueI(
		    core->cfg_list, EDV_CFG_PARM_ARCHIVER_SHOW_TOOL_BAR,
		    state, FALSE
		);
		EDVReconfiguredEmit(core);
	    }
	    break;

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

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

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


	  case EDV_ARCHIVER_OP_CONTENTS_LIST_FILTER:
	    EDVArchiverContentsFilter(archiver);
	    break;


	  case EDV_ARCHIVER_OP_MIME_TYPES:
	    EDVArchiverMIMETypes(archiver);
	    break;


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

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

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

	  case EDV_ARCHIVER_OP_RECYCLE_BIN:
	    EDVMapRecBin(core);
	    break;


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

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


	  case EDV_ARCHIVER_OP_HELP_ABOUT:
	    EDVAbout(core, toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_CONTENTS:
	    EDVHelp(core, "Contents", toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_FILE_BROWSER:
	    EDVHelp(core, "File Browser", toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_IMAGE_BROWSER:
	    EDVHelp(core, "Image Browser", toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_ARCHIVER:
	    EDVHelp(core, "Archiver", toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_RECYCLE_BIN:
	    EDVHelp(core, "Recycle Bin", toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_KEYS_LIST:
	    EDVHelp(core, "Keys List", toplevel);
	    break;
	  case EDV_ARCHIVER_OP_HELP_COMMON_OPERATIONS:
	    EDVHelp(core, "Common Operations", 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 = (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 = (opid != NULL) ? opid->archiver : NULL;
	if(archiver == NULL)
	    return;

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


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

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

	EDVArchiverSetBusy(archiver, TRUE);

	EDVSyncDisks(archiver->core);

	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;

	if(archiver == NULL)
	    return;

	core = archiver->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 EDVArchiverOPDeleteMethodRecycle(edv_archiver_struct *archiver)
{
	cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	core = archiver->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 EDVArchiverOPDeleteMethodPurge(edv_archiver_struct *archiver)
{
	cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

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

	cfg_list = core->cfg_list;

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


/*
 *	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;
	const gchar *ext_list[] = EDV_ARCHIVER_ARCHIVE_FILE_EXTENSIONS;

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

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

	/* Check and warn if write protect is enabled */
	if(EDVCheckWriteProtect(core, 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]
	    );
	FileBrowserTypeListNew(
            &ftype, &total_ftypes,
            "*.*", "All Files"
        );

	/* Query user for new archive */
	FileBrowserSetTransientFor(toplevel);
	status = FileBrowserGetResponse(
	    "Create Archive",
	    "Create", "Cancel",
	    NULL,			/* Use the last location */
	    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))
		{
		    /* The archive does not exist, so 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;

			/* Open the new archive */
			EDVArchiverOpenArchive(
			    archiver, new_path,
			    EDVArchiverCurrentPassword(archiver)
			);

			/* Report new object added */
			EDVObjectAddedEmit(
			    core, new_path, &stat_buf
			);
		    }
		    else
		    {
			/* Unable to create a new archive */
			const gint error_code = (gint)errno;
			gchar *error_msg = STRDUP(g_strerror(error_code));
			if(error_msg != NULL)
			{
			    gchar *s;

			    *error_msg = toupper(*error_msg);

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

		EDVArchiverUpdateMenus(archiver);
	    }

	    g_free(new_path);
	}

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

	/* Reset the 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;
	const gchar *ext_list[] = EDV_ARCHIVER_ARCHIVE_FILE_EXTENSIONS;

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

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

	/* Create the 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))
	    {
		/* Open the archive */
		EDVArchiverOpenArchive(
		    archiver,
		    new_path,
		    EDVArchiverCurrentPassword(archiver)
		);
	    }
	}

	/* Delete the 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;


	if(archiver == NULL)
	    return;

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

	EDVArchiverSyncConfiguration(archiver);
	EDVArchiverUnmap(archiver);

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


/*
 *	Returns a list of selected objects in the archive.
 *
 *	The calling function must delete the returned list and each
 *	object.
 */
static GList *EDVArchiverGetSelectedObjects(
	edv_archiver_struct *archiver
)
{
	GList *glist, *obj_list = NULL;
	GtkCList *clist;
	edv_archive_object_struct *obj;

	if(archiver == NULL)
	    return(obj_list);

	clist = (GtkCList *)archiver->contents_clist;
	if(clist == NULL)
	    return(obj_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(gtk_clist_get_row_data(
		clist, (gint)glist->data
	    ));
	    if(obj != NULL)
		obj_list = g_list_append(
		    obj_list, EDVArchObjectCopy(obj)
		);

	    glist = g_list_next(glist);
	}

	return(obj_list);
}

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

	gint status, nobjs, objects_added = 0;
	gchar *arch_obj = NULL, *add_dir = NULL;
	const gchar *error_msg;
	GtkWidget *toplevel;
	edv_core_struct *core;

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

	GList *src_paths_list = NULL, *new_paths_list = NULL;


	if(archiver == NULL)
	    return;

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

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

#define DO_FREE_LOCALS	{		\
 if(new_paths_list != NULL) {		\
  g_list_foreach(			\
   new_paths_list, (GFunc)g_free, NULL	\
  );					\
  g_list_free(new_paths_list);		\
  new_paths_list = NULL;		\
 }					\
					\
 if(src_paths_list != NULL) {		\
  g_list_foreach(			\
   src_paths_list, (GFunc)g_free, NULL	\
  );					\
  g_list_free(src_paths_list);		\
  src_paths_list = NULL;		\
 }					\
					\
 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 the list of objects to add */
	if(total_path_rtns > 0)
	{
	    gint i;
	    const gchar *s;
	    gchar *path;

	    for(i = 0; i < total_path_rtns; i++)
	    {
		s = path_rtn[i]; 
		if(STRISEMPTY(s))
		    continue;

		path = STRDUP(s);
		if(path == NULL)
		    continue;

		/* Path from file browser may have tailing
		 * deliminators, get rid of them
		 */
		StripPath(path);

		src_paths_list = g_list_append(src_paths_list, path);
	    }
	}
	if(src_paths_list == NULL)
	{
	    /* User specified no objects to add */
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}

	nobjs = g_list_length(src_paths_list);

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

	/* Record if the archive exists */
	if(!STRISEMPTY(arch_obj))
	{
	    struct stat stat_buf;
	    if(!stat(arch_obj, &stat_buf))
		archive_existed = TRUE;
	}

	status = 0;
	if(TRUE)
	{
	    /* Add the selected object(s) to the archive */
	    status = EDVArchAdd(
		core, arch_obj,
		src_paths_list,		/* List of objects to add to the
					 * archive */
		&new_paths_list,	/* Return list of objects added to
					 * the archive */
		EDVArchiverCurrentPassword(archiver),
		toplevel, TRUE, TRUE, &yes_to_all,
		recurse, compression, dereference_links
	    );

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

	    objects_added = g_list_length(new_paths_list);

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

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

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

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update the status bar */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = g_strdup_printf(
		    "Add operation canceled"
		);
	    else if(objects_added > 0)
		s = g_strdup_printf(
		    "Added %i %s",
		    objects_added,
		    (objects_added == 1) ? "object" : "objects"
		);
	    else
		s = g_strdup_printf(
		    "Unable to add %s",
		    (nobjs == 1) ? "object" : "objects"
		);
	    EDVStatusBarMessage(archiver->status_bar, s, FALSE);
	    g_free(s);
	}

	/* 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,
			preserve_directories = TRUE,
			preserve_timestamps = TRUE;
	gint status, nobjs, objects_extracted = 0;
	GList *obj_list = NULL;
	GtkWidget *toplevel;
	edv_core_struct *core;
	gchar *arch_obj = NULL, *dest_path = NULL;

	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 = archiver->core;
	if(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)EDVArchObjectDelete, NULL	\
  );						\
  g_list_free(obj_list);			\
  obj_list = NULL;				\
 }						\
						\
 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 the list of selected objects */
	obj_list = EDVArchiverGetSelectedObjects(archiver);
	if(obj_list == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	nobjs = g_list_length(obj_list);

	/* 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);
	    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, toplevel, arch_obj,
	    &preserve_directories,
	    &preserve_timestamps
	))
	{
	    /* User canceled */
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}


	status = 0;
	if(TRUE)
	{
	    const gchar *error_msg;
	    GList *new_paths_list = NULL;

	    /* Extract */
	    status = EDVArchExtract(
		core, arch_obj, obj_list,
		dest_path, &new_paths_list,
		EDVArchiverCurrentPassword(archiver),
		toplevel, TRUE, TRUE, &yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );

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

	    /* Report the extracted objects? */
	    if(new_paths_list != NULL)
	    {
		const gchar *path;
		struct stat lstat_buf;
		GList *glist;

		for(glist = new_paths_list;
		    glist != NULL;
		    glist = g_list_next(glist)
		)
		{
		    path = (gchar *)glist->data;
		    if(!STRISEMPTY(path) ? lstat(path, &lstat_buf) : TRUE)
			continue;

		    EDVObjectAddedEmit(
			core, path, &lstat_buf
		    );
		    objects_extracted++;
		}
	    }

	    /* Delete the list of extracted object paths */
	    if(new_paths_list != NULL)
	    {
		g_list_foreach(new_paths_list, (GFunc)g_free, NULL);
		g_list_free(new_paths_list);
	    }
	}

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

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

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update status bar */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Extract operation canceled"
		);
	    else if(objects_extracted > 0)
		s = g_strdup_printf(
		    "Extracted %i %s",
		    objects_extracted,
		    (objects_extracted == 1) ? "object" : "objects"
		);
	    else
		s = g_strdup_printf(
		    "Unable to extract %s",
		    (nobjs == 1) ? "object" : "objects"
		);
	    EDVStatusBarMessage(archiver->status_bar, s, FALSE);
	    g_free(s);
	}

	/* 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,
			preserve_directories = TRUE,
			preserve_timestamps = TRUE;
	gint status, nobjs, objects_extracted = 0;
	gchar *arch_obj = NULL, *dest_path = NULL;
	GList *obj_list = NULL;
	GtkWidget *toplevel;
	GtkCList *clist;
	edv_core_struct *core;

	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 = archiver->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)EDVArchObjectDelete, NULL	\
  );						\
  g_list_free(obj_list);			\
  obj_list = NULL;				\
 }						\
						\
 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 the list of all the objects in the archive */
	if(clist->rows > 0)
	{
	    const gint nrows = clist->rows;
	    gint i;
	    edv_archive_object_struct *obj;

	    for(i = 0; i < nrows; i++)
	    {
		obj = EDV_ARCHIVE_OBJECT(
		    gtk_clist_get_row_data(clist, i)
		);
		if(obj == NULL)
		    continue;

		obj_list = g_list_append(
		    obj_list, EDVArchObjectCopy(obj)
		);
	    }
	}
	if(obj_list == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	nobjs = g_list_length(obj_list);

	/* 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);
	    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, toplevel, arch_obj,
	    &preserve_directories,
	    &preserve_timestamps
	))
	{
	    /* User canceled */
	    EDVArchiverSetBusy(archiver, FALSE);
	    DO_FREE_LOCALS
	    return;
	}


	status = 0;
	if(TRUE)
	{
	    const gchar *error_msg;
	    GList *new_paths_list = NULL;

	    /* Extract */
	    status = EDVArchExtract(
		core, arch_obj, obj_list,
		dest_path, &new_paths_list,
		EDVArchiverCurrentPassword(archiver),
		toplevel, TRUE, TRUE, &yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );

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

	    /* Report the extracted objects? */
	    if(new_paths_list != NULL)
	    {
		const gchar *path;
		struct stat lstat_buf;
		GList *glist;

		for(glist = new_paths_list;
		    glist != NULL;
		    glist = g_list_next(glist)
		)
		{
		    path = (const gchar *)glist->data;
		    if(!STRISEMPTY(path) ? lstat(path, &lstat_buf) : TRUE)
			continue;

		    EDVObjectAddedEmit(
			core, path, &lstat_buf
		    );
		    objects_extracted++;
		}
	    }

	    /* Delete the list of extracted object paths */
	    g_list_foreach(new_paths_list, (GFunc)g_free, NULL);
	    g_list_free(new_paths_list);
	}

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

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

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update the status bar */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Extract operation canceled"
		);
	    else if(objects_extracted > 0)
		s = g_strdup_printf(
		    "Extracted %i %s",
		    objects_extracted,
		    (objects_extracted == 1) ? "object" : "objects"
		);
	    else
		s = g_strdup_printf(
		    "Unable to extract %s",
		    (nobjs == 1) ? "object" : "objects"
		);
	    EDVStatusBarMessage(archiver->status_bar, s, FALSE);
	    g_free(s);
	}

	/* 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 status, nobjs, objects_deleted = 0;
	gchar *arch_obj = NULL;
	const gchar *src_path, *error_msg;
	GList *obj_list = NULL;
	GtkWidget *toplevel;
	edv_archive_object_struct *obj;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	toplevel = archiver->toplevel;
	core = archiver->core;
	if(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)EDVArchObjectDelete, NULL	\
  );						\
  g_list_free(obj_list);			\
 }						\
						\
 g_free(arch_obj);				\
}

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

	/* Get the list of selected objects */
	obj_list = EDVArchiverGetSelectedObjects(archiver);
	if(obj_list == NULL)
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Get the path of the first object only if it is the only
	 * object in the list
	 */
	nobjs = g_list_length(obj_list);
	if(nobjs == 1)
	{
	    obj = EDV_ARCHIVE_OBJECT(obj_list->data);
	    src_path = (obj != NULL) ? obj->full_path : NULL;
	}
	else
	    src_path = NULL;

	EDVArchiverSetBusy(archiver, TRUE);

	/* Confirm delete */
	status = EDVConfirmArchiveDelete(
	    core, toplevel, src_path, nobjs
	);
	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 in the archive */
	status = EDVArchDelete(
	    core,
	    arch_obj,			/* Archive */
	    obj_list,			/* Objects to delete from the archive */
	    EDVArchiverCurrentPassword(archiver),
	    toplevel, TRUE, TRUE, &yes_to_all
	);

	/* Check for and report and errors */
	error_msg = EDVArchDeleteGetError();
	if(!STRISEMPTY(error_msg))
	{
	    EDVPlaySoundError(core);
	    EDVMessageObjectOPError(
		"Delete Object From Archive Error",
		error_msg,
		arch_obj,
		toplevel
	    );
	}

	/* Was the archive object successfully delected? */
	if(status == 0)
	{
	    /* Report object in archive being removed */
	    GList *glist;
	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

#if 0
/* Note that this may not be good since some archive formats have 
 * archive programs that do not report which objects were deleted
 * so it may be more accurate to skip this and just have the
 * EDVObjectModifiedEmit() below cause this archiver to reload
 */
		EDVArchiveObjectRemovedEmit(
		    core, arch_obj, obj->full_path
		);
#endif
		objects_deleted++;
	    }
	}

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

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

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

	/* Update the status bar */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Delete operation canceled"
		);
	    else if(objects_deleted > 0)
		s = g_strdup_printf(
		    "Deleted %i %s",
		    objects_deleted,
		    (objects_deleted == 1) ? "object" : "objects"
		);
	    else
		s = g_strdup_printf(
		    "Unable to delete %s",
		    (nobjs == 1) ? "object" : "objects"
		);
	    EDVStatusBarMessage(archiver->status_bar, s, FALSE);
	    g_free(s);
	}

	DO_FREE_LOCALS
#undef DO_FREE_LOCALS
}

/*
 *	Check Archive.
 */
void EDVArchiverOPCheck(edv_archiver_struct *archiver)
{
	gboolean yes_to_all = FALSE;
	gint status;
	gchar *arch_obj;
	const gchar *error_msg;
	GtkWidget *toplevel;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

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

	/* Get the current archive's path */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	    return;

	EDVArchiverSetBusy(archiver, TRUE);

	/* Check the archive */
	status = EDVArchChk(
	    core,
	    arch_obj,
	    EDVArchiverCurrentPassword(archiver),
	    toplevel,
	    TRUE, TRUE, &yes_to_all
	);

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

	/* Get the check results */
	error_msg = EDVArchChkGetError();
	if(!STRISEMPTY(error_msg))
	{
	    gchar *s = g_strdup_printf(
"The following problem was found in the archive\n\
\"%s\":\n\
\n\
%s\n\
\n\
You may attempt to fix this problem in the archive\n\
by going to Edit->Fix.",
		g_basename(arch_obj), error_msg
	    );
	    EDVPlaySoundWarning(core);
	    EDVMessageWarning(
		"Check Results",
		s,
		NULL,
		toplevel
	    );
	    g_free(s);
	}
	else if(status != -4)
	{
	    gchar *s = g_strdup_printf(
"There were no problems found in the archive\n\
\"%s\".",
		g_basename(arch_obj)
	    );
	    EDVPlaySoundCompleted(core);
	    EDVMessageInfo(
		"Check Results",
		s,
		NULL,
		toplevel
	    );
	    g_free(s);
	}

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update the status bar */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Check operation canceled"
		);
	    else
		s = STRDUP(
		    "Checked archive"
		);
	    EDVStatusBarMessage(archiver->status_bar, s, FALSE);
	    g_free(s);
	}

	g_free(arch_obj);
}

/*
 *	Fix Archive.
 */
void EDVArchiverOPFix(edv_archiver_struct *archiver)
{
	gboolean yes_to_all = FALSE;
	gint status;
	gchar *arch_obj;
	const gchar *error_msg;
	GtkWidget *toplevel;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

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

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

	/* Get the current archive's path */
	arch_obj = STRDUP(EDVArchiverCurrentLocation(archiver));
	if(arch_obj == NULL)
	    return;

	EDVArchiverSetBusy(archiver, TRUE);

	/* Confirm fix */
	if(TRUE)
	{
	    gint response;
	    gchar *msg = g_strdup_printf(
"Fix archive \"%s\"?",
		g_basename(arch_obj)
	    );
	    EDVPlaySoundQuestion(core);
	    CDialogSetTransientFor(toplevel);
	    response = CDialogGetResponse(
		"Confirm Fix",
		msg,
		NULL,
		CDIALOG_ICON_QUESTION,
		CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_NO,
		CDIALOG_BTNFLAG_YES
	    );
	    CDialogSetTransientFor(NULL);
	    g_free(msg);
	    if(response != CDIALOG_RESPONSE_YES)
	    {
		g_free(arch_obj);
		EDVArchiverSetBusy(archiver, FALSE);
		return;
	    }
	}

	/* Fix the archive */
	status = EDVArchFix(
	    core,
	    arch_obj,
	    EDVArchiverCurrentPassword(archiver),
	    toplevel,
	    TRUE, TRUE, &yes_to_all
	);

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

	/* Get the fix results */
	error_msg = EDVArchFixGetError();
	if(!STRISEMPTY(error_msg))
	{
	    EDVPlaySoundError(core);
	    EDVMessageObjectOPError(
		"Fix Error",
		error_msg,
		arch_obj,
		toplevel
	    );
	}
	else if(status != -4)
	{
	    EDVPlaySoundCompleted(core);
	}

	if(TRUE)
	{
	    /* Report that the archive has been modified */
	    struct stat lstat_buf;
	    if(!lstat(arch_obj, &lstat_buf))
		EDVObjectModifiedEmit(
		    core, arch_obj, arch_obj, &lstat_buf
		);
	}

	EDVArchiverSetBusy(archiver, FALSE);

	/* Update the status bar */
	if(TRUE)
	{
	    gchar *s;
	    if(status == -4)
		s = STRDUP(
		    "Fix operation canceled"
		);
	    else if(status == 0)
		s = STRDUP(
		    "Fixed archive"
		);
	    else
		s = STRDUP(
		    "Unable to fix archive"
		);
	    EDVStatusBarMessage(archiver->status_bar, s, FALSE);
	    g_free(s);
	}

	g_free(arch_obj);
}

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

	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	core = archiver->core;
	if((clist == NULL) || (core == 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;
	GtkCList *clist;


	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	core = archiver->core;
	if((clist == NULL) || (core == 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;
	GtkCList *clist;
	GList *glist, *selection;
	gint row, total_rows;


	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	core = archiver->core;
	if((clist == NULL) || (core == 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(
		archiver->core,
		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;

	if(archiver == NULL)
	    return;

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

	cfg_list = core->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 */
	    const gfloat	last_x = GTK_ADJUSTMENT_GET_VALUE(clist->hadjustment),
				last_y = GTK_ADJUSTMENT_GET_VALUE(clist->vadjustment);

	    /* Reget listing */
	    EDVArchiverContentsGetListing(
		archiver,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );

	    /* 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_dlg_struct *d;

	if(archiver == NULL)
	    return;

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

	/* Delete the previous Archive Info Dialog (if any) */
	d = archiver->arch_info;
	if(d != NULL)
	{
	    EDVArchiveInfoDelete(d);
	    archiver->arch_info = d = NULL;
	}

	EDVArchiverSetBusy(archiver, TRUE);

	/* Create a new Archive Info Dialog, load the current archive
	 * information, and map the Archive Info Dialog
	 */
	archiver->arch_info = d = EDVArchiveInfoNew(
	    archiver->core,
	    arch_obj,
	    EDVArchiverCurrentPassword(archiver),
	    archiver->toplevel
	);
	if(d != NULL)
	{
	    EDVArchiveInfoMap(d);
	    EDVArchiveInfoResetHasChanges(d, FALSE);
	    EDVArchiveInfoUpdateMenus(d);
	}

	EDVArchiverSetBusy(archiver, FALSE);

	g_free(arch_obj);
}


/*
 *	Sets the contents list filter.
 */
void EDVArchiverContentsFilter(edv_archiver_struct *archiver)
{
	gchar **strv;
	gint strc;
	GtkWidget *toplevel;

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

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

	PDialogDeleteAllPrompts();
	PDialogSetTransientFor(toplevel);
	PDialogAddPrompt(NULL, "Filter:", archiver->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(archiver->contents_list_filter);
		archiver->contents_list_filter = STRDUP(strv[0]);
	    }

	    EDVArchiverOPRefresh(archiver);
	}

	PDialogDeleteAllPrompts();

	EDVArchiverSetBusy(archiver, FALSE);
}

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

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

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

	EDVMapMIMETypesListWin(core, type_str, toplevel);
}

