#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <gtk/gtk.h>
#include <unistd.h>

#include "../include/disk.h"

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

#include "cfg.h"
#include "edv_types.h"
#include "edv_recycled_obj.h"
#include "endeavour2.h"
#include "edv_recbin_index.h"
#include "edv_recbin_sync.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.h"
#include "config.h"

#include "images/icon_trash_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.
 */


static gboolean EDVRecBinSyncIsStrAllDigits(const gchar *s);

static void EDVRecBinSyncIndexFile(
	edv_core_struct *core,
	const gchar *index_path,
	const gchar *recbin_path,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all, gint *status,
	void (*status_message_cb)(const gchar *, gpointer),
	void (*status_progress_cb)(gfloat, gpointer),
	gpointer data
);
static void EDVRecBinSyncObjects(
	edv_core_struct *core,
	const gchar *index_path,
	const gchar *recbin_path,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all, gint *status,
	void (*status_message_cb)(const gchar *, gpointer),
	void (*status_progress_cb)(gfloat, gpointer),
	gpointer data
);

gint EDVRecBinSync(
	edv_core_struct *core,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	void (*status_message_cb)(const gchar *, gpointer),
	void (*status_progress_cb)(gfloat, gpointer),
	gpointer data
);


static const gchar *last_error = NULL;


#define STATUS_MESSAGE(_s_)	{		\
 if(status_message_cb != NULL)			\
  status_message_cb((_s_), data);		\
}
#define STATUS_PROGRESS(_f_)	{		\
 if(status_progress_cb != NULL)			\
  status_progress_cb((_f_), data);		\
}

#define MESSAGE_ERROR(t,s)	{	\
 if(interactive) {			\
  EDVPlaySoundError(core);		\
  EDVMessageError(			\
   (t), (s), NULL, toplevel		\
  );					\
} }

#define MESSAGE_WARNING(t,s)	{	\
 if(interactive) {			\
  EDVPlaySoundWarning(core);	\
  EDVMessageWarning(			\
   (t), (s), NULL, toplevel		\
  );					\
} }

#define MESSAGE(t,s)	{		\
 if(interactive) {			\
 EDVPlaySoundInfo(core);		\
 CDialogSetTransientFor(toplevel);	\
 CDialogGetResponseIconData(		\
  (t), (s), NULL,			\
  (guint8 **)icon_trash_32x32_xpm,	\
  CDIALOG_BTNFLAG_OK,			\
  CDIALOG_BTNFLAG_OK			\
 );					\
 CDialogSetTransientFor(NULL);		\
} }

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


/*
 *	Checks if all the characters in s are digits.
 *
 *	If the string is empty or NULL then FALSE will be returned.
 */
static gboolean EDVRecBinSyncIsStrAllDigits(const gchar *s)
{
	if(STRISEMPTY(s))
	    return(FALSE);

	while(*s != '\0')
	{
	    if(!isdigit(*s))
		return(FALSE);
	    s++;
	}

	return(TRUE);
}

/*
 *	Scans the index file, fixes it as needed, and removes refering
 *	indices to non-existant recycled objects.
 *
 *	Inputs assumed valid.
 */
static void EDVRecBinSyncIndexFile(
	edv_core_struct *core,
	const gchar *index_path,
	const gchar *recbin_path,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all, gint *status,
	void (*status_message_cb)(const gchar *, gpointer),
	void (*status_progress_cb)(gfloat, gpointer),
	gpointer data
)
{
	guint index;
	gchar *recycled_obj_path;
	GList *missing_idx = NULL, *missing_name = NULL;
	edv_recbin_index_struct *rbi_ptr = EDVRecBinIndexOpen(
	    index_path
	);
	const edv_recycled_object_struct *obj;
	struct stat lstat_buf;

	/* Unable to open the recycle bin index file? */
	if(rbi_ptr == NULL)
	{
	    const gchar *error_msg = EDVRecBinIndexGetError();
	    if(error_msg != NULL)
	    {
		gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Incapaz de abrir el archivo del índice de cajón de recirculación:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_FRENCH)
"Incapable d'ouvrir recycler le fichier d'index d'huche:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_GERMAN)
"Unfähig offen, behälter index akte wiederzuverwerten:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Incapace per aprire il file di indice di contenitore per la raccolta differenziata:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_DUTCH)
"Onbekwaam open bak index dossier te recyclen:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Incapaz de abrir recicla arquivo de índice de caixa:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Maktesløs åpne resirkulasjons beholder indeksarkiv:\n\
\n\
    %s\n\
\n\
%s."
#else
"Unable to open the recycle bin index file:\n\
\n\
    %s\n\
\n\
%s."
#endif
		    , index_path, error_msg
		);
		MESSAGE_ERROR(
"Recycle Bin Error",
		    s
		);
		g_free(s);
	    }
	    if(*status == 0)
		*status = -1;
	    return;
	}

	STATUS_MESSAGE(
"Reading recycle bin index file..."
	);
	STATUS_PROGRESS(-1.0f);

	/* Iterate through all the recycled objects referenced by the
	 * recycle bin index file
	 */
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    STATUS_PROGRESS(-1.0f);

	    index = rbi_ptr->index;
	    obj = rbi_ptr->obj;

	    /* Generate the full path to the recycled object */
	    recycled_obj_path = g_strdup_printf(
		"%s%c%i",
		recbin_path, G_DIR_SEPARATOR, index
	    );
	    /* Does not exist? */
	    if(lstat(recycled_obj_path, &lstat_buf))
	    {
		missing_idx = g_list_append(
		    missing_idx, (gpointer)index
		);
		missing_name = g_list_append(
		    missing_name, STRDUP(obj->name)
		);
	    }
	    /* Is not a regular file? */
	    else if(!S_ISREG(lstat_buf.st_mode))
	    {
		missing_idx = g_list_append(
		    missing_idx, (gpointer)index
		);
		missing_name = g_list_append(
		    missing_name, STRDUP(obj->name)
		);
	    }

	    g_free(recycled_obj_path);
	}

	/* Close the recycle bin index file */
	EDVRecBinIndexClose(rbi_ptr);

 
	/* At this point we now have a list of missing recycled objects
	 * refered to by their index numbers, begin querying user for
	 * their removal
	 */
	if(missing_idx != NULL)
	{
	    gboolean got_cancel = FALSE;
	    gint response;
	    guint index;
	    const gchar *name;
	    GList	*glist_idx = missing_idx,
			*glist_name = missing_name;
	    gint i = 0, m = g_list_length(glist_idx);

	    STATUS_MESSAGE(
"Handling missing objects..."
	    );
	    STATUS_PROGRESS(0.0f);

	    /* Iterate through the missing index list */
	    while(glist_idx != NULL)
	    {
		STATUS_PROGRESS((m > 0) ? (gfloat)i / (gfloat)m : 0.0f);

		index = (guint)glist_idx->data;
		name = (const gchar *)glist_name->data;

		/* Query user for removal of this index */
		if(interactive && !(*yes_to_all))
		{
		    gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Refered objetivo reciclado de \"%s\" a por el índice:\n\
\n\
    #%i\n\
\n\
¿No exista, usted quiere quitar su referencia del archivo del índice?"
#elif defined(PROG_LANGUAGE_FRENCH)
"L'objet recyclé \"%s\" refered à par l'index:\n\
\n\
    #%i\n\
\n\
Ne pas exister, voulez-vous enlever sa référence du fichier d'index?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Wiederverwertet Objekt \"%s\" refered zu durch Index:\n\
\n\
    #%i\n\
\n\
Existieren sie, sie wollen herausnehmen seine verweisung von der Index akte nicht?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il refered di \"%s\" di oggetto riciclato a dall'indice:\n\
\n\
    #%i\n\
\n\
Non esiste, lei vuole togliere il suo riferimento dal file di indice?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Gerecyclde voorwerp \"%s\" refered te door index:\n\
\n\
    #%i\n\
\n\
Besta niet, u wil verwijderen zijne referentie van het index dossier?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Refered reciclado de \"%s\" de objeto a por índice:\n\
\n\
    #%i\n\
\n\
Não existe, quer retirar seua referência do arquivo de índice?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Resirkuleredd objekt \"%s\" refered til ved indeks:\n\
\n\
    #%i\n\
\n\
Finnes ikke, gjør De fjerner dets referanse fra indeksarkivet?"
#else
"Recycled object:\n\
\n\
    %s\n\
\n\
Refered to by index:\n\
\n\
    #%i\n\
\n\
Does not exist, do you want to remove its reference from the index file?"
#endif
			, name, index
		    );
		    EDVPlaySoundWarning(core);
		    CDialogSetTransientFor(toplevel);
		    response = CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
"Perder Recicló se Opone",
s,
"¿Mencionó un objeto en los objetos reciclados indexa el\n\
archivo no existe, usted quiere quitar la referencia al no\n\
objeto de existant de los objetos reciclados indexa el\n\
archivo?",
#elif defined(PROG_LANGUAGE_FRENCH)
"Manquer A Recyclé L'Objet",
s,
"Un referenced d'objet dans le fichier d'index d'objets\n\
recyclé n'existe pas, voulez-vous enlever la référence à\n\
l'objet de non-existant du fichier d'index d'objets recyclé?",
#elif defined(PROG_LANGUAGE_GERMAN)
"Verpassen Hat Objekt Wiederverwertet",
s,
"Ablegt ein objekt auf in den wiederverwerteten objekten akte,\n\
sie die verweisung zum nicht-existant objekt vom wiederverwerteten\n\
objekten index nicht hat verwiesen indiziert existiert wollen\n\
herausnehmen?",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Mancare Ha Riciclato L'Oggetto",
s,
"Un referenced di oggetto nel file di indice di oggetti riciclato\n\
non esiste, lei vuole togliere il riferimento all'oggetto di\n\
non-existant dal file di indice di oggetti riciclato?",
#elif defined(PROG_LANGUAGE_DUTCH)
"Missen Recyclde Voorwerp",
s,
"In niet de gerecyclde voorwerpen indexeer dossier bestaat, een\n\
voorwerp verwees naar u wil verwijderen de referentie aan het\n\
niete-existant voorwerp van het gerecycld voorwerpen index\n\
dossier?",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Ausente Que Recicla Objeto",
s,
"Um referenced de objeto no arquivo reciclado de índice de objetos\n\
não existe, quer retirar a referência ao objeto de não-existant do\n\
arquivo reciclado de índice de objetos?",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Mangling Resirkuleredd Objekt",
s,
"En objektreferanse i de resirkuleredde objektene registrerer\n\
arkiv ikke finnes, gjør De fjerner referanseen til\n\
ikke--existantobjektet fra det resirkuleredde\n\
objektindeksarkiv?",
#else
"Non-Existant Recycled Object",
s,
"An object referenced in the recycled objects index file\n\
does not exist, do you want to remove the reference to\n\
the non-existant object from the recycled objects index\n\
file?",
#endif
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_YES |
			CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO |
			CDIALOG_BTNFLAG_CANCEL |
			CDIALOG_BTNFLAG_HELP,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		}
		else
		{
		    response = CDIALOG_RESPONSE_YES_TO_ALL;
		}
		/* Handle response */
		switch(response)
		{
		  case CDIALOG_RESPONSE_YES_TO_ALL:
		    *yes_to_all = TRUE;
		  case CDIALOG_RESPONSE_YES:
		    /* Yes, remove the index from the index file */
		    EDVRecBinIndexRemove(index_path, index);
		    break;
		  case CDIALOG_RESPONSE_NO:
		    /* No, do not remove the index from the index file */
		    break;
		  default:
		    /* Cancel */
		    if(*status == 0)
			*status = -4;
		    got_cancel = TRUE;
		    break;
		}
		if(got_cancel)
		    break;

		/* Go to next the missing recycled object index */
		glist_idx = g_list_next(glist_idx);
		glist_name = g_list_next(glist_name);

		i++;
	    }

	    if(*status == 0)
		*status = -1;
	}

	STATUS_MESSAGE(NULL);
	STATUS_PROGRESS(0.0f);

	/* Delete list of index of missing recycled objects */
	g_list_free(missing_idx);
	g_list_foreach(missing_name, (GFunc)g_free, NULL);
	g_list_free(missing_name);
}


/*
 *	Scans the recycle bin directory and checks if any recycled
 *	objects are not listed in the index file, adds non-referenced
 *	recycled objects to the index file.
 *
 *      Inputs assumed valid.
 */
static void EDVRecBinSyncObjects(
	edv_core_struct *core,
	const gchar *index_path,
	const gchar *recbin_path,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all, gint *status,
	void (*status_message_cb)(const gchar *, gpointer),
	void (*status_progress_cb)(gfloat, gpointer),
	gpointer data
)
{
	gint i, strc;
	gchar **strv;
	const gchar	*obj_name,
			*idx_file_name = g_basename(index_path);
	GList *idx_list = NULL;
	edv_recbin_index_struct *rbi_ptr = EDVRecBinIndexOpen(
	    index_path
	);


	if(STRISEMPTY(idx_file_name))
	    idx_file_name = "";

	/* Able to open the recycle bin index file? */
	if(rbi_ptr != NULL)
	{
	    STATUS_MESSAGE(
"Reading recycle bin index file..."
	    );
	    STATUS_PROGRESS(-1.0f);

	    /* Get the list of indices */
	    while(!EDVRecBinIndexNext(rbi_ptr))
	    {
		STATUS_PROGRESS(-1.0f);
		idx_list = g_list_append(
		    idx_list, (gpointer)rbi_ptr->index
		);
	    }

	    /* Close the recycle bin index file */
	    EDVRecBinIndexClose(rbi_ptr);
	}

	STATUS_MESSAGE(
"Checking for inert objects..."
	);
	STATUS_PROGRESS(0.0f);

	/* Get the listing of every object in the recycled bin
	 * directory
	 */
	strv = GetDirEntNames2(recbin_path, &strc);
	for(i = 0; i < strc; i++)
	{
	    STATUS_PROGRESS((strc > 0) ? (gfloat)i / (gfloat)strc : 0.0f);

	    obj_name = strv[i];
	    if(STRISEMPTY(obj_name))
		continue;

	    /* Skip special directory notations and the index file */
	    if(!strcmp(obj_name, ".") ||
	       !strcmp(obj_name, "..") ||
	       !strcmp(obj_name, idx_file_name)
	    )
		continue;

	    /* Object name is not all digits? */
	    if(!EDVRecBinSyncIsStrAllDigits(obj_name))
	    {
		/* The object probably does not belong in this directory
		 * since it starts with a number, prompt to move the
		 * object to the user's home directory
		 */
		gint response;
		gboolean got_cancel = FALSE;

		if(*status == 0)
		    *status = -1;

		if(interactive && !(*yes_to_all))
		{
		    gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Un objeto denominó \"%s\" no aparece pertenecer\n\
en la guía reciclada de objetos.\n\
\n\
¿Mueve este objeto a la guía del hogar?"
#elif defined(PROG_LANGUAGE_FRENCH)
"Un objet a nommé \"%s\" n'apparaît pas d'appartenir\n\
dans l'annuaire d'objets recyclé.\n\
\n\
Transfère cet objet à l'annuaire de maison?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ein objekt erscheint \"%s\" nicht hat genannt,\n\
im wiederverwerteten objekten verzeichnis zu gehören.\n\
\n\
Bewegen sie dieses objekt zum heim verzeichnis?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Un oggetto ha dato un nome a \"%s\" non appare\n\
appartenere nell'elenco di oggetti riciclato.\n\
\n\
Muove quest'oggetto all'elenco di casa?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Een voorwerp verschijnt \"%s\" niet noemde\n\
om in de gerecyclde voorwerpen gids te horen.\n\
\n\
Beweeg deze voorwerp aan de huis gids?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Um objeto nomeou \"%s\" não aparece pertencer\n\
no guia reciclado de objetos.\n\
\n\
Mova este objeto ao guia de lar?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Et objekt kalt \"%s\" kommer ikke fram høre\n\
til i den resirkuleredde objektkatalog.\n\
\n\
Flytt dette objektet til hjemkatalogen?"
#else
"An inert object named \"%s\" was found in the recycle bin.\n\
\n\
Move this object to your home directory?"
#endif
			, obj_name
		    );
		    EDVPlaySoundQuestion(core);
		    CDialogSetTransientFor(toplevel);
		    response = CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
"El Objeto Inerte Encontró",
s,
"Un objeto se ha encontrado en la guía reciclada de\n\
objetos que no aparece para pertenecer allí. Para ayudar\n\
a mantener la guía reciclada de objetos lo limpia es\n\
recomienda que el objeto se sea mudado de la guía\n\
reciclada de objetos a la guía del hogar.",
#elif defined(PROG_LANGUAGE_FRENCH)
"L'Objet Inerte A Trouvé",
s,
"Un objet a été trouvé dans l'annuaire d'objets recyclé\n\
qui n'apparaît pas d'appartenir là-bas. Pour aider garder\n\
les objets recyclé que l'annuaire nettoie c'est recommandent\n\
que l'objet ait sorti de l'annuaire d'objets recyclé à\n\
l'annuaire de maison.",
#elif defined(PROG_LANGUAGE_GERMAN)
"Träges Objekt Hat Gefunden",
s,
"Ein objekt ist im wiederverwerteten objekten\n\
verzeichnis gefunden worden, das nicht erscheint, dort zu\n\
gehören. Zu helfen, um das wiederverwertete objekte\n\
verzeichnis zu behalten, um es zu reinigen, empfiehlt ist,\n\
daß das Objekt aus dem wiederverwerteten objekten verzeichnis\n\
zum heim verzeichnis bewegt ist.",
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Oggetto Inerte Ha Trovato",
s,
"Un oggetto è stato trovato nell'elenco di oggetti\n\
riciclato che non appare appartenere lí. Per aiutare tenere\n\
l'elenco di oggetti riciclato pulisce è raccomanda che\n\
l'oggetto è fuori mosso dell'elenco di oggetti riciclato\n\
all'elenco di casa.",
#elif defined(PROG_LANGUAGE_DUTCH)
"Inert Voorwerp Vond",
s,
"Een voorwerp is in de gerecyclde voorwerpen gids gevonden\n\
dat niet verschijnt om daar te horen. Om kost de gerecyclde\n\
voorwerpen gids volkomen het is, aanraadt te helpen dat het\n\
voorwerp uit de gerecyclde voorwerpen gids aan de huis gids\n\
bewogen is.",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Objeto Inerte Achou",
s,
"Um objeto foi achado no guia reciclado de objetos que não\n\
aparece pertencer aí. Ajudar mantem o guia reciclado de\n\
objetos limpar é recomenda que o objeto é movido para fora do\n\
guia reciclado de objetos ao guia de lar.",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Nøytralt Objekt Funnet",
s,
"Et objekt finner i den resirkuleredde objektkatalog som\n\
ikke kommer fram høre til der. Hjelpe beholder den\n\
resirkuleredde objektkatalog rengjør det er anbefaler at\n\
objektet flyttet ut av den resirkuleredde objektkatalog til\n\
hjemkatalogen.",
#else
"Inert Object Found",
s,
"An object has been found in the recycle bin directory that\n\
does not appear to belong there. To help keep the recycle\n\
bin directory clean it is recommend that the object be moved\n\
out of the recycle bin directory into your home directory\n\
for further manual examination.",
#endif
			CDIALOG_ICON_WARNING,
			CDIALOG_BTNFLAG_YES |
			CDIALOG_BTNFLAG_YES_TO_ALL |
			CDIALOG_BTNFLAG_NO |
			CDIALOG_BTNFLAG_CANCEL |
			CDIALOG_BTNFLAG_HELP,
			CDIALOG_BTNFLAG_YES
		    );
		    CDialogSetTransientFor(NULL);
		    g_free(s);
		}
		else
		{
		    response = CDIALOG_RESPONSE_YES_TO_ALL;
		}
		/* Handle response */
		switch(response)
		{
		  case CDIALOG_RESPONSE_YES_TO_ALL:
		    *yes_to_all = TRUE;
		  case CDIALOG_RESPONSE_YES:
		    /* Yes, move the object */
		    if(TRUE)
		    {
			gchar	*tar = g_strdup_printf(
			    "%s%c%s",
			    core->home_dir, G_DIR_SEPARATOR, obj_name
			),
				*src = g_strdup_printf(
			    "%s%c%s",
			    recbin_path, G_DIR_SEPARATOR, obj_name
			);
			if(rename(src, tar))
			{
			    const gint error_code = (gint)errno;
			    const gchar *error_msg = g_strerror(error_code);
			    gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Incapaz de mover objeto:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_FRENCH)
"Incapable de déplacer l'objet:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_GERMAN)
"Unfähig, objekt zu bewegen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Incapace per muovere l'oggetto:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_DUTCH)
"Onbekwaam voorwerp te bewegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Incapaz de mover objeto:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Maktesløs flytte objekt:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n\
\n\
%s."
#else
"Unable to move object:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n\
\n\
%s."
#endif
				, obj_name, core->home_dir, error_msg
			    );
			    MESSAGE_ERROR(
"Move Error",
				s
			    );
			    g_free(s);
			    if(*status == 0)
				*status = -1;
			}
			g_free(tar);
			g_free(src);
		    }
		    break;
		  case CDIALOG_RESPONSE_NO:
		    /* No, do not move the object */
		    break;
		  default:
		    /* Cancel */
		    if(*status == 0)
			*status = -4;
		    got_cancel = TRUE;
		    break;
		}
		if(got_cancel)
		    break;

		continue;
	    }

	    /* Each recycled object's name is a number, so get the
	     * numeric value of the object's name as the recycled
	     * object's index
	     */
	    if(TRUE)
	    {
		const guint index = (guint)ATOI(obj_name);
		gboolean in_list = FALSE;
		GList *glist = idx_list;
		while(glist != NULL)
		{
		    if((guint)glist->data == index)
		    {
			in_list = TRUE;
			break;
		    }
		    glist = g_list_next(glist);
		}

		/* Recycled object in the index file? */
		if(in_list)
		{
		    /* Object belongs in the recycled objects directory */
		    gchar *path = g_strdup_printf(
			"%s%c%s",
			recbin_path, G_DIR_SEPARATOR, obj_name
		    );

		    /* Set permissions */
		    if(chmod(path, S_IRUSR | S_IWUSR))
		    {
			const gint error_code = (gint)errno;
			const gchar *error_msg = g_strerror(error_code);
			if(error_msg != NULL)
			{
			    gchar *s = g_strdup_printf(
"%s:\n\
\n\
    %s",
				error_msg, obj_name
			    );
			    MESSAGE_ERROR(
				"CHMod Failed",
				s
			    );
			    g_free(s);
			}
			if(*status == 0)
			    *status = -1;
		    }

		    g_free(path);
		}
		else
		{
		    /* The object probably does not belong in this
		     * directory even though it starts with a number,
		     * prompt to move the object to the user's home
		     * directory
		     */
		    gint response;
		    gboolean got_cancel = FALSE;

		    if(*status == 0)
			*status = -1;

		    if(interactive && !(*yes_to_all))
		    {
			gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Un objeto denominó \"%s\" no aparece pertenecer\n\
en la guía reciclada de objetos.\n\
\n\
¿Mueve este objeto a la guía del hogar?"
#elif defined(PROG_LANGUAGE_FRENCH)
"Un objet a nommé \"%s\" n'apparaît pas d'appartenir\n\
dans l'annuaire d'objets recyclé.\n\
\n\
Transfère cet objet à l'annuaire de maison?"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ein objekt erscheint \"%s\" nicht hat genannt,\n\
im wiederverwerteten objekten verzeichnis zu gehören.\n\
\n\
Bewegen sie dieses objekt zum heim verzeichnis?"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Un oggetto ha dato un nome a \"%s\" non appare\n\
appartenere nell'elenco di oggetti riciclato.\n\
\n\
Muove quest'oggetto all'elenco di casa?"
#elif defined(PROG_LANGUAGE_DUTCH)
"Een voorwerp verschijnt \"%s\" niet noemde\n\
om in de gerecyclde voorwerpen gids te horen.\n\
\n\
Beweeg deze voorwerp aan de huis gids?"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Um objeto nomeou \"%s\" não aparece pertencer\n\
no guia reciclado de objetos.\n\
\n\
Mova este objeto ao guia de lar?"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Et objekt kalt \"%s\" kommer ikke fram høre\n\
til i den resirkuleredde objektkatalog.\n\
\n\
Flytt dette objektet til hjemkatalogen?"
#else
"An object named \"%s\" in the recycle bin\n\
directory does not have an entry in the recycle bin\n\
index file.\n\
\n\
Move this object to the home directory?"
#endif
			    , obj_name
			);
			EDVPlaySoundWarning(core);
			CDialogSetTransientFor(toplevel);
			response = CDialogGetResponse(
#if defined(PROG_LANGUAGE_SPANISH)
"El Objeto Inerte Encontró",
s,
"Un objeto se ha encontrado en la guía reciclada de\n\
objetos que no aparece para pertenecer allí. Para ayudar\n\
a mantener la guía reciclada de objetos lo limpia es\n\
recomienda que el objeto se sea mudado de la guía\n\
reciclada de objetos a la guía del hogar.",
#elif defined(PROG_LANGUAGE_FRENCH)
"L'Objet Inerte A Trouvé",
s,
"Un objet a été trouvé dans l'annuaire d'objets recyclé\n\
qui n'apparaît pas d'appartenir là-bas. Pour aider garder\n\
les objets recyclé que l'annuaire nettoie c'est recommandent\n\
que l'objet ait sorti de l'annuaire d'objets recyclé à\n\
l'annuaire de maison.",
#elif defined(PROG_LANGUAGE_GERMAN)
"Träges Objekt Hat Gefunden",
s,
"Ein objekt ist im wiederverwerteten objekten\n\
verzeichnis gefunden worden, das nicht erscheint, dort zu\n\
gehören. Zu helfen, um das wiederverwertete objekte\n\
verzeichnis zu behalten, um es zu reinigen, empfiehlt ist,\n\
daß das Objekt aus dem wiederverwerteten objekten verzeichnis\n\
zum heim verzeichnis bewegt ist.",
#elif defined(PROG_LANGUAGE_ITALIAN)
"L'Oggetto Inerte Ha Trovato",
s,
"Un oggetto è stato trovato nell'elenco di oggetti\n\
riciclato che non appare appartenere lí. Per aiutare tenere\n\
l'elenco di oggetti riciclato pulisce è raccomanda che\n\
l'oggetto è fuori mosso dell'elenco di oggetti riciclato\n\
all'elenco di casa.",
#elif defined(PROG_LANGUAGE_DUTCH)
"Inert Voorwerp Vond",
s,
"Een voorwerp is in de gerecyclde voorwerpen gids gevonden\n\
dat niet verschijnt om daar te horen. Om kost de gerecyclde\n\
voorwerpen gids volkomen het is, aanraadt te helpen dat het\n\
voorwerp uit de gerecyclde voorwerpen gids aan de huis gids\n\
bewogen is.",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Objeto Inerte Achou",
s,
"Um objeto foi achado no guia reciclado de objetos que não\n\
aparece pertencer aí. Ajudar mantem o guia reciclado de\n\
objetos limpar é recomenda que o objeto é movido para fora do\n\
guia reciclado de objetos ao guia de lar.",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Nøytralt Objekt Funnet",
s,
"Et objekt finner i den resirkuleredde objektkatalog som\n\
ikke kommer fram høre til der. Hjelpe beholder den\n\
resirkuleredde objektkatalog rengjør det er anbefaler at\n\
objektet flyttet ut av den resirkuleredde objektkatalog til\n\
hjemkatalogen.",
#else
"Inert Object Found",
s,
"An object has been found in the recycle bin directory that\n\
does not appear to belong there. To help keep the recycle\n\
bin directory clean it is recommend that the object be moved\n\
out of the recycle bin directory into your home directory\n\
for further manual examination.",
#endif
			    CDIALOG_ICON_WARNING,
			    CDIALOG_BTNFLAG_YES |
			    CDIALOG_BTNFLAG_YES_TO_ALL |
			    CDIALOG_BTNFLAG_NO |
			    CDIALOG_BTNFLAG_CANCEL |
			    CDIALOG_BTNFLAG_HELP,
			    CDIALOG_BTNFLAG_YES
			);
			CDialogSetTransientFor(NULL);
			g_free(s);
		    }
		    else
		    {
			response = CDIALOG_RESPONSE_YES_TO_ALL;
		    }
		    /* Handle response */
		    switch(response)
		    {
		      case CDIALOG_RESPONSE_YES_TO_ALL:
			*yes_to_all = TRUE;
		      case CDIALOG_RESPONSE_YES:
			/* Yes, move the object */
			if(TRUE)
			{
			    gchar   *tar = g_strdup_printf(
				"%s%c%s",
				core->home_dir, G_DIR_SEPARATOR, obj_name
			    ),
				    *src = g_strdup_printf(
				"%s%c%s",
				recbin_path, G_DIR_SEPARATOR, obj_name
			    );
			    if(rename(src, tar))
			    {
				const gint error_code = (gint)errno;
				const gchar *error_msg = g_strerror(error_code);
				gchar *s = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Incapaz de mover objeto:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_FRENCH)
"Incapable de déplacer l'objet:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_GERMAN)
"Unfähig, objekt zu bewegen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_ITALIAN)
"Incapace per muovere l'oggetto:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_DUTCH)
"Onbekwaam voorwerp te bewegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Incapaz de mover objeto:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n\
\n\
%s."
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Maktesløs flytte objekt:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n\
\n\
%s."
#else
"Unable to move object:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n\
\n\
%s."
#endif
				    , obj_name, core->home_dir, error_msg
				);
				MESSAGE_ERROR(
"Move Error",
				    s
				);
				g_free(s);
				if(*status == 0)
				    *status = -1;
			    }
			    g_free(tar);
			    g_free(src);
			}
			break;
		      case CDIALOG_RESPONSE_NO:
			/* No, do not move the object */
			break;
		      default:
			/* Cancel */
			if(*status == 0)
			    *status = -4;
			got_cancel = TRUE;
			break;
		    }
		    if(got_cancel)
			break;

		    continue;
		}
	    }
	}


	STATUS_MESSAGE(NULL);
	STATUS_PROGRESS(0.0f);

	/* Delete the indices list */
	g_list_free(idx_list);

	/* Delete list of recycled object names */
	for(i = 0; i < strc; i++)
	    g_free(strv[i]);
	g_free(strv);
}



/*
 *	Compacts the recycled objects and fixes any errors in the
 *	recycle bin.
 */
gint EDVRecBinSync(
	edv_core_struct *core,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	void (*status_message_cb)(const gchar *, gpointer),
	void (*status_progress_cb)(gfloat, gpointer),
	gpointer data
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	static gboolean lyes_to_all;
	const gchar *index_path;
	gchar *recbin_path = NULL;
	gulong time_start = (gulong)time(NULL);
	struct stat stat_buf;
	const cfg_item_struct *cfg_list;

	if(reenterent)
	{
	    status = -6;
	    return(status);
	}
	else
	{
	    reenterent = TRUE;
	}

#define POSTOP_AND_RETURN(rtn_val)	{	\
 /* Report final progress */			\
 if(status_progress_cb != NULL)			\
  status_progress_cb(1.0f, data);		\
						\
 g_free(recbin_path);				\
 recbin_path = NULL;				\
						\
 reenterent = FALSE;				\
 return(rtn_val);				\
}

	/* Reset last error */
	last_error = NULL;

	/* Reset status */
	status = 0;

	/* Use local yes_to_all if none is given */
	if(yes_to_all == NULL)
	{
	    lyes_to_all = FALSE;	/* Reset when using local */
	    yes_to_all = &lyes_to_all;
	}

	/* Report initial progress */
	if(status_progress_cb != NULL)
	    status_progress_cb(0.0f, data);

	/* Check for invalid input values */
	if(core == NULL)
	{
	    MESSAGE_ERROR(
"Input Error",
"Invalid value."
	    );
	    if(status == 0)
		status = -1;
	    POSTOP_AND_RETURN(status);
	}

	cfg_list = core->cfg_list;

	/* Get path to the recycle bin index file */
	index_path = EDV_GET_S(EDV_CFG_PARM_FILE_RECYCLE_BIN_INDEX);
	if(STRISEMPTY(index_path))
	{
	    MESSAGE_ERROR(
"Recycle Bin Error",
"Recycled objects index file is not defined"
	    );
	    if(status == 0)
		status = -1;
	    POSTOP_AND_RETURN(status);
	}

	/* Get the recycle bin directory */
	recbin_path = g_dirname(index_path);
	if(STRISEMPTY(recbin_path))
	{
	    MESSAGE_ERROR(
"Recycle Bin Error",
"Unable to determine recycle bin directory"
	    );
	    if(status == 0)
		status = -1;
	    POSTOP_AND_RETURN(status);
	}


	/* Begin checking for errors */

	/* Create the recycle bin directory as needed */
	if(stat((char *)recbin_path, &stat_buf))
	{
	    if(rmkdir((char *)recbin_path, S_IRUSR | S_IWUSR | S_IXUSR))
	    {
		gchar *s = g_strdup_printf(
"Unable to create the recycle bin directory:\n\
\n\
    %s\n\
\n\
Unable to continue with the operation since the recycle\n\
bin directory does not exist and it could not be\n\
created.\n\
\n\
Check if you have write permission to the above\n\
location.",
		    recbin_path
		);
		MESSAGE_ERROR(
"Create Directory Error",
s
		);
		g_free(s);
		if(status == 0)
		    status = -1;
		POSTOP_AND_RETURN(status);
	    }
	}
#ifdef S_ISDIR
	else if(!S_ISDIR(stat_buf.st_mode))
#else
	else if(TRUE)
#endif
	{
	    gchar *s = g_strdup_printf(
"Recycle bin directory path:\n\
\n\
    %s\n\
\n\
Is not a directory, the recycle bin directory must be\n\
a directory object.\n\
\n\
Try moving the object found at the above location to\n\
a different location or change the recycle bin\n\
directory's location.",
		recbin_path
	    );
	    MESSAGE_ERROR(
"Recycle Bin Error",
		s
	    );
	    g_free(s);
	    if(status == 0)
		status = -1;
	    POSTOP_AND_RETURN(status);
	}

	/* Update permissions on recycle bin directory */
	if(chmod((char *)recbin_path, S_IRUSR | S_IWUSR | S_IXUSR))
	{
	    const gint error_code = (gint)errno;
	    const gchar *error_msg = g_strerror(error_code);
	    gchar *s = g_strdup_printf(
"Unable to set the permissions on recycle bin directory:\n\
\n\
    %s\n\
\n\
%s.\n\
\n\
This may pose a security concearn since other users\n\
may be able to access the recycled objects.",
		recbin_path, error_msg
	    );
	    MESSAGE_ERROR(
"Change Mode Error",
		s
	    );
	    g_free(s);

	    if(status == 0)
		status = -1;

	    POSTOP_AND_RETURN(status);
	}


	/* Does the recycled objects index file exists? */
	if(!access((const char *)index_path, F_OK) && (status != -4))
	{
	    /* Update the permissions on the recycled objects index file */
	    if(chmod((const char *)index_path, S_IRUSR | S_IWUSR))
	    {
		const gint error_code = (gint)errno;
		const gchar *error_msg = g_strerror(error_code);
		gchar *s = g_strdup_printf(
"Unable to set permissions on the index file:\n\
\n\
    %s\n\
\n\
%s.\n\
\n\
This may pose a security concearn since other users\n\
may be able to access the recycled objects.",
		    index_path, error_msg
		);
		MESSAGE_ERROR(
"Change Mode Error",
		    s
		);
		g_free(s);

		if(status == 0)
		    status = -1;

		POSTOP_AND_RETURN(status);
	    }

	    /* Check for indices that refer to non-existant recycled
	     * objects
	     */
	    EDVRecBinSyncIndexFile(
		core,
		index_path, recbin_path,
		toplevel,
		show_progress, interactive,
		yes_to_all, &status,
		status_message_cb,
		status_progress_cb,
		data
	    );
	}



	/* Recycled objects index file exists? */
	if(status != -4)
	{
	    /* Check for recycled objects that are not listed in the
	     * index file
	     */
	    EDVRecBinSyncObjects(
		core,
		index_path, recbin_path,
		toplevel,
		show_progress, interactive,
		yes_to_all, &status,
		status_message_cb,
		status_progress_cb,
		data
	    );
	}



	/* Errors found? */
	if(status && (status != -4))
	{
	    MESSAGE(
"Recycle Bin Sync Results",
"Errors in the recycle bin were encountered and fixed.\n\
\n\
The recycle bin has been compacted."
	    );
	}


	/* Record history */
	EDVAppendHistory(
	    core,
	    EDV_HISTORY_SYNC_RECBIN,
	    time_start, (gulong)time(NULL),
	    status,
	    index_path,	/* Source */
	    NULL,		/* Target */
	    last_error		/* Comment */
	);

	POSTOP_AND_RETURN(status);
#undef POSTOP_AND_RETURN
}
