#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <time.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_archive_obj.h"
#include "edv_archive_add.h"
#include "endeavour2.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_folderfile_32x32.xpm"
#include "images/pdi_package_32x32.xpm"
#include "images/pdi_packagefile_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.
 */


#ifdef HAVE_LIBZIP
/*
 *	libzip source data:
 */
typedef struct {

	edv_core_struct	*core;
	gint		*status;
	GList		**new_paths_list_rtn;
	gulong		this_size,
			*cur_size,
			total_size;
	gchar		*arch_obj;

	GtkWidget	*toplevel;
	gboolean	show_progress;

	edv_object_type	type;
	gchar		*src_path,
			*tar_path;	/* Path of object in archive */
	FILE		*fp;
	gulong		bytes_read;	/* Bytes read for this object */

	int		zip_error[2];

} EDVArchAddZipSourceData;
#endif	/* HAVE_LIBZIP */


/* Error Message */
const gchar *EDVArchAddGetError(void);
static void EDVArchAddCopyErrorMessage(const gchar *msg);

/* Progress Dialog */
static void EDVArchAddMapProgressDialog(
	const gchar *label, const gfloat progress,
	GtkWidget *toplevel, const gboolean force_remap
);
static void EDVArchAddMapProgressDialogUnknown(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
);

/* Tabulate Size */
static gulong EDVArchAddCTotalSize(
	const gchar *arch_obj, const gchar *path,
	gint *nobjs,
	const gboolean recursive, const gboolean dereference_links
);

/* Add Object To Archive */
static gint EDVArchAddARJ(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);
static gint EDVArchAddLHA(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);
static gint EDVArchAddRAR(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);
static gint EDVArchAddTar(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links,
	const gboolean is_compress_compressed,
	const gboolean is_gzip_compressed,
	const gboolean is_bzip2_compressed
);
#ifdef HAVE_LIBXAR
static int32_t EDVArchAddXArErrorCB(
	int32_t sev, int32_t err, xar_errctx_t ctx, void *data
);
static void EDVArchAddXArIterate(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, xar_t xar,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gint *cur_nobjs, const gint nobjs,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean dereference_links
);
#endif
static gint EDVArchAddXAr(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);
#ifdef HAVE_LIBZIP
static struct zip *EDVOpenZipWriting(
	const gchar *arch_obj,
	gint *zip_error_code, gint *sys_error_code
);
static EDVArchAddZipSourceData *EDVArchAddZipSourceDataNew(void);
static void EDVArchAddZipSourceDataDelete(EDVArchAddZipSourceData *d);
static ssize_t EDVArchAddZipSourceCB(
	void *user_data, void *data,
	size_t len, enum zip_source_cmd cmd
);
static gint EDVArchAddZipIterateFile(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress, const gint compression
);
static gint EDVArchAddZipIterateDirectory(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress
);
static gint EDVArchAddZipIterateLink(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress, const gint compression
);
static void EDVArchAddZipIterate(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);
#endif	/* HAVE_LIBZIP */
static gint EDVArchAddZip(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);
gint EDVArchAdd(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
);


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


#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 *EDVArchAddGetError(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 EDVArchAddCopyErrorMessage(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 progress dialog as needed in animation mode for
 *	adding.
 */
static void EDVArchAddMapProgressDialog(
	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_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,
	    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();
}

/*
 *	Maps the progress dialog as needed in animation mode for adding
 *	with an unknown progress value (activity mode).
 */
static void EDVArchAddMapProgressDialogUnknown(
	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_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,
	    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();
}


/*
 *	Returns the size and number of objects of the specified path.
 *
 *	If recursive is TRUE and the specified path is a directory
 *	then the sizes and number of all the objects within that
 *	directory will be counted.
 *
 *	If dereference_links is TRUE and the specified path is a link
 *	then the size of the link's destination will be counted if it
 *	leads to a file. Otherwise the link's size is counted.
 *
 *	The *nobjs will be added with the number of objects
 *	counted.
 *
 *	Only the sizes of files and links will be counted, all other
 *	object sizes will not be counted.
 */
static gulong EDVArchAddCTotalSize(
	const gchar *arch_obj, const gchar *path,
	gint *nobjs,
	const gboolean recursive, const gboolean dereference_links
)
{
	gulong subtotal = 0l;
	mode_t m;
	struct stat stat_buf;

	if(STRISEMPTY(path))
	    return(subtotal);

	/* Skip the archive itself */
	if(!strcmp(arch_obj, path))
	    return(subtotal);

	if(lstat(path, &stat_buf))
	    return(subtotal);

	m = stat_buf.st_mode;

	/* Count this object */
	*nobjs = (*nobjs) + 1;

	/* Directory? */
#ifdef S_ISDIR
	if(S_ISDIR(m) && recursive)
#else
	if(FALSE)
#endif
	{
	    gint i, strc;
	    const gchar *name;
	    gchar *full_path;
	    gchar **strv = GetDirEntNames2(path, &strc);
	    for(i = 0; i < strc; i++)
	    {
		name = strv[i];
		if(name == NULL)
		    continue;

		/* Ignore current and parent directory notations */
		if(!strcmp((const char *)name, ".") ||
		   !strcmp((const char *)name, "..")
		)
		{
		    g_free(strv[i]);
		    continue;
		}

		full_path = STRDUP(PrefixPaths(
		    (const char *)path, (const char *)name
		));
		subtotal += EDVArchAddCTotalSize(
		    arch_obj, full_path,
		    nobjs,
		    recursive, dereference_links
		);
		g_free(full_path);
		g_free(strv[i]);
	    }
	    g_free(strv);
	}
	/* Link? */
#ifdef S_ISLNK
	else if(S_ISLNK(m))
#else
	else if(FALSE)
#endif
	{
	    /* Dereference this link? */
	    if(dereference_links)
	    {
		/* Get the destination object's stats */
		if(!stat(path, &stat_buf))
		{
		    const mode_t m = stat_buf.st_mode;

		    /* Destination is a directory? */
#ifdef S_ISDIR
		    if(S_ISDIR(m))
#else
		    if(FALSE)
#endif
		    {
			gint i, strc;
			const gchar *name;
			gchar *full_path;
			gchar **strv = GetDirEntNames2(path, &strc);
			for(i = 0; i < strc; i++)
			{
			    name = strv[i];
			    if(name == NULL)
				continue;

			    /* Ignore current and parent directory notations */
			    if(!strcmp((const char *)name, ".") ||
			       !strcmp((const char *)name, "..")
			    )
			    {
				g_free(strv[i]);
				continue;
			    }

			    full_path = STRDUP(PrefixPaths(
				(const char *)path, (const char *)name
			    ));
			    subtotal += EDVArchAddCTotalSize(
				arch_obj, full_path,
				nobjs,
				recursive, dereference_links
			    );
			    g_free(full_path);
			    g_free(strv[i]);
			}
			g_free(strv);
		    }
		    /* Destination is a file? */
#ifdef S_ISREG
		    else if(S_ISREG(stat_buf.st_mode))
#else
		    else if(FALSE)
#endif
		    {
			subtotal += (gulong)stat_buf.st_size;
		    }
		}
	    }
	    else
	    {
		/* Not dereferencing links so count this link's size */
		subtotal += (gulong)stat_buf.st_size;
	    }
	}
	/* File? */
#ifdef S_ISREG
	else if(S_ISREG(m))
#else
	else if(FALSE)
#endif
	{
	    subtotal += (gulong)stat_buf.st_size;
	}

	return(subtotal);
}


/*
 *	Add object to a ARJ archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchAddARJ(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint status, arch_obj_stat_result, p, nobjs;
	gulong total_size;
	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);			\
 g_free(stdout_path);			\
 g_free(stderr_path);			\
 g_free(cmd);				\
					\
 /* Restore the previous working dir */	\
 if(pwd != NULL) {			\
  EDVSetCWD(pwd);			\
  g_free(pwd);				\
 }					\
}

	/* Record the previous working directory and set the new
	 * working directory
	 */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVSetCWD(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);
	}

	/* Format add to archive command and calculate the number
	 * of objects and the total size
	 */
	status = 0;
	p = 0;
	nobjs = 0;
	total_size = 0l;
	if(cmd == NULL)
	{
	    const gchar *tpath;
	    GList *glist;

	    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(glist = tar_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		tpath = (const gchar *)glist->data;
		if(STRISEMPTY(tpath))
		    continue;

		/* Count the size and number of objects */
		total_size += EDVArchAddCTotalSize(
		    arch_obj, tpath,
		    &nobjs,
		    recursive, dereference_links
		);

		/* Seek tpath to the relative 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 the output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute the 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 the add command";
		DO_FREE_LOCALS
		return(-1);
	    }

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

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint buf_pos = 0, nobjs_added = 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;
			    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, "Adding") ||
		        strcasepfx(buf, "Replacing")
		       )
		    )
		    {
			/* Update the progress dialog's label */
			gchar *s = buf, *s2;
			const gchar *added_path;

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

			added_path = s;

			/* Append path to the list of new paths added
			 * to the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(added_path)
			    );

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

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

			continue;
		    }
		}

		if(need_break)
		    break;

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

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

/*
 *	Add object to a LHA archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchAddLHA(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	gint status, p, nobjs;
	gulong total_size;
	gchar   *parent_dir = NULL,
		*pwd = NULL,
		*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	FILE *fp;

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

	/* Record the previous working directory and set the new
	 * working directory
	 */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVSetCWD(parent_dir);

	/* Format add to archive command and calculate the number
	 * of objects and the total size
	 */
	status = 0;
	p = 0;
	nobjs = 0;
	total_size = 0l;
	if(cmd == NULL)
	{
	    const gchar *tpath;
	    GList *glist;

	    cmd = g_strdup_printf(
		"\"%s\" -aq1fo%i \"%s\"",
		prog_lha,
		CLIP((compression * 2 / 100) + 5, 5, 7),
		arch_obj
	    );
	    for(glist = tar_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		tpath = (const gchar *)glist->data;
		if(STRISEMPTY(tpath))
		    continue;

		/* Count the size and number of objects */
		total_size += EDVArchAddCTotalSize(
		    arch_obj, tpath,
		    &nobjs,
		    recursive, dereference_links
		);

		/* Seek tpath to the relative 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 the output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute the 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 the add command";
		DO_FREE_LOCALS
		return(-1);
	    }

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

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint buf_pos = 0, nobjs_added = 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(c == EOF)
			{
			    clearerr(fp);
			    break;
			}

			if(ISCR(c))
			{
			    got_complete_line = TRUE;
			    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)
		    {
			/* Update the progress dialog label */
			gchar *s = buf, *s2;

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

			/* Cap the end of the path deliminator */
			s2 = (gchar *)strstr((char *)s, " :");
			if(s2 != NULL)
			{
			    const gchar *added_path;

			    *s2 = '\0';

			    added_path = s;

			    /* Append path to the list of new paths
			     * added to the archive
			     */
			    if(new_paths_list_rtn != NULL)
			        *new_paths_list_rtn = g_list_append(
				    *new_paths_list_rtn, STRDUP(added_path)
				);

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

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

			continue;
		    }
		}

		if(need_break)
		    break;

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

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

/*
 *	Add object to a RAR archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchAddRAR(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint status, arch_obj_stat_result, p, nobjs;
	gulong total_size;
	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);			\
 g_free(stdout_path);			\
 g_free(stderr_path);			\
 g_free(cmd);				\
					\
 /* Restore the previous working dir */	\
 if(pwd != NULL) {			\
  EDVSetCWD(pwd);			\
  g_free(pwd);				\
 }					\
}

	/* Record the previous working directory and set the new
	 * working directory
	 */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVSetCWD(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);
	}

	/* Format add to archive command and calculate the number
	 * of objects and the total size
	 */
	status = 0;
	p = 0;
	nobjs = 0;
	total_size = 0l;
	if(cmd == NULL)
	{
	    const gchar *tpath;
	    GList *glist;

	    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(glist = tar_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		tpath = (const gchar *)glist->data;
		if(STRISEMPTY(tpath))
		    continue;

		/* Count the size and number of objects */
		total_size += EDVArchAddCTotalSize(
		    arch_obj, tpath,
		    &nobjs,
		    recursive, dereference_links
		);

		/* Seek tpath to the relative 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 the output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute the 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 the add command";
		DO_FREE_LOCALS
		return(-1);
	    }

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

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint buf_pos = 0, nobjs_added = 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;
			    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 &&
		       (strcasepfx(buf, "Adding  ") ||
		        strcasepfx(buf, "Updating  ")
		       )
		    )
		    {
			/* Update progress dialog label */
			gchar *s = buf, *s2;
			const gchar *added_path;

			/* 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 s at the end deliminator "  " */
			s2 = strstr(s, "  ");
			if(s2 == NULL)
			    s2 = strchr(s, ' ');
			if(s2 == NULL)
			    s2 = strchr(s, '\t');
			if(s2 != NULL)
			    *s2 = '\0';

			added_path = s;

			/* Append path to the list of new paths added
			 * to the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(added_path)
			    );

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

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

			continue;
		    }
		}

		if(need_break)
		    break;

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

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

/*
 *	Add object to a Tape Archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchAddTar(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links,
	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_compress = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_COMPRESS
	);
	const gchar *prog_uncompress = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_UNCOMPRESS
	);
	const gchar *prog_gzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_GZIP
	);
	const gchar *prog_gunzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_GUNZIP
	);
	const gchar *prog_bzip2 = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_BZIP2
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	gint p, status, nobjs;
	gulong total_size;
	gchar *s, *s2;
	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 the previous working dir */	\
 EDVSetCWD(pwd);				\
 g_free(pwd);				\
 pwd = NULL;				\
}

	/* Record the previous working directory and set the new
	 * working directory
	 */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVSetCWD(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);
	s = g_basename(arch_uncompressed_path);
	s2 = strstr(s, ".tgz");
	if(s2 == NULL)
	    s2 = strstr(s, ".tar.gz");
	if(s2 == NULL)
	    s2 = strstr(s, ".tar.bz2");
	if(s2 == NULL)
	    s2 = strstr(s, ".tar.Z");

	/* Set the new extension of the uncompressed path */
	if(s2 != NULL)
	    strcpy(s2, ".tar");


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

	/* Need to decompress the archive? */
	status = 0;
	p = 0;
	if(cmd != NULL)
	{
	    /* Execute the decompress archive command */
	    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 the command */
	    g_free(cmd);
	    cmd = NULL;

	    /* Map the progress dialog */
	    if(show_progress)
	    {
		gchar *p1 = EDVShortenPath(
		    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		), *msg = g_strdup_printf(
"Decompressing Archive:\n\
\n\
    %s\n",
		    p1
		);
		EDVArchAddMapProgressDialogUnknown(msg, toplevel, TRUE);
		g_free(msg);
		g_free(p1);
	    }

	    /* Wait for the decompress archive 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);
	    }
	}
	/* Error or user aborted? */
	if(status != 0)
	{
	    DO_FREE_LOCALS
	    return(status);
	}


	/* Format the add to archive command and calculate the number
	 * of objects and the total size
	 */
	nobjs = 0;
	total_size = 0l;
	if(cmd == NULL)
	{
	    const gchar *tpath;
	    GList *glist;

	    /* Format the add object(s) to archive command */
	    cmd = g_strdup_printf(
		"\"%s\" -r%s%s -v -f \"%s\"",
		prog_tar,
		recursive ? "" : " --no-recursion",
		dereference_links ? " -h" : "",
		arch_uncompressed_path
	    );
	    for(glist = tar_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		tpath = (const gchar *)glist->data;
		if(STRISEMPTY(tpath))
		    continue;

		/* Count the size and number of objects */
		total_size += EDVArchAddCTotalSize(
		    arch_obj, tpath,
		    &nobjs,
		    recursive, dereference_links
		);

		/* Seek tpath to the relative 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 the output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute the add object(s) 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 the add command";
	        DO_FREE_LOCALS
		return(-1);
	    }

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

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint buf_pos = 0, nobjs_added = 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;
			    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)
		    {
			const gchar *added_path = buf;

			/* Append path to the list of new paths added
			 * to the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(added_path)
			    );

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

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

			continue;
		    }
		}

		if(need_break)
		    break;

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

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

	/* Error or user aborted? */
	if(status != 0)
	{
	    DO_FREE_LOCALS
	    return(status);
	}

	/* Format the 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\" -%i -c \"%s\"",
		prog_bzip2,
		CLIP(compression * 9 / 100, 1, 9),
		arch_uncompressed_path
	    );
	else
	    cmd = NULL;

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

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

	    /* Map the progress dialog? */
	    if(show_progress)
	    {
		gchar *p1 = EDVShortenPath(
		    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		), *msg = g_strdup_printf(
"Compressing Archive:\n\
\n\
    %s\n",
		    p1
		);
		EDVArchAddMapProgressDialogUnknown(msg, toplevel, TRUE);
		g_free(msg);
		g_free(p1);
	    }

	    /* Wait for the 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 the uncompressed version of the archive */
	    UNLINK(arch_uncompressed_path);
	}

	DO_FREE_LOCALS

	return(status);
#undef DO_FREE_LOCALS
}

#ifdef HAVE_LIBXAR
/*
 *	X Archive library error callback.
 */
static int32_t EDVArchAddXArErrorCB(
	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 add \"",
		    g_basename(path),
		    "\" to the archive, ",
		    error_msg, ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVArchAddCopyErrorMessage(s);
		g_free(s);
		g_free(path);
	    }
	    else
	    {
		gchar *s = g_strconcat(
		    error_msg, ", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVArchAddCopyErrorMessage(s);
		g_free(s);
	    }
	    break;
	}

	return(0);
}

/*
 *	Adds one object to the X Archive.
 *
 *	The path specifies the full path to the object to add to the
 *	archive.
 *
 *	If recursive is TRUE and path refers to a directory then the
 *	directory and all the objects within the directory will be
 *	added to the archive.
 */
static void EDVArchAddXArIterate(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, xar_t xar,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gint *cur_nobjs, const gint nobjs,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean dereference_links
)
{
	mode_t m;
	struct stat stat_buf;
	gchar *in_arch_path = NULL;

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

	if(STRISEMPTY(path))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Skip the archive itself */
	if(!strcmp(arch_obj, path))
	{
	    DO_FREE_LOCALS
	    return;
	}

	/* Get this object's local stats */
	if(lstat(path, &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to add \"",
		g_basename(path),
		"\" to the archive, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVArchAddCopyErrorMessage(s);
	    g_free(s);
	    DO_FREE_LOCALS
	    *status = -1;
	    return;
	}

	m = stat_buf.st_mode;

	/* Generate the path of the file within the archive */
	if(EDVIsParentPath(parent, path))
	{
	    const char *s = path + STRLEN(parent);
	    while(*s == G_DIR_SEPARATOR)
		s++;
	    in_arch_path = STRDUP(s);
	}
	else
	{
	    in_arch_path = STRDUP(path);
	}
	if(in_arch_path == NULL)
	{
	    last_error = "Memory allocation error";
	    DO_FREE_LOCALS
	    *status = -3;
	    return;
	}

	/* Update the progress dialog */
	if(show_progress)
	{
	    gchar	*p1 = EDVShortenPath(
		path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    ),		*p2 = EDVShortenPath(
		arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    ),
			*msg = 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
	    );
	    EDVArchAddMapProgressDialog(
		msg,
		(nobjs > 0) ?
		    ((gfloat)(*cur_nobjs) / (gfloat)nobjs) : 0.0f,
		toplevel, FALSE
	    );
	    g_free(msg);
	    g_free(p1);
	    g_free(p2);
	    if(ProgressDialogIsQuery())
	    {
		if(ProgressDialogStopCount() > 0)
		{
		    DO_FREE_LOCALS
		    *status = -4;
		    return;
		}
	    }
	}

/* Reports an add error to the user and queries the user to
 * continue adding objects
 */
#define REPORT_ERROR_QUERY_CONTINUE_ADDING	{	\
 if(interactive) {					\
  if(!(*yes_to_all)) {					\
   /* Query the user to continue adding */		\
   gint response;					\
   gchar *s = g_strdup_printf(				\
"An error occured while adding the object:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n\
\n\
Continue adding subsequent objects?",			\
    path, arch_obj					\
   );							\
   EDVPlaySoundError(core);				\
   CDialogSetTransientFor(toplevel);			\
   response = CDialogGetResponse(			\
    "Add 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 adding? */					\
   if((response == CDIALOG_RESPONSE_NO) ||		\
      (response == CDIALOG_RESPONSE_CANCEL) ||		\
      (response == CDIALOG_RESPONSE_NOT_AVAILABLE)	\
   )							\
   {							\
    DO_FREE_LOCALS					\
    *status = -5;					\
    return;						\
   }							\
							\
   if(response == CDIALOG_RESPONSE_YES_TO_ALL)		\
    *yes_to_all = TRUE;					\
  }							\
 }							\
}

/* Report progress and return if user aborted */
#define REPORT_PROGRESS	{				\
 if(show_progress && (status == 0) && ProgressDialogIsQuery()) { \
  ProgressDialogUpdate(					\
   NULL, NULL, NULL, NULL,				\
   (nobjs > 0) ?					\
    ((gfloat)(*cur_nobjs) / (gfloat)nobjs) : 0.0f,	\
   EDV_DEF_PROGRESS_BAR_TICKS, TRUE			\
  );							\
  if(ProgressDialogStopCount() > 0) {			\
   DO_FREE_LOCALS					\
   *status = -4;					\
   return;						\
  }							\
 }							\
}

/* Increment *cur_nobjs and add _path_ to the new_paths_list_rtn */
#define COUNT_OBJECT_PATH(_path_)	{		\
 /* Count the object */					\
 *cur_nobjs = (*cur_nobjs) + 1;				\
							\
 /* Add the path to the list of objects added */	\
 if(((_path_) != NULL) && (new_paths_list_rtn != NULL))	\
  *new_paths_list_rtn = g_list_append(			\
   *new_paths_list_rtn, g_strdup(_path_)		\
  );							\
}

	/* Directory? */
#ifdef S_ISDIR
	if(S_ISDIR(m))
#else
	if(FALSE)
#endif
	{
	    /* Add this directory to the X Archive */
	    if(xar_add(xar, in_arch_path) != NULL)
	    {
		COUNT_OBJECT_PATH(path);
	    }
	    else
	    {
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Unable to add \"",
			g_basename(path),
			"\" to the archive",
			NULL
		    );
		    EDVArchAddCopyErrorMessage(s);
		    g_free(s);
		}
		*status = -1;
		REPORT_ERROR_QUERY_CONTINUE_ADDING
	    }

	    REPORT_PROGRESS

	    /* Recurse into this directory? */
	    if(recursive && (*status != -4))
	    {
		/* Add the objects within this directory into the
		 * X Archive
		 */
		gint i, strc;
		const gchar *name;
		gchar **strv = GetDirEntNames2(path, &strc);
		StringQSort(strv, strc);
		for(i = 0; i < strc; i++)
		{
		    name = strv[i];
		    if(name == NULL)
			continue;

		    /* Ignore current and parent directory notations */
		    if(!strcmp((const char *)name, ".") ||
		       !strcmp((const char *)name, "..")
		    )
		    {
			g_free(strv[i]);
			continue;
		    }

		    if((*status != -4) && (*status != -5))
		    {
			gchar *full_path = STRDUP(PrefixPaths(
			    (const char *)path, (const char *)name
			));
			EDVArchAddXArIterate(
			    core, status,
			    arch_obj, xar,
			    password,
			    parent,
			    full_path,
			    new_paths_list_rtn,
			    cur_nobjs, nobjs,
			    toplevel,
			    show_progress, interactive, yes_to_all,
			    recursive, dereference_links
			);
			g_free(full_path);
		    }
		    g_free(strv[i]);
		}
		g_free(strv);
	    }
	}
	/* Link? */
#ifdef S_ISLNK
	else if(S_ISLNK(m))
#else
	else if(FALSE)
#endif
	{
	    /* Dereference this link? */
	    if(dereference_links)
	    {
		/* Get the destination object's stats */
		if(stat(path, &stat_buf))
		{
		    const gint error_code = (gint)errno;
		    gchar *s = g_strconcat(
			"Unable to add \"",
			g_basename(path),
			"\" to the archive, ",
			g_strerror(error_code),
			NULL
	            );
	            EDVArchAddCopyErrorMessage(s);
	            g_free(s);
		    DO_FREE_LOCALS;
		    *status = -1;
		    return;
		}
		else
		{
		    const mode_t m = stat_buf.st_mode;

		    /* Destination is a directory? */
#ifdef S_ISDIR
		    if(S_ISDIR(m))
#else
		    if(FALSE)
#endif
		    {
			/* Add this directory to the X Archive */
			if(xar_add(xar, in_arch_path) != NULL)
			{
			    COUNT_OBJECT_PATH(path);
			}
			else
			{
			    if(last_error == NULL)
			    {
				gchar *s = g_strconcat(
				    "Unable to add \"",
				    g_basename(path),
				    "\" to the archive",
				    NULL
				);
				EDVArchAddCopyErrorMessage(s);
				g_free(s);
			    }
			    *status = -1;
			    REPORT_ERROR_QUERY_CONTINUE_ADDING
			}

			REPORT_PROGRESS

			/* Recurse into this directory? */
			if(recursive && (*status != -4))
			{
			    /* Add the objects within this directory
			     * into the X Archive
			     */
			    gint i, strc;
			    const gchar *name;
			    gchar **strv = GetDirEntNames2(path, &strc);
			    StringQSort(strv, strc);
			    for(i = 0; i < strc; i++)
			    {
				name = strv[i];
				if(name == NULL)
				    continue;

				/* Ignore current and parent directory notations */
				if(!strcmp((const char *)name, ".") ||
				   !strcmp((const char *)name, "..")
				)
				{
				    g_free(strv[i]);
				    continue;
				}

				if((*status != -4) && (*status != -5))
				{
				    gchar *full_path = STRDUP(PrefixPaths(
					(const char *)path, (const char *)name
				    ));
				    EDVArchAddXArIterate(
					core, status,
					arch_obj, xar,
					password,
					parent,
					full_path,
					new_paths_list_rtn,
					cur_nobjs, nobjs,
					toplevel,
					show_progress, interactive, yes_to_all,
					recursive, dereference_links
				    );
				    g_free(full_path);
				}
				g_free(strv[i]);
			    }
			    g_free(strv);
			}
		    }
		    /* Destination is a file or other object */
		    else
		    {
			/* Add this object to the archive */
			if(xar_add(xar, in_arch_path) != NULL)
			{
			    COUNT_OBJECT_PATH(path);
			}
			else
			{
			    if(last_error == NULL)
			    {
				gchar *s = g_strconcat(
				    "Unable to add \"",
				    g_basename(path),
				    "\" to the archive",
				    NULL
				);
				EDVArchAddCopyErrorMessage(s);
				g_free(s);
			    }
			    *status = -1;
			    REPORT_ERROR_QUERY_CONTINUE_ADDING
			}

			REPORT_PROGRESS
		    }
		}
	    }
	    else
	    {
		/* Add this link to the archive */
		if(xar_add(xar, in_arch_path) != NULL)
		{
		    COUNT_OBJECT_PATH(path);
		}
		else
		{
		    if(last_error == NULL)
		    {
			gchar *s = g_strconcat(
			    "Unable to add \"",
			    g_basename(path),
			    "\" to the archive",
			    NULL
			);
			EDVArchAddCopyErrorMessage(s);
			g_free(s);
		    }
		    *status = -1;
		    REPORT_ERROR_QUERY_CONTINUE_ADDING
		}

		REPORT_PROGRESS
	    }
	}
	/* File or other object */
	else
	{
	    /* Add this object to the archive */
	    if(xar_add(xar, in_arch_path) != NULL)
	    {
		COUNT_OBJECT_PATH(path);
	    }
	    else
	    {
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Unable to add \"",
			g_basename(path),
			"\" to the archive",
			NULL
		    );
		    EDVArchAddCopyErrorMessage(s);
		    g_free(s);
		}
		*status = -1;
		REPORT_ERROR_QUERY_CONTINUE_ADDING
	    }

	    REPORT_PROGRESS
	}

#undef REPORT_ERROR_QUERY_CONTINUE_ADDING
#undef REPORT_PROGRESS
#undef COUNT_OBJECT_PATH

	DO_FREE_LOCALS

#undef DO_FREE_LOCALS
}
#endif	/* HAVE_LIBXAR */

/*
 *	Add object to an X Archive.
 */
static gint EDVArchAddXAr(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
#ifdef HAVE_LIBXAR
	xar_t xar;
	gint status, cur_nobjs, nobjs;
	gulong cur_size, total_size;
	gchar *parent, *pwd;
	GList *glist;

	/* Get the parent directory of the archive, this will be the
	 * base directory in which all objects added into the X
	 * Archive will be relative to (have their parent paths striped
	 * of if their parent paths match this parent path)
	 */
	parent = g_dirname(arch_obj);
	if(parent == NULL)
	{
	    last_error = "Unable to obtain the parent directory of the archive";
	    return(-1);
	}

	/* Calculate the total uncompressed size of all the objects to
	 * be added to the archive
	 */
	nobjs = 0;
	total_size = 0l;
	for(glist = tar_paths_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	    total_size += EDVArchAddCTotalSize(
		arch_obj, (const gchar *)glist->data,
		&nobjs,
		recursive, dereference_links
	    );

	/* Record the current working directory and then change to the
	 * parent directory of the archive
	 */
	pwd = EDVGetCWD();
	EDVSetCWD(parent);

	/* Open the X Archive for writing */
	xar = xar_open(arch_obj, WRITE);
	if(xar == NULL)
	{
	    gchar *s = g_strconcat(
		"Unable to open the X Archive \"",
		g_basename(arch_obj),
		"\" for writing",
		NULL
	    );
	    EDVArchAddCopyErrorMessage(s);
	    g_free(s);
	    EDVSetCWD(pwd);
	    g_free(parent);
	    g_free(pwd);
	    return(-1);
	}

	/* Set the compression */
	if(compression > 60)
#if defined(XAR_OPT_VAL_BZIP2)
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_BZIP2);
#elif defined(XAR_OPT_VAL_BZIP)
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_BZIP);
#elif defined(XAR_OPT_VAL_GZIP)
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_GZIP);
#else
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_NONE);
#endif
	else if(compression > 0)
#if defined(XAR_OPT_VAL_GZIP)
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_GZIP);
#else
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_NONE);
#endif
	else
	    xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_NONE);

	/* Set the checksum */
	xar_opt_set(xar, XAR_OPT_TOCCKSUM, XAR_OPT_VAL_SHA1);



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

	/* Iterate through the list of objects to add */
	status = 0;
	cur_nobjs = 0;
	cur_size = 0l;
	for(glist = tar_paths_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    EDVArchAddXArIterate(
		core, &status,
		arch_obj, xar,
		password,
		parent,
		(const gchar *)glist->data,	/* Full path to the object to be added */
		new_paths_list_rtn,
		&cur_nobjs, nobjs,
		toplevel,
		show_progress, interactive, yes_to_all,
		recursive, dereference_links
	    );
	    if((status == -4) || (status == -5))
		break;
	}

	/* Close the X Archive */
	if(xar_close(xar))
	{
	    /* If not interrupted during the write and close then
	     * set the status to indicate error, otherwise the error
	     * was that the user aborted
	     */
	    if((status == 0) || (status != -4))
	    {
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Unable to close the X Archive \"",
			g_basename(arch_obj),
			"\"",
			NULL
		    );
		    EDVArchAddCopyErrorMessage(s);
		    g_free(s);
		}
		status = -1;
	    }
	}

	/* Restore the previous working directory */
	EDVSetCWD(pwd);
	g_free(parent);
	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
}


#ifdef HAVE_LIBZIP
/*
 *	Opens the PKZip archive for writing.
 *
 *	Returns the handle to the PKZip archive.
 */
static struct zip *EDVOpenZipWriting(
	const gchar *arch_obj,
	gint *zip_error_code, gint *sys_error_code
)
{
	struct stat stat_buf;
	struct zip *archive;

	/* If the PKZip archive exists but has a size of 0 bytes then
	 * it must be deleted first or else libzip will return an
	 * error stating that it is not a valid PKZip archive
	 */
	if(!stat(arch_obj, &stat_buf))
	{
	    if(stat_buf.st_size == 0l)
		unlink(arch_obj);
	}

	archive = zip_open(arch_obj, ZIP_CREATE, zip_error_code);
	if(sys_error_code != NULL)
	    *sys_error_code = errno;

	return(archive);
}

/*
 *	Creates the libzip source data.
 */
static EDVArchAddZipSourceData *EDVArchAddZipSourceDataNew(void)
{
	return((EDVArchAddZipSourceData *)g_malloc0(
	    sizeof(EDVArchAddZipSourceData)
	));
}

/*
 *	Deletes the libzip source data.
 *
 *	If the source file is still opened then it will be closed.
 */
static void EDVArchAddZipSourceDataDelete(EDVArchAddZipSourceData *d)
{
	if(d == NULL)
	    return;

	g_free(d->arch_obj);
	g_free(d->src_path);
	g_free(d->tar_path);
	if(d->fp != NULL)
	    fclose(d->fp);
	g_free(d);
}

/*
 *	libzip source callback.
 *
 *	For adding objects of any type to the PKZip archive by
 *	responding to libzip commands.
 */
static ssize_t EDVArchAddZipSourceCB(
	void *user_data, void *data,
	size_t len, enum zip_source_cmd cmd
)
{
	EDVArchAddZipSourceData *d = (EDVArchAddZipSourceData *)user_data;
	if(d == NULL)
	    return(-1);

	switch(cmd)
	{
	  case ZIP_SOURCE_OPEN:
	    /* Previously aborted? */
	    if(*d->status == -4)
		return(0);	/* Return successfully opened (even though not) */

	    /* Update the progress dialog to display the object
	     * being added to the archive?
	     */
	    if(d->show_progress)
	    {
		gchar	*p1 = EDVShortenPath(
		    d->src_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*p2 = EDVShortenPath(
		    d->arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*msg = 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
		);
		EDVArchAddMapProgressDialog(
		    msg,
		    (d->total_size > 0l) ?
	((gfloat)(*d->cur_size) / (gfloat)d->total_size) : 0.0f,
		    d->toplevel, FALSE
		);
		g_free(msg);
		g_free(p1);
		g_free(p2);
		if(ProgressDialogIsQuery())
		{
		    if(ProgressDialogStopCount() > 0)
		    {
			*d->status = -4;
			return(0);
		    }
		}
	    }

	    /* Open by object type */
	    switch(d->type)
	    {
	      case EDV_OBJECT_TYPE_UNKNOWN:
		{
		    int *zip_error = d->zip_error;
		    zip_error[0] = ZIP_ER_OPEN;
		    zip_error[1] = EINVAL;
		}
		last_error = "Unknown object type";
		*d->status = -2;
		return(-1);
		break;

	      case EDV_OBJECT_TYPE_FILE:
		/* Is the file already opened? */
		if(d->fp != NULL)
		{
		    /* Rewind it */
		    rewind(d->fp);
		    d->bytes_read = 0l;
		}
		/* Is the source file known? */
		else if(d->src_path != NULL)
		{
		    /* Open the source file */
		    d->fp = fopen((const char *)d->src_path, "rb");
		    if(d->fp == NULL)
		    {
			/* Return success to libzip and only indicate
			 * local error because otherwise libzip will
			 * not add subsequent objects and revert the
			 * entire archive
			 */
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to add \"",
			    g_basename(d->src_path),
			    "\" to the archive, ",
			    g_strerror(error_code),
			    NULL
			);
			EDVArchAddCopyErrorMessage(s);
			g_free(s);
			*d->status = -1;
			return(0);
		    }
		    d->bytes_read = 0l;
		}
		else
		{
		    int *zip_error = d->zip_error;
		    zip_error[0] = ZIP_ER_INTERNAL;
		    zip_error[1] = EINVAL;
		    last_error = "Source file not specified";
		    return(-1);
		}
		break;

	      case EDV_OBJECT_TYPE_DIRECTORY:
	      case EDV_OBJECT_TYPE_LINK:
	      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	      case EDV_OBJECT_TYPE_FIFO:
	      case EDV_OBJECT_TYPE_SOCKET:
		d->bytes_read = 0l;
		break;

	      case EDV_OBJECT_TYPE_ERROR:
		/* Ignore error objects */
		d->bytes_read = 0l;   
		break;
	    }

	    /* Add this object to the list of objects added */
	    if(d->new_paths_list_rtn != NULL)
		*d->new_paths_list_rtn = g_list_append(
		    *d->new_paths_list_rtn, STRDUP(d->src_path)
		);

	    return(0);
	    break;

	  case ZIP_SOURCE_READ:
	    /* Previously aborted? */
	    if(*d->status == -4)
		return(0);	/* Return 0 bytes read (which is a success) */

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

	    /* Read by object type */
	    switch(d->type)
	    {
	      case EDV_OBJECT_TYPE_UNKNOWN:
		{
		    int *zip_error = d->zip_error;
		    zip_error[0] = ZIP_ER_READ;
		    zip_error[1] = EINVAL;
		}
		last_error = "Unknown object type";
		*d->status = -2;
		return(-1);
		break;

	      case EDV_OBJECT_TYPE_FILE:
		/* Was the file opened? */
		if(d->fp != NULL)
		{
		    gint units_read;

		    /* Read the requested length of bytes from the
		     * file into the specified buffer
		     */
		    if(len > 0)
			units_read = (gint)fread(
			    data, sizeof(guint8), len, d->fp
			);
		    else
			units_read = 0;

		    /* Read error? */
		    if(units_read < 0)
		    {
			/* Return success to libzip and only indicate
			 * local error because otherwise libzip will
			 * not add subsequent objects and revert the
			 * entire archive
			 */
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to add \"",
			    g_basename(d->src_path),
			    "\" to the archive, ",
			    g_strerror(error_code),
			    NULL
			);
			EDVArchAddCopyErrorMessage(s);
			g_free(s);
			*d->status = -1;
			return(0);	/* Return no bytes read */
		    }

		    /* Increment the number of bytes read for this
		     * object
		     */
		    d->bytes_read += (gulong)(units_read * sizeof(guint8));

		    /* Increment the total number of bytes read for
		     * all the files to be added to the archive
		     */
		    *d->cur_size = (*d->cur_size) +
			(gulong)(units_read * sizeof(guint8));

		    /* Return the number of bytes read */
		    return((ssize_t)(units_read * sizeof(guint8)));
		}
		else
		{
		    /* A file may not have been able to be opened but
		     * we should not return error to libzip because
		     * libzip will not add subsequent objects and
		     * revert the entire archive so instead return 0
		     * bytes read
		     */
		    return(0);	/* Return no bytes read */
		}
		break;

	      case EDV_OBJECT_TYPE_DIRECTORY:
		return(0);
		break;

	      case EDV_OBJECT_TYPE_LINK:
		/* Is the path to the source file known? */
		if(d->src_path != NULL)
		{
		    gint bytes_read, value_len;
		    const gulong prev_pos = d->bytes_read;
		    gchar *value;

		    /* Already read this or no need to? */
		    if(prev_pos >= d->this_size)
			return(0);	/* Return no bytes read */

		    /* Get the link's destination value */
		    value = EDVGetLinkValue(d->src_path);
		    if(value == NULL)
		    {
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to add \"",
			    g_basename(d->src_path),
			    "\" to the archive, ",
			    g_strerror(error_code),
			    NULL
			);
			EDVArchAddCopyErrorMessage(s);
			g_free(s);
			*d->status = -1;
			return(0);	/* Return no bytes read */
		    }

		    value_len = STRLEN(value);

		    /* Copy the link's destination value to libzip's
		     * read buffer and update bytes_read to indicate
		     * the number of bytes coppied to it
		     */
		    if((data != NULL) && (len > 0))
		    {
			const gchar	*src = value + prev_pos,
					*src_end = value + value_len;
			gchar	*tar = (gchar *)data,
				*tar_end = tar + len;

			bytes_read = 0;		/* Reset */

			while((src < src_end) && (tar < tar_end))
			{
			    *tar = *src;
			    tar++;
			    src++;
			    bytes_read++;
			}

			/* Increment the number of bytes read for
			 * this object
			 */
			d->bytes_read += (gulong)bytes_read;

			/* Increment the total number of bytes read
			 * for all the files to be added to the
			 * archive
			 */
			*d->cur_size = (*d->cur_size) + (gulong)bytes_read;
		    }
		    else
		    {
			bytes_read = 0;
		    }

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

		    /* Return the number of bytes read */
		    return((ssize_t)bytes_read);
		}
		else
		{
		    int *zip_error = d->zip_error;
		    zip_error[0] = ZIP_ER_INTERNAL;
		    zip_error[1] = EINVAL;
		    last_error = "Source file not specified";
		    return(-1);
		}
		break;

	      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
	      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
	      case EDV_OBJECT_TYPE_FIFO:
	      case EDV_OBJECT_TYPE_SOCKET:
		return(0);
		break;

	      case EDV_OBJECT_TYPE_ERROR:
		/* Do not report any data read for error objects */
		return(0);
		break;
	    }
	    break;

	  case ZIP_SOURCE_CLOSE:
	    /* Close the object as needed, regardless of if it was
	     * opened or not or if there was an error
	     */
	    if(d->fp != NULL)
	    {
		fclose(d->fp);
		d->fp = NULL;
	    }

	    /* Report progress only if there were no errors and
	     * and not user aborted
	     */
	    if(d->show_progress && ProgressDialogIsQuery() &&
	       (*d->status == 0)
	    )
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (d->total_size > 0l) ?
	((gfloat)(*d->cur_size) / (gfloat)d->total_size) : 0.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
	    }

	    return(0);
	    break;

	  case ZIP_SOURCE_STAT:
	    if((data != NULL) && (len >= sizeof(struct zip_stat)))
	    {
		struct stat src_stat;
		struct zip_stat *zstat = (struct zip_stat *)data;

		/* Is the object already opened? */
		if(d->fp != NULL)
		{
		    /* Get the statistics of the opened object */
		    if(fstat(fileno(d->fp), &src_stat))
		    {
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to add \"",
			    g_basename(d->src_path),
			    "\" to the archive, ",
			    g_strerror(error_code),
			    NULL
			);
			EDVArchAddCopyErrorMessage(s);
			g_free(s);
			memset(&src_stat, 0x00, sizeof(struct stat));
			*d->status = -1;
		    }
		}
		else
		{
		    /* Get the statistics */
		    if(lstat(d->src_path, &src_stat))
		    {
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to add \"",
			    g_basename(d->src_path),
			    "\" to the archive, ",
			    g_strerror(error_code),
			    NULL
			);
			EDVArchAddCopyErrorMessage(s);
			g_free(s);
			memset(&src_stat, 0x00, sizeof(struct stat));
			*d->status = -1;
		    }
		}

		/* Get the size of this object for our data since
		 * it was not known at its initialization
		 */
		d->this_size = (gulong)src_stat.st_size;

		/* Set the zip stat values
		 *
		 * Although not explicitly stated in the libzip
		 * documentation as of libzip 0.7, libzip's code
		 * suggests that only members; crc, mtime, size,
		 * comp_size and comp_method be set
		 */
		zstat->crc = 0;
		zstat->mtime = src_stat.st_mtime;
		switch(d->type)
		{
		  case EDV_OBJECT_TYPE_UNKNOWN:
		    zstat->size = 0l;
		    break;
		  case EDV_OBJECT_TYPE_FILE:
		    zstat->size = src_stat.st_size;
		    break;
		  case EDV_OBJECT_TYPE_DIRECTORY:
		    zstat->size = 0l;
		    break;
		  case EDV_OBJECT_TYPE_LINK:
		    zstat->size = src_stat.st_size;
		    break;
		  case EDV_OBJECT_TYPE_DEVICE_BLOCK:
		  case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
		  case EDV_OBJECT_TYPE_FIFO:
		  case EDV_OBJECT_TYPE_SOCKET:
		    zstat->size = 0l;
		    break;
		  case EDV_OBJECT_TYPE_ERROR:
		    zstat->size = 0l;
		    break;
		}
		zstat->comp_size = -1;
		zstat->comp_method = ZIP_CM_STORE;
		zstat->encryption_method = ZIP_EM_NONE;

		/* We return the size of struct zip_stat instead of 0
		 * to indicate success, even though the libzip
		 * documentation which states that we should return 0
		 * for success, this is due to the fact that libzip,
		 * actually, expects the size of struct zip_stat and
		 * 0 or -1 are both errors in this case
		 */
		return((ssize_t)sizeof(struct zip_stat));
	    }
	    else
	    {
		int *zip_error = d->zip_error;
		zip_error[0] = ZIP_ER_INTERNAL;
		zip_error[1] = EINVAL;
		last_error =
"libzip requested to obtain statistics without providing a buffer";
		*d->status = -3;
		return(-1);
	    }
	    break;

	  case ZIP_SOURCE_ERROR:
	    if((data != NULL) && (len >= (2 * sizeof(int))))
	    {
		memcpy(data, d->zip_error, (2 * sizeof(int)));
		return((ssize_t)(2 * sizeof(int)));
	    }
	    else
	    {
		int *zip_error = d->zip_error;
		zip_error[0] = ZIP_ER_INTERNAL;
		zip_error[1] = EINVAL;
		return(-1);
	    }
	    break;

	  case ZIP_SOURCE_FREE:
	    EDVArchAddZipSourceDataDelete(d);
	    return(0);
	    break;
	}

	return(-1);
}

/*
 *	Adds the file specified by path to the PKZip archive.
 *
 *	Called by EDVArchAddZipIterate().
 */
static gint EDVArchAddZipIterateFile(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress, const gint compression
)
{
	struct zip_source *zsrc;
	gint i;
	gchar *tar_path;
	EDVArchAddZipSourceData *d;

	/* Generate the path of the file within the archive */
	if(EDVIsParentPath(parent, path))
	{
	    const char *s = path + STRLEN(parent);
	    while(*s == G_DIR_SEPARATOR)
		s++;
	    tar_path = STRDUP(s);
	}
	else
	{
	    tar_path = STRDUP(path);
	}
	if(tar_path == NULL)
	{
	    last_error = "Memory allocation error";
	    *status = -3;
	    return(*status);
	}

	/* Create a new and set up the libzip source data */
	d = EDVArchAddZipSourceDataNew();
	if(d == NULL)
	{
	    g_free(tar_path);
	    last_error = "Memory allocation error";
	    *status = -3;
	    return(*status);
	}
	d->core = core;
	d->status = status;
	d->new_paths_list_rtn = new_paths_list_rtn;
	d->this_size = 0l;
	d->cur_size = cur_size;
	d->total_size = total_size;
	d->arch_obj = STRDUP(arch_obj);
	d->toplevel = toplevel;
	d->show_progress = show_progress;
	d->type = EDV_OBJECT_TYPE_FILE;
	d->src_path = STRDUP(path);
	d->tar_path = STRDUP(tar_path);
	d->fp = NULL;
	d->bytes_read = 0l;
	d->zip_error[0] = ZIP_ER_OK;
	d->zip_error[1] = 0;

	/* Check if the object already exists in the PKZip archive */
	i = zip_name_locate(archive, tar_path, 0);

	/* Set the libzip source function */
	zsrc = zip_source_function(archive, EDVArchAddZipSourceCB, d);
	if(zsrc == NULL)
	{
	    EDVArchAddCopyErrorMessage(zip_strerror(archive));
	    EDVArchAddZipSourceDataDelete(d);
	    g_free(tar_path);
	    *status = -1;
	    return(*status);
	}

	/* Replace or add the file? */
	if(i > -1)
	{
	    /* Replace the existing file */
	    if(zip_replace(archive, i, zsrc) < 0)
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		zip_source_free(zsrc);
		g_free(tar_path);
		*status = -1;
		return(*status);
	    }
	}
	else
	{
	    /* Add the file */
	    if(zip_add(archive, tar_path, zsrc) < 0)
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		zip_source_free(zsrc);
		g_free(tar_path);
		*status = -1;
		return(*status);
	    }
	}

	g_free(tar_path);

	return(0);
}

/*
 *	Adds the directory specified by path to the PKZip archive.
 *
 *	Does not add any objects within the directory into the archive.
 *
 *	Called by EDVArchAddZipIterate().
 */
static gint EDVArchAddZipIterateDirectory(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress
)
{
	struct zip_source *zsrc;
	gint i;
	gchar *tar_path;
	EDVArchAddZipSourceData *d;

	/* Generate the path of the file within the archive
	 *
	 * All directories in PKZip archives must have a tailing
	 * deliminator character to denote that it is a directory
	 */
	if(EDVIsParentPath(parent, path))
	{
	    const char *s = path + STRLEN(parent);
	    while(*s == G_DIR_SEPARATOR)
		s++;
	    tar_path = g_strconcat(s, G_DIR_SEPARATOR_S, NULL);
	}
	else
	{
	    tar_path = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
	}
	if(tar_path == NULL)
	{
	    last_error = "Memory allocation error";
	    *status = -3;
	    return(*status);
	}

	/* Create a new and set up the libzip source data */
	d = EDVArchAddZipSourceDataNew();
	if(d == NULL)
	{
	    g_free(tar_path);
	    last_error = "Memory allocation error";
	    *status = -3;
	    return(*status);
	}
	d->core = core;
	d->status = status;
	d->new_paths_list_rtn = new_paths_list_rtn;
	d->this_size = 0l;
	d->cur_size = cur_size;
	d->total_size = total_size;
	d->arch_obj = STRDUP(arch_obj);
	d->toplevel = toplevel;
	d->show_progress = show_progress;
	d->type = EDV_OBJECT_TYPE_DIRECTORY;
	d->src_path = STRDUP(path);
	d->tar_path = STRDUP(tar_path);
	d->fp = NULL;
	d->bytes_read = 0l;
	d->zip_error[0] = ZIP_ER_OK;
	d->zip_error[1] = 0;

	/* Check if the object already exists within the archive */
	i = zip_name_locate(archive, tar_path, 0);

	/* Set the libzip source function */
	zsrc = zip_source_function(archive, EDVArchAddZipSourceCB, d);
	if(zsrc == NULL)
	{
	    EDVArchAddCopyErrorMessage(zip_strerror(archive));
	    EDVArchAddZipSourceDataDelete(d);
	    g_free(tar_path);
	    *status = -1;
	    return(*status);
	}

	/* Does the object already exist within the archive? */
	if(i > -1)
	{
	    /* Replace the object */
	    if(zip_replace(archive, i, zsrc) < 0)
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		zip_source_free(zsrc);
		g_free(tar_path);
		*status = -1;
		return(*status);
	    }
	}
	else
	{
	    /* Add the directory */
	    if(zip_add(archive, tar_path, zsrc) < 0)
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		zip_source_free(zsrc);
		g_free(tar_path);
		*status = -1;
		return(*status);
	    }
	}

	g_free(tar_path);

	return(0);
}

/*
 *	Adds the link specified by path to the PKZip archive.
 *
 *	The link is not dereferenced.
 *
 *	Called by EDVArchAddZipIterate().
 */
static gint EDVArchAddZipIterateLink(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress, const gint compression
)
{
	struct zip_source *zsrc;
	gint i;
	gchar *tar_path;
	EDVArchAddZipSourceData *d;

	/* Generate the path of the file within the archive
	 *
	 * All links in PKZip archives must have a tailing '@'
	 * character to denote that it is a link
	 */
	if(EDVIsParentPath(parent, path))
	{
	    const char *s = path + STRLEN(parent);
	    while(*s == G_DIR_SEPARATOR)
		s++;
	    tar_path = g_strconcat(s, "@", NULL);
	}
	else
	{
	    tar_path = g_strconcat(path, "@", NULL);
	}
	if(tar_path == NULL)
	{
	    last_error = "Memory allocation error";
	    *status = -3;
	    return(*status);
	}

	/* Create a new and set up the libzip source data */
	d = EDVArchAddZipSourceDataNew();
	if(d == NULL)
	{
	    g_free(tar_path);
	    last_error = "Memory allocation error";
	    *status = -3;
	    return(*status);
	}
	d->core = core;
	d->status = status;
	d->new_paths_list_rtn = new_paths_list_rtn;
	d->this_size = 0l;
	d->cur_size = cur_size;
	d->total_size = total_size;
	d->arch_obj = STRDUP(arch_obj);
	d->toplevel = toplevel;
	d->show_progress = show_progress;
	d->type = EDV_OBJECT_TYPE_LINK;
	d->src_path = STRDUP(path);
	d->tar_path = STRDUP(tar_path);
	d->fp = NULL;
	d->bytes_read = 0l;
	d->zip_error[0] = ZIP_ER_OK;
	d->zip_error[1] = 0;

	/* Check if the object already exists within the archive */
	i = zip_name_locate(archive, tar_path, 0);

	/* Set the libzip source function */
	zsrc = zip_source_function(archive, EDVArchAddZipSourceCB, d);
	if(zsrc == NULL)
	{
	    EDVArchAddCopyErrorMessage(zip_strerror(archive));
	    EDVArchAddZipSourceDataDelete(d);
	    g_free(tar_path);
	    *status = -1;
	    return(*status);
	}

	/* Does the object already exist within the archive? */
	if(i > -1)
	{
	    /* Replace the object */
	    if(zip_replace(archive, i, zsrc) < 0)
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		zip_source_free(zsrc);
		g_free(tar_path);
		*status = -1;
		return(*status);
	    }
	}
	else
	{
	    /* Add the directory */
	    if(zip_add(archive, tar_path, zsrc) < 0)
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		zip_source_free(zsrc);
		g_free(tar_path);
		*status = -1;
		return(*status);
	    }
	}

	g_free(tar_path);

	return(0);
}

/*
 *	Adds the object specified by path to the PKZip archive.
 *
 *	Called by EDVArchAddZip().
 */
static void EDVArchAddZipIterate(
	edv_core_struct *core, gint *status,
	const gchar *arch_obj, struct zip *archive,
	const gchar *password,
	const gchar *parent,		/* Parent directory of the archive */
	const gchar *path,		/* The object to add to the archive */
	GList **new_paths_list_rtn,
	gulong *cur_size, const gulong total_size,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
	mode_t m;
	struct stat stat_buf;

	if(STRISEMPTY(path))
	    return;

	/* Skip the archive itself */
	if(!strcmp(arch_obj, path))
	    return;

	if(lstat(path, &stat_buf))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to add \"",
		g_basename(path),
		"\" to the archive, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVArchAddCopyErrorMessage(s);
	    g_free(s);
	    *status = -1;
	    return;
	}

	m = stat_buf.st_mode;

/* Reports an add error to the user and queries the user to
 * continue adding objects
 */
#define REPORT_ERROR_QUERY_CONTINUE_ADDING	{	\
 if(interactive) {					\
  if(!(*yes_to_all)) {					\
   /* Query the user to continue adding */		\
   gint response;					\
   gchar *s = g_strdup_printf(				\
"An error occured while adding the object:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n\
\n\
Continue adding subsequent objects?",			\
    path, arch_obj					\
   );							\
   EDVPlaySoundError(core);				\
   CDialogSetTransientFor(toplevel);			\
   response = CDialogGetResponse(			\
    "Add 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 adding? */					\
   if((response == CDIALOG_RESPONSE_NO) ||		\
      (response == CDIALOG_RESPONSE_CANCEL) ||		\
      (response == CDIALOG_RESPONSE_NOT_AVAILABLE)	\
   )							\
   {							\
    *status = -5;					\
    return;						\
   }							\
							\
   if(response == CDIALOG_RESPONSE_YES_TO_ALL)		\
    *yes_to_all = TRUE;					\
  }							\
 }							\
}

	/* Directory? */
#ifdef S_ISDIR
	if(S_ISDIR(m))
#else
	if(FALSE)
#endif
	{
	    /* Add this directory to the PKZip archive (but not the
	     * objects within it)
	     */
	    if(EDVArchAddZipIterateDirectory(
		core, status,
		arch_obj, archive,
		password,
		parent,		/* Parent directory of the archive */
		path,		/* The object to add to the archive */
		new_paths_list_rtn,
		cur_size, total_size,
		toplevel,
		show_progress
	    ))
	    {
		REPORT_ERROR_QUERY_CONTINUE_ADDING
	    }

	    /* Recurse into this directory? */
	    if(recursive && (*status != -4))
	    {
		/* Add the objects within this directory into the
		 * PKZip archive
		 */
		gint i, strc;
		const gchar *name;
		gchar **strv = GetDirEntNames2(path, &strc);
		StringQSort(strv, strc);
		for(i = 0; i < strc; i++)
		{
		    name = strv[i];
		    if(name == NULL)
			continue;

		    /* Ignore current and parent directory notations */
		    if(!strcmp((const char *)name, ".") ||
		       !strcmp((const char *)name, "..")
		    )
		    {
			g_free(strv[i]);
			continue;
		    }

		    if((*status != -4) && (*status != -5))
		    {
			gchar *full_path = STRDUP(PrefixPaths(
			    (const char *)path, (const char *)name
			));
			EDVArchAddZipIterate(
			    core, status,
			    arch_obj, archive,
			    password,
			    parent,
			    full_path,
			    new_paths_list_rtn,
			    cur_size, total_size,
			    toplevel,
			    show_progress, interactive, yes_to_all,
			    recursive, compression, dereference_links
			);
			g_free(full_path);
		    }
		    g_free(strv[i]);
		}
		g_free(strv);
	    }
	}
	/* Link? */
#ifdef S_ISLNK
	else if(S_ISLNK(m))
#else
	else if(FALSE)
#endif
	{
	    /* Dereference this link? */
	    if(dereference_links)
	    {
		/* Get the destination object's stats */
		if(stat(path, &stat_buf))
		{
		    const gint error_code = (gint)errno;
		    gchar *s = g_strconcat(
			"Unable to add \"",
			g_basename(path),
			"\" to the archive, ",
			g_strerror(error_code),
			NULL
	            );
	            EDVArchAddCopyErrorMessage(s);
	            g_free(s);
		    *status = -1;
		    return;
		}
		else
		{
		    const mode_t m = stat_buf.st_mode;

		    /* Destination is a directory? */
#ifdef S_ISDIR
		    if(S_ISDIR(m))
#else
		    if(FALSE)
#endif
		    {
			/* Add this directory to the PKZip archive (but
			 * not the objects within it)
			 */
			if(EDVArchAddZipIterateDirectory(
			    core, status,
			    arch_obj, archive,
			    password,
			    parent,	/* Parent directory of the archive */
			    path,	/* The object to add to the archive */
			    new_paths_list_rtn,
			    cur_size, total_size,
			    toplevel,
			    show_progress
			))
			{                    
			    REPORT_ERROR_QUERY_CONTINUE_ADDING
			}

			/* Recurse into this directory? */
			if(recursive && (*status != -4))
			{
			    /* Add the objects within this directory
			     * into the PKZip archive
			     */
			    gint i, strc;
			    const gchar *name;
			    gchar **strv = GetDirEntNames2(path, &strc);
			    StringQSort(strv, strc);
			    for(i = 0; i < strc; i++)
			    {
				name = strv[i];
				if(name == NULL)
				    continue;

				/* Ignore current and parent directory notations */
				if(!strcmp((const char *)name, ".") ||
				   !strcmp((const char *)name, "..")
				)
				{
				    g_free(strv[i]);
				    continue;
				}

				if((*status != -4) && (*status != -5))
				{
				    gchar *full_path = STRDUP(PrefixPaths(
					(const char *)path, (const char *)name
				    ));
				    EDVArchAddZipIterate(
					core, status,
					arch_obj, archive,
					password,
					parent,
					full_path,
					new_paths_list_rtn,
					cur_size, total_size,
					toplevel,
					show_progress, interactive, yes_to_all,
					recursive, compression, dereference_links
				    );
				    g_free(full_path);
				}
				g_free(strv[i]);
			    }
			    g_free(strv);
			}
		    }
		    /* Destination is a file? */
#ifdef S_ISREG
		    else if(S_ISREG(m))
#else
		    else if(FALSE)
#endif
		    {
			/* Add this file to the archive */
			if(EDVArchAddZipIterateFile(
			    core, status,
			    arch_obj, archive,
			    password,
			    parent,	/* Parent directory of the archive */
			    path,	/* The object to add to the archive */
			    new_paths_list_rtn,
			    cur_size, total_size,
			    toplevel,
			    show_progress, compression
			))
			{
			    REPORT_ERROR_QUERY_CONTINUE_ADDING
			}
		    }
		    /* Destination is some other type of object */
		    else
		    {
			/* Ignore this object type */
		    }
		}
	    }
	    else
	    {
		/* Add this link to the archive */
		if(EDVArchAddZipIterateLink(
		    core, status,
		    arch_obj, archive,
		    password,
		    parent,	/* Parent directory of the archive */
		    path,	/* The object to add to the archive */
		    new_paths_list_rtn,
		    cur_size, total_size,
		    toplevel,
		    show_progress, compression
		))
		{
		    REPORT_ERROR_QUERY_CONTINUE_ADDING
		}
	    }
	}
	/* File? */
#ifdef S_ISREG
	else if(S_ISREG(m))
#else
	else if(FALSE)
#endif
	{
	    /* Add this file to the archive */
	    if(EDVArchAddZipIterateFile(
		core, status,
		arch_obj, archive,
		password,
		parent,		/* Parent directory of the archive */
		path,		/* The object to add to the archive */
		new_paths_list_rtn,
		cur_size, total_size,
		toplevel,
		show_progress, compression
	    ))
	    {
		REPORT_ERROR_QUERY_CONTINUE_ADDING
	    }
	}
	/* Destination is some other type of object */
	else
	{
	    /* Ignore this object type */
	}

#undef REPORT_ERROR_QUERY_CONTINUE_ADDING
}
#endif	/* HAVE_LIBZIP */

/*
 *	Add object to a PKZip archive.
 *
 *	Inputs assumed valid.
 */
static gint EDVArchAddZip(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
#ifdef HAVE_LIBZIP
	struct zip *archive;
	gint status, zip_error_code, sys_error_code, nobjs;
	gulong cur_size, total_size;
	gchar *parent;
	GList *glist;

	/* Get the parent directory of the archive, this will be the
	 * base directory in which all objects added into the PKZip
	 * archive will be relative to (have their parent paths striped
	 * of if their parent paths match this parent path)
	 */
	parent = g_dirname(arch_obj);
	if(parent == NULL)
	{
	    last_error = "Unable to obtain the parent directory of the archive";
	    return(-1);
	}

	/* Calculate the total uncompressed size of all the objects to
	 * be added to the archive
	 */
	nobjs = 0;
	total_size = 0l;
	for(glist = tar_paths_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	    total_size += EDVArchAddCTotalSize(
		arch_obj, (const gchar *)glist->data,
		&nobjs,
		recursive, dereference_links
	    );

	/* Open the PKZip archive for writing */
	archive = EDVOpenZipWriting(
	    arch_obj,
	    &zip_error_code, &sys_error_code
	);
	if(archive == NULL)
	{
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error_code, sys_error_code
	    );
	    last_error = last_error_buf;
	    g_free(parent);
	    return(-1);
	}

	/* Iterate through the list of objects to add */
	status = 0;
	cur_size = 0l;
	for(glist = tar_paths_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    EDVArchAddZipIterate(
		core, &status,
		arch_obj, archive,
		password,
		parent,
		(const gchar *)glist->data,	/* Full path to the object to be added */
		new_paths_list_rtn,
		&cur_size, total_size,
		toplevel,
		show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	    if((status == -4) || (status == -5))
		break;
	}

	/* Write/flush changes to the PKZip archive and close it
	 *
	 * This is where libzip actually adds the object to the archive
	 * by calling our EDVArchAddZipSourceCB() write callback function
	 *
	 * The progress will be reported and the status and cur_size
	 * will be updated by calls to EDVArchAddZipSourceCB() made in
	 * zip_close()
	 */
	if(zip_close(archive))
	{
	    /* If not interrupted during the write and close then
	     * set the status to indicate error, otherwise the error
	     * was that the user aborted
	     */
	    if((status == 0) || (status != -4))
	    {
		EDVArchAddCopyErrorMessage(zip_strerror(archive));
		status = -1;
	    }
	}

	g_free(parent);

	return(status);
#else
	const gchar *prog_zip = CFGItemListGetValueS(
	    core->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 the previous working dir */	\
 EDVSetCWD(pwd);				\
 g_free(pwd);				\
 pwd = NULL;				\
}

	/* Record the previous working directory and set the new
	 * working directory
	 */
	pwd = EDVGetCWD();
	parent_dir = g_dirname(arch_obj);
	EDVSetCWD(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)
	{
	    const gchar *tpath;
	    GList *glist;

	    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(glist = tar_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		tpath = (const gchar *)glist->data;
		if(STRISEMPTY(tpath))
		    continue;

		/* Seek tpath to the relative 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 the output file paths */
	    stdout_path = EDVTmpName(NULL);
	    stderr_path = EDVTmpName(NULL);

	    /* Execute the 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 the add command";
		DO_FREE_LOCALS
		return(-1);
	    }

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

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

	    /* Begin reading the 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(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;
			    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)
		    {
			/* Update progress dialog label */
			gchar *s = buf, *s2;
			const gchar *added_path;

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

			added_path = s;

			/* Append path to the list of new paths added
			 * to the archive
			 */
			if(new_paths_list_rtn != NULL)
			    *new_paths_list_rtn = g_list_append(
				*new_paths_list_rtn, STRDUP(added_path)
			    );

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

		if(need_break)
		    break;

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

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

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


/*
 *	Adds the object(s) to the archive.
 *
 *	The arch_obj specifies the archive.
 *
 *	The tar_paths_list specifies the list of objects to add to the
 *	archive.
 *
 *	If new_paths_list_rtn is not NULL then a list of paths
 *	describing the objects that have been added to the archive will
 *	be returned. The calling function must delete the returned list
 *	and each string.
 *
 *	If password is not NULL then a password will be used to
 *	encrypt the objects being added to the archive (if the archive
 *	format supports encryption).
 *
 *	If recursive is TRUE then all the objects in any directory
 *	object specified in tar_paths_list will be added as well.
 *
 *	The compression specifies the compression level from 0 to
 *	100 where 0 is no compression and 100 is maximum compression.
 *	This value has different affects on different archive formats.
 *
 *	If dereference_links is TRUE then the link's destination will
 *	be added instead of the link itself.
 */
gint EDVArchAdd(
	edv_core_struct *core, const gchar *arch_obj,
	GList *tar_paths_list,
	GList **new_paths_list_rtn,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gint compression,
	const gboolean dereference_links
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	const gulong time_start = (gulong)time(NULL);
	const gchar *s, *arch_name;
	gchar *path;
	GList *glist, *ltar_paths_list = NULL;

#define DO_FREE_LOCALS	{		\
 g_list_foreach(			\
  ltar_paths_list, (GFunc)g_free, NULL	\
 );					\
 g_list_free(ltar_paths_list);		\
 ltar_paths_list = NULL;		\
}

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

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

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

	/* Reset the last error message */
	last_error = NULL;

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

	arch_name = g_basename(arch_obj);

	/* Make a copy of the list of objects to be added to the
	 * archive as ltar_paths_list
	 */
	for(glist = tar_paths_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    s = (const gchar *)glist->data;
	    if(STRISEMPTY(s))
		continue;

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

	    /* Simplify target object path notation, stripping
	     * tailing deliminators
	     */
	    EDVSimplifyPath(path);

	    ltar_paths_list = g_list_append(ltar_paths_list, path);
	}
	if(ltar_paths_list == NULL)
	{
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

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

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchAddARJ(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchAddLHA(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchAddRAR(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchAddTar(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		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 = EDVArchAddTar(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		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 = EDVArchAddTar(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		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 = EDVArchAddTar(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* X Archive */
	else if(EDVIsExtension(arch_name, ".xar"))
	{
	    status = EDVArchAddXAr(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		password,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchAddZip(
		core, arch_obj,
		ltar_paths_list,
		new_paths_list_rtn,
		password,
		toplevel, show_progress, interactive, yes_to_all,
		recursive, compression, dereference_links
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	    status = -2;
	}

	/* Record history */
	if(status != 0)
	{
	    const gulong time_end = (gulong)time(NULL);
	    const gchar *first_src_obj = (ltar_paths_list != NULL) ?
		(const gchar *)ltar_paths_list->data : NULL;

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

	    for(glist = ltar_paths_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
		EDVAppendHistory(
		    core,
		    EDV_HISTORY_ARCHIVE_OBJECT_ADD,
		    time_start, time_end,
		    status,
		    (const gchar *)glist->data,	/* Source */
		    arch_obj,			/* 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
}
