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

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

#include "progressdialog.h"

#include "cfg.h"
#include "edv_types.h"
#include "edv_archive_obj.h"
#include "edv_archive_delete.h"
#include "endeavour2.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#include "images/pdi_trash01_20x20.xpm"
#include "images/pdi_trash02_20x20.xpm"
#include "images/pdi_trash03_20x20.xpm"
#include "images/pdi_trash04_20x20.xpm"
#include "images/pdi_trash05_20x20.xpm"
#include "images/pdi_trash06_20x20.xpm"
#include "images/pdi_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.
 */


/* Error Message */
const gchar *EDVArchDeleteGetError(void);
static void EDVArchDeleteCopyErrorMessage(const gchar *msg);

static void EDVArchDeleteMapProgressDialog(
	const gchar *label, const gfloat progress,
	GtkWidget *toplevel, const gboolean force_remap
);
static void EDVArchDeleteMapProgressDialogUnknown(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
);

#ifdef HAVE_LIBZIP
static struct zip *EDVOpenZipWriting(
	const gchar *arch_obj,
        gint *zip_error_code, gint *sys_error_code
);
#endif

/* Delete Object From Archive */
static gint EDVArchDeleteARJ(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
static gint EDVArchDeleteRAR(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
static gint EDVArchDeleteTar(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean is_compress_compressed,
	const gboolean is_gzip_compressed,
	const gboolean is_bzip2_compressed
);
static gint EDVArchDeleteZip(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVArchDelete(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);


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 *EDVArchDeleteGetError(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 EDVArchDeleteCopyErrorMessage(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
 *	deleting.
 */
static void EDVArchDeleteMapProgressDialog(
	const gchar *label, const gfloat progress,
	GtkWidget *toplevel, const gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

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

	ProgressDialogSetTransientFor(toplevel);

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

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Borrar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Effacer",
	    label,
	    "Arręt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Löschen",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Cancellare",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Schrappen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Anular",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Stryking",
	    label,
	    "Stans",
#else
	    "Deleting",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    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 deleting
 *	with an unknown progress value (activity mode).
 */
static void EDVArchDeleteMapProgressDialogUnknown(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

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

	ProgressDialogSetTransientFor(toplevel);

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

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Borrar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Effacer",
	    label,
	    "Arręt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Löschen",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Cancellare",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Schrappen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Anular",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Stryking",
	    label,
	    "Stans",
#else
	    "Deleting",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    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();
}


#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);
}
#endif	/* HAVE_LIBZIP */


/*
 *	Deletes the object(s) from the ARJ archive.
 */
static gint EDVArchDeleteARJ(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	gint status = 0, p;
	gchar *cmd = NULL;
	GList *glist;

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

	/* Format the delete objects from archive command */
	if(cmd == NULL)
	{
	    edv_archive_object_struct *obj;

	    cmd = g_strdup_printf(
		"\"%s\" d -i -y \"%s\"",
		prog_arj, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the delete command";
		DO_FREE_LOCALS
		return(-2);
	    }
	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, obj->full_path);
		cmd = G_STRCAT(cmd, "\"");
	    }

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

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

	    /* Begin monitoring the deleting processing until it
	     * finishes or until user abort
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Deletes the object(s) from the LHA archive.
 */
static gint EDVArchDeleteLHA(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	gint status = 0, p;
	gchar *cmd = NULL;
	GList *glist;

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

	/* Format the delete objects from archive command */
	if(cmd == NULL)
	{
	    edv_archive_object_struct *obj;

	    cmd = g_strdup_printf(
		"\"%s\" -df \"%s\"",
		prog_lha, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the delete command";
		DO_FREE_LOCALS
		return(-2);
	    }
	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, obj->full_path);
		cmd = G_STRCAT(cmd, "\"");
	    }

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

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

	    /* Begin monitoring the deleting processing until it
	     * finishes or until user abort
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;
			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Deletes the object(s) from the RAR archive.
 */
static gint EDVArchDeleteRAR(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	gint status = 0, p;
	gchar *cmd = NULL;
	GList *glist;

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

	/* Format the delete objects from archive command */
	if(cmd == NULL)
	{
	    edv_archive_object_struct *obj;

	    cmd = g_strdup_printf(
		"\"%s\" d -y -c- \"%s\"",
		prog_rar, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the delete command";
		DO_FREE_LOCALS
		return(-2);
	    }
	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, obj->full_path);
		cmd = G_STRCAT(cmd, "\"");
	    }

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

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

	    /* Begin monitoring the deleting processing until it
	     * finishes or until user abort
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;
			status = -4;
			break;
		    }
		}
		usleep(8000);
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Deletes the object(s) from the Tape Archive.
 */
static gint EDVArchDeleteTar(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	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 status, p, nobjs;
	gchar *s, *s2;
	gchar	*cmd = NULL,
		*arch_uncompressed_path = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;
	FILE *fp;
	GList *glist;

#define DO_FREE_LOCALS			\
{					\
 g_free(cmd);				\
 g_free(stdout_path);			\
 g_free(stderr_path);			\
 g_free(arch_uncompressed_path);	\
}

	/* 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");
	/* Explicitly set new postfix (yes we have enough allocation for
	 * 4 bytes, see above match criteria
	 */
	if(s2 != NULL)
	    strcpy(s2, ".tar");

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

	/* 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
		);
		EDVArchDeleteMapProgressDialogUnknown(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 delete object from archive command */
	nobjs = 0;
	if(cmd == NULL)
	{
	    edv_archive_object_struct *obj;

	    cmd = g_strdup_printf(
		"\"%s\" --delete -v -f \"%s\"",
		prog_tar, arch_uncompressed_path
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the delete command";
		DO_FREE_LOCALS
		return(-2);
	    }
	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, obj->full_path);
		cmd = G_STRCAT(cmd, "\"");

		nobjs++;
	    }

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

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

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

	/* Open the output file for reading
	 *
	 * Note that the tar program does not provide any output when
	 * deleting objects from the archive so no output will ever
	 * be printed
	 */
	status = 0;
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint buf_pos = 0, nobjs_deleted = 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? */
		    if(got_complete_line)
		    { 
			const gchar *s = buf;

			/* Update the progress dialog label */
			if(show_progress)
			{
			    gchar	*p1 = EDVShortenPath(
				s, 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)
"Borrar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrappen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anular:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryking:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Deleting:\n\
\n\
    %s\n\
\n\
From:\n\
\n\
    %s\n"
#endif   
				, p1, p2
			    );
			    EDVArchDeleteMapProgressDialog(
				msg, progress, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}

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

			continue;
		    }
		}

		if(need_break)
		    break;

		/* Check if the delete 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 as needed */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -c \"%s\"",
		prog_compress, arch_uncompressed_path
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -c \"%s\"",
		prog_gzip, arch_uncompressed_path
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -c \"%s\"",
		prog_bzip2, arch_uncompressed_path
	    );
	else
	    cmd = NULL;

	/* Need to compress the archive? */
	if(cmd != NULL)
	{
	    /* Execute the compress archive command */
	    p = (gint)ExecOE((const char *)cmd, (const char *)arch_obj, "/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
		);
		EDVArchDeleteMapProgressDialogUnknown(msg, toplevel, TRUE);
		g_free(msg);
		g_free(p1);
	    }

	    /* Wait for the compress 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);
	    }

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Deletes the object(s) from the PKZip archive.
 */
static gint EDVArchDeleteZip(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
#ifdef HAVE_LIBZIP
	struct zip *archive;
	gint n, status, obj_num, zip_error_code, sys_error_code;
	const gint nobjs = g_list_length(obj_list);
	const gchar *path;
	gchar *tar_path;
	GList *glist;
	edv_archive_object_struct *obj;


	/* Iterate through the objects to delete */
	status = 0;
	obj_num = 0;
	for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

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

	    obj_num++;

	    /* Display the object being deleted? */
	    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)
"Borrar:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Effacer:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Löschen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Cancellare:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrappen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Anular:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Stryking:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Deleting:\n\
\n\
    %s\n\
\n\
From:\n\
\n\
    %s\n"
#endif   
		    , p1, p2
		);
		EDVArchDeleteMapProgressDialog(
		    msg,
		    (nobjs > 0) ?
			((gfloat)obj_num / (gfloat)nobjs) : 0.0f,
		    toplevel, FALSE
		);
		g_free(msg);
		g_free(p1);
		g_free(p2);

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

	    /* 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;
		status = -1;
		break;
	    }

	    /* Report progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (nobjs > 0) ?
			((gfloat)obj_num / (gfloat)nobjs) : 0.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    zip_close(archive);
		    status = -4;
		    break;
		}
	    }

	    /* Format the target path, the path of the object within
	     * the PKZip archive
	     *
	     * If the object is a directory then a tailing deliminator
	     * must be appended or else it will not be matched
	     */
	    if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
		tar_path = g_strdup_printf(
		    "%s%c",
		    path, G_DIR_SEPARATOR
		);
	    else
		tar_path = STRDUP(path);
	    if(tar_path == NULL)
	    {
		zip_close(archive);
		last_error = "Memory allocation error";
		status = -3;
		break;
	    }

	    /* Look for the object in the PKZip archive */
	    n = zip_name_locate(archive, tar_path, 0);
	    if(n < 0)
	    {
		/* Object not in the archive, ignore */
		zip_close(archive);
		g_free(tar_path);
		continue;
	    }

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

	    /* Delete this object in the PKZip archive */
	    if(zip_delete(archive, n))
	    {
		/* Unable to delete this object */
                gchar *s = g_strconcat(
                    "Unable to delete \"",
                    g_basename(tar_path),
                    "\" from the archive, ",
                    (const gchar *)zip_strerror(archive),
                    NULL
                );
                EDVArchDeleteCopyErrorMessage(s);
                g_free(s);
		zip_close(archive);
		g_free(tar_path);
		status = -1;
		break;
	    }

	    /* Write/flush changes to the PKZip archive and close it */
	    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 != -4)
		{
		    EDVArchDeleteCopyErrorMessage(zip_strerror(archive));
		    g_free(tar_path);
		    status = -1;
		    break;
		}
	    }

	    /* Report the final progress? */
	    if(show_progress && ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    (nobjs > 0) ?
			((gfloat)(obj_num + 1) / (gfloat)nobjs) : 0.0f,
		    EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		if(ProgressDialogStopCount() > 0)
		{
		    g_free(tar_path);
		    status = -4;
		    break;
		}
	    }

	    g_free(tar_path);
	}

	return(status);
#else
	const gchar *prog_zip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ZIP
	);
	gint status = 0, p;
	gchar *cmd = NULL;

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

	/* Format the delete from archive command */
	if(cmd == NULL)
	{
	    GList *glist;
	    edv_archive_object_struct *obj;

	    cmd = g_strdup_printf(
		"\"%s\" -d -q \"%s\"",
		prog_zip, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the delete command";
		DO_FREE_LOCALS
		return(-2);
	    }
	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		cmd = G_STRCAT(cmd, " \"");
		cmd = G_STRCAT(cmd, obj->full_path);
		cmd = G_STRCAT(cmd, "\"");
	    }

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

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

	    /* Begin monitoring the deleting processing until it
	     * finishes or until user abort
	     */
	    while(ExecProcessExists(p))
	    {
		if(show_progress && ProgressDialogIsQuery())
		{
		    ProgressDialogUpdateUnknown(
			NULL, NULL, NULL, NULL, TRUE
		    );
		    if(ProgressDialogStopCount() > 0)
		    {
			INTERRUPT(p);
			p = 0;

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

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

/*
 *	Deletes the object(s) from the archive.
 *
 *	The arch_obj specifies the archive.
 *
 *	The obj_list specifies the list of objects to delete from the
 *	archive.
 */
gint EDVArchDelete(
	edv_core_struct *core,
	const gchar *arch_obj, GList *obj_list,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	gint status;
	gulong time_start = (gulong)time(NULL);
	const gchar *arch_name;

#define DO_FREE_LOCALS		{	\
}

	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) ||
	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-2);
	}

	/* No objects to delete from the archive? */
	if(obj_list == NULL)
	{
	    reenterent = FALSE;
	    return(0);
	}

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

	/* ARJ Archive */
	arch_name = g_basename(arch_obj);
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    status = EDVArchDeleteARJ(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchDeleteLHA(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchDeleteRAR(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchDeleteTar(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all,
		TRUE,		/* Is compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (GZip) */
	else if(EDVIsExtension(arch_name, ".tgz .tar.gz"))
	{
	    status = EDVArchDeleteTar(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all,
		FALSE,		/* Not compress compressed */
		TRUE,		/* Is gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (BZip2) */
	else if(EDVIsExtension(arch_name, ".tar.bz2"))
	{
	    status = EDVArchDeleteTar(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		TRUE		/* Is bzip2 compressed */
	    );
	}
	/* Tape Archive */
	else if(EDVIsExtension(arch_name, ".tar"))
	{
	    status = EDVArchDeleteTar(
		core, arch_obj, obj_list,
		toplevel, show_progress, interactive, yes_to_all,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    status = EDVArchDeleteZip(
		core, arch_obj, obj_list, password,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	    status = -2;
	}

	/* Record history */
	if(status)
	{
	    const gulong time_end = (gulong)time(NULL);
	    edv_archive_object_struct *obj = (obj_list != NULL) ?
		EDV_ARCHIVE_OBJECT(obj_list->data) : NULL;

	    EDVAppendHistory(
		core,
		EDV_HISTORY_ARCHIVE_OBJECT_DELETE,
		time_start, time_end,
		status,
		arch_obj,		/* Source */
		(obj != NULL) ? obj->full_path : NULL, /* Target */
		last_error		/* Comment */
	    );
	}
	else
	{
	    const gulong time_end = (gulong)time(NULL);
	    GList *glist;
	    edv_archive_object_struct *obj;

	    for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	    {
		obj = EDV_ARCHIVE_OBJECT(glist->data);
		if(obj == NULL)
		    continue;

		EDVAppendHistory(
		    core,
		    EDV_HISTORY_ARCHIVE_OBJECT_DELETE,
		    time_start, time_end,
		    status,
		    arch_obj,		/* Source */
		    obj->full_path,	/* Target */
		    last_error		/* Comment */
		);
	    }
	}

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

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