#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <gtk/gtk.h>

#include "../include/string.h"

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

#include "edv_types.h"
#include "edv_date.h"
#include "cfg.h"
#include "edv_id.h"
#include "edv_obj.h"
#include "edv_archive_obj.h"
#include "edv_archive_stat.h"
#include "edv_mime_type.h"
#include "edv_mime_types_list.h"
#include "archiver.h"
#include "archiver_cb.h"
#include "archiver_contents_list.h"
#include "endeavour2.h"
#include "edv_cb.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"

#include "edv_cfg_list.h"
#include "config.h"


typedef struct _stat_list_data_struct	stat_list_data_struct;
#define STAT_LIST_DATA(p)		((stat_list_data_struct *)(p))


#if 0
static edv_archive_object_struct *EDVArchiverNewErrorObject(
	const gchar *path
);
#endif

static gint EDVArchiverContentsStatProgressCB(
	const gchar *arch_path,
	edv_archive_object_struct *obj,
	const gulong i, const gulong m,
	gpointer data
);

static void EDVArchiverContentsResetColumns(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist
);

static void EDVArchiverContentsSetCellName(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellSize(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_dir_size, const gboolean hide_link_size
);
static void EDVArchiverContentsSetCellCompressedSize(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_dir_size, const gboolean hide_link_size
);
static void EDVArchiverContentsSetCellType(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellPermissions(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_link_permissions
);
static void EDVArchiverContentsSetCellOwner(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellGroup(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellDateAccess(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity,
	const gchar *date_format
);
static void EDVArchiverContentsSetCellDateModified(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity,
	const gchar *date_format
);
static void EDVArchiverContentsSetCellDateChanged(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity,
	const gchar *date_format
);
static void EDVArchiverContentsSetCellLocation(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellLinkedTo(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellDeviceType(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellCompression(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_dir_size, const gboolean hide_link_size
);
static void EDVArchiverContentsSetCellMethod(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetCellCRC(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
);
static void EDVArchiverContentsSetRow(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist,
	edv_archive_object_struct *obj,
	const gint row
);

static gint EDVArchiverContentsAppendObject(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist,
	edv_archive_object_struct *obj
);
static void EDVArchiverContentsAppendListing(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist,
	const gchar *arch_path,
	const gchar *filter,
	const gchar *password,
	const gboolean update_status_bar
);

/* Finding */
gint EDVArchiverContentsFindRowByPath(
	edv_archiver_struct *archiver, const gchar *path
);

/* Realize Listing */
void EDVArchiverContentsRealizeListing(edv_archiver_struct *archiver);

/* Get Listing */
const gchar *EDVArchiverContentsGetError(edv_archiver_struct *archiver);
void EDVArchiverContentsGetListing(
	edv_archiver_struct *archiver,
	const gboolean update_status_bar
);
void EDVArchiverContentsClear(edv_archiver_struct *archiver);

/* Object Callbacks */
void EDVArchiverContentsObjectAddedNotify(
	edv_archiver_struct *archiver, const gchar *path,
	const struct stat *lstat_buf
);
void EDVArchiverContentsObjectModifiedNotify(
	edv_archiver_struct *archiver, const gchar *path,
	const gchar *new_path, const struct stat *lstat_buf
);
void EDVArchiverContentsObjectRemovedNotify(
	edv_archiver_struct *archiver, const gchar *path
);

/* Archive Object Callbacks */
void EDVArchiverContentsArchiveObjectAddedNotify(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, edv_archive_object_struct *obj
);
void EDVArchiverContentsArchiveObjectModifiedNotify(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, const gchar *new_path,
	edv_archive_object_struct *obj
);
void EDVArchiverContentsArchiveObjectRemovedNotify(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path
);


static const gchar *get_listing_last_error;


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


struct _stat_list_data_struct {

	edv_archiver_struct	*archiver;
	gint	last_progress_percent;

};


#if 0
/* 
 *	Creates a new error object.
 *
 *	The path specifies the full path to the error object.
 */
static edv_archive_object_struct *EDVArchiverNewErrorObject(
	const gchar *path
)
{
	edv_archive_object_struct *obj = EDVArchObjectNew();
	if(obj == NULL)
	    return(NULL);

	obj->type = EDV_OBJECT_TYPE_ERROR;
	if(path != NULL)
	{
	    const gchar *s;

	    obj->full_path = STRDUP(path);

	    s = strrchr(path, G_DIR_SEPARATOR);
	    if(s != NULL)
		s++;
	    else
		s = path;
	    obj->name = STRDUP(s);
	}

	return(obj);
}
#endif


/*
 *	Stat list progress callback.
 */
static gint EDVArchiverContentsStatProgressCB(
	const gchar *arch_path,
	edv_archive_object_struct *obj,
	const gulong i, const gulong m,
	gpointer data
)
{
	edv_archiver_struct *archiver;
	stat_list_data_struct *d = STAT_LIST_DATA(data);
	if(d == NULL)
	    return(-1);

	archiver = d->archiver;
	if(archiver == NULL)
	    return(-1);

	if(m > 0l)
	{
	    const gint progress_percent = (gint)(i * 100l / m);
	    if(progress_percent > d->last_progress_percent)
	    {
		EDVStatusBarProgress(
		    archiver->status_bar,
		    (gfloat)progress_percent / 100.0f,
		    TRUE
		);
		d->last_progress_percent = progress_percent;
	    }
	}
	else
	{
	    EDVStatusBarProgress(archiver->status_bar, -1.0f, TRUE);
	}

	return(0);
}

/*
 *	Resets the Contents List's column headings to the names and
 *	ordering specified by the configuration.
 *
 *	All inputs assumed valid.
 */
static void EDVArchiverContentsResetColumns(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist
)
{
	gint i, width;
	const gchar *title = NULL;
	GList *glist;
	GtkJustification justify = GTK_JUSTIFY_LEFT;
	GtkRcStyle *lists_rcstyle = core->lists_rcstyle;
	cfg_intlist_struct	*column_types_intlist,
				*column_width_intlist;
	const cfg_item_struct *cfg_list = core->cfg_list;
	edv_archiver_column_type column_type;

	/* Get column types mapping */
	column_types_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_ARCHIVER_CONTENTS_COLUMN
	);
	if(column_types_intlist == NULL)
	    return;

	column_width_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_ARCHIVER_CONTENTS_COLUMN_WIDTH
	);
	if(column_width_intlist == NULL)
	    return;

	/* Update clist column settings */
	gtk_clist_column_titles_active(clist);
	gtk_clist_column_titles_show(clist);
	gtk_clist_set_auto_sort(clist, FALSE);
	gtk_clist_set_sort_type(clist, GTK_SORT_DESCENDING);

#if 0
/* Already set */
	/* Change clist selection mode to GTK_SELECTION_EXTENDED
	 *
	 * The selection mode can change whenever the contents list is
	 * updated
	 */
	gtk_clist_set_selection_mode(clist, GTK_SELECTION_EXTENDED);
#endif

	/* Iterate through each column */
	for(i = 0, glist = column_types_intlist->list;
	    glist != NULL;
	    i++, glist = g_list_next(glist)
	)
	{
	    column_type = (edv_archiver_column_type)glist->data;

	    /* Get the width for this column type */
	    width = (gint)g_list_nth_data(
		column_width_intlist->list,
		(guint)column_type
	    );

	    /* Get column title and justification  based on the
	     * column type
	     */
	    switch(column_type)
	    {
	      case EDV_ARCHIVER_COLUMN_TYPE_NAME:
		title = "Name";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_SIZE:
		title = "Size";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_COMPRESSED_SIZE:
		title = "Compressed";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_TYPE:
		title = "Type";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_PERMISSIONS:
		title = "Permissions";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_OWNER:
		title = "Owner";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_GROUP:
		title = "Group";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DATE_ACCESS:
		title = "Date Access";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DATE_MODIFIED:
		title = "Date Modified";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DATE_CHANGED:
		title = "Date Changed";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_LOCATION:
		title = "Location";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_LINKED_TO:
		title = "Linked To";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DEVICE_TYPE:
		title = "Device Type";
		justify = GTK_JUSTIFY_RIGHT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_COMPRESSION:
		title = "Compression";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_METHOD:
		title = "Method";
		justify = GTK_JUSTIFY_LEFT;
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_CRC:
		title = "CRC";
		justify = GTK_JUSTIFY_LEFT;
		break;
	    }

	    gtk_clist_set_column_visibility(clist, i, TRUE);
	    gtk_clist_set_column_auto_resize(
		clist, i, FALSE
	    );
	    gtk_clist_set_column_title(clist, i, title);
	    gtk_clist_set_column_width(clist, i, width);
	    gtk_clist_set_column_justification(clist, i, justify);
	}
	/* Reset the rest of the columns */
	for(; i < clist->columns; i++)
	    gtk_clist_set_column_visibility(clist, i, FALSE);

	/* Set RC style after column headings have been mapped */
	if(lists_rcstyle != NULL)
	    gtk_widget_modify_style_recursive(
		GTK_WIDGET(clist), lists_rcstyle
	    );
}


/*
 *	Sets the archive object name for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellName(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	const gchar *name = obj->name;
	GdkPixmap *pixmap, *pixmap_hid;
	GdkBitmap *mask, *mask_hid;

	if(STRISEMPTY(name))
	    name = obj->full_path;

	/* Get pixmap and mask for the given object */
	EDVMatchObjectIcon(
	    NULL, 0,
	    core->mimetype, core->total_mimetypes,
	    obj->type,
	    name,
	    TRUE,
	    obj->permissions,
	    0,				/* Small icons */
	    &pixmap, &mask,
	    NULL, NULL,
	    NULL, NULL,
	    &pixmap_hid, &mask_hid
	);

	/* Hidden? */
	if((name != NULL) ?
	    ((*name == '.') && (pixmap_hid != NULL)) : FALSE
	)
	{
	    if(g_strcasecmp(name, "..") && g_strcasecmp(name, "."))
	    {
		pixmap = pixmap_hid;
		mask = mask_hid;
	    }
	}

	/* Set cell */
	if(pixmap != NULL)
	    gtk_clist_set_pixtext(
		clist, row, column,
		(name != NULL) ? name : "(null)",
		EDV_LIST_PIXMAP_TEXT_SPACING,
		pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, row, column,
		(name != NULL) ? name : "(null)"
	    );
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object size for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellSize(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_dir_size, const gboolean hide_link_size
)
{
	const gint border_minor = 2;
	const edv_object_type type = obj->type;
	const gchar *s;

	/* Get string for size */
	if(hide_dir_size && (type == EDV_OBJECT_TYPE_DIRECTORY))
	    s = "";
	else if(hide_link_size && (type == EDV_OBJECT_TYPE_LINK))
	    s = "";
	else if((type == EDV_OBJECT_TYPE_DEVICE_BLOCK) ||
		(type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	)
	    s = "";
	else
	    s = EDVGetObjectSizeStr(core, obj->size);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, s
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, -border_minor
	);
}

/*
 *	Sets the archive object compressed size for the specified cell
 *	on the given clist.
 */
static void EDVArchiverContentsSetCellCompressedSize(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_dir_size, const gboolean hide_link_size
)
{
	const gint border_minor = 2;
	const edv_object_type type = obj->type;
	const gchar *s;

	/* Get string for size */
	if(hide_dir_size && (type == EDV_OBJECT_TYPE_DIRECTORY))
	    s = "";
	else if(hide_link_size && (type == EDV_OBJECT_TYPE_LINK))
	    s = "";
	else if((type == EDV_OBJECT_TYPE_DEVICE_BLOCK) ||
		(type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	)
	    s = "";
	else
	    s = EDVGetObjectSizeStr(core, obj->compressed_size);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, s
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, -border_minor
	);
}

/*
 *	Sets the archive object type for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellType(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	gchar *type_str;

	/* Get MIME Type type string for the given object */
	EDVMatchObjectTypeString(
	    core->mimetype, core->total_mimetypes,
	    obj->type,
	    obj->permissions,
	    obj->name,
	    &type_str
	);

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, (type_str != NULL) ? type_str : ""
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object permissions for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellPermissions(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_link_permissions
)
{
	edv_permission_flags permissions = obj->permissions;
	gchar s[80];

	/* Format permissions string */
	if(hide_link_permissions && (obj->type == EDV_OBJECT_TYPE_LINK))
	    *s = '\0';
	else
	    g_snprintf(
		s, sizeof(s),
		"%c%c%c%c%c%c%c%c%c",
	    (permissions & EDV_PERMISSION_UREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_UWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_SETUID) ?
		'S' :
		((permissions & EDV_PERMISSION_UEXECUTE) ? 'x' : '-'),
	    (permissions & EDV_PERMISSION_GREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_GWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_SETGID) ?
		'G' :
		((permissions & EDV_PERMISSION_GEXECUTE) ? 'x' : '-'),
	    (permissions & EDV_PERMISSION_AREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_AWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_STICKY) ?
		'T' :
		((permissions & EDV_PERMISSION_AEXECUTE) ? 'x' : '-')
	    );

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, s
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object owner for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellOwner(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	/* Get owner name */
	const gchar *owner_name = obj->owner_name;
	if(owner_name == NULL)
	    owner_name = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, owner_name
	);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object owner for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellGroup(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	/* Get group name */
	const gchar *group_name = obj->group_name;
	if(group_name == NULL)
	    group_name = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, group_name
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object last access date for the specified cell
 *	on the given clist.
 */
static void EDVArchiverContentsSetCellDateAccess(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity,
	const gchar *date_format
)
{
	const gchar *date_str;

	/* Get string for date accessed */
	gulong t = obj->access_time;
	if(t > 0)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object last modified date for the specified cell
 *	on the given clist.
 */
static void EDVArchiverContentsSetCellDateModified(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity,
	const gchar *date_format
)
{
	const gchar *date_str;

	/* Get string for date modified */
	gulong t = obj->modify_time;
	if(t > 0)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object last changed date for the specified cell
 *	on the given clist.
 */
static void EDVArchiverContentsSetCellDateChanged(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const edv_date_relativity date_relativity,
	const gchar *date_format
)
{
	const gchar *date_str;

	/* Get string for date changed */
	gulong t = obj->change_time;
	if(t > 0)
	    date_str = EDVDateFormatString(
		t, date_format, date_relativity
	    );
	else
	    date_str = "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, date_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the archive object location relative to the archive for the
 *	specified cell on the given clist.
 */
static void EDVArchiverContentsSetCellLocation(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	gchar *path, *location;
	const gchar *full_path = obj->full_path;
	GdkBitmap *mask = NULL;
	GdkPixmap *pixmap = NULL;


	/* Get copy of path that we want displayed */
	if(STRISEMPTY(full_path))
	    path = STRDUP("");
	else if(g_path_is_absolute(full_path))
	    path = STRDUP(full_path);
	else
	    path = g_strdup_printf(
		".%c%s",
		G_DIR_SEPARATOR, full_path
	    );

	/* Get location */
	location = g_dirname(path);
	if(location == NULL)
	    location = STRDUP("");

	if(!STRISEMPTY(location))
	{
	    edv_mime_type_struct *m = EDVMimeTypesListMatchType(
		core->mimetype, core->total_mimetypes,
		NULL,
		EDV_MIME_TYPE_TYPE_INODE_DIRECTORY,
		FALSE
	    );
	    if(m != NULL)
	    {
		pixmap = m->small_pixmap[EDV_MIME_TYPE_ICON_STATE_STANDARD];
		mask = m->small_mask[EDV_MIME_TYPE_ICON_STATE_STANDARD];
	    }
	}

	/* Set cell */
	if(pixmap != NULL)
	    gtk_clist_set_pixtext(
		clist, row, column,
		location,
		EDV_LIST_PIXMAP_TEXT_SPACING,
		pixmap, mask
	    );
	else
	    gtk_clist_set_text(
		clist, row, column, location
	    );
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);

	g_free(path);
	g_free(location);
}

/*
 *	Sets the archive object linked to path for the specified cell
 *	on the given clist.
 */
static void EDVArchiverContentsSetCellLinkedTo(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	/* Get link destination */
	const gchar *link_dest = (obj->link_value != NULL) ?
	    obj->link_value : "";

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, link_dest
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the object device type for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellDeviceType(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	const gint border_minor = 2;
	const edv_object_type type = obj->type;
	gint major, minor;
	gchar num_str[2 * 80];


	/* Is object a block or character device? */
	if((type == EDV_OBJECT_TYPE_DEVICE_BLOCK) ||
	   (type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	)
	{
	    EDVGetDeviceNumbers(
		(dev_t)obj->device_type, &major, &minor
	    );
	    g_snprintf(
		num_str, sizeof(num_str),
		"%i, %i",
		major, minor
	    );
	}
	else
	{
	    *num_str = '\0';
	}

	/* Set cell */
	gtk_clist_set_text(
	    clist, row, column, num_str
	);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, -border_minor
	);
}

/*
 *	Sets the object compression for the specified cell on the given
 *	clist.
 */
static void EDVArchiverContentsSetCellCompression(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column,
	const gboolean hide_dir_size, const gboolean hide_link_size
)
{
#if 1
	GdkBitmap *mask = NULL;
	GdkPixmap *pixmap;

	/* Get compression ratio and compression type string */
	if(hide_dir_size && (obj->type == EDV_OBJECT_TYPE_DIRECTORY))
	{
	    pixmap = NULL;
	}
	else if(hide_link_size && (obj->type == EDV_OBJECT_TYPE_LINK))
	{
	    pixmap = NULL;
	}
	else
	{
	    const gfloat ratio = obj->compression_ratio;
	    if(ratio >= 0.0f)
	    {
		GtkCListColumn *column_ptr = ((column >= 0) && (column < clist->columns)) ?
		    &clist->column[column] : NULL;
		const gint	width = (column_ptr != NULL) ?
		    column_ptr->width : 60,
				height = GTK_CLIST_ROW_HEIGHT_SET(clist) ?
					(clist->row_height - 4) : -1;

		pixmap = EDVNewProgressPixmap(
		    width, height,
		    ratio,
		    TRUE,			/* Draw value */
		    GTK_ORIENTATION_HORIZONTAL,
		    FALSE,			/* Not reverse */
		    gtk_widget_get_style(GTK_WIDGET(clist)),
		    &mask
		);
	    }
	    else
		pixmap = NULL;
	}

	/* Set cell */
	if(pixmap != NULL)
	    gtk_clist_set_pixmap(clist, row, column, pixmap, mask);
	else
	    gtk_clist_set_text(clist, row, column, "");
	gtk_clist_set_cell_style(clist, row, column, NULL);
	GDK_PIXMAP_UNREF(pixmap);
	GDK_BITMAP_UNREF(mask);
#else
	const gint border_minor = 2;
	gchar s[40];

	/* Get compression ratio and compression type string */
	if(hide_dir_size && (obj->type == EDV_OBJECT_TYPE_DIRECTORY))
	{
	    *s = '\0';
	}
	else if(hide_link_size && (obj->type == EDV_OBJECT_TYPE_LINK))
	{
	    *s = '\0';
	}
	else
	{
	    const gfloat ratio = obj->compression_ratio;
	    if(ratio >= 0.0f)
		g_snprintf(
		    s, sizeof(s),
		    "%.1f%%",
		    ratio * 100.0f
		);
	    else
		*s = '\0';
	}

	/* Set cell */
	gtk_clist_set_text(clist, row, column, s);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, -border_minor
	);
#endif
}

/*
 *	Sets the object method of storage for the specified cell on the
 *	given clist.
 */
static void EDVArchiverContentsSetCellMethod(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	gchar *method;

	if((obj->method != NULL) && (obj->encryption != NULL))
	    method = g_strconcat(
		obj->method,
		" + ",
		obj->encryption,
		NULL
	    );
	else if(obj->method != NULL)
	    method = STRDUP(obj->method);
	else
	    method = STRDUP("");

	/* Set cell */
	gtk_clist_set_text(clist, row, column, method);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);

	g_free(method);
}

/*
 *	Sets the object CRC checksum for the specified cell on the
 *	given clist.
 */
static void EDVArchiverContentsSetCellCRC(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist, edv_archive_object_struct *obj,
	const gint row, const gint column
)
{
	const gchar *crc = obj->crc;
	if(crc == NULL)
	    crc = "";

	/* Set cell */
	gtk_clist_set_text(clist, row, column, crc);
	gtk_clist_set_cell_style(clist, row, column, NULL);
	gtk_clist_set_shift(
	    clist, row, column, 0, 0
	);
}

/*
 *	Sets the Contents List's row to the values of the object.
 *
 *	The clist specifies the Contents List.
 *
 *	The obj specifies the archive object.
 *
 *	The row specifies the row.
 *
 *	All inputs assumed valid.
 */
static void EDVArchiverContentsSetRow(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist,
	edv_archive_object_struct *obj,
	const gint row
)
{
	gint i;
	gboolean hide_dir_size, hide_link_size, hide_link_permissions;
	const gchar *date_format;
	GList *glist;
	edv_date_relativity date_relativity;
	edv_archiver_column_type column_type;
	cfg_intlist_struct *column_types_intlist;
	const cfg_item_struct *cfg_list = core->cfg_list;

	/* Get column types mapping */
	column_types_intlist = EDV_GET_INTLIST(
	    EDV_CFG_PARM_ARCHIVER_CONTENTS_COLUMN
	);
	if(column_types_intlist == NULL)
	    return;

	/* Get additional display options */
	date_relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	date_format = EDV_GET_S(EDV_CFG_PARM_DATE_FORMAT);
	hide_dir_size = EDV_GET_B(EDV_CFG_PARM_ARCHIVER_CONTENTS_HIDE_DIR_SIZE);
	hide_link_size = EDV_GET_B(EDV_CFG_PARM_ARCHIVER_CONTENTS_HIDE_LINK_SIZE);
	hide_link_permissions = EDV_GET_B(EDV_CFG_PARM_ARCHIVER_CONTENTS_HIDE_LINK_PERMISSIONS);

	/* Iterate through each column */
	for(i = 0, glist = column_types_intlist->list;
	    glist != NULL;
	    i++, glist = g_list_next(glist)
	)
	{
	    column_type = (edv_archiver_column_type)glist->data;
	    switch(column_type)
	    {
	      case EDV_ARCHIVER_COLUMN_TYPE_NAME:
		EDVArchiverContentsSetCellName(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_SIZE:
		EDVArchiverContentsSetCellSize(
		    core, archiver, clist, obj,
		    row, i,
		    hide_dir_size, hide_link_size
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_COMPRESSED_SIZE:
		EDVArchiverContentsSetCellCompressedSize(
		    core, archiver, clist, obj,
		    row, i,
		    hide_dir_size, hide_link_size
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_TYPE:
		EDVArchiverContentsSetCellType(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_PERMISSIONS:
		EDVArchiverContentsSetCellPermissions(
		    core, archiver, clist, obj,
		    row, i, hide_link_permissions
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_OWNER:
		EDVArchiverContentsSetCellOwner(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_GROUP:
		EDVArchiverContentsSetCellGroup(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DATE_ACCESS:
		EDVArchiverContentsSetCellDateAccess(
		    core, archiver, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DATE_MODIFIED:
		EDVArchiverContentsSetCellDateModified(
		    core, archiver, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DATE_CHANGED:
		EDVArchiverContentsSetCellDateChanged(
		    core, archiver, clist, obj,
		    row, i, date_relativity, date_format
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_LOCATION:
		EDVArchiverContentsSetCellLocation(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_LINKED_TO:
		EDVArchiverContentsSetCellLinkedTo(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_DEVICE_TYPE:
		EDVArchiverContentsSetCellDeviceType(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_COMPRESSION:
		EDVArchiverContentsSetCellCompression(
		    core, archiver, clist, obj,
		    row, i,
		    hide_dir_size, hide_link_size
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_METHOD:
		EDVArchiverContentsSetCellMethod(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	      case EDV_ARCHIVER_COLUMN_TYPE_CRC:
		EDVArchiverContentsSetCellCRC(
		    core, archiver, clist, obj,
		    row, i
		);
		break;
	    }
	}
}

/*
 *	Appends the object to the Contents List.
 *
 *	The clist specifies the Contents List.
 *
 *	The obj specifies the archive object who's values will be used
 *	to append a new row on the Contents List and will be
 *	transfered to the Content List's row data. The obj should not
 *	be referenced again after this call.
 *
 *	Returns the new row index or -1 on error.
 *
 *	All inputs assumed valid.
 */
static gint EDVArchiverContentsAppendObject(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist,
	edv_archive_object_struct *obj
)
{
	const gint columns = MAX(clist->columns, 1);
	gint i, new_row;
	gchar **strv;

	/* Allocate the row cell values */
	strv = (gchar **)g_malloc0(columns * sizeof(gchar *));
	for(i = 0; i < columns; i++)
	    strv[i] = "";

	/* Append the new row */
	new_row = gtk_clist_append(clist, strv);

	/* Delete the row cell values */
	g_free(strv);

	/* Failed to appended row? */
	if(new_row < 0)
	{
	    EDVArchObjectDelete(obj);
	    return(-1);
	}

	/* Update the new row's values */
	EDVArchiverContentsSetRow(
	    core, archiver, clist,
	    obj,
	    new_row
	);

	/* Set this object as the new row's data */
	gtk_clist_set_row_data_full(
	    clist, new_row,
	    obj, EDVArchiverContentsItemDestroyCB
	);

	return(new_row);
}

/*
 *	Appends the objects from the archive in the current location to
 *	the Contents List.
 *
 *	The clist specifies the Contents List.
 *
 *	The arch_path specifies the full path to the archive.
 *
 *	The password specifies the password.
 *
 *	If update_status_bar is TRUE then the status bar will be
 *	updated during this operation.
 *
 *	If an error occures during this operation then
 *	get_listing_last_error will be updated with the error message.
 *
 *	All inputs assumed valid.
 */
static void EDVArchiverContentsAppendListing(
	edv_core_struct *core, edv_archiver_struct *archiver,
	GtkCList *clist,
	const gchar *arch_path,
	const gchar *filter,
	const gchar *password,
	const gboolean update_status_bar
)
{
	gint new_row;
	GList *glist, *obj_list;
	edv_archive_object_struct *obj;
	stat_list_data_struct *d = STAT_LIST_DATA(g_malloc0(
	    sizeof(stat_list_data_struct)
	));
	if(d != NULL)
	{
	    d->archiver = archiver;
	    d->last_progress_percent = -1;
	}

	/* Get the statistics of all the objects in the archive */
	obj_list = EDVArchStatList(
	    core,
	    arch_path,
	    NULL,			/* No paths list */
	    filter,
	    password,
	    update_status_bar ?
		EDVArchiverContentsStatProgressCB : NULL,
	    d
	);

	g_free(d);

	/* Record any warnings when obtaining the list of objects */
	get_listing_last_error = EDVArchStatGetError();

	/* Iterate the list of objects in the archive */
	for(glist = obj_list; glist != NULL; glist = g_list_next(glist))
	{
	    obj = EDV_ARCHIVE_OBJECT(glist->data);
	    if(obj == NULL)
		continue;

	    /* Append the row and transfer the object to the new row,
	     * the object should not be referenced again after this
	     * point
	     */
	    new_row = EDVArchiverContentsAppendObject(
		core, archiver, clist,
		obj
	    );
	}

	g_list_free(obj_list);
}

/*
 *	Returns the row index of the contents clist that contains an
 *	archive object structure who's full_path matches the given path.
 *
 *	Can return -1 on failed match.
 */
gint EDVArchiverContentsFindRowByPath(
	edv_archiver_struct *archiver, const gchar *path
)
{
	gint i;
	GtkCList *clist;
	edv_archive_object_struct *obj;

	if((archiver == NULL) || (path == NULL))
	    return(-1);

	clist = (GtkCList *)archiver->contents_clist;
	if(clist == NULL)
	    return(-1);

	/* Iterate through all rows */
	for(i = 0; i < clist->rows; i++)
	{
	    obj = EDV_ARCHIVE_OBJECT(
		gtk_clist_get_row_data(clist, i)
	    );
	    if((obj != NULL) ? (obj->full_path == NULL) : TRUE)
		continue;

	    if(!strcmp(obj->full_path, path))
		return(i);
	}

	return(-1);
}


/*
 *	Updates all rows on the Contents List with each row's
 *	associated archive object.
 *
 *	This should be called when MIME Types or Devices have been
 *	added/modified/removed or after reconfiguration.
 *
 *	This function should not be used to get a new listing, use
 *	EDVArchiverContentsGetListing() instead.
 */
void EDVArchiverContentsRealizeListing(edv_archiver_struct *archiver)
{
	gint i;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;
	edv_archive_object_struct *obj;

	if(archiver == NULL)
	    return;

	w = archiver->contents_clist;
	core = archiver->core;
	if((w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	gtk_clist_freeze(clist);

	/* Update the columns */
	EDVArchiverContentsResetColumns(core, archiver, clist);

	/* Update all the rows */
	for(i = 0; i < clist->rows; i++)
	{
	    obj = EDV_ARCHIVE_OBJECT(
		gtk_clist_get_row_data(clist, i)
	    );
	    if(obj == NULL)
		continue;

	    /* Update this row's values */
	    EDVArchiverContentsSetRow(
		core, archiver, clist,
		obj,
		i			/* Row */
	    );
	}

	gtk_clist_thaw(clist);
}


/*
 *	Returns a statically allocated string describing the last
 *	warning that occured while calling
 *	EDVArchiverContentsGetListing() or NULL if there was none.
 */
const gchar *EDVArchiverContentsGetError(edv_archiver_struct *archiver)
{
	return((archiver != NULL) ? get_listing_last_error : NULL);
}


/*
 *	Clears the Contents List, resets the columns, and gets a new
 *	listing of archive objects.
 *
 *	If update_status_bar is TRUE then the status bar will be updated
 *	during this operation.
 */
void EDVArchiverContentsGetListing(
	edv_archiver_struct *archiver,
	const gboolean update_status_bar
)
{
	const gchar *cur_path, *filter, *password;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;

	get_listing_last_error = NULL;

	if(archiver == NULL)
	    return;

	cur_path = EDVArchiverCurrentLocation(archiver);
	filter = archiver->contents_list_filter;
	password = EDVArchiverCurrentPassword(archiver);
	w = archiver->contents_clist;
	core = archiver->core;
	if((cur_path == NULL) || (password == NULL) ||
	   (w == NULL) || (core == NULL)
	)
	    return;

	clist = GTK_CLIST(w);

	/* Report the initial progress? */
	if(update_status_bar)
	{
	    EDVStatusBarMessage(
		archiver->status_bar,
		"Loading archive objects...",
		FALSE
	    );
	    EDVStatusBarProgress(
		archiver->status_bar, 0.0f, TRUE
	    );
	}

	gtk_clist_freeze(clist);

	/* Clear the list */
	EDVArchiverContentsClear(archiver);

	/* Update the columns */
	EDVArchiverContentsResetColumns(core, archiver, clist);

	/* Get the list of objects from the archive */
	EDVArchiverContentsAppendListing(
	    core, archiver,
	    clist,
	    cur_path,
	    filter,
	    password,
	    update_status_bar
	);

	gtk_clist_thaw(clist);

	/* Report the final progress? */
	if(update_status_bar)
	{
	    EDVStatusBarMessage(
		archiver->status_bar,
		"Archive objects loaded",
		FALSE
	    );
	    EDVStatusBarProgress(
		archiver->status_bar, 0.0f, TRUE
	    );
	}
}

/*
 *	Deletes all rows in the Contents List.
 */
void EDVArchiverContentsClear(edv_archiver_struct *archiver)
{
	GtkCList *clist;

	if(archiver == NULL)
	    return;

	clist = (GtkCList *)archiver->contents_clist;
	if(clist == NULL)
	    return;

	gtk_clist_freeze(clist);
	gtk_clist_clear(clist);
	gtk_clist_thaw(clist);
}


/*
 *	Object added notify callback.
 */
void EDVArchiverContentsObjectAddedNotify(
	edv_archiver_struct *archiver, const gchar *path,
	const struct stat *lstat_buf
)
{
	const gchar *cur_path;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if((archiver == NULL) || (path == NULL) || (lstat_buf == NULL))
	    return;

	cur_path = EDVArchiverCurrentLocation(archiver);
	clist = (GtkCList *)archiver->contents_clist;
	core = archiver->core;
	if((clist == NULL) || (core == NULL))
	    return;

	cfg_list = core->cfg_list;

	/* Is the added path the same as the current location? */
	if(!strcmp(path, cur_path))
	{
	    /* Reget the listing */
	    EDVArchiverContentsGetListing(
		archiver,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );
	}
}

/*
 *	Object modified notify callback.
 */
void EDVArchiverContentsObjectModifiedNotify(
	edv_archiver_struct *archiver, const gchar *path,
	const gchar *new_path, const struct stat *lstat_buf
)
{
	const gchar *cur_path;
	GtkCList *clist;
	const cfg_item_struct *cfg_list;
	edv_core_struct *core;

	if((archiver == NULL) || (path == NULL) || (lstat_buf == NULL))
	    return;

	if(new_path == NULL)
	    new_path = path;

	cur_path = EDVArchiverCurrentLocation(archiver);
	clist = (GtkCList *)archiver->contents_clist;
	core = archiver->core;
	if((clist == NULL) || (core == NULL))
	    return;

	cfg_list = core->cfg_list;

	/* Is the modified path the same as the current location? */
	if(!strcmp(new_path, cur_path))
	{
	    /* Reget the listing */
	    EDVArchiverContentsGetListing(
		archiver,
		EDV_GET_B(EDV_CFG_PARM_LISTS_ANIMATE_UPDATES)
	    );
	}
}

/*
 *	Object removed notify callback.
 */
void EDVArchiverContentsObjectRemovedNotify(
	edv_archiver_struct *archiver, const gchar *path
)
{
	const gchar *cur_path;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;

	if(archiver == NULL)
	    return;

	cur_path = EDVArchiverCurrentLocation(archiver);
	w = archiver->contents_clist;
	core = archiver->core;
	if((cur_path == NULL) || (w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	/* Is the removed path the same as the current location? */
	if(!strcmp(path, cur_path))
	{
	    /* Clear the listing */
	    EDVArchiverContentsClear(archiver);
	}
}


/*
 *	Archive object added notify.
 *
 *	The path specifies the previous path of the object in the
 *	archive.
 *
 *	The path specifies the path of the added object in the
 *	archive.
 *
 *	The obj specifies the values of the added object in the
 *	archive.
 */
void EDVArchiverContentsArchiveObjectAddedNotify(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path,
	edv_archive_object_struct *obj
)
{
	gint row;
	const gchar *cur_path;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;

	if((archiver == NULL) || (arch_path == NULL) ||
	   (path == NULL) || (obj == NULL)
	)
	    return;

	cur_path = EDVArchiverCurrentLocation(archiver);
	w = archiver->contents_clist;
	core = archiver->core;
	if((cur_path == NULL) || (w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	/* Skip if the archives are different */
	if(strcmp(cur_path, arch_path))
	    return;

	/* Check if the path of the added archive object describes an
	 * archive object that is already in the list
	 */
	row = EDVArchiverContentsFindRowByPath(archiver, path);
	if((row >= 0) && (row < clist->rows))
	{
	    /* Update the existing object in the list */
	    EDVArchObjectDelete(EDV_ARCHIVE_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    ));
	    gtk_clist_set_row_data_full(
		clist, row,
		EDVArchObjectCopy(obj), EDVArchiverContentsItemDestroyCB
	    );
	    EDVArchiverContentsSetRow(
		core, archiver, clist,
		obj,
		row
	    );
	}
	else
	{
	    /* Append this object to the list */
	    edv_archive_object_struct *new_obj = EDVArchObjectCopy(obj);
	    if(new_obj != NULL)
		EDVArchiverContentsAppendObject(
		    core, archiver, clist,
		    new_obj
		);
	}
}

/*
 *	Archive object modified notify.
 *
 *	The arch_path specifies the full_path to the archive.
 *
 *	The path specifies the previous path of the object in the
 *	archive.
 *
 *	The new_path specifies the new path of the object in the
 *	archive.
 *
 *	The obj specifies the values for the modified object in the
 *	archive.
 */
void EDVArchiverContentsArchiveObjectModifiedNotify(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path, const gchar *new_path,
	edv_archive_object_struct *obj
)
{
	gint row;
	const gchar *cur_path;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;

	if((archiver == NULL) || (arch_path == NULL) ||
	   (path == NULL) || (obj == NULL)
	)
	    return;

	if(new_path == NULL)
	    new_path = path;

	cur_path = EDVArchiverCurrentLocation(archiver);
	w = archiver->contents_clist;
	core = archiver->core;
	if((cur_path == NULL) || (w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	/* Skip if the archives are different */
	if(strcmp(cur_path, arch_path))
	    return;

	/* Check if the old path of the modified archive object is in
	 * the listing
	 */
	row = EDVArchiverContentsFindRowByPath(archiver, path);
	if((row >= 0) && (row < clist->rows))
	{
	    /* Update the existing object in the list */
	    EDVArchObjectDelete(EDV_ARCHIVE_OBJECT(
		gtk_clist_get_row_data(clist, row)
	    ));
	    gtk_clist_set_row_data_full(
		clist, row,
		EDVArchObjectCopy(obj), EDVArchiverContentsItemDestroyCB
	    );
	    EDVArchiverContentsSetRow(
		core, archiver, clist,
		obj,
		row
	    );
	}
}

/*
 *	Archive object removed notify.
 *
 *	The arch_path specifies the full_path to the archive.
 *
 *	The path specifies the object in the archive that has been
 *	removed.
 */
void EDVArchiverContentsArchiveObjectRemovedNotify(
	edv_archiver_struct *archiver, const gchar *arch_path,
	const gchar *path
)
{
	gint row;
	const gchar *cur_path;
	GtkWidget *w;
	GtkCList *clist;
	edv_core_struct *core;

	if((archiver == NULL) || (arch_path == NULL) ||
	   (path == NULL)
	)
	    return;

	cur_path = EDVArchiverCurrentLocation(archiver);
	w = archiver->contents_clist;
	core = archiver->core;
	if((cur_path == NULL) || (w == NULL) || (core == NULL))
	    return;

	clist = GTK_CLIST(w);

	/* Skip if the archives are different */
	if(strcmp(cur_path, arch_path))
	    return;

	/* Check if the removed path of the modified archive object is
	 * in the listing
	 */
	row = EDVArchiverContentsFindRowByPath(archiver, path);
	if((row >= 0) && (row < clist->rows))
	{
	    /* Remove this object */
	    gtk_clist_remove(clist, row);
	}
}
