#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <glib.h>

#include "../include/string.h"
#include "../include/fio.h"
#include "../include/disk.h"

#include "edvtypes.h"
#include "edvmimetypes.h"
#include "edvmimetypesfio.h"
#include "edvutils.h"
#include "config.h"


void EDVMimeTypeListMailcapImport(
	const gchar *filename,
	edv_mimetype_struct ***list, gint *total,
	gint insert_index,
	gboolean update, gboolean only_newer,
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer progress_data,
	void (*added_cb)(gpointer, gint, edv_mimetype_struct *),
	gpointer added_data,
	void (*modified_cb)(gpointer, gint, edv_mimetype_struct *),
	gpointer modified_data
);
void EDVMimeTypeListMailcapExport(
	const gchar *filename,
	edv_mimetype_struct **list, gint total,
	gboolean include_read_only,
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer progress_data
);


#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 ISCR(c)		(((c) == '\n') || ((c) == '\r'))
#define ISEOF(c)	((int)(c) == EOF)


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
	if(s != NULL) {
	    if(s2 != NULL) {
		gchar *sr = g_strconcat(s, s2, NULL);
		g_free(s);
		s = sr;
	    }
	} else {
	    if(s2 != NULL)
		s = STRDUP(s2);
	    else
		s = STRDUP("");
	}
	return(s);
}

static gchar *GET_CMD_NAME(const gchar *s)
{
	gchar *s2 = STRDUP(s), *s_end;
	if(s2 == NULL)
	    return(NULL);

	for(s_end = s2; *s_end != '\0'; s_end++)
	{
	    if(ISBLANK(*s_end))
	    {
		*s_end = '\0';
		break;
	    }
	}

	return(s2);
}

static gchar *GET_CMD_ARGS(const gchar *s)
{
	const gchar *s2 = (s != NULL) ?
	    strpbrk((const char *)s, " \t") : NULL;
	if(s2 == NULL)
	    return(NULL);

	while(ISBLANK(*s2))
	    s2++;

	return(STRDUP(s2));
}


/*
 *	Imports the MIME Types from the MailCap format file.
 *
 *	The insert_index specifies the position at which to insert the
 *	MIME Types read from file to the list, or it can be -1 to
 *	append to the list.
 */
void EDVMimeTypeListMailcapImport(
	const gchar *filename,
	edv_mimetype_struct ***list, gint *total,
	gint insert_index,
	gboolean update, gboolean only_newer,
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer progress_data,
	void (*added_cb)(gpointer, gint, edv_mimetype_struct *),
	gpointer added_data,
	void (*modified_cb)(gpointer, gint, edv_mimetype_struct *),
	gpointer modified_data
)
{
	gboolean aborted = FALSE, in_quotes, mt_exists;
	gint c;
	gulong	file_size = 0l,
		last_modified = 0l;
	gchar *s, *s_end, buf[10000];
	FILE *fp;
	GList *val_list, *mt_imported_list = NULL;
	gint mt_num = -1;
	edv_mimetype_struct *mt_ptr = NULL;
	struct stat stat_buf;

	if((list == NULL) || (total == NULL) || STRISEMPTY(filename))
	    return;

	/* Open the Mailcap file for reading */
	fp = FOpen((const char *)filename, "rb");
	if(fp == NULL)
	    return;

	/* Get file statistics */
	if(!fstat(fileno(fp), &stat_buf))
	{
	    file_size = (gulong)stat_buf.st_size;
	    last_modified = (gulong)stat_buf.st_mtime;
	}

	/* Report initial progress */
	if(progress_cb != NULL)
	{
	    if(progress_cb(progress_data, 0l, file_size))
		aborted = TRUE;
	}

	/* Begin parsing the Mailcap file
	 *
	 * Each line specifies a MIME Type with a list of ';'
	 * separated values, example:
	 *
	 * image/png; xzgv '%s'; test=test -n "$DISPLAY"
	 *
	 * The first value is always the MIME Type
	 *
	 * The second value is always the command
	 *
	 * Subsequent values follow the format <parm>=<value>
	 */
	while(TRUE)
	{
	    if(aborted)
		break;

	    /* Get the first non-blank character of the current line */
	    c = (gint)fgetc(fp);
	    while(!ISEOF(c) && ISBLANK(c))
		c = (gint)fgetc(fp);

	    /* End of file? */
	    if(ISEOF(c))
	    {
		break;
	    }
	    /* Empty line? */
	    else if(ISCR(c))
	    {
		continue;
	    }
	    /* Comment? */
	    else if(c == '#')
	    {
		/* Seek to start of next line */
		while(!ISEOF(c) && !ISCR(c))
		    c = (gint)fgetc(fp);
		if(ISEOF(c))
		    break;
		else
		    continue;
	    }

	    /* Report progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(
		     progress_data,
		     (gulong)ftell(fp), file_size
		))
		{
		    aborted = TRUE;
		    break;
		}
	    }

	    /* Get values list for this line */
	    val_list = NULL;
	    in_quotes = FALSE;
	    while(TRUE)
	    {
		/* Get next value */
		for(s = buf, s_end = s + sizeof(buf);
		    s < s_end;
		    s++
		)
		{
		    /* End of file? */
		    if(ISEOF(c))
		    {
			*s = '\0';
			break;
		    }
		    /* Escape? */
		    else if(c == '\\')
		    {
			/* Get character that is to be escaped */
			c = (gint)fgetc(fp);
			/* Do not store these escaped characters */
			if(ISCR(c))
			{
			    s--;
			}
			/* End of file? */
			else if(ISEOF(c))
			{
			    *s = '\0';
			    break;
			}
			/* All else store the escaped character with
			 * the escape
			 */
			else
			{
			    *s++ = '\\';
			    if(s < s_end)
				*s = (gchar)c;
			}
		    }
		    /* Quote? */
		    else if(c == '"')
		    {
			in_quotes = !in_quotes;
			*s = (gchar)c;
		    }
		    /* End of value? */
		    else if(c == ';')
		    {
			if(in_quotes)
			{
			    *s = (gchar)c;
			}
			else
			{
			    *s = '\0';
			    break;  
			}
		    }
		    /* End of line? */
		    else if(ISCR(c))
		    {
			if(in_quotes)
			{
			    /* Store this as a space */
			    *s = ' ';
			}
			else
			{
			    *s = '\0';
			    break;  
			}
		    }
		    /* All else store this character */
		    else
		    {
			*s = (gchar)c;
		    }

		    c = (gint)fgetc(fp);
		}

		/* Null terminate the value and strip spaces */
		buf[sizeof(buf) - 1] = '\0';
		strstrip(buf);

		/* Append this value to the values list */
		val_list = g_list_append(
		    val_list, STRDUP(buf)
		);

		/* End of line reached? */
		if(ISCR(c))
		    break;
		else
		    c = (gint)fgetc(fp);
	    }


	    /* Begin creating new imported MIME Type */

	    /* Reset contexts */
	    mt_num = -1;
	    mt_ptr = NULL;
	    mt_exists = FALSE;

	    /* Check if the MIME Type already exists? */
	    if(update)
	    {
		GList *glist = g_list_nth(val_list, 0);
		const gchar *type = (glist != NULL) ? 
		    (gchar *)glist->data : NULL;
		if(!STRISEMPTY(type))
		{
		    /* Check if this MIME Type is already in the list
		     * so that no duplicate MIME Type will be created
		     */
		    mt_ptr = EDVMimeTypeMatchListByType(
			*list, *total, &mt_num, type, FALSE
		    );
		    if(mt_ptr != NULL)
		    {
			/* MIME Type already exists */
			mt_exists = TRUE;

			/* Check if the MIME Type was already defined
			 * in this file
			 */
			for(glist = mt_imported_list;
			    glist != NULL;
			    glist = g_list_next(glist)
			)
			{
			    if(EDV_MIMETYPE(glist->data) == mt_ptr)
				break;
			}
			if(glist != NULL)
			{
			    /* Already defined in this file, so set
			     * it NULL so that it is not defined again
			     */
#if 0
			    g_printerr(
 "%s: Warning: Redefination of \"%s\" ignored.\n",
				filename, type
			    );
#endif
			    mt_ptr = NULL;
			    mt_num = -1;
			}

			/* Check if this MIME Type is newer than the one
			 * on file and we are only want to import
			 * MIME Types that are newer on file
			 */
                        if(only_newer && (mt_ptr != NULL))
                        {
                            if(mt_ptr->modify_time >= last_modified)
                            {
                                mt_ptr = NULL;
                                mt_num = -1;
                            }
                        }
		    }
		}
	    }

	    /* Insert or append? */
	    if((mt_ptr == NULL) && !mt_exists)
	    {
		/* Insert */
		if((insert_index >= 0) && (insert_index < *total))
		{
		    /* Allocate more pointers */
		    gint i = MAX(*total, 0);
		    *total = i + 1;
		    *list = (edv_mimetype_struct **)g_realloc(
		        *list,
		        (*total) * sizeof(edv_mimetype_struct *)
		    );
		    if(*list == NULL)
		    {
			g_printerr(
			    "%s: Memory allocation error.\n",
			    filename
			);
		        *total = 0;
		        break;
		    }

		    mt_num = insert_index;

		    /* Shift pointers */
		    for(i = (*total) - 1; i > mt_num; i--)
			(*list)[i] = (*list)[i - 1];

		    /* Create new MIME Type */
		    (*list)[mt_num] = mt_ptr = EDVMimeTypeNew(
		        EDV_MIMETYPE_CLASS_FORMAT,
		        NULL,		/* Value */
		        NULL,		/* Type */
		        NULL		/* Description */
		    );

		    insert_index++;
	        }
		else
		{
		    /* Append */

		    /* Allocate more pointers */
		    mt_num = MAX(*total, 0); 
		    *total = mt_num + 1;
		    *list = (edv_mimetype_struct **)g_realloc(
		        *list,
		        (*total) * sizeof(edv_mimetype_struct *)
		    );
		    if(*list == NULL)
		    {
			g_printerr(
			    "%s: Memory allocation error.\n",
			    filename
			);
		        *total = 0;
		        break;
		    }

		    /* Create new MIME Type */
		    (*list)[mt_num] = mt_ptr = EDVMimeTypeNew(
		        EDV_MIMETYPE_CLASS_FORMAT,
		        NULL,		/* Value */
		        NULL,		/* Type */
		        NULL		/* Description */
		    );

		    insert_index = -1;
	        }
	    }

	    /* MIME Type valid for value setting? */
	    if((mt_ptr != NULL) && (mt_num >= 0) && (mt_num < *total))
	    {
		gint i;
		GList *glist;
		edv_mimetype_struct *m = mt_ptr;

/* Seeks s past the '=' and any blank characters */
#define SEEK_PAST_VALUE_ARG_DELIM	{	\
 for(; *s != '\0'; s++) {			\
  if(*s == '=') {				\
   s++;						\
   while(ISBLANK(*s))				\
    s++;					\
   break;					\
  }						\
 }						\
}
#define ADD_COMMAND(_m_,_n_,_c_)	{	\
 gint i;					\
						\
 /* Look for command with existing name */	\
 for(i = 0; i < (_m_)->total_commands; i++) {	\
  if(STRISEMPTY((_m_)->command_name[i]))	\
   continue;					\
  if(!g_strcasecmp((_m_)->command_name[i], (_n_))) \
   break;					\
 }						\
 /* Need to append command? */			\
 if(i >= (_m_)->total_commands) {		\
  i = MAX((_m_)->total_commands, 0);		\
  (_m_)->total_commands = i + 1;		\
						\
  (_m_)->command_name = (gchar **)g_realloc(	\
   (_m_)->command_name,				\
   (_m_)->total_commands * sizeof(gchar *)	\
  );						\
  (_m_)->command = (gchar **)g_realloc(		\
   (_m_)->command,				\
   (_m_)->total_commands * sizeof(gchar *)	\
  );						\
						\
  (_m_)->command_name[i] = NULL;		\
  (_m_)->command[i] = NULL;			\
 }						\
						\
 g_free((_m_)->command_name[i]);		\
 (_m_)->command_name[i] = STRDUP(_n_);		\
 g_free((_m_)->command[i]);			\
 (_m_)->command[i] = STRDUP(_c_);		\
}

		/* Record this MIME Type as being imported from this
		 * file
		 */
		mt_imported_list = g_list_append(
		    mt_imported_list, m 
		);

		/* Iterate through the values list and apply values
		 * to the new MIME Type
		 */
		for(glist = val_list, i = 0;
		    glist != NULL;
		    glist = g_list_next(glist), i++
		)
		{
		    s = (gchar *)glist->data;
		    if(STRISEMPTY(s))
			continue;

		    /* First value is always the MIME Type */
		    if(i == 0)
		    {
			g_free(m->type);
			m->type = STRDUP(s);
		    }
		    /* Second value is always the command */
		    else if(i == 1)
		    {
			gchar *prog, *args;

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;
				}
			    }
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "default", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }

		    /* Name Template? */
		    else if(strcasepfx((const char *)s, "nametemplate"))
		    {
			SEEK_PAST_VALUE_ARG_DELIM

#if 0
/* Ignore, this does not provide information about extensions, but
 * rather information on generating tempory files
 *
 * Mailcap files do not provide any information on extensions
 */
			substr((char *)s, "%s", "");

			g_free(m->value);
			m->value = STRDUP(s);
#endif
		    }
		    /* Description? */
		    else if(strcasepfx((const char *)s, "description"))
		    {
			SEEK_PAST_VALUE_ARG_DELIM

			/* Remove first and last quotes if any */
			substr(s, "\"", "");

			g_free(m->description);
			m->description = STRDUP(s);
		    }
		    /* Installer? */
		    else if(strcasepfx((const char *)s, "notes"))
		    {
			SEEK_PAST_VALUE_ARG_DELIM

			/* Ignore */
		    }
		    /* Test? */
		    else if(strcasepfx((const char *)s, "test"))
		    {
			gchar *prog, *args;

			SEEK_PAST_VALUE_ARG_DELIM

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;
				}
			    }
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "test", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }
		    /* View? */
		    else if(strcasepfx((const char *)s, "view"))
		    {
			gchar *prog, *args;

			SEEK_PAST_VALUE_ARG_DELIM

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;
				}
			    }
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "view", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }
		    /* Edit? */
		    else if(strcasepfx((const char *)s, "edit"))
		    {
			gchar *prog, *args;

			SEEK_PAST_VALUE_ARG_DELIM

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;
				}
			    }
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "edit", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }
		    /* Print? */
		    else if(strcasepfx((const char *)s, "print"))
		    {
			gchar *prog, *args;

			SEEK_PAST_VALUE_ARG_DELIM

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;
				}
			    }
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "print", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }
		    /* Compose? */
		    else if(strcasepfx((const char *)s, "compose"))
		    {
			gchar *prog, *args;

			SEEK_PAST_VALUE_ARG_DELIM

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{                            
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;   
				}
			    }    
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "compose", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }
		    /* Compose Typed? */
		    else if(strcasepfx((const char *)s, "composetyped"))
		    {
			gchar *prog, *args;

			SEEK_PAST_VALUE_ARG_DELIM

			substr((char *)s, "'%s'", "\"%s\"");
			prog = GET_CMD_NAME(s);
			args = GET_CMD_ARGS(s);

			if(prog != NULL)
			{
			    gchar *cmd;
			    if(*prog != '/')
			    {
				gchar *s = EDVWhich(prog);
				if(s != NULL)
				{
				    g_free(prog);
				    prog = s;
				}
			    }
			    if(args != NULL)
				cmd = g_strdup_printf("%s %s", prog, args);
			    else
				cmd = STRDUP(prog);
			    ADD_COMMAND(m, "composetyped", cmd);
			    g_free(cmd);
			}

			g_free(prog);
			g_free(args);
		    }

		    /* Needs Terminal? */
		    else if(strcasepfx((const char *)s, "needsterminal"))
		    {
/* TODO, perhaps append the terminal run command to all commands */
		    }
		    /* Copious Output? */
		    else if(strcasepfx((const char *)s, "copiousoutput"))
		    {
			/* Ignore */
		    }
		    /* Textual Newlines? */
		    else if(strcasepfx((const char *)s, "textualnewlines"))
		    {
			/* Ignore */
		    }
		    else
		    {
			g_printerr(
"%s: MIME Type %s: Unsupported value \"%s\".\n",
			    filename, m->type,
			    s
			);
		    }


		}

		/* Update time stamps */
		if(last_modified > m->modify_time)
		    m->modify_time = last_modified;
		if(last_modified > m->change_time)
		    m->change_time = last_modified;

		/* Report MIME Type added or modified */
		if(mt_exists)
		{
		    if(modified_cb != NULL)
			modified_cb(modified_data, mt_num, m);
		}
		else
		{
		    if(added_cb != NULL)
			added_cb(added_data, mt_num, m);
		}

#undef ADD_COMMAND
#undef SEEK_PAST_VALUE_ARG_DELIM
	    }

	    /* Delete values list */
	    g_list_foreach(val_list, (GFunc)g_free, NULL);
	    g_list_free(val_list);

	    /* End of file? */
	    if(ISEOF(c))
		break;
	}

	/* Close the MailCap file */
	FClose(fp);

	/* Delete imported MIME Types list */
	g_list_free(mt_imported_list);

	/* Report final progress */
	if((progress_cb != NULL) && !aborted)
	    progress_cb(
		progress_data, file_size, file_size
	    );
}

/*
 *	Exports the MIME Types to the MailCap format file.
 *
 *	If include_read_only is TRUE then MIME Types that are marked
 *	as read only will also be exported.
 */
void EDVMimeTypeListMailcapExport(
	const gchar *filename,
	edv_mimetype_struct **list, gint total,
	gboolean include_read_only,
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer progress_data
)
{
	gboolean aborted = FALSE;
	gint mt_num;
	edv_mimetype_struct *mt_ptr;
	gchar *line, *parent;
	FILE *fp;

	if((list == NULL) || STRISEMPTY(filename))
	    return;

	/* Get parent directory and create it as needed */
	parent = g_dirname(filename);
	if(parent != NULL)
	{
	    rmkdir((char *)parent, S_IRUSR | S_IWUSR | S_IXUSR);
	    g_free(parent);
	}

	/* Open the Mailcap file for writing */
	fp = FOpen((const char *)filename, "wb");
	if(fp == NULL)
	    return;

	/* Report initial progress */
	if(progress_cb != NULL)
	{
	    if(progress_cb(
		progress_data, 0l, (gulong)total
	    ))
		aborted = TRUE;
	}


	/* Write header */
	fprintf(
	    fp,
"# Mailcap\n\
#\n\
# Generated by %s Version %s\n\
\n",
	    PROG_NAME_FULL, PROG_VERSION
	);


	/* Iterate through the MIME Types list */
	for(mt_num = 0; mt_num < total; mt_num++)
	{
	    if(aborted)
		break;

	    mt_ptr = list[mt_num];
	    if(mt_ptr == NULL)
		continue;

	    /* Skip MIME Types that are marked read only, meaning they
	     * should not be saved to file since they are created
	     * internally or are loaded from a global configuration
	     */
	    if(!include_read_only && mt_ptr->read_only)
		continue;

	    /* MIME Type string must be defined */
	    if(STRISEMPTY(mt_ptr->type))
		continue;

	    /* Report progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(
		    progress_data, (gulong)mt_num, (gulong)total
		))
		{
		    aborted = TRUE;
		    break;
		}
	    }


	    /* Begin formatting line */
	    line = NULL;

	    /* First value is the type */
	    line = G_STRCAT(line, mt_ptr->type);

	    if(mt_ptr->total_commands > 0)
	    {
		gint n;
		gchar *s, *s2;
		const gchar *name, *cmd;

		/* Second value is the default command */
		cmd = EDVMimeTypeGetCommandByName(mt_ptr, "default");
		if(STRISEMPTY(cmd))
		    cmd = mt_ptr->command[0];
		if(!STRISEMPTY(cmd))
		{
		    s = STRDUP(cmd);
		    substr(s, "\"%s\"", "'%s'");
		    line = G_STRCAT(line, ";");
		    line = G_STRCAT(line, s);
		    g_free(s);
		}

		/* Write subsequent commands */
		for(n = 0; n < mt_ptr->total_commands; n++)
		{
		    name = mt_ptr->command_name[n];
		    cmd = mt_ptr->command[n];
		    if(STRISEMPTY(name) || STRISEMPTY(cmd))
			continue;

		    if(!g_strcasecmp(name, "default"))
			continue;

		    s2 = STRDUP(cmd);
		    substr(s2, "\"%s\"", "'%s'");
		    s = g_strdup_printf("%s=%s", name, s2);
		    line = G_STRCAT(line, ";");
		    line = G_STRCAT(line, s);
		    g_free(s);
		    g_free(s2);
		}

		/* Extensions */
		if(!STRISEMPTY(mt_ptr->value))
		{
		    s = g_strdup_printf("nametemplate=%%s%s", mt_ptr->value);
		    for(s2 = s; *s2 != '\0'; s2++)
		    {
			if(ISBLANK(*s2))
			{
			    *s2 = '\0';
			    break;
			}
		    }
		    line = G_STRCAT(line, ";");
		    line = G_STRCAT(line, s);
		    g_free(s);
		}

		/* Description */
		if(!STRISEMPTY(mt_ptr->description))
		{
		    s = g_strdup_printf("description=\"%s\"", mt_ptr->description);
		    line = G_STRCAT(line, ";");
		    line = G_STRCAT(line, s);
		    g_free(s);
		}
	    }

	    /* Write this line */
	    fprintf(fp, "%s\n", (const char *)line);

	    g_free(line);
	}

	/* Close the MailCap file */
	FClose(fp);

	/* Report final progress */
	if((progress_cb != NULL) && !aborted)
	    progress_cb(
		progress_data, (gulong)total, (gulong)total
	    );
}
