#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <gtk/gtk.h>

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

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

#include "cfg.h"
#include "edvtypes.h"
#include "edvdate.h"
#include "edvarchobj.h"
#include "edvarchfio.h"
#include "endeavour.h"
#include "edvarchop.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"

#include "images/pdi_file01_20x20.xpm"
#include "images/pdi_file02_20x20.xpm"
#include "images/pdi_file03_20x20.xpm"
#include "images/pdi_file04_20x20.xpm"
#include "images/pdi_file05_20x20.xpm"
#include "images/pdi_file06_20x20.xpm"
#include "images/pdi_trash01_20x20.xpm"
#include "images/pdi_trash02_20x20.xpm"
#include "images/pdi_trash03_20x20.xpm"
#include "images/pdi_trash04_20x20.xpm"
#include "images/pdi_trash05_20x20.xpm"
#include "images/pdi_trash06_20x20.xpm"
#include "images/pdi_folder_32x32.xpm"
#include "images/pdi_folderfile_32x32.xpm"
#include "images/pdi_package_32x32.xpm"
#include "images/pdi_packagefile_32x32.xpm"

#include "images/icon_replace_file_32x32.xpm"


static gchar *last_error = NULL;


/*
 *	Functions that return a gint have the following meanings for
 *	the return value:
 *
 *	0	Success (no error)
 *	-1	General error
 *	-2	Ambiguous, not found, other error
 *	-3	Systems error (out of memory/out of disk space)
 *	-4	User responded with "Cancel"
 *	-5	User responded with "No" or response is not available
 *	-6	Call would cause reentry to this function
 */

const gchar *EDVArchOPGetError();

static gboolean EDVArchOPFPHasData(FILE *fp);
static void EDVArchOPAppendStringList(
	gchar ***strv, gint *strc,
	const gchar *s
);

static gint EDVArchOPConfirmOverwriteExtract(
	edv_core_struct *core_ptr, GtkWidget *toplevel,
	const gchar *src_path, const gchar *tar_path,
	const edv_archive_object_struct *src_obj_stat,
	const struct stat *tar_lstat_buf
);

static void EDVArchOPMapProgressDialogDeleteUnknown(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
);
static void EDVArchOPMapProgressDialogAddUnknown(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
);
static void EDVArchOPMapProgressDialogExtractUnknown(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
);

/* Delete Object From Archive */
static gint EDVArchOPDeleteARJ(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
);
static gint EDVArchOPDeleteRAR(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
);
static gint EDVArchOPDeleteTar(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
);
static gint EDVArchOPDeleteZip(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
);
gint EDVArchOPDelete(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct *obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
);

/* Add Object To Archive */
static gint EDVArchOPAddARJ(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
);
static gint EDVArchOPAddLHA(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
);
static gint EDVArchOPAddRAR(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
);
static gint EDVArchOPAddTar(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
);
static gint EDVArchOPAddZip(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
);
gint EDVArchOPAdd(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
);

/* Extract Object From Archive */
static gint EDVArchOPExtractARJ(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
);
static gint EDVArchOPExtractLHA(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
);
static gint EDVArchOPExtractRAR(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
);
static gint EDVArchOPExtractTar(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
);
static gint EDVArchOPExtractZip(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
);
gint EDVArchOPExtract(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_obj,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
);


#define ISCR(c)		(((c) == '\n') || ((c) == '\r'))

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

#define UNLINK(p)	(((p) != NULL) ? (gint)unlink((const char *)(p)) : -1)
#define INTERRUPT(i)	(((i) > 0) ? (gint)kill((int)(i), SIGINT) : -1)


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
	if(s != NULL) {
	    if(s2 != NULL) {
		gchar *sr = g_strconcat(s, s2, NULL);
		g_free(s);
		s = sr;
	    }
	} else {
	    if(s2 != NULL)
		s = STRDUP(s2);
	    else
		s = STRDUP("");
	}
	return(s);
}


/*
 *	Returns the last error message as a statically allocated
 *	string or NULL if there was no previous error.
 */
const gchar *EDVArchOPGetError()
{
	return(last_error);
}


/*
 *	Checks if the given FILE pointer has data ready to be read.
 */
static gboolean EDVArchOPFPHasData(FILE *fp)
{
	gint fd = (fp != NULL) ? fileno(fp) : -1;
	fd_set rfds;
	struct timeval tv;
	if(fd < 0)
	    return(FALSE);

	/* Clear file descriptor set return */
	FD_ZERO(&rfds);
	/* Add our fd to the list of file descriptors to check */
	FD_SET(fd, &rfds);
	/* Set wait period to 0 seconds */
	tv.tv_sec = 0;		/* Seconds */
	tv.tv_usec = 0;		/* Microseconds */

	switch(select(fd + 1, &rfds, NULL, NULL, &tv))
	{
	  case -1:	/* Error (probably no data available) */
	    /* Error */
	    switch(errno)
	    {
	      case EBADF:       /* Invalid descriptor */
		return(FALSE);
		break;
	      case EINTR:       /* Non blocked signal was caught */
		return(FALSE);
		break;
	      case ENOMEM:	/* System is out of memory */
		return(FALSE);
		break;
	      default:		/* Unknown error */
		return(FALSE);
		break;
	    }
	    break;
	  case 0:	/* No data available */
	    return(FALSE);
	    break;
	  default:	/* Data is available */
	    return(TRUE);
	    break;
	}
}

/*
 *	Appends the string s to the string list.
 */
static void EDVArchOPAppendStringList(
	gchar ***strv, gint *strc,
	const gchar *s
)
{
	gint i;

	if((strv == NULL) || (strc == NULL) || (s == NULL))
	    return;

	i = MAX(*strc, 0);
	*strc = i + 1;
	*strv = (gchar **)g_realloc(
	    *strv,
	    (*strc) * sizeof(gchar *)
	);
	if(*strv == NULL)
	{
	    *strc = 0;
	}
	else
	{
	    (*strv)[i] = STRDUP(s);
	}
}


/*
 *      Maps the confirmation dialog and queries user for replacing the
 *      given src_path with the tar_path.
 *
 *      Returns one of CDIALOG_RESPONSE_*.
 */
static gint EDVArchOPConfirmOverwriteExtract(
	edv_core_struct *core_ptr, GtkWidget *toplevel,
	const gchar *src_path, const gchar *tar_path,
	const edv_archive_object_struct *src_obj_stat,
	const struct stat *tar_lstat_buf
)
{
	gchar *buf, *src_date, *tar_date;
	gint status;
	edv_date_relativity relativity;
	const gchar *format;
	gulong src_size, tar_size;
	const cfg_item_struct *cfg_list;

	if(core_ptr == NULL)
	    return(CDIALOG_RESPONSE_NOT_AVAILABLE);

	cfg_list = core_ptr->cfg_list;

	/* Get date relativity and format */
	relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	format = EDV_GET_S(
	    EDV_CFG_PARM_DATE_FORMAT
	);

	/* Get date strings for source and target objects */
	src_date = STRDUP(EDVDateFormatString(
	    (src_obj_stat != NULL) ?
		(gulong)src_obj_stat->modify_time : 0l,
	    format, relativity
	));
	tar_date = STRDUP(EDVDateFormatString(
	    (tar_lstat_buf != NULL) ?
		(gulong)tar_lstat_buf->st_mtime : 0l,
	    format, relativity
	));

	/* Get sizes of source and target objects in units of bytes */
	src_size = (gulong)((src_obj_stat != NULL) ?
	    src_obj_stat->size : 0
	);
	tar_size = (gulong)((tar_lstat_buf != NULL) ?
	    tar_lstat_buf->st_size : 0
	);

	/* Format confirm overwrite message */
	buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Reemplace:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Con:\n\
\n\
    %s (%ld byte%s) %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Remplacer:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Avec:\n\
\n\
    %s (%ld byte%s) %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ersetzen Sie:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Mit:\n\
\n\
    %s (%ld byte%s) %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Sostituire:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Con:\n\
\n\
    %s (%ld byte%s) %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Vervang:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Met:\n\
\n\
    %s (%ld byte%s) %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Reponha:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Com:\n\
\n\
    %s (%ld byte%s) %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Erstatt:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
Med:\n\
\n\
    %s (%ld byte%s) %s\n"
#else
"Replace:\n\
\n\
    %s (%ld byte%s) %s\n\
\n\
With:\n\
\n\
    %s (%ld byte%s) %s\n"
#endif
	    ,
	    tar_path, tar_size, (tar_size == 1) ? "" : "s", tar_date,
	    src_path, src_size, (src_size == 1) ? "" : "s", src_date
	);

	EDVPlaySoundWarning(core_ptr);
	CDialogSetTransientFor(toplevel);
	status = CDialogGetResponseIconData(
#if defined(PROG_LANGUAGE_SPANISH)
"Confirme Escriba Para Reemplazar"
#elif defined(PROG_LANGUAGE_FRENCH)
"Confirmer Superposer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Bestätigen Sie Überschreibt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Confermare Sovrascrivere"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bevestiig Beschrijft"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Confirme Overwrite"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Bekreft Overwrite"
#else
"Confirm Overwrite"
#endif
	    , buf, NULL,
	    (guint8 **)icon_replace_file_32x32_xpm,
/* Note that we are unable to have a "No" option, it must be yes or
 * cancel the entire operation
 */
	    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
	    CDIALOG_BTNFLAG_CANCEL,
	    CDIALOG_BTNFLAG_CANCEL
	);
	CDialogSetTransientFor(NULL);

	g_free(buf);
	g_free(tar_date);
	g_free(src_date);

	return(status);
}

/*
 *	Maps the progress dialog as needed in animation mode for deleting
 *	with an unknown progress value (activity mode).
 */
static void EDVArchOPMapProgressDialogDeleteUnknown(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

	/* Already mapped? */
	if(ProgressDialogIsQuery())
	{
	    /* Check if the progress dialog needs to be unmapped and
	     * remapped again
	     */
	    if(force_remap)
	    {
		ProgressDialogBreakQuery(FALSE);
	    }
	    else
	    {
		/* Already mapped and does not need unmapping, so just
		 * update the progress message
		 */
		ProgressDialogUpdateUnknown(
		    NULL, label, NULL, NULL, TRUE
		);
		return;
	    }
	}

	ProgressDialogSetTransientFor(toplevel);

	start_icon_data[0] = (guint8 **)pdi_packagefile_32x32_xpm;
	start_icon_data[1] = (guint8 **)pdi_package_32x32_xpm;
	start_icon_data[2] = (guint8 **)pdi_package_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_trash01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_trash02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_trash03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_trash04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_trash05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_trash06_20x20_xpm;
	end_icon_data[0] = (guint8 **)NULL;
	end_icon_data[1] = (guint8 **)NULL;
	end_icon_data[2] = (guint8 **)NULL;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Borrar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Effacer",
	    label,
	    "Arrêt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Löschen",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Cancellare",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Schrappen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Anular",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Stryking",
	    label,
	    "Stans",
#else
	    "Deleting",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    500l,
	    10000
	);
	ProgressDialogUpdateUnknown(
	    NULL, NULL, NULL, NULL, TRUE
	);

	/* Flush output so dialog gets mapped and we catch the beginning
	 * of the operation (some WM need this)
	 */
	gdk_flush();
}

/*
 *	Maps the progress dialog as needed in animation mode for adding
 *	with an unknown progress value (activity mode).
 */
static void EDVArchOPMapProgressDialogAddUnknown(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

	/* Already mapped? */
	if(ProgressDialogIsQuery())
	{
	    /* Check if the progress dialog needs to be unmapped and
	     * remapped again
	     */
	    if(force_remap)
	    {
		ProgressDialogBreakQuery(FALSE);
	    }
	    else
	    {
		/* Already mapped and does not need unmapping, so just
		 * update the progress message
		 */
		ProgressDialogUpdateUnknown(
		    NULL, label, NULL, NULL, TRUE
		);
		return;
	    }
	}

	ProgressDialogSetTransientFor(toplevel);

	start_icon_data[0] = (guint8 **)pdi_folderfile_32x32_xpm;
	start_icon_data[1] = (guint8 **)pdi_folderfile_32x32_xpm;
	start_icon_data[2] = (guint8 **)pdi_folderfile_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_file01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_file02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_file03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_file04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_file05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_file06_20x20_xpm;
	end_icon_data[0] = (guint8 **)pdi_package_32x32_xpm;
	end_icon_data[1] = (guint8 **)pdi_package_32x32_xpm;
	end_icon_data[2] = (guint8 **)pdi_packagefile_32x32_xpm;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Agregar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Addition",
	    label,
	    "Arrêt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Hinzufügen",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "L'Aggiunta",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Toevoegen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Adicionar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Tilføying",
	    label,
	    "Stans",
#else
	    "Adding",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    500l,
	    10000
	);
	ProgressDialogUpdateUnknown(
	    NULL, NULL, NULL, NULL, TRUE
	);

	/* Flush output so dialog gets mapped and we catch the beginning
	 * of the operation (some WM need this)
	 */
	gdk_flush();
}

/*
 *	Maps the progress dialog as needed in animation mode for
 *	extracting.
 */
static void EDVArchOPMapProgressDialogExtractUnknown(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

	/* Already mapped? */
	if(ProgressDialogIsQuery())
	{
	    /* Check if the progress dialog needs to be unmapped and
	     * remapped again
	     */
	    if(force_remap)
	    {
		ProgressDialogBreakQuery(FALSE);
	    }
	    else
	    {
		/* Already mapped and does not need unmapping, so just
		 * update the progress message
		 */
		ProgressDialogUpdateUnknown(
		    NULL, label, NULL, NULL, TRUE
		);
		return;
	    }
	}

	ProgressDialogSetTransientFor(toplevel);

	start_icon_data[0] = (guint8 **)pdi_packagefile_32x32_xpm;
	start_icon_data[1] = (guint8 **)pdi_packagefile_32x32_xpm;
	start_icon_data[2] = (guint8 **)pdi_packagefile_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_file01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_file02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_file03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_file04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_file05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_file06_20x20_xpm;
	end_icon_data[0] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[1] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[2] = (guint8 **)pdi_folderfile_32x32_xpm;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Extraer",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Extraire",
	    label,
	    "Arrêt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Extrahieren",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Estrarre",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Onttrekken",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Extrair",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Trekking Ut",
	    label,
	    "Stans",
#else
	    "Extracting",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    500l,
	    10000
	);
	ProgressDialogUpdateUnknown(
	    NULL, NULL, NULL, NULL, TRUE
	);

	/* Flush output so dialog gets mapped and we catch the beginning
	 * of the operation (some WM need this)
	 */
	gdk_flush();
}


/*
 *	Delete object from a ARJ archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPDeleteARJ(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint status = 0;
	gint p;
	gchar *cmd = NULL;

#define DO_FREE_LOCALS	\
{			\
 g_free(cmd);		\
 cmd = NULL;		\
}

	/* Format delete object from archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"%s d -i -y \"%s\" \"%s\"",
		prog_arj, arch_obj, src_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate delete command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Execute delete object from archive command */
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute delete command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Begin monitoring deleting processing, waiting for it
	     * to finish or if user aborts
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Delete object from a LHA archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPDeleteLHA(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	gint status = 0;
	gint p;
	gchar *cmd = NULL;

#define DO_FREE_LOCALS	{	\
 g_free(cmd);			\
 cmd = NULL;			\
}

	/* Format delete object from archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"%s -df \"%s\" \"%s\"",
		prog_lha, arch_obj, src_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate delete command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Execute delete object from archive command */
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute delete command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Begin monitoring deleting processing, waiting for it
	     * to finish or if user aborts
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *      Delete object from a RAR archive.
 *
 *      Inputs assumed valid.
 */
static gint EDVArchOPDeleteRAR(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint status = 0;
	gint p;
	gchar *cmd = NULL;

#define DO_FREE_LOCALS	\
{			\
 g_free(cmd);		\
 cmd = NULL;		\
}

	/* Format delete object from archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"%s d -y \"%s\" \"%s\"",
		prog_rar, arch_obj, src_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate delete command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Execute delete object from archive command */
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute delete command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Begin monitoring deleting processing, waiting for it
	     * to finish or if user aborts
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Delete object from a Tape Archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPDeleteTar(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
)
{
	const gchar *prog_tar = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_TAR
	);
	const gchar *prog_compress = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_COMPRESS
	);
	const gchar *prog_uncompress = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_UNCOMPRESS
	);
	const gchar *prog_gzip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_GZIP
	);
	const gchar *prog_gunzip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_GUNZIP
	);
	const gchar *prog_bzip2 = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_BZIP2
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	gint status = 0;
	gint p;
	gchar *strptr, *strptr2;
	gchar *cmd = NULL, *arch_uncompressed_path = NULL;


#define DO_FREE_LOCALS			\
{					\
 g_free(cmd);				\
 cmd = NULL;				\
 g_free(arch_uncompressed_path);	\
 arch_uncompressed_path = NULL;		\
}

	/* Generate tar plain path from the given arch_obj which is
	 * gauranteed to have a .tar, .tgz, .tar.gz, or .tar.bz2
	 * postfix
	 */
	arch_uncompressed_path = STRDUP(arch_obj);
	strptr = (gchar *)EDVGetPathName(arch_uncompressed_path);
	strptr2 = strstr(strptr, ".tgz");
	if(strptr2 == NULL)
	    strptr2 = strstr(strptr, ".tar.gz");
	if(strptr2 == NULL)
	    strptr2 = strstr(strptr, ".tar.bz2");
	if(strptr2 == NULL)
	    strptr2 = strstr(strptr, ".tar.Z");
	/* Explicitly set new postfix (yes we have enough allocation for
	 * 4 bytes, see above match criteria
	 */
	if(strptr2 != NULL)
	    strcpy(strptr2, ".tar");


	/* Format decompress archive command (as needed) */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"%s \"%s\"",
		prog_uncompress, arch_obj
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"%s \"%s\"",
		prog_gunzip, arch_obj
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"%s \"%s\"",
		prog_bunzip2, arch_obj
	    );
	else
	    cmd = NULL;

	/* Execute decompress archive command (as needed) */
	if(cmd != NULL)
	{
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute decompress command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Wait for decompress process to finish */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}
	/* User canceled? */
	if(status)
	{
	    DO_FREE_LOCALS
	    return(status);
	}


	/* Format delete object from archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"%s --delete -f \"%s\" \"%s\"",
		prog_tar, arch_uncompressed_path, src_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate delete command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Execute delete object from archive command */
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute delete command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Wait for decompress process to finish */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}


	/* Format compress archive command (as needed) */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"%s -c \"%s\"",
		prog_compress, arch_uncompressed_path
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"%s -c \"%s\"",
		prog_gzip, arch_uncompressed_path
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"%s -c \"%s\"",
		prog_bzip2, arch_uncompressed_path
	    );
	else
	    cmd = NULL;

	/* Execute compress archive command (as needed) */
	if(cmd != NULL)
	{
	    p = (gint)ExecOE((const char *)cmd, (const char *)arch_obj, "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute compress command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Wait for compress process to finish */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }

	    /* Remove uncompressed copy of the archive */
	    UNLINK(arch_uncompressed_path);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Delete object from a PKZip archive.
 *
 *      Inputs assumed valid.
 */
static gint EDVArchOPDeleteZip(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	const gchar *src_obj,
	edv_archive_object_struct *src_obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
)
{
	const gchar *prog_zip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_ZIP
	);
	gint status = 0;
	gint p;
	gchar *cmd = NULL;


#define DO_FREE_LOCALS	{	\
 g_free(cmd);			\
 cmd = NULL;			\
}

	/* Format delete from archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"%s -d -q \"%s\" \"%s\"",
		prog_zip, arch_obj, src_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate delete command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Execute delete from archive command */
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute delete command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Begin monitoring deleting processing, waiting for it
	     * to finish or if user aborts
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Deletes the object specified by obj_stat from the archive
 *	specified by arch_obj.
 */
gint EDVArchOPDelete(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct *obj_stat,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gulong time_start = (gulong)time(NULL);
	const gchar *arch_name, *src_obj;


#define DO_FREE_LOCALS	{	\
}

	/* Reset returns */

	/* Leave yes_to_all as whatever value the calling function
	 * originally had it set to
	 */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	/* Reset last error pointer */
	last_error = NULL;


	if((core_ptr == NULL) || STRISEMPTY(arch_obj) || (obj_stat == NULL) ||
	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-1);
	}

	arch_name = EDVGetPathName(arch_obj);

	/* Get full path of the object in the archive to delete */
	src_obj = obj_stat->full_path;
	if(src_obj == NULL)
	{
	    last_error =
 "Path not found in source archive object stats";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

	/* Update progress? */
	if(show_progress)
	{
	    gchar *p1 = EDVCopyShortenPath(
		src_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Borrar:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrappen:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anular:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryking:\n\
\n\
    %s\n"
#else
"Deleting:\n\
\n\
    %s\n"
#endif
		, p1
	    );

	    EDVArchOPMapProgressDialogDeleteUnknown(
		buf, toplevel, FALSE
	    );

	    g_free(buf);
	    g_free(p1);
	}


	/* Begin deleting the source object from the archive object
	 * arch_obj. The deleting method will be determined by taking
	 * the extension of the archive object's name
	 */

	status = -1;

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchOPDeleteARJ(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchOPDeleteLHA(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchOPDeleteRAR(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchOPDeleteTar(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all,
		TRUE,		/* Is compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (GZip) */
	else if(EDVIsExtension(arch_name, ".tgz .tar.gz"))
	{
	    status = EDVArchOPDeleteTar(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all,
		FALSE,		/* Not compress compressed */
		TRUE,		/* Is gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (BZip2) */
	else if(EDVIsExtension(arch_name, ".tar.bz2"))
	{
	    status = EDVArchOPDeleteTar(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		TRUE		/* Is bzip2 compressed */
	    );
	}
	/* Tape Archive */
	else if(EDVIsExtension(arch_name, ".tar"))
	{
	    status = EDVArchOPDeleteTar(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchOPDeleteZip(
		core_ptr, arch_obj, src_obj, obj_stat,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	}

	/* Record history */
	EDVAppendHistory(
	    core_ptr,
	    EDV_HISTORY_ARCHIVE_OBJECT_DELETE,
	    time_start, (gulong)time(NULL),
	    status,
	    arch_obj,		/* Source full path is the archive */
	    src_obj,		/* Target full path is the archive object */
	    last_error		/* Use last_error as comment if not NULL */
	);

	DO_FREE_LOCALS
	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}


/*
 *	Add object to a ARJ archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPAddARJ(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint status, arch_obj_stat_result;
	gint p = 0;
	gchar	*parent_dir = NULL,
		*pwd = NULL,
		*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	FILE *fp;
	struct stat arch_obj_stat_buf;

#define DO_FREE_LOCALS	{		\
 g_free(parent_dir);			\
 parent_dir = NULL;			\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
 g_free(cmd);				\
 cmd = NULL;				\
					\
 /* Restore previous working dir */	\
 EDVCHDir(pwd);				\
 g_free(pwd);				\
 pwd = NULL;				\
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVCHDir(parent_dir);

	/* If the ARJ archive exists and is empty then it must
	 * be removed first before adding objects to it
	 */
	arch_obj_stat_result = stat((const char *)arch_obj, &arch_obj_stat_buf);
	if(!arch_obj_stat_result)
	{
	    if(arch_obj_stat_buf.st_size == 0l)
		UNLINK(arch_obj);
	}

	status = 0;

	/* Format add to archive command */
	if(cmd == NULL)
	{
	    gint i;
	    const gchar *tpath;

	    cmd = g_strdup_printf(
		"%s a -i -y -m%i %s\"%s\"",
		prog_arj,
		CLIP(compression * 4 / 100, 0, 4),
		recursive ? "-r " : "",
		arch_obj
	    );
	    for(i = 0; i < total_tar_paths; i++)
	    {
		tpath = tar_path[i];
		if(tpath == NULL)
		    continue;

		/* Generate relative path to the target path as
		 * needed
		 */
		if(EDVIsParentPath(parent_dir, tpath))
		{
		    tpath += STRLEN(parent_dir);
		    while(*tpath == G_DIR_SEPARATOR)
			tpath++;
		}

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, tpath);
		cmd = G_STRCAT(cmd, "\"");
	    }
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate add command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Generate output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute add object to archive command */
	    p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	    if(p <= 0)
	    {
		last_error = "Unable to execute add command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;
	}

	/* Open output file for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}


		/* Check if there is new data to be read from the
		 * output file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;
			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress &&
		       (strcasepfx(buf, "Adding") ||
		        strcasepfx(buf, "Replacing")
		       )
		    )
		    {
			/* Update progress dialog label */
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2;

			/* Set s to the start of the loaded line buffer
			 * and seek past the first "Adding:" prefix
			 */
			while(ISBLANK(*s))
			    s++;
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			/* Cap the space character after path */
			s2 = strchr(s, ' ');
			if(s2 != NULL)
			    *s2 = '\0';

			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Agregar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Addition:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Hinzufügen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aggiunta:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Toevoegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Adicionar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Tilføying:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Adding:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogAddUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_path, total_new_obj_paths,
			    s
			);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check
		     * if the need_break marker was already set by
		     * the previous loop
		     *
		     * If it was set then we actually break out of
		     * this loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE. Which will be tested on
		     * the next loop if there is still no more data to be
		     * read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Add object to a LHA archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPAddLHA(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	gint status;
	gint p = 0;
	gchar   *parent_dir = NULL,
		*pwd = NULL,
		*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	FILE *fp;

#define DO_FREE_LOCALS	{		\
 g_free(parent_dir);			\
 parent_dir = NULL;			\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
 g_free(cmd);				\
 cmd = NULL;				\
					\
 /* Restore previous working dir */	\
 EDVCHDir(pwd);				\
 g_free(pwd);				\
 pwd = NULL;				\
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVCHDir(parent_dir);

	status = 0;

	/* Format add to archive command */
	if(cmd == NULL)
	{
	    gint i;
	    const gchar *tpath;

	    cmd = g_strdup_printf(
		"%s -afo%i \"%s\"",
		prog_lha,
		CLIP((compression * 2 / 100) + 5, 5, 7),
		arch_obj
	    );
	    for(i = 0; i < total_tar_paths; i++)
	    {
		tpath = tar_path[i];
		if(tpath == NULL)
		    continue;

		/* Generate relative path to the target path as
		 * needed
		 */
		if(EDVIsParentPath(parent_dir, tpath))
		{
		    tpath += STRLEN(parent_dir);
		    while(*tpath == G_DIR_SEPARATOR)
			tpath++;
		}

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, tpath);
		cmd = G_STRCAT(cmd, "\"");
	    }
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate add command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Generate output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute add object to archive command */
	    p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	    if(p <= 0)
	    {
		last_error = "Unable to execute add command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;
	}

	/* Open output file for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}


		/* Check if there is new data to be read from the
		 * output file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;
			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress)
		    {
			/* Update progress dialog label */
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2;

			while(ISBLANK(*s))
			    s++;

			/* Cap the space character after path */
			for(s2 = s; *s2 != '\0'; s2++)
			{
			    if(ISSPACE(*s2))
			    {
				*s2 = '\0';
				break;
			    }
			}

			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Agregar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Addition:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Hinzufügen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aggiunta:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Toevoegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Adicionar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Tilføying:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Adding:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogAddUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_path, total_new_obj_paths,
			    s
			);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check
		     * if the need_break marker was already set by
		     * the previous loop
		     *
		     * If it was set then we actually break out of
		     * this loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE
		     *
		     * Which will be tested on the next loop if there
		     * is still no more data to be read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Add object to a RAR archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPAddRAR(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint status, arch_obj_stat_result;
	gint p = 0;
	gchar	*parent_dir = NULL,
		*pwd = NULL,
		*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	FILE *fp;
	struct stat arch_obj_stat_buf;

#define DO_FREE_LOCALS	{		\
 g_free(parent_dir);			\
 parent_dir = NULL;			\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
 g_free(cmd);				\
 cmd = NULL;				\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVCHDir(parent_dir);

	/* If the RAR archive exists and is empty then it must
	 * be removed first before adding objects to it
	 */
	arch_obj_stat_result = stat((const char *)arch_obj, &arch_obj_stat_buf);
	if(!arch_obj_stat_result)
	{
	    if(arch_obj_stat_buf.st_size == 0l)
		UNLINK(arch_obj);
	}

	status = 0;

	/* Format add to archive command */
	if(cmd == NULL)
	{
	    gint i;
	    const gchar *tpath;

	    cmd = g_strdup_printf(
		"%s a%s -m%i%s -y \"%s\"",
		prog_rar,
		dereference_links ? "" : " -ol",
		CLIP(compression * 5 / 100, 0, 5),
		recursive ? " -r" : "",
		arch_obj
	    );
	    for(i = 0; i < total_tar_paths; i++)
	    {
		tpath = tar_path[i];
		if(tpath == NULL)
		    continue;

		/* Generate relative path to the target path as
		 * needed
		 */
		if(EDVIsParentPath(parent_dir, tpath))
		{
		    tpath += STRLEN(parent_dir);
		    while(*tpath == G_DIR_SEPARATOR)
			tpath++;
		}

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, tpath);
		cmd = G_STRCAT(cmd, "\"");
	    }
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate add command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Generate output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute add object to archive command */
	    p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	    if(p <= 0)
	    {
		last_error = "Unable to execute add command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;
	}

	/* Open output file for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}


		/* Check if there is new data to be read from the
		 * output file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;
			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress &&
		       (strcasepfx(buf, "Adding") ||
		        strcasepfx(buf, "Updating")
		       )
		    )
		    {
			/* Update progress dialog label */
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2;

			/* Set s to the start of the loaded line buffer
			 * and seek past the "Adding" or "Updating"
			 * prefix
			 */
			while(ISBLANK(*s))
			    s++;
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			/* Cap the first blank character after the path */
			for(s2 = s; *s2 != '\0'; s2++)
			{
			    if(ISBLANK(*s2))
			    {
				*s2 = '\0';
				break;
			    }
			}

			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Agregar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Addition:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Hinzufügen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aggiunta:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Toevoegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Adicionar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Tilføying:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Adding:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogAddUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_path, total_new_obj_paths,
			    s
			);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check
		     * if the need_break marker was already set by
		     * the previous loop
		     *
		     * If it was set then we actually break out of
		     * this loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE. Which will be tested on
		     * the next loop if there is still no more data to be
		     * read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Add object to a Tape Archive.
 *
 *      Inputs assumed valid.
 */
static gint EDVArchOPAddTar(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
)
{
	const gchar *prog_tar = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_TAR
	);
	const gchar *prog_compress = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_COMPRESS
	);
	const gchar *prog_uncompress = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_UNCOMPRESS
	);
	const gchar *prog_gzip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_GZIP
	);
	const gchar *prog_gunzip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_GUNZIP
	);
	const gchar *prog_bzip2 = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_BZIP2
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	gint status;
	gint p = 0;
	gchar *strptr, *strptr2;
	gchar	*parent_dir = NULL,
		*pwd = NULL,
		*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL,
		*arch_uncompressed_path = NULL;
	FILE *fp;

#define DO_FREE_LOCALS			\
{					\
 g_free(parent_dir);			\
 parent_dir = NULL;			\
 g_free(cmd);				\
 cmd = NULL;				\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
 g_free(arch_uncompressed_path);	\
 arch_uncompressed_path = NULL;		\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVCHDir(parent_dir);

	/* Generate tar plain path from the given arch_obj which is
	 * gauranteed to have a .tar, .tgz, .tar.gz, or .tar.bz2
	 * postfix
	 */
	arch_uncompressed_path = STRDUP(arch_obj);
	strptr = (gchar *)EDVGetPathName(arch_uncompressed_path);
	strptr2 = strstr(strptr, ".tgz");
	if(strptr2 == NULL)
	    strptr2 = strstr(strptr, ".tar.gz");
	if(strptr2 == NULL)
	    strptr2 = strstr(strptr, ".tar.bz2");
	if(strptr2 == NULL)
	    strptr2 = strstr(strptr, ".tar.Z");
	/* Explicitly set new postfix (yes we have enough allocation for
	 * 4 bytes, see above match criteria)
	 */
	if(strptr2 != NULL)
	    strcpy(strptr2, ".tar");


	/* Format decompress archive command */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"%s \"%s\"",
		prog_uncompress, arch_obj
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"%s \"%s\"",
		prog_gunzip, arch_obj
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"%s -q \"%s\"",
		prog_bunzip2, arch_obj
	    );
	else
	    cmd = NULL;

	status = 0;

	/* Execute decompress archive command (as needed) */
	if(cmd != NULL)
	{
	    p = (gint)ExecOE((const char *)cmd, "/dev/null", "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute decompress command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Wait for decompress process to finish */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}
	/* User canceled? */
	if(status)
	{
	    DO_FREE_LOCALS
	    return(status);
	}


	/* Format add object to archive command */
	if(cmd == NULL)
	{
	    gint i;
	    const gchar *tpath;

	    cmd = g_strdup_printf(
		"%s -r%s%s -v -f \"%s\"",
		prog_tar,
		recursive ? "" : " --no-recursion",
		dereference_links ? " -h" : "",
		arch_uncompressed_path
	    );
	    for(i = 0; i < total_tar_paths; i++)
	    {
		tpath = tar_path[i];
		if(tpath == NULL)
		    continue;

		/* Generate relative path to the target path as
		 * needed
		 */
		if(EDVIsParentPath(parent_dir, tpath))
		{
		    tpath += STRLEN(parent_dir);
		    while(*tpath == G_DIR_SEPARATOR)
			tpath++;
		}

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, tpath);
		cmd = G_STRCAT(cmd, "\"");
	    }
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate add command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Generate output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute add object to archive command */
	    p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	    if(p <= 0)
	    {
		last_error = "Unable to execute add command";
	        DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;
	}

	/* Open output file for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len		10000
	    gint buf_pos = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}

		/* Check if there is new data to be read from the output
		 * file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;
			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress)
		    {
			const gchar *s = buf;

			/* Update progress dialog label */
			gchar *p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			gchar *p2 = EDVCopyShortenPath(
			    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			gchar *buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Agregar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Addition:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Hinzufügen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aggiunta:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Toevoegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Adicionar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Tilføying:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Adding:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogAddUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_path, total_new_obj_paths,
			    s
			);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check if
		     * the need_break marker was already set by the previous
		     * loop. If it was set then we actually break out of
		     * this loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE. Which will be tested on
		     * the next loop if there is still no more data to be
		     * read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	/* User canceled? */
	if(status)
	{
	    DO_FREE_LOCALS
	    return(status);
	}

	/* Format compress archive command */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"%s -c \"%s\"",
		prog_compress, arch_uncompressed_path
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"%s -%i -c \"%s\"",
		prog_gzip,
		CLIP(compression * 9 / 100, 1, 9),
		arch_uncompressed_path
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"%s -c \"%s\"",
		prog_bzip2, arch_uncompressed_path
	    );
	else
	    cmd = NULL;

	/* Execute compress archive command (as needed) */
	if(cmd != NULL)
	{
	    p = (gint)ExecOE((const char *)cmd, (const char *)arch_obj, "/dev/null");
	    if(p <= 0)
	    {
		last_error = "Unable to execute compress command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Wait for compress process to finish */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }

	    /* Remove uncompressed version of archive */
	    UNLINK(arch_uncompressed_path);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Add object to a PKZip archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPAddZip(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
)
{
	const gchar *prog_zip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_ZIP
	);
	gint status, arch_obj_stat_result;
	gint p = 0;
	gchar	*parent_dir = NULL,
		*pwd = NULL,
		*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	FILE *fp;
	struct stat arch_obj_stat_buf;

#define DO_FREE_LOCALS			\
{					\
 g_free(parent_dir);			\
 parent_dir = NULL;			\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(cmd);				\
 cmd = NULL;				\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVCHDir(parent_dir);

	/* If the PKZip archive exists and is empty then it must
	 * be removed first before adding objects to it
	 */
	arch_obj_stat_result = stat((const char *)arch_obj, &arch_obj_stat_buf);
	if(!arch_obj_stat_result)
	{
	    if(arch_obj_stat_buf.st_size == 0l)
		UNLINK(arch_obj);
	}

	status = 0;

	/* Format add object to archive command */
	if(cmd == NULL)
	{
	    gint i;
	    const gchar *tpath;

	    cmd = g_strdup_printf(
		"%s -%i %s %s \"%s\"",
		prog_zip,
		CLIP(compression * 9 / 100, 0, 9),
		recursive ? "-r" : "",
		dereference_links ? "" : "-y",
		arch_obj
	    );
	    for(i = 0; i < total_tar_paths; i++)
	    {
		tpath = tar_path[i];
		if(tpath == NULL)
		    continue;

		/* Generate relative path to the target path as
		 * needed
		 */
		if(EDVIsParentPath(parent_dir, tpath))
		{
		    tpath += STRLEN(parent_dir);
		    while(*tpath == G_DIR_SEPARATOR)
			tpath++;
		}

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, tpath);
		cmd = G_STRCAT(cmd, "\"");
	    }
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate add command";
		DO_FREE_LOCALS
		return(-2);
	    }

	    /* Generate output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute add object to archive command */
	    p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	    if(p <= 0)
	    {
		last_error = "Unable to execute add command";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;
	}

	/* Open output file for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}


		/* Check if there is new data to be read from the
		 * output file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;
			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress)
		    {
			/* Update progress dialog label */
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2;

			/* Seek s past the first prefix */
			while(ISBLANK(*s))
			    s++;
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			/* Cap the space character after path */
			s2 = strchr(s, ' ');
			if(s2 != NULL)
			    *s2 = '\0';


			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Agregar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Addition:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Hinzufügen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Aggiunta:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Toevoegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Adicionar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Tilføying:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Adding:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogAddUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_path, total_new_obj_paths,
			    s
			);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check
		     * if the need_break marker was already set by the
		     * previous loop
		     *
		     * If it was set then we actually break out of
		     * this loop.
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE. Which will be tested
		     * on the next loop if there is still no more data
		     * to be read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}


/*
 *	Add object specified by tar_path to the archive specified by
 *	arch_obj.
 */
gint EDVArchOPAdd(
	edv_core_struct *core_ptr, const gchar *arch_obj,
	gchar **tar_path, gint total_tar_paths,
	gchar ***new_obj_path, gint *total_new_obj_paths,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean recursive, gint compression, gboolean dereference_links
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gint i;
	gulong time_start = (gulong)time(NULL);
	const gchar *arch_name;
	gchar **ltar_path = NULL;
	gint total_ltar_paths = 0;

#define DO_FREE_LOCALS	{		\
 for(i = 0; i < total_ltar_paths; i++)  \
  g_free(ltar_path[i]);                 \
 g_free(ltar_path);                     \
 ltar_path = NULL;                      \
 total_ltar_paths = 0;                  \
}

	/* Reset returns */
	if(new_obj_path != NULL)
	    *new_obj_path = NULL;
	if(total_new_obj_paths != NULL)
	    *total_new_obj_paths = 0;

	/* Leave yes_to_all as whatever value the calling function
	 * originally had it set to
	 */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	/* Reset last error pointer */
	last_error = NULL;


	if((core_ptr == NULL) || STRISEMPTY(arch_obj) ||
	   (tar_path == NULL) || (total_tar_paths <= 0) ||
	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-1);
	}

        arch_name = EDVGetPathName(arch_obj);

	/* Make a copy of the list of objects to be added to the
	 * archive
	 */
	total_ltar_paths = total_tar_paths;
	ltar_path = (gchar **)g_realloc(
	    ltar_path, total_ltar_paths * sizeof(gchar *)
	);
	for(i = 0; i < total_ltar_paths; i++)
	{
	    ltar_path[i] = STRDUP(tar_path[i]);
	    /* Simplify target object path notation, stripping
	     * tailing deliminators
	     */
	    EDVSimplifyPath(ltar_path[i]);
	}

	/* Begin adding the target objects to the archive. The adding
	 * method will be determined by taking the extension of the
	 * archive's name
	 */

	status = -1;

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchOPAddARJ(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchOPAddLHA(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchOPAddRAR(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchOPAddTar(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links,
		TRUE,		/* Is compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (GZip) */
	else if(EDVIsExtension(arch_name, ".tgz .tar.gz"))
	{
	    status = EDVArchOPAddTar(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links,
		FALSE,		/* Not compress compressed */
		TRUE,		/* Is gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (BZip2) */
	else if(EDVIsExtension(arch_name, ".tar.bz2"))
	{
	    status = EDVArchOPAddTar(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		TRUE		/* Is bzip2 compressed */
	    );
	}
	/* Tape Archive */
	else if(EDVIsExtension(arch_name, ".tar"))
	{
	    status = EDVArchOPAddTar(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchOPAddZip(
		core_ptr, arch_obj,
		ltar_path, total_ltar_paths,
		new_obj_path, total_new_obj_paths,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	}

	/* Record history */
	if(status)
	{
	    gulong time_end = (gulong)time(NULL);
	    const gchar *first_src_obj = (total_ltar_paths > 0) ?
		ltar_path[0] : NULL;

	    EDVAppendHistory(
		core_ptr,
		EDV_HISTORY_ARCHIVE_OBJECT_ADD,
		time_start, time_end,
		status,
		first_src_obj,		/* Source */
		arch_obj,		/* Target */
		last_error		/* Comment */
	    );
	}
	else
	{
	    gulong time_end = (gulong)time(NULL);

	    for(i = 0; i < total_ltar_paths; i++)
		EDVAppendHistory(
		    core_ptr,
		    EDV_HISTORY_ARCHIVE_OBJECT_ADD,
		    time_start, time_end,
		    status,
		    ltar_path[i],	/* Source */
		    arch_obj,		/* Target */
		    last_error		/* Comment */
		);
	}

	DO_FREE_LOCALS
	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}


/*
 *	Extract object from a ARJ archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPExtractARJ(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint i, status;
	gint p;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_aobj_path;
	const edv_archive_object_struct *src_aobj_ptr;
	FILE *fp;

#define DO_FREE_LOCALS	{		\
 g_free(cmd);				\
 cmd = NULL;				\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVCHDir(dest_path))
	{
	    last_error =
"Unable to change working directory to the destination directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Format extract object from archive command */
	cmd = g_strdup_printf(
	    "%s %c -i -r -y \"%s\"",
	    prog_arj,
	    preserve_directories ? 'x' : 'e',
	    arch_obj
	);
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}
	/* Append source objects to extract command string */
	for(i = 0; i < total_src_aobjs; i++)
	{
	    src_aobj_ptr = src_aobj[i];
	    if(src_aobj_ptr == NULL)
		continue;

	    src_aobj_path = src_aobj_ptr->full_path;
	    if(STRISEMPTY(src_aobj_path))
		continue;

	    if(src_aobj_ptr->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_aobj_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		/* If directory does not have a tailing deliminator then
		 * we must append one or else it will not get matched
		 */
		if(len > 1)
		{
		    if(src_aobj_path[len - 1] != G_DIR_SEPARATOR)
		    {
			gchar delim_str[2];
			delim_str[0] = G_DIR_SEPARATOR;
			delim_str[1] = '\0';
			cmd = G_STRCAT(cmd, delim_str);
		    }
		}
		cmd = G_STRCAT(cmd, "\"");
	    }
	    else
	    {
		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		cmd = G_STRCAT(cmd, "\"");
	    }
	}
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}


	/* Generate output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Begin extracting */

	status = 0;

	/* Execute extract object from archive command */
	p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	if(p <= 0)
	{
	    last_error = "Unable to execute extract command";
	    DO_FREE_LOCALS
	    return(-1);
	}
	else
	{
	    /* Delete extract command, it is no longer needed */
	    g_free(cmd);
	    cmd = NULL;
	}


	/* Open output file on this end for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0, line_count = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}

		/* Check if there is new data to be read from the output
		 * file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    line_count++;

			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;

			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress &&
		       (strcasepfx(buf, "Extracting"))
		    )
		    {
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2, *new_path;

			/* Seek past spaces */
			while(ISBLANK(*s))
			    s++;

			/* Skip lines that do not contain prefixes
			 * that we are looking for
			 */
			if(!strcasepfx(s, "Extracting "))
			    continue;

			/* Seek s past the prefix to the path value */
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			/* Cap the first blank character after the path */
			for(s2 = s; *s2 != '\0'; s2++)
			{
			    if(ISBLANK(*s2))
			    {
				*s2 = '\0';
				break;
			    }
			}

			new_path = STRDUP(PrefixPaths(dest_path, s));
			StripPath(new_path);

			/* Update progress dialog label */
			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    new_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Extraer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Extraire:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Extrahieren:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Estrarre:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Onttrekken:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Extrair:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Trekking Ut:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Extracting:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogExtractUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_rtn, total_new_obj_rtns,
			    new_path
			);

			g_free(new_path);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check if
		     * the need_break marker was already set by the
		     * previous loop
		     *
		     * If it was set then we actually break out of this
		     * loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE
		     *
		     * Which will be tested on the next loop if there is
		     * still no more data to be read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *      Extract object from a LHA archive.
 *
 *      Inputs assumed valid.
 */
static gint EDVArchOPExtractLHA(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	gint i, status;
	gint p;
	gchar   *cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_aobj_path;
	const edv_archive_object_struct *src_aobj_ptr;
	FILE *fp;

#define DO_FREE_LOCALS  {               \
 g_free(cmd);                           \
 cmd = NULL;                            \
 g_free(stdout_path);                   \
 stdout_path = NULL;                    \
 g_free(stderr_path);                   \
 stderr_path = NULL;                    \
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVCHDir(dest_path))
	{
	    last_error =
"Unable to change working directory to the destination directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Format extract object from archive command */
	cmd = g_strdup_printf(
	    "%s -%cf \"%s\"",
	    prog_lha,
	    preserve_directories ? 'x' : 'e',
	    arch_obj
	);
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}
	/* Append source objects to extract command string */
	for(i = 0; i < total_src_aobjs; i++)
	{
	    src_aobj_ptr = src_aobj[i];
	    if(src_aobj_ptr == NULL)
		continue;

	    src_aobj_path = src_aobj_ptr->full_path;
	    if(STRISEMPTY(src_aobj_path))
		continue;

	    if(src_aobj_ptr->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_aobj_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		/* If directory does not have a tailing deliminator then
		 * we must append one or else it will not get matched
		 */
		if(len > 1)
		{
		    if(src_aobj_path[len - 1] != G_DIR_SEPARATOR)
		    {
			gchar delim_str[2];
			delim_str[0] = G_DIR_SEPARATOR;
			delim_str[1] = '\0';
			cmd = G_STRCAT(cmd, delim_str);
		    }
		}
		cmd = G_STRCAT(cmd, "\"");
	    }
	    else
	    {
		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		cmd = G_STRCAT(cmd, "\"");
	    }
	}
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}


	/* Generate output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Begin extracting */

	status = 0;

	/* Execute extract object from archive command */
	p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	if(p <= 0)
	{
	    last_error = "Unable to execute extract command";
	    DO_FREE_LOCALS
	    return(-1);
	}
	else
	{
	    /* Delete extract command, it is no longer needed */
	    g_free(cmd);
	    cmd = NULL;
	}


	/* Open output file on this end for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0, line_count = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}

		/* Check if there is new data to be read from the output
		 * file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    line_count++;

			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;

			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress)
		    {
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2, *new_path;

			/* Seek past spaces */
			while(ISBLANK(*s))
			    s++;

			/* If there is a create directory prefix then
			 * seek past it
			 */
			if(strcasepfx(s, "Making directory"))
			{
			    while((*s != '"') && (*s != '\0'))
				s++;
			    if(*s == '"')
				s++;

			    s2 = strchr(s, '"');
			    if(s2 != NULL)
				*s2 = '\0';
			}
			else
			{
			    /* Cap the first blank character after the path */
			    for(s2 = s; *s2 != '\0'; s2++)
			    {
				if(ISBLANK(*s2))
			        {
				    *s2 = '\0';
				    break;
				}
			    }
			}

			new_path = STRDUP(PrefixPaths(dest_path, s));
			StripPath(new_path);

			/* Update progress dialog label */
			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    new_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Extraer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Extraire:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Extrahieren:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Estrarre:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Onttrekken:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Extrair:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Trekking Ut:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Extracting:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogExtractUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_rtn, total_new_obj_rtns,
			    new_path
			);

			g_free(new_path);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check if
		     * the need_break marker was already set by the
		     * previous loop
		     *
		     * If it was set then we actually break out of this 
		     * loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE
		     *
		     * Which will be tested on the next loop if there
		     * is still no more data to be read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Extract object from a RAR archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPExtractRAR(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint i, status;
	gint p;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_aobj_path;
	const edv_archive_object_struct *src_aobj_ptr;
	FILE *fp;

#define DO_FREE_LOCALS	{		\
 g_free(cmd);				\
 cmd = NULL;				\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVCHDir(dest_path))
	{
	    last_error =
"Unable to change working directory to the destination directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Format extract object from archive command */
	cmd = g_strdup_printf(
	    "%s %c%s -kb -o+ -y \"%s\"",
	    prog_rar,
	    preserve_directories ? 'x' : 'e',
	    preserve_timestamps ? " -tsm -tsc -tsa" : "",
	    arch_obj
	);
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}
	/* Append source objects to extract command string */
	for(i = 0; i < total_src_aobjs; i++)
	{
	    src_aobj_ptr = src_aobj[i];
	    if(src_aobj_ptr == NULL)
		continue;

	    src_aobj_path = src_aobj_ptr->full_path;
	    if(STRISEMPTY(src_aobj_path))
		continue;

	    /* Do not put tailing deliminators on directories for
	     * directory objects in RAR archives
	     */

	    cmd = G_STRCAT(cmd, " \"");
	    cmd = G_STRCAT(cmd, src_aobj_path);
	    cmd = G_STRCAT(cmd, "\"");
	}
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}


	/* Generate output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Begin extracting */

	status = 0;

	/* Execute extract object from archive command */
	p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	if(p <= 0)
	{
	    last_error = "Unable to execute extract command";
	    DO_FREE_LOCALS
	    return(-1);
	}
	else
	{
	    /* Delete extract command, it is no longer needed */
	    g_free(cmd);
	    cmd = NULL;
	}


	/* Open output file on this end for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0, line_count = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}

		/* Check if there is new data to be read from the output
		 * file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    line_count++;

			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;

			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress &&
		       (strcasepfx(buf, "Extracting") ||
		        strcasepfx(buf, "Creating"))
		    )
		    {
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2, *new_path;

			/* Seek s past the prefix to the path value */
			while(ISBLANK(*s))
			    s++;
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			/* Cap the first blank character after the path */
			for(s2 = s; *s2 != '\0'; s2++)
			{
			    if(ISBLANK(*s2))
			    {
				*s2 = '\0';
				break;
			    }
			}

			new_path = STRDUP(PrefixPaths(dest_path, s));
			StripPath(new_path);

			/* Update progress dialog label */
			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    new_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Extraer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Extraire:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Extrahieren:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Estrarre:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Onttrekken:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Extrair:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Trekking Ut:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Extracting:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogExtractUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_rtn, total_new_obj_rtns,
			    new_path
			);

			g_free(new_path);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check if
		     * the need_break marker was already set by the previous
		     * loop. If it was set then we actually break out of
		     * this loop.
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE. Which will be tested on
		     * the next loop if there is still no more data to be
		     * read.
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Extract object from a Tape Archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPExtractTar(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
)
{
	const gchar *prog_tar = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_TAR
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	gint i, status;
	gint p;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_aobj_path;
	const edv_archive_object_struct *src_aobj_ptr;
	FILE *fp;

#define DO_FREE_LOCALS	{		\
 g_free(cmd);				\
 cmd = NULL;				\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVCHDir(dest_path))
	{
	    last_error =
"Unable to change working directory to the destination directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Format extract object from archive command */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"%s -Z -x%s -v -f \"%s\"",
		prog_tar,
		preserve_timestamps ? "" : " -m",
		arch_obj
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"%s -z -x%s -v -f \"%s\"",
		prog_tar,
		preserve_timestamps ? "" : " -m",
		arch_obj
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"%s \"--use-compress-program=%s\" -x%s -v -f \"%s\"",
		prog_tar,
		prog_bunzip2,
		preserve_timestamps ? "" : " -m",
		arch_obj
	    );
	else
	    cmd = g_strdup_printf(
		"%s -x%s -v -f \"%s\"",
		prog_tar,
		preserve_timestamps ? "" : " -m",
		arch_obj
	    );
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}
	/* Append source objects to extract command string */
	for(i = 0; i < total_src_aobjs; i++)
	{
	    src_aobj_ptr = src_aobj[i];
	    if(src_aobj_ptr == NULL)
		continue;

	    src_aobj_path = src_aobj_ptr->full_path;
	    if(STRISEMPTY(src_aobj_path))
		continue;

	    if(src_aobj_ptr->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_aobj_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		/* If directory does not have a tailing deliminator then
		 * we must append one or else it will not get matched
		 */
		if(len > 1)
		{
		    if(src_aobj_path[len - 1] != G_DIR_SEPARATOR)
		    {
			gchar delim_str[2];
			delim_str[0] = G_DIR_SEPARATOR;
			delim_str[1] = '\0';
			cmd = G_STRCAT(cmd, delim_str);
		    }
		}
		cmd = G_STRCAT(cmd, "\"");
	    }
	    else
	    {
		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		cmd = G_STRCAT(cmd, "\"");
	    }
	}
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}


	/* Generate output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Begin extracting */

	status = 0;

	/* Execute extract object from archive command */
	p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	if(p <= 0)
	{
	    last_error = "Unable to execute extract command";
	    DO_FREE_LOCALS
	    return(-1);
	}
	else
	{
	    /* Delete extract command, it is no longer needed */
	    g_free(cmd);
	    cmd = NULL;
	}


	/* Open output file on this end for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0, line_count = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}


		/* Check if there is new data to be read from the output
		 * file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    line_count++;

			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;

			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress)
		    {
			gchar	*p1, *p2, *buf2,
				*new_path = STRDUP(PrefixPaths(dest_path, buf));
			StripPath(new_path);

			/* Update progress dialog label */
			p1 = EDVCopyShortenPath(
			    buf, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    new_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Extraer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Extraire:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Extrahieren:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Estrarre:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Onttrekken:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Extrair:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Trekking Ut:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Extracting:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogExtractUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_rtn, total_new_obj_rtns,
			    new_path
			);

			g_free(new_path);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check
		     * if the need_break marker was already set by the
		     * previous loop
		     *
		     * If it was set then we actually break out of this
		     * loop
		     */
		    if(need_break)
			break;

		    /* Check if add process has exited, if it has then
		     * we set need_break to TRUE
		     * Which will be tested on the next loop if there
		     * is still no more data to be read
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Extract object from a PKZip archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchOPExtractZip(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
)
{
	const gchar *prog_unzip = CFGItemListGetValueS(
	    core_ptr->cfg_list, EDV_CFG_PARM_PROG_UNZIP
	);
	gint i, status;
	gint p;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_aobj_path;
	const edv_archive_object_struct *src_aobj_ptr;
	FILE *fp;

#define DO_FREE_LOCALS	{		\
 g_free(cmd);				\
 cmd = NULL;				\
 g_free(stdout_path);			\
 stdout_path = NULL;			\
 g_free(stderr_path);			\
 stderr_path = NULL;			\
					\
 /* Restore previous working dir */     \
 EDVCHDir(pwd);                         \
 g_free(pwd);                           \
 pwd = NULL;                            \
}

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVCHDir(dest_path))
	{
	    last_error =
"Unable to change working directory to the destination directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Format extract object from archive command */
	cmd = g_strdup_printf(
	    "%s -o -X \"%s\"",
	    prog_unzip, arch_obj
	);
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}
	/* Append source objects to extract command string */
	for(i = 0; i < total_src_aobjs; i++)
	{
	    src_aobj_ptr = src_aobj[i];
	    if(src_aobj_ptr == NULL)
		continue;

	    src_aobj_path = src_aobj_ptr->full_path;
	    if(STRISEMPTY(src_aobj_path))
		continue;

	    if(src_aobj_ptr->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_aobj_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		/* If directory does not have a tailing deliminator then
		 * we must append one or else it will not get matched
		 */
		if(len > 1)
		{
		    if(src_aobj_path[len - 1] != G_DIR_SEPARATOR)
		    {
			gchar delim_str[2];
			delim_str[0] = G_DIR_SEPARATOR;
			delim_str[1] = '\0';
			cmd = G_STRCAT(cmd, delim_str);
		    }
		}
		cmd = G_STRCAT(cmd, "\"");
	    }
	    else
	    {
		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_aobj_path);
		cmd = G_STRCAT(cmd, "\"");
	    }
	}
	if(cmd == NULL)
	{
	    last_error = "Unable to generate extract command";
	    DO_FREE_LOCALS
	    return(-3);
	}


	/* Generate output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Begin extracting */

	status = 0;

	/* Execute extract object from archive command */
	p = (gint)ExecOE((const char *)cmd, (const char *)stdout_path, (const char *)stderr_path);
	if(p <= 0)
	{
	    last_error = "Unable to execute extract command";
	    DO_FREE_LOCALS
	    return(-1);
	}
	else
	{
	    /* Delete command */
	    g_free(cmd);
	    cmd = NULL;
	}


	/* Open output file for reading */
	fp = FOpen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
#define buf_len         10000
	    gint buf_pos = 0, line_count = 0;
	    gchar buf[buf_len];
	    gboolean need_break = FALSE;

	    /* Begin reading output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

			status = -4;
			break;
		    }
		}

		/* Check if there is new data to be read from the output
		 * file
		 */
		if(EDVArchOPFPHasData(fp))
		{
		    gint c;
		    gboolean got_complete_line = FALSE;

		    /* Copy all available data from the current output
		     * file position to its end to the line buffer buf
		     */
		    while(TRUE)
		    {
			c = fgetc(fp);
			if(c == EOF)
			    break;

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    line_count++;

			    if(buf_pos < buf_len)
				buf[buf_pos] = '\0';
			    else
				buf[buf_len - 1] = '\0';
			    buf_pos = 0;

			    break;
			}

			if(buf_pos < buf_len)
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line && show_progress)
		    {
			gchar *s = buf, *s2;
			gchar *p1, *p2, *buf2, *new_path;

			/* Seek past spaces */
			while(ISBLANK(*s))
			    s++;

			/* Skip lines that do not contain prefixes
			 * that we are looking for
			 */
			if(!strcasepfx(s, "creating:") &&
			   !strcasepfx(s, "updating:") &&
			   !strcasepfx(s, "inflating:") &&
			   !strcasepfx(s, "extracting:") &&
			   !strcasepfx(s, "linking:")
			)
			    continue;

			/* Seek s past the prefix to the path value */
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			/* Cap the first blank character after the path */
			for(s2 = s; *s2 != '\0'; s2++)
			{
			    if(ISSPACE(*s2))
			    {
				*s2 = '\0';
				break;
			    }
			}

			new_path = STRDUP(PrefixPaths(dest_path, s));
			StripPath(new_path);

			/* Update progress dialog label */
			p1 = EDVCopyShortenPath(
			    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			p2 = EDVCopyShortenPath(
			    new_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			);
			buf2 = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Extraer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Extraire:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Extrahieren:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Estrarre:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Onttrekken:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Extrair:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Trekking Ut:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Extracting:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
			    , p1, p2
			);
			EDVArchOPMapProgressDialogExtractUnknown(
			    buf2, toplevel, FALSE
			);
			g_free(buf2);
			g_free(p1);
			g_free(p2);

			/* Append path of this archive object path to
			 * the list of new archive objects
			 */
			EDVArchOPAppendStringList(
			    new_obj_rtn, total_new_obj_rtns,
			    new_path
			);

			g_free(new_path);

			continue;
		    }
		}

		if(TRUE)
		{
		    /* No new data to be read from output file, check
		     * if the need_break marker was already set by the
		     * previous loop
		     *
		     * If it was set then we actually break out of this
		     * loop
		     */
		    if(need_break)
			break;

		    /* Check if the process has exited, if it has then
		     * we set need_break to TRUE (which will be tested
		     * on the next loop if there is still no more data
		     * to be read)
		     */
		    if(!ExecProcessExists(p))
			need_break = TRUE;
		}

		usleep(8000);
	    }

	    FClose(fp);
#undef buf_len
	}

	/* Remove output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Extracts the object specified in the archive object stat
 *	structure src_obj_stat from the archive object specified by arch_obj
 *	to the destination specified as tar_obj.
 *
 *	The tar_obj must specify the exact absolute path name of the object
 *	to extract to.
 *
 *	If new_obj_rtn is not NULL, then on success it's location will
 *	be set to a dynamically allocated string specifying the location
 *	of the extracted object. The calling function must delete
 *	this string.
 */
gint EDVArchOPExtract(
	edv_core_struct *core_ptr,
	const gchar *arch_obj,
	edv_archive_object_struct **src_aobj, gint total_src_aobjs,
	const gchar *dest_path,
	gchar ***new_obj_rtn, gint *total_new_obj_rtns,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean *yes_to_all,
	gboolean preserve_directories,
	gboolean preserve_timestamps
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gulong time_start = (gulong)time(NULL);
	const gchar *arch_name;
	gchar *ldest_path = NULL;

#define DO_FREE_LOCALS	{	\
 g_free(ldest_path);		\
 ldest_path = NULL;		\
}

	/* Reset returns */
	if(new_obj_rtn != NULL)
	    *new_obj_rtn = NULL;
	if(total_new_obj_rtns != NULL)
	    *total_new_obj_rtns = 0;
	/* Leave yes_to_all as was */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	/* Reset last error pointer */
	last_error = NULL;


	if((core_ptr == NULL) || STRISEMPTY(arch_obj) ||
	   (src_aobj == NULL) || (total_src_aobjs <= 0) ||
	   STRISEMPTY(dest_path) || (yes_to_all == NULL)
	)
	{
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

	arch_name = EDVGetPathName(arch_obj);

	/* Make a copy of the destination path and simplify it */
	ldest_path = STRDUP(dest_path);
	EDVSimplifyPath(ldest_path);

	if(!g_path_is_absolute(ldest_path))
	{
	    last_error = "Destination path is not an absolute path";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* Do overwrite check? */
	if(interactive && !(*yes_to_all))
	{
	    /* Do overwrite check by iterating through the given list of
	     * objects in the archive and check an object at the would
	     * be extracted location exists
	     */
	    gint i;
	    gchar *extracted_path;
	    const gchar *in_archive_path;
	    const edv_archive_object_struct *aobj_ptr;
	    gboolean got_cancel = FALSE;
	    struct stat lstat_buf;

	    /* Iterate through the given list of objects in the archive
	     * and check if any of them exists
	     */
	    for(i = 0; i < total_src_aobjs; i++)
	    {
		aobj_ptr = src_aobj[i];
		if(aobj_ptr == NULL)
		    continue;

		in_archive_path = aobj_ptr->full_path;
		if(STRISEMPTY(in_archive_path))
		    continue;

		/* Generate the path of the would be extracted location
		 * for this object in the archive
		 */
		extracted_path = STRDUP(
		    PrefixPaths(ldest_path, in_archive_path)
		);
		if(extracted_path == NULL)
		    continue;

		StripPath(extracted_path);

		/* Check if an object already exists at the would be
		 * extracted location, if an object exists then prompt
		 * for extract overwrite
		 */
		if(lstat((const char *)extracted_path, &lstat_buf))
		    status = CDIALOG_RESPONSE_YES;
		else
		    status = EDVArchOPConfirmOverwriteExtract(
			core_ptr, toplevel,
			in_archive_path,	/* Source object in archive path*/
			extracted_path,		/* Target extracted object path */
			aobj_ptr,		/* Source object in archive stats */
			&lstat_buf		/* Target extracted object stats */
		    );

		/* Delete copy of would be extracted path */
		g_free(extracted_path);

		/* Check user response */
		switch(status)
		{
		  case CDIALOG_RESPONSE_YES_TO_ALL:
		    *yes_to_all = TRUE;
		  case CDIALOG_RESPONSE_YES:
		    break;
		  default:	/* All else assume cancel */
		    got_cancel = TRUE;
		    break;
		}
		if(*yes_to_all)
		    break;
		if(got_cancel)
		    break;
	    }

	    /* User canceled? */
	    if(got_cancel)
	    {
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(-4);
	    }
	}

	/* Check if the destination path does not exist, if it does not
	 * then we first need to update the list of new path returns
	 * with all non-existent compoent directories of the destination
	 * path and create all compoent directories of the destination
	 * path
	 */
	if(access(ldest_path, F_OK))
	{
	    /* Record each compoent of the destination path that does
	     * not exist as a new object in the new_obj_rtn list
	     */
	    if((new_obj_rtn != NULL) && (total_new_obj_rtns != NULL))
	    {
		/* Seek to first deliminator */
		gchar *s = strchr(
		    ldest_path + STRLEN("/"),
		    G_DIR_SEPARATOR
		);

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

		    /* If this directory does not exist, then add it to
		     * the list of new objects
		     */
		    if(access(ldest_path, F_OK))
			EDVArchOPAppendStringList(
			    new_obj_rtn, total_new_obj_rtns,
			    ldest_path
			);

		    /* Restore deliminator */
		    *s = G_DIR_SEPARATOR;

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

		/* Last directory compoent does not exist (from the
		 * very first check), so add it to the list of new
		 * objects
		 */
		EDVArchOPAppendStringList(
		    new_obj_rtn, total_new_obj_rtns,
		    ldest_path
		);
	    }

	    /* Create destination directory */
	    if(rmkdir(ldest_path, S_IRUSR | S_IWUSR | S_IXUSR))
	    {
		last_error = "Unable to create destination path";
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(-2);
	    }
	}


	/* Begin extracting the source object to the destination specified
	 * by dest_obj from the archive object arch_obj
	 *
	 * The extracting method will be determined by taking the
	 * extension of the archive object's name
	 */

	status = -1;

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchOPExtractARJ(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchOPExtractLHA(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchOPExtractRAR(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchOPExtractTar(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps,
		TRUE,		/* Is compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE           /* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (GZip) */
	else if(EDVIsExtension(arch_name, ".tgz .tar.gz"))
	{
	    status = EDVArchOPExtractTar(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps,
		FALSE,		/* Not compress compressed */
		TRUE,		/* Is gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (BZip2) */
	else if(EDVIsExtension(arch_name, ".tar.bz2"))
	{
	    status = EDVArchOPExtractTar(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps,
		FALSE,          /* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		TRUE		/* Is bzip2 compressed */
	    );
	}
	/* Tape Archive */
	else if(EDVIsExtension(arch_name, ".tar"))
	{
	    status = EDVArchOPExtractTar(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchOPExtractZip(
		core_ptr, arch_obj, src_aobj, total_src_aobjs,
		ldest_path, new_obj_rtn, total_new_obj_rtns,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	}

	/* Record history */
	EDVAppendHistory(
	    core_ptr,
	    EDV_HISTORY_ARCHIVE_OBJECT_EXTRACT,
	    time_start, (gulong)time(NULL),
	    status,
	    arch_obj,		/* Source full path is the archive */
	    ldest_path,		/* Extract destination full path */
	    last_error		/* Use last_error as comment if not NULL */
	);

	DO_FREE_LOCALS
	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}
