#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <utime.h>
#include <signal.h>
#include <sys/stat.h>
#ifdef HAVE_LIBXAR
# include <xar/xar.h>
#endif
#ifdef HAVE_LIBZIP
# include <zip.h>
#endif
#include <gtk/gtk.h>
#include <unistd.h>

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

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

#include "cfg.h"
#include "edv_types.h"
#include "edv_date.h"
#include "edv_archive_obj.h"
#include "edv_archive_extract.h"
#include "endeavour2.h"
#include "edv_op.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.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_folder_32x32.xpm"
#include "images/pdi_folderfile_32x32.xpm"
#include "images/pdi_packagefile_32x32.xpm"

#include "images/icon_replace_file_32x32.xpm"


/*
 *	Return values legend:
 *
 *	0	Success.
 *	-1	General error.
 *	-2	Invalid value.
 *	-3	Systems error; out of memory or out of disk space.
 *	-4	User responded with "Cancel".
 *	-5	User responded with "No" or response was not available.
 *	-6	Call would cause reentry.
 */


/* Error Message */
const gchar *EDVArchExtractGetError(void);
static void EDVArchExtractCopyErrorMessage(const gchar *msg);

static gint EDVArchExtractConfirmOverwrite(
	edv_core_struct *core, 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 EDVArchExtractMapProgressDialog(
	const gchar *label, const gfloat progress,
	GtkWidget *toplevel, const gboolean force_remap
);
#if 0
static void EDVArchExtractMapProgressDialogUnknown(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
);
#endif

static gint EDVArchExtractRMkDir(const gchar *path, GList **new_paths_list_rtn);

/* Extract Object From Archive */
static gint EDVArchExtractARJ(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
);
static gint EDVArchExtractLHA(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
);
static gint EDVArchExtractRAR(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
);
static gint EDVArchExtractTar(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps,
	const gboolean is_compress_compressed,
	const gboolean is_gzip_compressed,
	const gboolean is_bzip2_compressed
);
#ifdef HAVE_LIBXAR
static int32_t EDVArchExtractXArErrorCB(
	int32_t sev, int32_t err, xar_errctx_t ctx, void *data
);
#endif
static gint EDVArchExtractXAr(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
);
static gint EDVArchExtractZip(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
);
gint EDVArchExtract(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
);


static gchar last_error_buf[256];
static const gchar *last_error = NULL;


#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 *EDVArchExtractGetError(void)
{
	return(last_error);
}

/*
 *	Coppies the error message specified by msg to the last error
 *	message buffer and sets last_error to point to that buffer.
 */
static void EDVArchExtractCopyErrorMessage(const gchar *msg)
{
	if(msg == NULL)
	    return;

	strncpy(last_error_buf, msg, sizeof(last_error_buf));
	last_error_buf[sizeof(last_error_buf) - 1] = '\0';
	last_error = last_error_buf;
}


/*
 *	Maps the confirmation dialog and queries user for replacing the
 *	given src_path with the tar_path.
 *
 *	Returns one of CDIALOG_RESPONSE_*.
 */
static gint EDVArchExtractConfirmOverwrite(
	edv_core_struct *core, 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 == NULL)
	    return(CDIALOG_RESPONSE_NOT_AVAILABLE);

	cfg_list = core->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);
	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
 *	extracting.
 */
static void EDVArchExtractMapProgressDialog(
	const gchar *label, const gfloat progress,
	GtkWidget *toplevel, const 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
		 */
		ProgressDialogUpdate(
		    NULL, label, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, 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,
	    EDV_DEF_PROGRESS_DLG_ANIM_INT,
	    EDV_DEF_PROGRESS_DLG_ANIM_INC
	);
	ProgressDialogUpdate(
	    NULL, NULL, NULL, NULL,
	    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	);

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

#if 0
/*
 *	Maps the progress dialog as needed in animation mode for
 *	extracting.
 */
static void EDVArchExtractMapProgressDialogUnknown(
	const gchar *label, GtkWidget *toplevel,
	const 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,
	    EDV_DEF_PROGRESS_DLG_ANIM_INT,
	    EDV_DEF_PROGRESS_DLG_ANIM_INC
	);
	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();
}
#endif


/*
 *	Creates the parent directories as needed of the directory
 *	specified by path and adds the created directories to
 *	new_paths_list_rtn.
 *
 *	The specified path must be a directory.
 */
static gint EDVArchExtractRMkDir(const gchar *path, GList **new_paths_list_rtn)
{
	struct stat stat_buf;
	gchar *dpath;

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

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

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

	    if(new_paths_list_rtn != NULL)
	    {
		/* Seek to first deliminator */
		gchar *s = strchr(
		    dpath + STRLEN("/"),
		    G_DIR_SEPARATOR
		);

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

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

		    /* Restore deliminator */
		    *s = G_DIR_SEPARATOR;

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

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

	    /* Create the destination directory */
	    if(rmkdir(
		dpath,
		(~m) &
		    (S_IRUSR | S_IWUSR | S_IXUSR |
		     S_IRGRP | S_IWGRP | S_IXGRP |
		     S_IROTH | S_IWOTH | S_IXOTH)
	    ))
	    {
		g_free(dpath);
		return(-1);
	    }
	}

	g_free(dpath);

	return(0);
}


/*
 *	Extract the objects from the ARJ archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchExtractARJ(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive, gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint p, status, nobjs_extracted;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_path;
	const edv_archive_object_struct *obj;
	GList *glist;
	FILE *fp;

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

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVSetCWD(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 the objects to extract to the command string */
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    src_path = obj->full_path;
	    if(STRISEMPTY(src_path))
		continue;

	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_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_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_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;
	nobjs_extracted = 0;

	/* Execute the extract objects 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)
	{
	    gint buf_pos = 0, line_count = 0;
	    gfloat progress = 0.0f;
	    gchar buf[10000];
	    gboolean need_break = FALSE;

	    /* Begin reading the output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			progress, EDV_DEF_PROGRESS_BAR_TICKS, 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(FPending(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 = (gint)fgetc(fp);
			if((int)c == EOF)
			{
			    clearerr(fp);
			    break;
			}

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

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

			    break;
			}

			if(buf_pos < sizeof(buf))
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    if(got_complete_line &&
		       strcasepfx(buf, "Extracting")
		    )
		    {
			gchar *s = buf, *s2, *extracted_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;
			    }
			}

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

			/* Append this path to the list of paths
			 * extracted from the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(extracted_path)
			    );

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(extracted_path))
			{
			    gchar	*p1 = EDVShortenPath(
				s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				extracted_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = 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
			    );
			    EDVArchExtractMapProgressDialog(
				msg, progress, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}

			nobjs_extracted++;
			progress = (nobjs > 0) ?
			    ((gfloat)nobjs_extracted / (gfloat)nobjs) : 0.0f;

			g_free(extracted_path);

			continue;
		    }
		}

		if(need_break)
		    break;

		/* Check if the extract 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);
	}

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

	/* Report the final progress? */
	if(show_progress && (status == 0) &&
	   ProgressDialogIsQuery()
	)
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -4;
	}

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

/*
 *      Extract object from a LHA archive.
 *
 *      Inputs assumed valid.
 */
static gint EDVArchExtractLHA(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive, gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	gint status, p, nobjs_extracted;
	gchar   *cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_path;
	GList *glist;
	const edv_archive_object_struct *obj;
	FILE *fp;

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

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVSetCWD(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\" -%cvf \"%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 the objects to extract to the command string */
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    src_path = obj->full_path;
	    if(STRISEMPTY(src_path))
		continue;

	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_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_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_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;
	nobjs_extracted = 0;

	/* Execute the extract objects 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);
	}

	g_free(cmd);
	cmd = NULL;

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

	    /* Begin reading the output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			progress, EDV_DEF_PROGRESS_BAR_TICKS, 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(FPending(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 = (gint)fgetc(fp);
			if((int)c == EOF)
			{
			    clearerr(fp);
			    break;
			}

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

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

			    break;
			}

			if(buf_pos < sizeof(buf))
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    if(got_complete_line)
		    {
			gchar *s = buf, *s2;

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

			s2 = (gchar *)strstr((char *)s, " :");

			if(strcasepfx((char *)s, "Making directory \""))
			{
			    gchar *extracted_path;

			    s += STRLEN("Making directory \"");
			    s2 = (gchar *)strrchr((char *)s, '"');
			    if(s2 != NULL)
				*s2 = '\0';

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

			    /* Append this path to the list of paths
			     * extracted from the archive
			     */
			    if(new_paths_list_rtn != NULL)
				*new_paths_list_rtn = g_list_append(
				    *new_paths_list_rtn, STRDUP(extracted_path)
				);
			}
			else if(s2 != NULL)
			{
			    gchar *extracted_path;

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

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

			    /* Append this path to the list of paths
			     * extracted from the archive
			     */
			    if(new_paths_list_rtn != NULL)
				*new_paths_list_rtn = g_list_append(
				    *new_paths_list_rtn, STRDUP(extracted_path)
				);

			    /* Update the progress dialog's label? */
			    if(show_progress && !STRISEMPTY(extracted_path))
			    {
			        gchar	*p1 = EDVShortenPath(
				    s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
				),
					*p2 = EDVShortenPath(
				extracted_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
				),
					*msg = 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
				);
				EDVArchExtractMapProgressDialog(
				    msg, progress, toplevel, FALSE
				);
				g_free(msg);
				g_free(p1);
				g_free(p2);
			    }

			    nobjs_extracted++;
			    progress = (nobjs > 0) ?
				((gfloat)nobjs_extracted / (gfloat)nobjs) : 0.0f;

			    g_free(extracted_path);
			}

			continue;
		    }
		}

		if(need_break)
		    break;

		/* Check if the extract 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);
	}

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

	/* Report the final progress? */
	if(show_progress && (status == 0) &&
	   ProgressDialogIsQuery()
	)
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -4;
	}

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Extract object from a RAR archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchExtractRAR(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive, gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint status, p, nobjs_extracted;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_path;
	GList *glist;
	const edv_archive_object_struct *obj;
	FILE *fp;

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

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

	/* Format the extract object(s) from the archive command */
	cmd = g_strdup_printf(
	    "\"%s\" %c%s -kb -o+ -y -c- \"%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 the objects to extract to the command string */
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    src_path = obj->full_path;
	    if(STRISEMPTY(src_path))
		continue;

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

	    cmd = G_STRCAT(cmd, " \"");
	    cmd = G_STRCAT(cmd, src_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;
	nobjs_extracted = 0;

	/* Execute the extract objects 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);
	}

	g_free(cmd);
	cmd = NULL;

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

	    /* Begin reading the output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			progress, EDV_DEF_PROGRESS_BAR_TICKS, 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(FPending(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 = (gint)fgetc(fp);
			if((int)c == EOF)
			{
			    clearerr(fp);
			    break;
			}

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

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

			    break;
			}

			if(buf_pos < sizeof(buf))
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    if(got_complete_line &&
		       (strcasepfx(buf, "Extracting  ") ||
		        strcasepfx(buf, "Creating  "))
		    )
		    {
			gchar *s = buf, *s2, *extracted_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 s at the end deliminator "  "
			 *
			 * The RAR archiver has no formal deliminator
			 * to denote the end of the object's name and
			 * the "OK" string for each line
			 */
			s2 = strstr(s, "  ");
			if(s2 == NULL)
			    s2 = strchr(s, ' ');
			if(s2 == NULL)
			    s2 = strchr(s, '\t');
			if(s2 != NULL)
			    *s2 = '\0';

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

			/* Append this path to the list of paths
			 * extracted from the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(extracted_path)
			    );

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(extracted_path))
			{
			    gchar	*p1 = EDVShortenPath(
				s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				extracted_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = 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
			    );
			    EDVArchExtractMapProgressDialog(
				msg, progress, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}

			nobjs_extracted++;
			progress = (nobjs > 0) ?
			    ((gfloat)nobjs_extracted / (gfloat)nobjs) : 0.0f;

			g_free(extracted_path);

			continue;
		    }
		}

		if(need_break)
		    break;

		/* Check if the extract 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);
	}

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

	/* Report the final progress? */
	if(show_progress && (status == 0) &&
	   ProgressDialogIsQuery()
	)
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -4;
	}

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Extract object from a Tape Archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchExtractTar(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive, gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps,
	const gboolean is_compress_compressed,
	const gboolean is_gzip_compressed,
	const gboolean is_bzip2_compressed
)
{
	const gchar *prog_tar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_TAR
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	gint status, p, nobjs_extracted;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_path;
	GList *glist;
	const edv_archive_object_struct *obj;
	FILE *fp;

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

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVSetCWD(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 the objects to extract to the command string */
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    src_path = obj->full_path;
	    if(STRISEMPTY(src_path))
		continue;

	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_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_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_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;
	nobjs_extracted = 0;

	/* Execute the extract objects 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);
	}

	g_free(cmd);
	cmd = NULL;

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

	    /* Begin reading the output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			progress, EDV_DEF_PROGRESS_BAR_TICKS, 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(FPending(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 = (gint)fgetc(fp);
			if((int)c == EOF)
			{
			    clearerr(fp);
			    break;
			}

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

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

			    break;
			}

			if(buf_pos < sizeof(buf))
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }
		    if(got_complete_line)
		    {
			gchar *extracted_path = STRDUP(PrefixPaths(dest_path, buf));
			StripPath(extracted_path);

			/* Append this path to the list of paths
			 * extracted from the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(extracted_path)
			    );

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(extracted_path))
			{
			    gchar	*p1 = EDVShortenPath(
				buf, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				extracted_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = 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
			    );
			    EDVArchExtractMapProgressDialog(
				msg, progress, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}

			nobjs_extracted++;
			progress = (nobjs > 0) ?
			    ((gfloat)nobjs_extracted / (gfloat)nobjs) : 0.0f;

			g_free(extracted_path);

			continue;
		    }
		}

		if(need_break)
		    break;

		/* Check if the extract 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);
	}

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

	/* Report the final progress? */
	if(show_progress && (status == 0) &&
	   ProgressDialogIsQuery()
	)
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -4;
	}

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

#ifdef HAVE_LIBXAR
/*
 *	X Archive library error callback.
 */
static int32_t EDVArchExtractXArErrorCB(
	int32_t sev, int32_t err, xar_errctx_t ctx, void *data
)
{
	xar_file_t xar_fp;
	gint error_code;
	gchar *path;
	const gchar *error_msg;
/*	const gchar *arch_obj = (const gchar *)data; */

	xar_fp = xar_err_get_file(ctx);
	error_msg = (const gchar *)xar_err_get_string(ctx);
	error_code = (gint)xar_err_get_errno(ctx);

	switch(sev)
	{
	  case XAR_SEVERITY_DEBUG:
	  case XAR_SEVERITY_INFO:
	  case XAR_SEVERITY_WARNING:
	  case XAR_SEVERITY_NORMAL:
	    /* Ignore these */
	    break;

	  case XAR_SEVERITY_NONFATAL:
	  case XAR_SEVERITY_FATAL:
	    path = (xar_fp != NULL) ? (gchar *)xar_get_path(xar_fp) : NULL;
	    if(path != NULL)
	    {
		gchar *s = g_strconcat(
		    "Unable to extract \"",
		    g_basename(path),
		    "\" from the archive, ",
		    error_msg,
		    ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVArchExtractCopyErrorMessage(s);
		g_free(s);
		g_free(path);
	    }
	    else
	    {   
		gchar *s = g_strconcat(
		    error_msg, ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVArchExtractCopyErrorMessage(s);
		g_free(s);
	    }
	    break;
	}

	return(0);
}
#endif	/* HAVE_LIBXAR */

/*
 *	Extract object from an X Archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchExtractXAr(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
)
{
#ifdef HAVE_LIBXAR
	xar_t xar;
	xar_iter_t i1;
	xar_file_t xar_fp;
	struct stat stat_buf;
	gint status;
	gulong cur_size, total_size;
	gchar *p1, *src_path, *tar_path, *parent, *pwd;
	GList *glist;
	edv_archive_object_struct *obj;

	/* Open the X Archive for reading */
	xar = xar_open(arch_obj, READ);
	if(xar == NULL)
	{
	    gchar *s = g_strconcat(
		"Unable to open the X archive \"",
		g_basename(arch_obj),
		"\" for reading",
		NULL
	    );
	    EDVArchExtractCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Set the error callback */
	xar_register_errhandler(
	    xar,
	    EDVArchExtractXArErrorCB, (void *)arch_obj
	);

	/* Iterate through the list of objects to extract in order
	 * to calculate the total uncompressed size
	 */
	total_size = 0l;
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    total_size += obj->size;
	}

	/* Record the previous working directory */
	pwd = EDVGetCWD();

	/* Set the extraction directory as the current working
	 * directory
	 */
	EDVSetCWD(dest_path);

	/* Iterate through the list of objects to extract and extract
	 * each one
	 */
	cur_size = 0l;
	status = 0;
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    /* Get the path of the object in the archive */
	    src_path = STRDUP(obj->full_path);
	    if(STRISEMPTY(src_path))
	    {
		g_free(src_path);
		continue;
	    }

	    /* Format the path of the object to extract
	     *
	     * Note that if preserve_directories is FALSE it will have
	     * no affect because the libxar API always extracts
	     * preserving directories
	     */
	    tar_path = STRDUP(PrefixPaths(
		(const char *)dest_path,
		(const char *)src_path
	    ));
	    if(STRISEMPTY(tar_path))
	    {
		last_error =
"Unable to generate the extracted object's path";
		g_free(src_path);
		g_free(tar_path);
		status = -1;
		break;
	    }

	    EDVSimplifyPath(tar_path);

	    /* Update the progress dialog to display the current
	     * object being extracted?
	     */
	    if(show_progress)
	    {
		gchar	*p1 = EDVShortenPath(
		    src_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*p2 = EDVShortenPath(
		    tar_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*msg = 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
		);
		EDVArchExtractMapProgressDialog(
		    msg,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : -1.0f,
		    toplevel, FALSE
		);
		g_free(msg);
		g_free(p1);
		g_free(p2);

		if(ProgressDialogStopCount() > 0)
		{
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    /* Need to create the parent directory(ies)? */
	    parent = g_dirname(tar_path);
	    if(parent != NULL)
	    {
		const gint	stat_status = lstat((const char *)parent, &stat_buf),
				error_code = errno;
		if((stat_status != 0) && (error_code == ENOENT))
		{
		    /* Create each parent directory(ies) and add
		     * them to the list of extracted objects
		     */
		    if(EDVArchExtractRMkDir(parent, new_paths_list_rtn))
		    {
			gchar *s = g_strconcat(
"Unable to create the parent directories to extract \"",
			    g_basename(tar_path),
			    "\" from the archive",
			    NULL
			);
			EDVArchExtractCopyErrorMessage(s);
			g_free(s);
			g_free(parent);
			g_free(src_path);
			g_free(tar_path);
			status = -1;
			break;
		    }
		}
		g_free(parent);
	    }

	    /* Report progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : 0.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    /* Create an X Archive iterator and find this object in
	     * the X Archive
	     */
	    i1 = xar_iter_new(xar);
	    if(i1 == 0)
	    {
		g_free(src_path);
		g_free(tar_path);
		status = -1;
		break;
	    }
	    for(xar_fp = xar_file_first(xar, i1);
		xar_fp != NULL;
		xar_fp = xar_file_next(i1)
	    )
	    {
		p1 = (gchar *)xar_get_path(xar_fp);
		if(p1 == NULL)
		    continue;

		if(!strcmp(src_path, p1))
		{
		    g_free(p1);
		    break;
		}

		g_free(p1);
	    }
	    /* No such object in the X Archive? */
	    if(xar_fp == NULL)
	    {
		xar_iter_free(i1);
		g_free(src_path);
		g_free(tar_path);
		continue;
	    }

	    /* Report progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : 0.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    xar_iter_free(i1);
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    /* Extract this object */
	    if(xar_extract(xar, xar_fp) != 0)
	    {
		status = -1;

		if(interactive)
		{
		    if(!(*yes_to_all))
		    {
			/* Query the user to continue extracting */
			gint response;
			gchar *s = g_strdup_printf(
"An error occured while extracting the object:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n\
\n\
Continue extracting subsequent objects?",
			    src_path, tar_path
			);
			EDVPlaySoundError(core);
			CDialogSetTransientFor(toplevel);
			response = CDialogGetResponse(
			    "Extract Failed",
			    s,
			    NULL,
			    CDIALOG_ICON_ERROR,
			    CDIALOG_BTNFLAG_YES |
				CDIALOG_BTNFLAG_YES_TO_ALL |
				CDIALOG_BTNFLAG_NO,
			    CDIALOG_BTNFLAG_YES
			);
			CDialogSetTransientFor(NULL);
			g_free(s);

			/* Stop extracting? */
			if((response == CDIALOG_RESPONSE_NO) ||
			   (response == CDIALOG_RESPONSE_CANCEL) ||
			   (response == CDIALOG_RESPONSE_NOT_AVAILABLE)
			)
			{
			    if(last_error == NULL)
			    {
				gchar *s = g_strconcat(
				    "Unable to extract \"",
				    g_basename(tar_path),
				    "\" from the archive",
				    NULL
				);
				EDVArchExtractCopyErrorMessage(s);
				g_free(s);
			    }

			    /* Count this object's size after extraction */
			    cur_size += obj->size;

			    /* Append the target path to the list of extracted objects */
			    if(new_paths_list_rtn != NULL)
				*new_paths_list_rtn = g_list_append(
				    *new_paths_list_rtn, STRDUP(tar_path)
				);

			    xar_iter_free(i1);
			    g_free(src_path);
			    g_free(tar_path);
			    break;
			}

			if(response == CDIALOG_RESPONSE_YES_TO_ALL)
			    *yes_to_all = TRUE;
		    }
		}
	    }

	    /* Delete the X Archive iterator */
	    xar_iter_free(i1);

	    /* Count this object's size after extraction */
	    cur_size += obj->size;

	    /* Append the target path to the list of extracted objects */
	    if(new_paths_list_rtn != NULL)
		*new_paths_list_rtn = g_list_append(
		    *new_paths_list_rtn, STRDUP(tar_path)
		);

	    g_free(src_path);
	    g_free(tar_path);

	    /* Report the progress for this object? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : 0.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    status = -4;
		    break;
		}
	    }
	}

	/* Close the X Archive */
	if(xar_close(xar))
	{
	    /* No previous error and no user abort? */
	    if((status == 0) || (status != -4))
	    {
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
"Unable to close the X Archive \"",
			g_basename(arch_obj),
			"\"",
			NULL
		    );
		    EDVArchExtractCopyErrorMessage(s);
		    g_free(s);
		}
		status = -1;
	    }
	}

	/* Restore the previous working directory */
	EDVSetCWD(pwd);
	g_free(pwd);

	/* Report the final progress? */
	if(show_progress && (status == 0) && ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -4;
	}

	return(status);
#else
	last_error = "Unsupported archive format";
	return(-2);
#endif	/* HAVE_LIBXAR */
}

/*
 *	Extract object from a PKZip archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchExtractZip(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list, const gint nobjs,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive, gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
)
{
#ifdef HAVE_LIBZIP
	struct stat stat_buf;
	struct zip *archive;
	gint status, zip_error_code;
	gulong cur_size, total_size;
	gchar *src_path, *tar_path, *parent;
	GList *glist;
	edv_object_type type;
	edv_archive_object_struct *obj;

	/* Open the PKZip archive for reading */
	archive = zip_open(arch_obj, 0, &zip_error_code);
	if(archive == NULL)
	{
	    const gint sys_error_code = (gint)errno;
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error_code, sys_error_code
	    );
	    last_error = last_error_buf;
	    return(-1);
	}

	/* Iterate through the list of objects to extract in order
	 * to calculate the total uncompressed size
	 */
	total_size = 0l;
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    /* Count only the sizes of files and links */
	    type = obj->type;
	    if((type == EDV_OBJECT_TYPE_FILE) ||
	       (type == EDV_OBJECT_TYPE_LINK)
	    )
		total_size += obj->size;
	}

	/* Iterate through the list of objects to extract and
	 * extract each one
	 */
	cur_size = 0l;
	status = 0;
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    /* Get the path of the object in the archive */
	    src_path = STRDUP(obj->full_path);
	    if(STRISEMPTY(src_path))
	    {
		g_free(src_path);
		continue;
	    }

	    /* Format the path of the object to extract */
	    if(preserve_directories)
		tar_path = STRDUP(PrefixPaths(
		    (const char *)dest_path,
		    (const char *)src_path
		));
	    else
		tar_path = STRDUP(PrefixPaths(
		    (const char *)dest_path,
		    (const char *)g_basename(src_path)
		));
	    if(STRISEMPTY(tar_path))
	    {
		g_free(src_path);
		g_free(tar_path);
		last_error =
"Unable to generate the extracted object's path";
		status = -1;
		break;
	    }

	    EDVSimplifyPath(tar_path);

	    /* Update the progress dialog to display the current
	     * object being extracted?
	     */
	    if(show_progress)
	    {
		gchar	*p1 = EDVShortenPath(
		    src_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*p2 = EDVShortenPath(
		    tar_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*msg = 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
		);
		EDVArchExtractMapProgressDialog(
		    msg,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : -1.0f,
		    toplevel, FALSE
		);
		g_free(msg);
		g_free(p1);
		g_free(p2);

		if(ProgressDialogStopCount() > 0)
		{
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    /* Need to create the parent directory(ies)? */
	    parent = g_dirname(tar_path);
	    if(parent != NULL)
	    {
		const gint	stat_status = lstat((const char *)parent, &stat_buf),
				error_code = errno;
		if((stat_status != 0) && (error_code == ENOENT))
		{
		    /* Create each parent directory(ies) and add
		     * them to the list of extracted objects
		     */
		    if(EDVArchExtractRMkDir(parent, new_paths_list_rtn))
		    {
			gchar *s = g_strconcat(
"Unable to create the parent directories to extract \"",
			    g_basename(tar_path),
			    "\" from the archive",
			    NULL
			);
			EDVArchExtractCopyErrorMessage(s);
			g_free(s);
			g_free(parent);
			g_free(src_path);
			g_free(tar_path);
			status = -1;
			break;
		    }
		}
		g_free(parent);
	    }

	    /* Report progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : -1.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

/* Reports that there was an error extracting the object and queries
 * the user to continue extracting, if the user responds with yes
 * then the extracting will continue, otherwise the loop will be
 * broken
 */
#define ERROR_QUERY_CONTINUE_EXTRACT	{	\
 /* Append the target path to the list of extracted objects */ \
 if(new_paths_list_rtn != NULL)			\
  *new_paths_list_rtn = g_list_append(		\
   *new_paths_list_rtn, STRDUP(tar_path)	\
  );						\
						\
 /* Need to query the user? */			\
 if(interactive) {				\
  if(!(*yes_to_all)) {				\
   /* Query the user to continue extracting */	\
   gint response;				\
   gchar *s = g_strdup_printf(			\
"An error occured while extracting the object:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n\
\n\
Continue extracting subsequent objects?",	\
    src_path, tar_path				\
   );						\
   EDVPlaySoundError(core);			\
   CDialogSetTransientFor(toplevel);		\
   response = CDialogGetResponse(		\
    "Extract Failed",				\
    s,						\
    NULL,					\
    CDIALOG_ICON_ERROR,				\
    CDIALOG_BTNFLAG_YES |			\
     CDIALOG_BTNFLAG_YES_TO_ALL |		\
     CDIALOG_BTNFLAG_NO,			\
    CDIALOG_BTNFLAG_YES				\
   );						\
   CDialogSetTransientFor(NULL);		\
   g_free(s);					\
						\
   /* Stop extracting? */			\
   if((response == CDIALOG_RESPONSE_NO) ||	\
      (response == CDIALOG_RESPONSE_CANCEL) ||	\
      (response == CDIALOG_RESPONSE_NOT_AVAILABLE) \
   )						\
   {						\
    g_free(src_path);				\
    g_free(tar_path);				\
    break;					\
   }						\
						\
   if(response == CDIALOG_RESPONSE_YES_TO_ALL)	\
    *yes_to_all = TRUE;				\
  }						\
 }						\
						\
 /* If this point is reached then we should	\
  * continue extracting				\
  */						\
 g_free(src_path);				\
 g_free(tar_path);				\
 continue;					\
}

	    /* Directory? */
	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		/* Extract the directory by creating it as needed */
		if(stat((const char *)tar_path, &stat_buf))
		{
		    /* Directory does not exist, create it */
		    const guint m = EDVGetUMask();

		    /* Create the directory */
		    if(mkdir(
			tar_path,
			(~m) &
			(S_IRUSR | S_IWUSR | S_IXUSR |
			 S_IRGRP | S_IWGRP | S_IXGRP |
			 S_IROTH | S_IWOTH | S_IXOTH)
		    ))
		    {
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to extract \"",
			    g_basename(tar_path),
			    "\" from the archive, ",
			    g_strerror(error_code),
			    NULL
			);
			EDVArchExtractCopyErrorMessage(s);
			g_free(s);
			status = -1;
			ERROR_QUERY_CONTINUE_EXTRACT
		    }

		    /* Append the target path to the list of extracted objects */
		    if(new_paths_list_rtn != NULL)
			*new_paths_list_rtn = g_list_append(
			    *new_paths_list_rtn, STRDUP(tar_path)
			);
		}
		else
		{
		    /* Object exists, check if it is not a directory */
#ifdef S_ISDIR
		    if(!S_ISDIR(stat_buf.st_mode))
#else
		    if(FALSE)
#endif
		    {
			/* Object exists and is not a directory, remove
			 * the object and create the directory
			 */
			const guint m = EDVGetUMask();

			/* Remove the existing object */
			UNLINK(tar_path);

			/* Create the directory */
			if(mkdir(
			    tar_path,
			    (~m) &
			    (S_IRUSR | S_IWUSR | S_IXUSR |
			     S_IRGRP | S_IWGRP | S_IXGRP |
			     S_IROTH | S_IWOTH | S_IXOTH)
			))
			{
			    const gint error_code = (gint)errno;
			    gchar *s = g_strconcat(
				"Unable to extract \"",
				g_basename(tar_path),
				"\" from the archive, ",
				g_strerror(error_code),
				NULL
			    );
			    EDVArchExtractCopyErrorMessage(s);
			    g_free(s);
			    status = -1;
			    ERROR_QUERY_CONTINUE_EXTRACT
			}

			/* Append the target path to the list of extracted objects */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(tar_path)
			    );
		    }
		}
	    }
	    /* Link? */
 	    else if(obj->type == EDV_OBJECT_TYPE_LINK)
	    {
		struct zip_file *src_fp;
		gint bytes_read;
		const gulong value_len = MAX(obj->size, 0l);
		gchar *value, *src_path_pc;

		/* Open the source file in the PKZip archive */
		src_path_pc = g_strconcat(src_path, "@", NULL);
		src_fp = zip_fopen(archive, src_path_pc, 0);
		g_free(src_path_pc);
		if(src_fp == NULL)
		{
		    gchar *s = g_strconcat(
			"Unable to extract \"",
			g_basename(tar_path),
			"\" from the archive, ",
			(const gchar *)zip_strerror(archive),
			NULL
		    );
		    EDVArchExtractCopyErrorMessage(s);
		    g_free(s);
		    status = -1;
		    ERROR_QUERY_CONTINUE_EXTRACT
		}

		/* Allocate the link's destination value */
		value = (gchar *)g_malloc((value_len + 1) * sizeof(gchar));
		if(value == NULL)
		{
		    last_error = "Memory allocation error";
		    zip_fclose(src_fp);
		    g_free(src_path);
		    g_free(tar_path);
		    status = -3;
		    break;
		}

		/* Read the link's destination avlue from the source
		 * file in the PKZip archive
		 */
		if(value_len > 0)
		{
		    bytes_read = (gint)zip_fread(src_fp, value, value_len);
		    if(bytes_read < 0)
		    {
			gchar *s = g_strconcat(
			    "Unable to extract \"",
			    g_basename(tar_path),
			    "\" from the archive, ",
			    (const gchar *)zip_file_strerror(src_fp),
			    NULL
			);
			EDVArchExtractCopyErrorMessage(s);
			g_free(s);
			g_free(value);
			zip_fclose(src_fp);
			status = -1;
			ERROR_QUERY_CONTINUE_EXTRACT
		    }
		}
		else
		{
		    bytes_read = 0;
		}

		if((gulong)bytes_read < value_len)
		    value[bytes_read] = '\0';
		else
		    value[value_len] = '\0';

		/* Close the source and target files */
		zip_fclose(src_fp);

		/* Remove the target object in case it already exists */
		UNLINK(tar_path);

		/* Create the link */
		if(symlink(
		    (const char *)value,
		    (const char *)tar_path
		) != 0)
		{
		    const gint error_code = (gint)errno;
		    gchar *s = g_strconcat(
			"Unable to extract \"",
			g_basename(tar_path),
			"\" from the archive, ",
			g_strerror(error_code),
			NULL
		    );
		    EDVArchExtractCopyErrorMessage(s);
		    g_free(s);
		    g_free(value);
		    status = -1;
		    ERROR_QUERY_CONTINUE_EXTRACT
		}

		/* Delete the link's destination value */
		g_free(value);

		cur_size += (gulong)bytes_read;

		/* Append the target path to the list of extracted objects */
		if(new_paths_list_rtn != NULL)
		    *new_paths_list_rtn = g_list_append(
			*new_paths_list_rtn, STRDUP(tar_path)
		    );
	    }
	    /* All else extract the file */
 	    else
	    {
		FILE *tar_fp;
		struct zip_file *src_fp;
		gint tar_fd;
		const guint m = EDVGetUMask();
		gulong block_size;

		/* Open the source file in the PKZip archive */
		src_fp = zip_fopen(archive, src_path, 0);
		if(src_fp == NULL)
		{
		    gchar *s = g_strconcat(
			"Unable to extract \"",
			g_basename(tar_path),
			"\" from the archive, ",
			(const gchar *)zip_strerror(archive),
			NULL
		    );
		    EDVArchExtractCopyErrorMessage(s);
		    g_free(s);
		    status = -1;
		    ERROR_QUERY_CONTINUE_EXTRACT
		}

		/* Remove the target object in case it exists */
		UNLINK(tar_path);

		/* Open the target file for writing */
		tar_fp = fopen((const char *)tar_path, "wb");
		if(tar_fp == NULL)
		{
		    const gint error_code = (gint)errno;
		    gchar *s = g_strconcat(
			"Unable to extract \"",
			g_basename(tar_path),
			"\" from the archive, ",
			g_strerror(error_code),
			NULL
		    );
		    EDVArchExtractCopyErrorMessage(s);
		    g_free(s);
		    zip_fclose(src_fp);
		    status = -1;
		    ERROR_QUERY_CONTINUE_EXTRACT
		}

		/* Append the target path to the list of extracted objects */
		if(new_paths_list_rtn != NULL)
		    *new_paths_list_rtn = g_list_append(
			*new_paths_list_rtn, STRDUP(tar_path)
		    );

		tar_fd = fileno(tar_fp);

		/* Set the file permissions */
		fchmod(
		    tar_fd,
		    (~m) &
		    (S_IRUSR | S_IWUSR |
		     S_IRGRP | S_IWGRP |
		     S_IROTH | S_IWOTH)
		);

		/* Calculate the block size */
		if(fstat(tar_fd, &stat_buf))
		{
		    block_size = 0l;
		}
		else
		{
		    block_size = (gulong)stat_buf.st_blksize;
		}
		if(block_size > 0l)
		{
		    /* Extract one block size at a time */
		    gint bytes_read, bytes_written;
		    const gint read_buf_len = (gint)block_size;
		    guint8 *read_buf = (guint8 *)g_malloc(
			block_size * sizeof(guint8)
		    );
		    if(read_buf == NULL)
		    {
			last_error = "Memory allocation error";
			zip_fclose(src_fp);
			fclose(tar_fp);
			g_free(src_path);
			g_free(tar_path);
			status = -3;
			break;
		    }

		    while(TRUE)
		    {
			/* Read one block from the source file in the archive */
			bytes_read = (gint)zip_fread(
			    src_fp,
			    read_buf, (int)read_buf_len
			);
			if(bytes_read <= 0)
			{
			    if(bytes_read < 0)
			    {
				gchar *s = g_strconcat(
				    "Unable to extract \"",
				    g_basename(tar_path),
				    "\" from the archive, ",
				    (const gchar *)zip_file_strerror(src_fp),
				    NULL
				);
				EDVArchExtractCopyErrorMessage(s);
				g_free(s);
				status = -1;
			    }
			    break;
			}

			/* Write the block to the file */
			bytes_written = (gint)fwrite(
			    read_buf, sizeof(guint8), (size_t)bytes_read, tar_fp
			);

			/* Add to the current size */
			if(bytes_written > 0)
			    cur_size += (gulong)bytes_written;

			/* Write error? */
			if(bytes_written != bytes_read)
			{
			    const gint error_code = (gint)errno;
			    gchar *s = g_strconcat(
				"Unable to extract \"",
				g_basename(tar_path),
				"\" from the archive, ",
				g_strerror(error_code),
				NULL
			    );
			    EDVArchExtractCopyErrorMessage(s);
			    g_free(s);
			    status = -1;
			    break;
			}

			/* Update progress */
			if(show_progress && ProgressDialogIsQuery())
			{
			    ProgressDialogUpdate(
				NULL, NULL, NULL, NULL,
				(total_size > 0l) ?
		      ((gfloat)cur_size / (gfloat)total_size) : -1.0f,
				EDV_DEF_PROGRESS_BAR_TICKS, TRUE
			    );
			    if(ProgressDialogStopCount() > 0)
			    {
				status = -4;
				break;
			    }
			}
		    }

		    /* Delete the read buffer */
		    g_free(read_buf);
		}
		else
		{
		    /* File system IO block size not available,
		     * extract one character at a time
		     */
		    gint bytes_read, bytes_written;
		    const gint buf_len = 1;
		    guint8 buf[1];

		    while(TRUE)
		    {
			/* Read one character from the source file in the archive */
			bytes_read = (gint)zip_fread(src_fp, buf, (int)buf_len);
			if(bytes_read <= 0)
			{
			    if(bytes_read < 0)
			    {
				gchar *s = g_strconcat(
				    "Unable to extract \"",
				    g_basename(tar_path),
				    "\" from the archive, ",
				    (const gchar *)zip_file_strerror(src_fp),
				    NULL
				);
				EDVArchExtractCopyErrorMessage(s);
				g_free(s);
				status = -1;
			    }
			    break;
			}

			/* Write the character to the file */
			if(fputc((int)buf[0], tar_fp) != EOF)
			    bytes_written = 1;
			else
			    bytes_written = 0;

			/* Add to the current size */
			if(bytes_written > 0)
			    cur_size += (gulong)bytes_written;

			/* Write error? */
			if(bytes_written != bytes_read)
			{
			    const gint error_code = (gint)errno;
			    gchar *s = g_strconcat(
				"Unable to extract \"",
				g_basename(tar_path),
				"\" from the archive, ",
				g_strerror(error_code),
				NULL
			    );
			    EDVArchExtractCopyErrorMessage(s);
			    g_free(s);
			    status = -1;
			    break;
			}

			/* Update progress */
			if(show_progress && ProgressDialogIsQuery())
			{
			    ProgressDialogUpdate(
				NULL, NULL, NULL, NULL,
				(total_size > 0l) ?
		      ((gfloat)cur_size / (gfloat)total_size) : -1.0f,
				EDV_DEF_PROGRESS_BAR_TICKS, TRUE
			    );
			    if(ProgressDialogStopCount() > 0)
			    {
				status = -4;
				break;
			    }
			}
		    }
		}

		/* Close the source and target files */
		zip_fclose(src_fp);
		fclose(tar_fp);

		/* Did an error occure or user aborted while writing
		 * the extracted object?
		 */
		if(status != 0)
		{
		    if(status == -4)
		    {
			break;
		    }
		    else
		    {
			ERROR_QUERY_CONTINUE_EXTRACT
		    }
		}
	    }

	    /* Report progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : -1.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    /* Preserve timestamps? */
	    if(preserve_timestamps &&
	       (obj->type != EDV_OBJECT_TYPE_LINK)
	    )
	    {
		struct utimbuf utime_buf;
		utime_buf.actime = (time_t)obj->access_time;
		utime_buf.modtime = (time_t)obj->modify_time;
		utime((const char *)tar_path, &utime_buf);
	    }

	    /* Report the final progress for this object? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : -1.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    g_free(src_path);
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    g_free(src_path);
	    g_free(tar_path);

#undef ERROR_QUERY_CONTINUE_EXTRACT
	}

	/* Close the PKZip archive */
	if(zip_close(archive))
	{
	    if((status == 0) || (status == -4))
	    {
		EDVArchExtractCopyErrorMessage(
		    (const gchar *)zip_strerror(archive)
		);
		status = -1;
	    }
	}

	return(status);
#else
	const gchar *prog_unzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_UNZIP
	);
	gint status, p, nobjs_extracted;
	gchar	*cmd = NULL,
		*pwd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	const gchar *src_path;
	GList *glist;
	const edv_archive_object_struct *obj;
	FILE *fp;

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

	/* Record previous working dir and set new working dir */
	pwd = EDVGetCWD();
	if(EDVSetCWD(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 the objects to extract to the command string */
	for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    src_path = obj->full_path;
	    if(STRISEMPTY(src_path))
		continue;

	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	    {
		gint len = STRLEN(src_path);

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, src_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_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_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;
	nobjs_extracted = 0;

	/* Execute the extract objects 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);
	}

	g_free(cmd);
	cmd = NULL;

	/* Open output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint buf_pos = 0, line_count = 0;
	    gfloat progress = 0.0f;
	    gchar buf[10000];
	    gboolean need_break = FALSE;

	    /* Begin reading the output file */
	    while(TRUE)
	    {
		/* Update progress? */
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdate(
			NULL, NULL, NULL, NULL,
			progress, EDV_DEF_PROGRESS_BAR_TICKS, 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(FPending(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 = (gint)fgetc(fp);
			if((int)c == EOF)
			{
			    clearerr(fp);
			    break;
			}

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

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

			    break;
			}

			if(buf_pos < sizeof(buf))
			{
			    buf[buf_pos] = c;
			    buf_pos++;
			}
		    }

		    /* Got a complete line from the output file and the
		     * progress dialog is mapped?
		     */
		    if(got_complete_line)
		    {
			gchar *s = buf, *s2, *extracted_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;
			    }
			}

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

			/* Append this path to the list of paths
			 * extracted from the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(extracted_path)
			    );

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(extracted_path))
			{
			    gchar	*p1 = EDVShortenPath(
				s, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				extracted_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = 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
			    );
			    EDVArchExtractMapProgressDialog(
				msg, progress, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}

			nobjs_extracted++;
			progress = (nobjs > 0) ?
			    ((gfloat)nobjs_extracted / (gfloat)nobjs) : 0.0f;

			g_free(extracted_path);

			continue;
		    }
		}

		if(need_break)
		    break;

		/* Check if the extract 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);
	}

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

	/* Report the final progress? */
	if(show_progress && (status == 0) &&
	   ProgressDialogIsQuery()
	)
	{
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		status = -4;
	}

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
#endif	/* !HAVE_LIBZIP */
}

/*
 *	Extracts objects from the archive.
 *
 *	The arch_obj specifies the archive.
 *
 *	The objs_list specifies the list of objects to extract from the
 *	archive.
 *
 *	The dest_path specifies the absolute path of the location to
 *	extract the objects in the archive to.
 *
 *	If new_paths_list_rtn is not NULL then a list of paths
 *	describing the objects that have been extracted from the archive
 *	will be returned. The calling function must delete the returned
 *	list and each string.
 */
gint EDVArchExtract(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *objs_list,
	const gchar *dest_path,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive, gboolean *yes_to_all,
	const gboolean preserve_directories,
	const gboolean preserve_timestamps
)
{
	static gboolean reenterent = FALSE;
	const gulong time_start = (gulong)time(NULL);
	gint status, nobjs;
	const gchar *arch_name;
	gchar *ldest_path = NULL;
	GList *glist;

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

	/* Reset returns */
	if(new_paths_list_rtn != NULL)
	    *new_paths_list_rtn = NULL;

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || STRISEMPTY(arch_obj) ||
	   (objs_list == NULL) || STRISEMPTY(dest_path) ||
	   (yes_to_all == NULL)
	)
	{
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

	arch_name = g_basename(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))
	{
	    gchar *s = g_strconcat(
		"The specified path to the extract location \"",
		g_basename(ldest_path),
		"\" is not an absolute path",
		NULL
	    );
	    EDVArchExtractCopyErrorMessage(s);
	    g_free(s);
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* Get the total number of objects to extract */
	nobjs = g_list_length(objs_list);

	/* 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
	     */
	    gboolean got_cancel = FALSE;
	    gchar *tar_path;
	    const gchar *src_path;
	    edv_archive_object_struct *obj;
	    struct stat lstat_buf;

	    /* Iterate through the list of objects to extract and check
	     * if their extracted locations already exist, prompt the
	     * user to overwrite or cancel in each case
	     */
	    for(glist = objs_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		/* Get the path of the object within the archive */
		src_path = obj->full_path;
		if(STRISEMPTY(src_path))
		    continue;

		/* Generate the path of the would be extracted location
		 * for this object in the archive
		 */
		if(preserve_directories)
		    tar_path = STRDUP(PrefixPaths(
			ldest_path, src_path
		    ));
		else
		    tar_path = STRDUP(PrefixPaths(
			ldest_path, g_basename(src_path)
		    ));
		if(tar_path == NULL)
		    continue;

		SimplifyPath(tar_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 *)tar_path, &lstat_buf))
		    status = CDIALOG_RESPONSE_YES;
		else
		    status = EDVArchExtractConfirmOverwrite(
			core, toplevel,
			src_path,	/* Source object in archive path*/
			tar_path,	/* Target extracted object path */
			obj,		/* Source object in archive stats */
			&lstat_buf	/* Target extracted object stats */
		    );

		g_free(tar_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 aborted? */
	    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(EDVArchExtractRMkDir(ldest_path, new_paths_list_rtn))
	{
	    gchar *s = g_strconcat(
		"Unable to create the extract location \"",
		g_basename(ldest_path),
		"\"",
		NULL
	    );
	    EDVArchExtractCopyErrorMessage(s);
	    g_free(s);
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}


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

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchExtractARJ(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchExtractLHA(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchExtractRAR(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchExtractTar(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		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 = EDVArchExtractTar(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		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 = EDVArchExtractTar(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		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 = EDVArchExtractTar(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* X Archive */
	else if(EDVIsExtension(arch_name, ".xar"))
	{
	    status = EDVArchExtractXAr(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		password,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchExtractZip(
		core, arch_obj, objs_list, nobjs,
		ldest_path, new_paths_list_rtn,
		password,
		toplevel, show_progress, interactive, yes_to_all,
		preserve_directories,
		preserve_timestamps
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	    status = -2;
	}

	/* Record history */
	EDVAppendHistory(
	    core,
	    EDV_HISTORY_ARCHIVE_OBJECT_EXTRACT,
	    time_start, (gulong)time(NULL),
	    status,
	    arch_obj,		/* Source */
	    ldest_path,		/* Target */
	    last_error		/* Comment */
	);

	/* Need to flush disk changes since the archive may have been
	 * modified on another process and the changes have not reached
	 * our process yet
	 */
	sync();

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