/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Jeffrey Stedfast <fejj@ximian.com>
 *
 *  Copyright 2002 Ximian, Inc. (www.ximian.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <ctype.h>

#include <bonobo.h>
#include <bonobo-conf/bonobo-config-database.h>
#include <gal/util/e-xml-utils.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

#include <camel/camel-file-utils.h>
#include <camel/camel-store.h>

struct _storeinfo {
	char *base_url;
	char *namespace;
	char *encoded_namespace;
	char dir_sep;
	GPtrArray *folders;
};



static char
find_dir_sep (const char *lsub_response)
{
	register const unsigned char *inptr;
	const unsigned char *inend;
	
	inptr = (const unsigned char *) lsub_response;
	inend = inptr + strlen (inptr);
	
	if (strncmp (inptr, "* LSUB (", 8))
		return '\0';
	
	inptr += 8;
	while (inptr < inend && *inptr != ')')
		inptr++;
	
	if (inptr >= inend)
		return '\0';
	
	inptr++;
	while (inptr < inend && isspace ((int) *inptr))
		inptr++;
	
	if (inptr >= inend)
		return '\0';
	
	if (*inptr == '\"')
		inptr++;
	
	return inptr < inend ? *inptr : '\0';
}

static void
si_free (struct _storeinfo *si)
{
	int i;
	
	g_free (si->base_url);
	g_free (si->namespace);
	g_free (si->encoded_namespace);
	if (si->folders) {
		for (i = 0; i < si->folders->len; i++)
			g_free (si->folders->pdata[i]);
		g_ptr_array_free (si->folders, TRUE);
	}
	g_free (si);
}

static unsigned char tohex[16] = {
	'0', '1', '2', '3', '4', '5', '6', '7',
	'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};

static char *
hex_encode (const char *in, size_t len)
{
	const unsigned char *inend = in + len;
	unsigned char *inptr, *outptr;
	char *outbuf;
	
	outptr = outbuf = g_malloc ((len * 3) + 1);
	
	inptr = (unsigned char *) in;
	while (inptr < inend) {
		if (*inptr > 127 || isspace ((int) *inptr)) {
			*outptr++ = '%';
			*outptr++ = tohex[(*inptr >> 4) & 0xf];
			*outptr++ = tohex[*inptr & 0xf];
			inptr++;
		} else
			*outptr++ = *inptr++;
	}
	
	*outptr = '\0';
	
	return outbuf;
}

#define HEXVAL(c) (isdigit (c) ? (c) - '0' : tolower (c) - 'a' + 10)

static char *
hex_decode (const char *in, size_t len)
{
	const unsigned char *inend = in + len;
	unsigned char *inptr, *outptr;
	char *outbuf;
	
	outptr = outbuf = g_malloc (len + 1);
	
	inptr = (unsigned char *) in;
	while (inptr < inend) {
		if (*inptr == '%') {
			if (isxdigit ((int) inptr[1]) && isxdigit ((int) inptr[2])) {
				*outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
				inptr += 3;
			} else
				*outptr++ = *inptr++;
		} else
			*outptr++ = *inptr++;
	}
	
	*outptr = '\0';
	
	return outbuf;
}

static char *
parse_lsub (const char *lsub, char *dir_sep)
{
	const unsigned char *inptr = (const unsigned char *) lsub;
	const unsigned char *inend;
	int inlen, quoted = 0;
	
	inend = inptr + strlen (inptr);
	if (strncmp (inptr, "* LSUB (", 8))
		return NULL;
	
	inptr += 8;
	while (inptr < inend && *inptr != ')')
		inptr++;
	
	if (inptr >= inend)
		return NULL;
	
	inptr++;
	while (inptr < inend && isspace ((int) *inptr))
		inptr++;
	
	if (inptr >= inend)
		return NULL;
	
	/* skip over the dir sep */
	if (*inptr == '\"')
		inptr++;
	
	*dir_sep = (char) *inptr++;
	if (*inptr == '\"')
		inptr++;
	
	if (inptr >= inend)
		return NULL;
	
	while (inptr < inend && isspace ((int) *inptr))
		inptr++;
	
	if (inptr >= inend)
		return NULL;
	
	if (*inptr == '\"') {
		inptr++;
		quoted = 1;
	} else
		quoted = 0;
	
	inlen = strlen (inptr) - quoted;
	
	return g_strndup (inptr, inlen);
}

static void
cache_upgrade (struct _storeinfo *si, const char *folder_name)
{
	const char *old_folder_name = folder_name;
	char *oldpath, *newpath, *p;
	struct dirent *dent;
	DIR *dir = NULL;
	
	if (si->namespace && strcmp ("INBOX", folder_name)) {
		if (!strncmp (old_folder_name, si->namespace, strlen (si->namespace))) {
			old_folder_name += strlen (si->namespace);
			if (*old_folder_name == si->dir_sep)
				old_folder_name++;
		}
	}
	
	oldpath = g_strdup_printf ("%s/evolution/mail/imap/%s/%s", getenv ("HOME"),
				   si->base_url + 7, old_folder_name);
	
	newpath = g_strdup_printf ("%s/evolution/mail/imap/%s/folders/%s",
				   getenv ("HOME"), si->base_url + 7, folder_name);
	
	if (!strcmp (folder_name, "folders"))
		goto special_case_folders;
	
	if (si->dir_sep != '/') {
		p = newpath + strlen (newpath) - strlen (folder_name) - 1;
		while (*p) {
			if (*p == si->dir_sep)
				*p = '/';
			p++;
		}
	}
	
	/* make sure all parent directories exist */
	if ((p = strrchr (newpath, '/'))) {
		*p = '\0';
		camel_mkdir_hier (newpath, 0755);
		*p = '/';
	}
	
	if (rename (oldpath, newpath) == -1) {
		fprintf (stderr, "Failed to upgrade cache for imap folder %s/%s: %s\n",
			 si->base_url, folder_name, g_strerror (errno));
	}
	
	g_free (oldpath);
	g_free (newpath);
	
	return;
	
 special_case_folders:
	
	/* the user had a toplevel folder named "folders" */
	if (camel_mkdir_hier (newpath, 0755) == -1) {
		/* we don't bother to check EEXIST because well, if
                   folders/folders exists then we're pretty much
                   fucked */
		goto exception;
	}
	
	if (!(dir = opendir (oldpath)))
		goto exception;
	
	while ((dent = readdir (dir))) {
		char *old_path, *new_path;
		
		if (!strcmp (dent->d_name, ".") || !strcmp (dent->d_name, ".."))
			continue;
		
		old_path = g_strdup_printf ("%s/%s", oldpath, dent->d_name);
		new_path = g_strdup_printf ("%s/%s", newpath, dent->d_name);
		
		/* make sure all parent directories exist */
		if ((p = strrchr (new_path, '/'))) {
			*p = '\0';
			camel_mkdir_hier (new_path, 0755);
			*p = '/';
		}
		
		if (rename (old_path, new_path) == -1) {
			g_free (old_path);
			g_free (new_path);
			goto exception;
		}
		
		g_free (old_path);
		g_free (new_path);
	}
	
	closedir (dir);
	
	g_free (oldpath);
	g_free (newpath);
	
	return;
	
 exception:
	
	fprintf (stderr, "Failed to upgrade cache for imap folder %s/%s: %s\n",
		 si->base_url, folder_name, g_strerror (errno));
	
	if (dir)
		closedir (dir);
	
	g_free (oldpath);
	g_free (newpath);
}

static int
foldercmp (const void *f1, const void *f2)
{
	const char **folder1 = (const char **) f1;
	const char **folder2 = (const char **) f2;
	
	return strcmp (*folder1, *folder2);
}

static void
cache_upgrade_and_free (gpointer key, gpointer val, gpointer user_data)
{
	struct _storeinfo *si = val;
	GPtrArray *folders;
	char *path = NULL;
	char dir_sep;
	int i;
	
	if (si->folders) {
		path = g_strdup_printf ("%s/evolution/mail/imap/%s/folders",
					getenv ("HOME"), si->base_url + 7);
		
		if (mkdir (path, 0755) == -1 && errno != EEXIST) {
			fprintf (stderr, "Failed to create directory %s: %s", path, g_strerror (errno));
			goto exception;
		}
		
		g_free (path);
		folders = g_ptr_array_new ();
		for (i = 0; i < si->folders->len; i++) {
			if ((path = parse_lsub (si->folders->pdata[i], &dir_sep))) {
				g_ptr_array_add (folders, path);
			}
		}
		
		/* sort the folders so that parents get created before
                   their children */
		qsort (folders->pdata, folders->len, sizeof (void *), foldercmp);
		
		for (i = 0; i < folders->len; i++) {
			cache_upgrade (si, folders->pdata[i]);
			g_free (folders->pdata[i]);
		}
	}
	
	si_free (si);
	
	return;
	
 exception:
	
	fprintf (stderr, "Could not upgrade imap cache for %s: %s\n",
		 si->base_url + 7, g_strerror (errno));
	
	g_free (path);
	
	si_free (si);
}

static char *
get_base_url (const char *protocol, const char *uri)
{
	unsigned char *base_url, *p;
	
	p = (unsigned char *) uri + strlen (protocol) + 1;
	if (!strncmp (p, "//", 2))
		p += 2;
	
	base_url = p;
	p = strchr (p, '/');
	base_url = g_strdup_printf ("%s://%.*s", protocol, p ? (int) (p - base_url) : (int) strlen (base_url), base_url);
	
	return base_url;
}

static char *
imap_namespace (const char *uri)
{
	unsigned char *name, *p;
	
	if ((name = strstr (uri, ";namespace=\"")) == NULL)
		return NULL;
	
	name += strlen (";namespace=\"");
	p = name;
	while (*p && *p != '\"')
		p++;
	
	return g_strndup (name, p - name);
}

static char *
find_folder (GPtrArray *folders, const char *folder, char *dir_sep)
{
	const unsigned char *inptr, *inend;
	int inlen, len, diff, i;
	int quoted;
	
	len = strlen (folder);
	
	for (i = 0; i < folders->len; i++) {
		inptr = folders->pdata[i];
		inend = inptr + strlen (inptr);
		if (strncmp (inptr, "* LSUB (", 8))
			continue;
		
		inptr += 8;
		while (inptr < inend && *inptr != ')')
			inptr++;
		
		if (inptr >= inend)
			continue;
		
		inptr++;
		while (inptr < inend && isspace ((int) *inptr))
			inptr++;
		
		if (inptr >= inend)
			continue;
		
		/* skip over the dir sep */
		if (*inptr == '\"')
			inptr++;
		
		*dir_sep = *inptr++;
		if (*inptr == '\"')
			inptr++;
		
		if (inptr >= inend)
			continue;
		
		while (inptr < inend && isspace ((int) *inptr))
			inptr++;
		
		if (inptr >= inend)
			continue;
		
		if (*inptr == '\"') {
			inptr++;
			quoted = 1;
		} else
			quoted = 0;
		
		inlen = strlen (inptr) - quoted;
		if (len > inlen)
			continue;
		
		diff = inlen - len;
		if (!strncmp (inptr + diff, folder, len))
			return hex_encode (inptr, inlen);
	}
	
	*dir_sep = '\0';
	
	return NULL;
}

static char *
imap_url_upgrade (GHashTable *imap_sources, const char *uri)
{
	struct _storeinfo *si;
	unsigned char *base_url, *folder, *p, *new = NULL;
	char dir_sep;
	
	base_url = get_base_url ("imap", uri);
	
	fprintf (stderr, "checking for %s... ", base_url);
	if (!(si = g_hash_table_lookup (imap_sources, base_url))) {
		fprintf (stderr, "not found.\n");
		g_warning ("Unknown imap account: %s", base_url);
		g_free (base_url);
		return NULL;
	}
	
	fprintf (stderr, "found.\n");
	p = (unsigned char *) uri + strlen (base_url) + 1;
	if (!strcmp (p, "INBOX")) {
		new = g_strdup_printf ("%s/INBOX", base_url);
		g_free (base_url);
		return new;
	}
	
	p = hex_decode (p, strlen (p));
	
	fprintf (stderr, "checking for folder %s on %s... ", p, base_url);
	folder = si->folders ? find_folder (si->folders, p, &dir_sep) : NULL;
	if (folder == NULL) {
		fprintf (stderr, "not found.\n");
		folder = p;
		if (si->namespace) {
			if (!si->dir_sep) {
				fprintf (stderr, "checking for directory separator in namespace param... ");
				if (*si->namespace == '/') {
					dir_sep = '/';
				} else {
					p = si->namespace;
					while (*p && !ispunct ((int) *p))
						p++;
					
					dir_sep = (char) *p;
				}
			} else {
				dir_sep = si->dir_sep;
			}
			
			if (dir_sep) {
				fprintf (stderr, "found: '%c'\n", dir_sep);
				p = folder;
				folder = hex_encode (folder, strlen (folder));
				new = g_strdup_printf ("%s/%s%c%s", base_url, si->encoded_namespace, dir_sep, folder);
				g_free (folder);
				folder = p;
				
				p = new + strlen (base_url) + 1;
				while (*p) {
					if (*p == dir_sep)
						*p = '/';
					p++;
				}
			} else {
				fprintf (stderr, "not found.");
				g_warning ("Cannot update settings for imap folder %s: unknown directory separator", uri);
			}
		} else {
			g_warning ("Cannot update settings for imap folder %s: unknown namespace", uri);
		}
		
		g_free (base_url);
		g_free (folder);
		
		return new;
	} else
		g_free (p);
	
	fprintf (stderr, "found.\n");
	new = g_strdup_printf ("%s/%s", base_url, folder);
	g_free (folder);
	
	if (!si->dir_sep)
		si->dir_sep = dir_sep;
	
	if (dir_sep) {
		p = new + strlen (base_url) + 1;
		while (*p) {
			if (*p == dir_sep)
				*p = '/';
			p++;
		}
	}
	
	g_free (base_url);
	
	return new;
}

static char *
exchange_url_upgrade (const char *uri)
{
	unsigned char *base_url, *folder;
	char *url;
	
	base_url = get_base_url ("exchange", uri);
	folder = (unsigned char *) uri + strlen (base_url) + 1;
	
	if (strncmp (folder, "exchange/", 9))
		return g_strdup (uri);
	
	folder += 9;
	while (*folder && *folder != '/')
		folder++;
	if (*folder == '/')
		folder++;
	
	folder = hex_decode (folder, strlen (folder));
	url = g_strdup_printf ("%s/personal/%s", base_url, folder);
	g_free (base_url);
	g_free (folder);
	
	return url;
}

static int
mailer_upgrade_account_info (Bonobo_ConfigDatabase db, const char *key, int num, GHashTable *imap_sources)
{
	char *path, *uri, *new;
	int i;
	
	for (i = 0;  i < num; i++) {
		path = g_strdup_printf ("/Mail/Accounts/account_%s_folder_uri_%d", key, i);
		uri = bonobo_config_get_string (db, path, NULL);
		if (uri) {
			if (!strncmp (uri, "imap:", 5)) {
				new = imap_url_upgrade (imap_sources, uri);
				if (new) {
					bonobo_config_set_string (db, path, new, NULL);
					g_free (new);
				}
			} else if (!strncmp (uri, "exchange:", 9)) {
				new = exchange_url_upgrade (uri);
				bonobo_config_set_string (db, path, new, NULL);
				g_free (new);
			}
		}
		
		g_free (uri);
		g_free (path);
	}
	
	return 0;
}

static int
mailer_upgrade_xml_file (GHashTable *imap_sources, const char *filename)
{
	unsigned char *buffer, *inptr, *start, *uri, *new;
	ssize_t nread = 0, nwritten, n;
	gboolean url_need_upgrade;
	struct stat st;
	size_t len;
	char *bak;
	int fd;
	
	bak = g_strdup_printf ("%s.bak-1.0", filename);
	if (stat (bak, &st) != -1) {
		/* seems we have already converted this file? */
		fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename);
		g_free (bak);
		return 0;
	}
	
	if (stat (filename, &st) == -1 || (fd = open (filename, O_RDONLY)) == -1) {
		/* file doesn't exist? I guess nothing to upgrade here */
		fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno));
		g_free (bak);
		return 0;
	}
	
	start = buffer = g_malloc (st.st_size + 1);
	do {
		do {
			n = read (fd, buffer + nread, st.st_size - nread);
		} while (n == -1 && errno == EINTR);
		
		if (n > 0)
			nread += n;
	} while (n != -1 && nread < st.st_size);
	buffer[nread] = '\0';
	
	if (nread < st.st_size) {
		/* failed to load the entire file? */
		fprintf (stderr, "\nFailed to load %s: %s\n", filename, strerror (errno));
		g_free (buffer);
		g_free (bak);
		close (fd);
		return -1;
	}
	
	close (fd);
	
	inptr = buffer;
	url_need_upgrade = FALSE;
	do {
		inptr = strstr (inptr, "uri=\"");
		if (inptr) {
			inptr += 5;
			url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9);
		}
	} while (inptr && !url_need_upgrade);
	
	if (inptr == NULL) {
		/* no imap urls in this xml file, so no need to "upgrade" it */
		fprintf (stdout, "\nNo updates required for %s\n", filename);
		g_free (buffer);
		g_free (bak);
		return 0;
	}
	
	if (rename (filename, bak) == -1) {
		/* failed to backup xml file */
		fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno));
		g_free (buffer);
		g_free (bak);
		return -1;
	}
	
	if ((fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) {
		/* failed to create new xml file */
		fprintf (stderr, "\nFailed to create new %s: %s\n", filename, strerror (errno));
		rename (bak, filename);
		g_free (buffer);
		g_free (bak);
		return -1;
	}
	
	while (inptr != NULL) {
		len = inptr - start;
		nwritten = 0;
		do {
			do {
				n = write (fd, start + nwritten, len - nwritten);
			} while (n == -1 && errno == EINTR);
			
			if (n > 0)
				nwritten += n;
		} while (n != -1 && nwritten < len);
		
		if (nwritten < len)
			goto exception;
		
		start = inptr;
		while (*start && *start != '"')
			start++;
		
		uri = g_strndup (inptr, start - inptr);
		if (!strncmp (uri, "imap:", 5)) {
			if ((new = imap_url_upgrade (imap_sources, uri)) == NULL) {
				new = uri;
				uri = NULL;
			}
		} else if (!strncmp (uri, "exchange:", 9)) {
			new = exchange_url_upgrade (uri);
		} else {
			new = uri;
			uri = NULL;
		}
		g_free (uri);
		
		nwritten = 0;
		len = strlen (new);
		do {
			do {
				n = write (fd, new + nwritten, len - nwritten);
			} while (n == -1 && errno == EINTR);
			
			if (n > 0)
				nwritten += n;
		} while (n != -1 && nwritten < len);
		
		g_free (new);
		
		if (nwritten < len)
			goto exception;
		
		inptr = start;
		url_need_upgrade = FALSE;
		do {
			inptr = strstr (inptr, "uri=\"");
			if (inptr) {
				inptr += 5;
				url_need_upgrade = !strncmp (inptr, "imap:", 5) || !strncmp (inptr, "exchange:", 9);
			}
		} while (inptr && !url_need_upgrade);
	}
	
	nwritten = 0;
	len = strlen (start);
	do {
		do {
			n = write (fd, start + nwritten, len - nwritten);
		} while (n == -1 && errno == EINTR);
		
		if (n > 0)
			nwritten += n;
	} while (n != -1 && nwritten < len);
	
	if (nwritten < len)
		goto exception;
	
	if (fsync (fd) == -1)
		goto exception;
	
	close (fd);
	g_free (buffer);
	
	fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak);
	
	g_free (bak);
	
	return 0;
	
 exception:
	
	fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno));
	
	close (fd);
	g_free (buffer);
	unlink (filename);
	rename (bak, filename);
	g_free (bak);
	
	return -1;
}

static char *
shortcuts_upgrade_uri (GHashTable *accounts, GHashTable *imap_sources, const char *account, const char *folder)
{
	char *url, *name, *decoded, *new = NULL;
	struct _storeinfo *si;
	int type;
	
	type = GPOINTER_TO_INT ((si = g_hash_table_lookup (accounts, account)));
	if (type == 1) {
		/* exchange */
		decoded = hex_decode (folder, strlen (folder));
		name = g_strdup_printf ("personal/%s", decoded);
		g_free (decoded);
		
		return name;
	} else {
		/* imap */
		url = g_strdup_printf ("%s/%s", si->base_url, folder);
		new = imap_url_upgrade (imap_sources, url);
		g_free (url);
		
		if (new) {
			name = new + strlen (si->base_url) + 1;
			name = hex_decode (name, strlen (name));
			g_free (new);
			
			return name;
		}
	}
	
	return NULL;
}

static int
shortcuts_upgrade_xml_file (GHashTable *accounts, GHashTable *imap_sources, const char *filename)
{
	char *bak, *uri, *account, *folder, *new, *new_uri, *type;
	struct stat st;
	xmlDoc *doc;
	xmlNode *group, *item;
	int account_len;
	gboolean changed = FALSE;
	
	bak = g_strdup_printf ("%s.bak-1.0", filename);
	if (stat (bak, &st) != -1) {
		/* seems we have already converted this file? */
		fprintf (stderr, "\n%s already exists, assuming %s has already been upgraded\n", bak, filename);
		g_free (bak);
		return 0;
	}
	
	if (stat (filename, &st) == -1) {
		/* file doesn't exist? I guess nothing to upgrade here */
		fprintf (stderr, "\nCould not open %s: %s\n", filename, strerror (errno));
		g_free (bak);
		return 0;
	}
	
	doc = xmlParseFile (filename);
	if (!doc || !doc->xmlRootNode) {
		/* failed to load/parse the file? */
		fprintf (stderr, "\nFailed to load %s\n", filename);
		g_free (bak);
		return -1;
	}
	
	for (group = doc->xmlRootNode->xmlChildrenNode; group; group = group->next) {
		for (item = group->xmlChildrenNode; item; item = item->next) {
			/* Fix IMAP/Exchange URIs */
			uri = xmlNodeGetContent (item);
			if (!strncmp (uri, "evolution:/", 11)) {
				if (!strcmp (uri, "evolution:/local/Inbox")) {
					xmlNodeSetContent (item, "default:mail");
					changed = TRUE;
				} else if (!strcmp (uri, "evolution:/local/Calendar")) {
					xmlNodeSetContent (item, "default:calendar");
					changed = TRUE;
				} else if (!strcmp (uri, "evolution:/local/Contacts")) {
					xmlNodeSetContent (item, "default:contacts");
					changed = TRUE;
				} else if (!strcmp (uri, "evolution:/local/Tasks")) {
					xmlNodeSetContent (item, "default:tasks");
					changed = TRUE;
				} else {
					account_len = strcspn (uri + 11, "/");
					account = g_strndup (uri + 11, account_len);
					if (g_hash_table_lookup (accounts, account)) {
						folder = uri + 11 + account_len;
						if (*folder)
							folder++;
						new = shortcuts_upgrade_uri (accounts, imap_sources, account, folder);
						new_uri = g_strdup_printf ("evolution:/%s/%s", account, new);
						xmlNodeSetContent (item, new_uri);
						changed = TRUE;
						g_free (new_uri);
					}
					g_free (account);
				}
			}
			xmlFree (uri);
			
			/* Fix LDAP shortcuts */
			type = xmlGetProp (item, "type");
			if (type) {
				if (!strcmp (type, "ldap-contacts")) {
					xmlSetProp (item, "type", "contacts/ldap");
					changed = TRUE;
				}
				xmlFree (type);
			}
		}
	}
	
	if (!changed) {
		fprintf (stdout, "\nNo updates required for %s\n", filename);
		xmlFreeDoc (doc);
		g_free (bak);
		return 0;
 	}
	
	if (rename (filename, bak) == -1) {
		/* failed to backup xml file */
		fprintf (stderr, "\nFailed to create backup file %s: %s\n", bak, strerror (errno));
		xmlFreeDoc (doc);
		g_free (bak);
		return -1;
	}
	
	if (e_xml_save_file (filename, doc) == -1) {
		fprintf (stderr, "\nFailed to save updated settings to %s: %s\n\n", filename, strerror (errno));
		xmlFreeDoc (doc);
		unlink (filename);
		rename (bak, filename);
		g_free (bak);
		return -1;
	}
	
	fprintf (stdout, "\nSuccessfully upgraded %s\nPrevious settings saved in %s\n\n", filename, bak);
	
	xmlFreeDoc (doc);
	g_free (bak);
	
	return 0;
}


static int
mailer_upgrade (Bonobo_ConfigDatabase db)
{
	GHashTable *imap_sources, *accounts;
	char *path, *uri;
	char *account, *transport;
	int num, i;
	
	if ((num = bonobo_config_get_long_with_default (db, "/Mail/Accounts/num", 0, NULL)) == 0) {
		/* nothing to upgrade */
		return 0;
	}
	
	accounts = g_hash_table_new (g_str_hash, g_str_equal);
	imap_sources = g_hash_table_new (g_str_hash, g_str_equal);
	for (i = 0; i < num; i++) {
		struct _storeinfo *si;
		struct stat st;
		char *string;
		guint32 tmp;
		FILE *fp;
		int j;
		
		path = g_strdup_printf ("/Mail/Accounts/source_url_%d", i);
		uri = bonobo_config_get_string (db, path, NULL);
		g_free (path);
		if (uri && !strncmp (uri, "imap:", 5)) {
			path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i);
			account = bonobo_config_get_string (db, path, NULL);
			g_free (path);
			
			si = g_new (struct _storeinfo, 1);
			si->base_url = get_base_url ("imap", uri);
			si->namespace = imap_namespace (uri);
			si->encoded_namespace = NULL;
			si->dir_sep = '\0';
			si->folders = NULL;
			
			path = si->base_url + 7;
			
			path = g_strdup_printf ("%s/evolution/mail/imap/%s/storeinfo", getenv ("HOME"), path);
			if (stat (path, &st) != -1 && (fp = fopen (path, "r")) != NULL) {
				camel_file_util_decode_uint32 (fp, &tmp);
				camel_file_util_decode_uint32 (fp, &tmp);
				
				j = 0;
				si->folders = g_ptr_array_new ();
				while (camel_file_util_decode_string (fp, &string) != -1) {
					if (j++ > 0) {
						g_ptr_array_add (si->folders, string);
					} else {
						if (!si->namespace)
							si->namespace = string;
						else
							g_free (string);
						
						camel_file_util_decode_uint32 (fp, &tmp);
						si->dir_sep = (char) tmp & 0xff;
					}
				}
				
				fclose (fp);
			}
			g_free (path);
			
			if (si->folders && si->folders->len > 0)
				si->dir_sep = find_dir_sep (si->folders->pdata[0]);
			
			if (si->namespace) {
				/* strip trailing dir_sep from namespace if it's there */
				j = strlen (si->namespace) - 1;
				if (si->namespace[j] == si->dir_sep)
					si->namespace[j] = '\0';
				
				/* set the encoded version of the namespace */
				si->encoded_namespace = g_strdup (si->namespace);
				for (j = 0; j < strlen (si->encoded_namespace); j++) {
					if (si->encoded_namespace[j] == '/')
						si->encoded_namespace[j] = '.';
				}
			}
			
			g_hash_table_insert (imap_sources, si->base_url, si);
			
			if (account)
				g_hash_table_insert (accounts, account, si);
		} else if (uri && !strncmp (uri, "exchange:", 9)) {
			/* Upgrade transport uri */
			path = g_strdup_printf ("/Mail/Accounts/transport_url_%d", i);
			transport = bonobo_config_get_string (db, path, NULL);
			if (transport && !strncmp (transport, "exchanget:", 10))
				bonobo_config_set_string (db, path, uri, NULL);
			g_free (transport);
			g_free (path);
			
			path = g_strdup_printf ("/Mail/Accounts/account_name_%d", i);
			account = bonobo_config_get_string (db, path, NULL);
			g_free (path);
			
			if (account)
				g_hash_table_insert (accounts, account, GINT_TO_POINTER (1));
		}
		
		g_free (uri);
	}
	
	if (g_hash_table_size (accounts) == 0) {
		/* user doesn't have any imap/exchange accounts - nothing to upgrade */
		g_hash_table_destroy (imap_sources);
		return 0;
	}
	
	/* upgrade user's account info (bug #29135) */
	mailer_upgrade_account_info (db, "drafts", num, imap_sources);
	mailer_upgrade_account_info (db, "sent", num, imap_sources);
	
	/* upgrade user's filters/vfolders (bug #24451) */
	path = g_strdup_printf ("%s/evolution/filters.xml", getenv ("HOME"));
	mailer_upgrade_xml_file (imap_sources, path);
	g_free (path);
	
	path = g_strdup_printf ("%s/evolution/vfolders.xml", getenv ("HOME"));
	mailer_upgrade_xml_file (imap_sources, path);
	g_free (path);
	
	/* upgrade user's shortcuts (there's no bug # for this one) */
	path = g_strdup_printf ("%s/evolution/shortcuts.xml", getenv ("HOME"));
	shortcuts_upgrade_xml_file (accounts, imap_sources, path);
	g_free (path);
	
	g_hash_table_foreach (imap_sources, cache_upgrade_and_free, NULL);
	g_hash_table_destroy (imap_sources);
#if 0
	path = g_strdup_printf ("%s/evolution/mail/imap", getenv ("HOME"));
	bak = g_strdup_printf ("%s.bak-1.0", path);
	
	if (rename (path, bak) == -1)
		fprintf (stderr, "\nFailed to backup Evolution 1.0's IMAP cache: %s\n", strerror (errno));
	
	g_free (path);
	g_free (bak);
#endif
	
	return 0;
}

static Bonobo_ConfigDatabase
get_config_db (void)
{
	Bonobo_ConfigDatabase db;
	CORBA_Environment ev;
	
	CORBA_exception_init (&ev);
	
	db = bonobo_get_object ("wombat:", "Bonobo/ConfigDatabase", &ev);
	if (BONOBO_EX (&ev) || db == CORBA_OBJECT_NIL) {
		fprintf (stderr, "get_config_db(): Could not get the config database object '%s'",
			 bonobo_exception_get_text (&ev));
		db = CORBA_OBJECT_NIL;
	}
	
	CORBA_exception_free (&ev);
	
	return db;
}

static int
upgrade (void)
{
	Bonobo_ConfigDatabase db;
	CORBA_Environment ev;
	
	if ((db = get_config_db ()) == CORBA_OBJECT_NIL)
		g_error ("Could not get config db");
	
	mailer_upgrade (db);
	
	CORBA_exception_init (&ev);
	Bonobo_ConfigDatabase_sync (db, &ev);
	
	gtk_main_quit ();
	
	return FALSE;
}

int main (int argc, char **argv)
{
	CORBA_ORB orb;
	
	gnome_init ("evolution-upgrade", "1.0", argc, argv);
	
	if ((orb = oaf_init (argc, argv)) == NULL)
		g_error ("Cannot init oaf");
	
	if (bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL) == FALSE)
		g_error ("Cannot init bonobo");
	
	gtk_idle_add ((GtkFunction) upgrade, NULL);
	
	bonobo_main ();
	
	return 0;
}
