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

#include "../include/string.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_chk.h"
#include "endeavour2.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#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 *EDVArchChkGetError(void);
static void EDVArchChkCopyErrorMessage(const gchar *msg);

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

/* Delete Object From Archive */
static gint EDVArchChkARJ(
	edv_core_struct *core,
	const gchar *arch_obj,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
static gint EDVArchChkLHA(
	edv_core_struct *core,
	const gchar *arch_obj,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
static gint EDVArchChkRAR(
	edv_core_struct *core,
	const gchar *arch_obj,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
static gint EDVArchChkTar(
	edv_core_struct *core,
	const gchar *arch_obj,
	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 EDVArchChkZip(
	edv_core_struct *core,
	const gchar *arch_obj,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVArchChk(
	edv_core_struct *core,
	const gchar *arch_obj,
	const gchar *password,
	GtkWidget *toplevel,
	gboolean const 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)


/*
 *	Returns the last error message as a statically allocated
 *	string or NULL if there was no previous error.
 */
const gchar *EDVArchChkGetError(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 EDVArchChkCopyErrorMessage(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 for checking.
 */
static void EDVArchChkMapProgressDialog(
	const gchar *label, const gfloat progress,
	GtkWidget *toplevel, const gboolean force_remap
)
{
	/* 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);
	ProgressDialogMap(
	    "Checking",
	    label,
	    (const guint8 **)pdi_packagefile_32x32_xpm,
	    "Stop"
	);
	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 for checking with an
 *	unknown progress value (activity mode).
 */
static void EDVArchChkMapProgressDialogUnknown(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
)
{
	/* 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);
	ProgressDialogMap(
	    "Checking",
	    label,
	    (const guint8 **)pdi_packagefile_32x32_xpm,
	    "Stop"
	);
	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();
}


/*
 *	Checks the ARJ archive.
 */
static gint EDVArchChkARJ(
	edv_core_struct *core,
	const gchar *arch_obj,
	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
	);
	FILE *fp;
	gint status, p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;

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

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

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

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

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

	/* Open the output file for reading */
	status = 0;
	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 &&
		       strcasepfx(buf, "Testing")
		    )
		    {
			gchar *s = buf, *s2;
			const gchar *chk_path;

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

			/* Cap the space character after path */
			s2 = strstr(s, "  ");
			if(s2 == NULL)
			    s2 = strpbrk(s, " \t\r\n");
			if(s2 != NULL)
			    *s2 = '\0';

			chk_path = s;

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(chk_path))
			{
			    gchar       *p1 = EDVShortenPath(
				chk_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = g_strdup_printf(
"Checking:\n\
\n\
    %s\n\
\n\
In:\n\
\n\
    %s\n"
				, p1, p2
			    );
			    EDVArchChkMapProgressDialogUnknown(
				msg, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}
			continue;
		    }
		    else if(got_complete_line &&
			    strcasepfx(buf, "Error")
		    )
		    {
			/* Update the progress dialog's label */
			gchar *s = buf;

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

			if(last_error == NULL)
			    EDVArchChkCopyErrorMessage(s);
			if((status == 0) || (status == -4))
			    status = -1;

			continue;
		    }
		}

		if(need_break)
		    break;

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

		usleep(8000);
	    }

	    fclose(fp);
	}


	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

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

		if(!STRISEMPTY(s))
		{
		    EDVArchChkCopyErrorMessage(s);
		    if((status == 0) || (status == -4))
			status = -1;
		    break;
		}
	    }

	    fclose(fp);
	}

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Checks the LHA archive.
 */
static gint EDVArchChkLHA(
	edv_core_struct *core,
	const gchar *arch_obj,
	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
	);
	FILE *fp;
	gint status, p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;

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

	/* Format the check archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"\"%s\" -tq1 \"%s\"",
		prog_lha, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the check command";
		DO_FREE_LOCALS
		return(-2);
	    }

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

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

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

	/* Open the output file for reading */
	status = 0;
	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)
		    {
			gchar *s = buf, *s2;
			const gchar *chk_path;

			/* Cap the space character after the path */
			s2 = strpbrk(s, " \t\n\r");
			if(s2 != NULL)
			    *s2 = '\0';

			chk_path = s;

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(chk_path))
			{
			    gchar       *p1 = EDVShortenPath(
				chk_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = g_strdup_printf(
"Checking:\n\
\n\
    %s\n\
\n\
In:\n\
\n\
    %s\n"
				, p1, p2
			    );
			    EDVArchChkMapProgressDialogUnknown(
				msg, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}
			continue;
		    }
		}

		if(need_break)
		    break;

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

		usleep(8000);
	    }

	    fclose(fp);
	}


	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

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

		if(!STRISEMPTY(s))
		{
		    EDVArchChkCopyErrorMessage(s);
		    if((status == 0) || (status == -4))
			status = -1;
		    break;
		}
	    }

	    fclose(fp);
	}

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Checks the RAR archive.
 */
static gint EDVArchChkRAR(
	edv_core_struct *core,
	const gchar *arch_obj,
	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
	);
	FILE *fp;
	gint status, p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;

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

	/* Format the check archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"\"%s\" t -y \"%s\"",
		prog_rar, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the check command";
		DO_FREE_LOCALS
		return(-2);
	    }

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

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

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

	/* Open the output file for reading */
	status = 0;
	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 &&
		       strcasepfx(buf, "Testing  ")
		    )
		    {
			/* Update the progress dialog's label */
			gchar *s = buf, *s2;
			const gchar *chk_path;

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

			/* Cap the space character after the path */
			s2 = strstr(s, "  ");
			if(s2 == NULL)
			    s2 = strpbrk(s, " \t\r\n");
			if(s2 != NULL)
			    *s2 = '\0';

			chk_path = s;

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(chk_path))
			{
			    gchar       *p1 = EDVShortenPath(
				chk_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = g_strdup_printf(
"Checking:\n\
\n\
    %s\n\
\n\
In:\n\
\n\
    %s\n"
				, p1, p2
			    );
			    EDVArchChkMapProgressDialogUnknown(
				msg, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}
			continue;
		    }
		}

		if(need_break)
		    break;

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

		usleep(8000);
	    }

	    fclose(fp);
	}


	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

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

		if(!STRISEMPTY(s))
		{
		    EDVArchChkCopyErrorMessage(s);
		    if((status == 0) || (status == -4))
			status = -1;
		    break;
		}
	    }

	    fclose(fp);
	}

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Checks the Tape archive.
 */
static gint EDVArchChkTar(
	edv_core_struct *core,
	const gchar *arch_obj,
	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_uncompress = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_UNCOMPRESS
	);
	const gchar *prog_gunzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_GUNZIP
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	FILE *fp;
	gint status = 0, p;
	gchar	*cmd = NULL,
		*stdout_path = NULL,
		*stderr_path = NULL;

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

	/* Format the check archive command */
	if(cmd == NULL)
	{
	    if(is_compress_compressed)
		cmd = g_strdup_printf(
		    "\"%s\" \"--use-compress-program=%s\" -t -f \"%s\"",
		    prog_tar, prog_uncompress, arch_obj
		);
	    else if(is_gzip_compressed)
		cmd = g_strdup_printf(
		    "\"%s\" \"--use-compress-program=%s\" -t -f \"%s\"",
		    prog_tar, prog_gunzip, arch_obj
		);
	    else if(is_bzip2_compressed)
		cmd = g_strdup_printf(
		    "\"%s\" \"--use-compress-program=%s\" -t -f \"%s\"",
		    prog_tar, prog_bunzip2, arch_obj
		);
	    else
		cmd = g_strdup_printf(
		    "\"%s\" -t -f \"%s\"",
		    prog_tar, arch_obj
		);
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the check command";
		DO_FREE_LOCALS
		return(-2);
	    }

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

	    /* Execute the check 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 check archive 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 the progress dialog's label */
			const gchar *chk_path = buf;

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(chk_path))
			{
			    gchar       *p1 = EDVShortenPath(
				chk_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = g_strdup_printf(
"Checking:\n\
\n\
    %s\n\
\n\
In:\n\
\n\
    %s\n"
				, p1, p2
			    );
			    EDVArchChkMapProgressDialogUnknown(
				msg, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}
			continue;
		    }
		}

		if(need_break)
		    break;

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

		usleep(8000);
	    }

	    fclose(fp);
	}


	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

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

		if(!STRISEMPTY(s))
		{
		    EDVArchChkCopyErrorMessage(s);
		    if((status == 0) || (status == -4))
			status = -1;
		    break;
		}
	    }

	    fclose(fp);
	}

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Checks the PKZip archive.
 */
static gint EDVArchChkZip(
	edv_core_struct *core,
	const gchar *arch_obj,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
#ifdef HAVE_LIBZIP
	struct stat stat_buf;
	struct zip *archive;
	struct zip_stat zip_stat_buf;
	struct zip_file *zip_fp;
	gint	status, i, nobjs, len,
		read_buf_len, bytes_read,
		zip_error_code;
	gulong cur_size, total_size;
	const gchar *name;
	gchar *clean_name;
	guint8 *read_buf;
	edv_object_type type;

	if(stat(arch_obj, &stat_buf))
	{
	    const gint error_code = (int)errno;
	    EDVArchChkCopyErrorMessage(g_strerror(error_code));
	    return(-1);
	}

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

	/* Get the total number of objects in the archive */
	nobjs = zip_get_num_files(archive);

	/* Iterate through each object and calculate the total size */
	total_size = 0l;
	for(i = 0; i < nobjs; i++)
	{
	    if(zip_stat_index(archive, i, 0, &zip_stat_buf) == 0)
		total_size += (gulong)zip_stat_buf.size;
	}

	/* Iterate through each object */
	status = 0;
	cur_size = 0l;
	for(i = 0; i < nobjs; i++)
	{
	    if(zip_stat_index(archive, i, 0, &zip_stat_buf))
	    {
		EDVArchChkCopyErrorMessage(zip_strerror(archive));
		status = -1;
		break;
	    }

	    /* Get the object's name, clean name (without the type
	     * postfix character), and type
	     */
	    name = zip_stat_buf.name;
	    clean_name = STRDUP(name);
	    len = STRLEN(clean_name);
	    switch((len > 1) ? clean_name[len - 1] : '\0')
	    {
	      case G_DIR_SEPARATOR:
		type = EDV_OBJECT_TYPE_DIRECTORY;
		clean_name[len - 1] = '\0';
		break;
	      case '@':
		type = EDV_OBJECT_TYPE_LINK;
		clean_name[len - 1] = '\0';
		break;
	      default:
		type = EDV_OBJECT_TYPE_FILE;
		break;
	    }

	    /* Update the progress dialog to display the current
	     * object being extracted?
	     */
	    if(show_progress)
	    {
		gchar   *p1 = EDVShortenPath(
		    clean_name, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*p2 = EDVShortenPath(
		    arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
		),
			*msg = g_strdup_printf(
"Checking:\n\
\n\
    %s\n\
\n\
In:\n\
\n\
    %s\n"
		    , p1, p2
		);
		EDVArchChkMapProgressDialog(
		    msg,
		    (total_size > 0l) ?
			((gfloat)cur_size / (gfloat)total_size) : -1.0f,
		    toplevel, FALSE
		);
		g_free(msg);
		g_free(p1);
		g_free(p2);

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

	    /* Open the file in the PKZip archive */
	    zip_fp = zip_fopen_index(archive, i, 0);
	    if(zip_fp == NULL)
	    {
		EDVArchChkCopyErrorMessage(zip_strerror(archive));
		g_free(clean_name);
		status = -1;
		break;
	    }

	    /* Allocate the read buffer */
	    read_buf_len = MAX((int)stat_buf.st_blksize, 1);
	    read_buf = (guint8 *)g_malloc(read_buf_len * sizeof(guint8));
	    if(read_buf == NULL)
	    {
		last_error = "Memory allocation error";
		g_free(clean_name);
		zip_fclose(zip_fp);
		status = -3;
		break;
	    }

	    /* Read the file in the PKZip archive and check for errors */
	    while(TRUE)
	    {
		bytes_read = (gint)zip_fread(
		    zip_fp, read_buf, (int)read_buf_len
		);
		if(bytes_read <= 0)
		{
		    if(bytes_read < 0)
		    {
			EDVArchChkCopyErrorMessage(zip_file_strerror(zip_fp));
			status = -1;
		    }
		    break; 
		}

		cur_size += (gulong)bytes_read;

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

	    /* Delete the read buffer */
	    g_free(read_buf);

	    /* Close the file in the PKZip archive */
	    zip_fclose(zip_fp);

	    g_free(clean_name);

	    if(status != 0)
		break;
	}

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

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

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

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

	/* Format the check archive command */
	if(cmd == NULL)
	{
	    cmd = g_strdup_printf(
		"\"%s\" -T -v \"%s\"",
		prog_zip, arch_obj
	    );
	    if(cmd == NULL)
	    {
		last_error = "Unable to generate the check command";
		DO_FREE_LOCALS
		return(-2);
	    }

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

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

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

	/* Open the output file for reading */
	status = 0;
	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 &&
		       strcasepfx(buf, "    testing:")
		    )
		    {
			/* Update the progress dialog's label */
			gchar *s = buf, *s2;
			const gchar *chk_path = buf;

			while(ISBLANK(*s))
			    s++;
			while(!ISBLANK(*s) && (*s != '\0'))
			    s++;
			while(ISBLANK(*s))
			    s++;

			s2 = strstr(s, "  ");
			if(s2 == NULL)
			    s2 = strpbrk(s, " \t\n\r");
			if(s2 != NULL)
			    *s2 = '\0';

			chk_path = s;

			/* Update the progress dialog's label? */
			if(show_progress && !STRISEMPTY(chk_path))
			{
			    gchar       *p1 = EDVShortenPath(
				chk_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*p2 = EDVShortenPath(
				arch_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
			    ),
					*msg = g_strdup_printf(
"Checking:\n\
\n\
    %s\n\
\n\
In:\n\
\n\
    %s\n"
				, p1, p2
			    );
			    EDVArchChkMapProgressDialogUnknown(
				msg, toplevel, FALSE
			    );
			    g_free(msg);
			    g_free(p1);
			    g_free(p2);
			}
			continue;
		    }
		}

		if(need_break)
		    break;

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

		usleep(8000);
	    }

	    fclose(fp);
	}


	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

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

		if(!STRISEMPTY(s))
		{
		    EDVArchChkCopyErrorMessage(s);
		    if((status == 0) || (status == -4))
			status = -1;
		    break;
		}
	    }

	    fclose(fp);
	}

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

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

/*
 *	Checks the archive.
 *
 *	The arch_obj specifies the archive.
 */
gint EDVArchChk(
	edv_core_struct *core,
	const gchar *arch_obj,
	const gchar *password,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
/*	const gulong time_start = (gulong)time(NULL); */
	gint status;
	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;
	}

	last_error = NULL;

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

	/* 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 = EDVArchChkARJ(
		core, arch_obj,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    status = EDVArchChkLHA(
		core, arch_obj,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    status = EDVArchChkRAR(
		core, arch_obj,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    status = EDVArchChkTar(
		core, arch_obj,
		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 = EDVArchChkTar(
		core, arch_obj,
		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 = EDVArchChkTar(
		core, arch_obj,
		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 = EDVArchChkTar(
		core, arch_obj,
		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 = EDVArchChkZip(
		core, arch_obj, password,
		toplevel, show_progress, interactive, yes_to_all
	    );
	}
	else
	{
	    last_error = "Unsupported archive format";
	    status = -2;
	}

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